《Essential C++》读书笔记(四)

来源:互联网 发布:中盈数据恢复机教程 编辑:程序博客网 时间:2024/05/01 14:50

第三章泛型编程风格续

3.6如何设计一个泛型算法

继续上一次的问题,快完全泛型的find()。

template<typename IteratorType,typename elemType>

IteratorType find(IteratorType first,IteratorType last,const elemType &value){

    for(;first!=last;++first)

        if(value==*first)

            return first;

    return last;

}


让我们想一下该函数的功能,在一段元素范围内找出第一个等于特定值的元素,并返回指向该元素的指针。
但如果我们想要找出第一个小于、大于或是满足一定条件的元素,该函数便无法满足了,这就提出了一个问题,如何将“比较操作”参数化?
这个挺闹心。
既然要将“比较操作”参数化,那么要实现的便是标准库find_if()的功能了。
第一种方法便是便是增加一个形参,用以接受一个函数指针。
第二种同样是增加一个形参,不过接受的是一个function object。
所谓的function object是以个类的对象。由于该类重载了函数调用操作符‘()’,所以该对象可以像函数一样被调用。
其实这都是同一种方法,对于函数模板来说,没有什么不同,仅仅是传递的实参不同而已。其声明为:

template<typename InputIterator,typename T>

InputIterator find_if1(InputIterator first,InputIterator last,T p);

因为是模板嘛,所以只是传入的实参类型不同而已。
书中给出了一个传递函数指针的例子:

vector<int> filter_ver1(const vector<int> &vec,int filter_value,bool(*pred)(int,int)){

    vector<int> nvec;

    for(int ix=0;ix<vec.size();++ix)

        if(pred(vec[ix],filter_value))

            nvec.push_back(vec[ix]);

    return nvec;

}

下面的问题便是学习function objects了。
本来我们定义的函数就可以实现各种功能了,为什么还需要function object呢?书中给出了解释:主要是为了效率。我们可以令call运算符称为inline。因而清除“通过函数指针来调用函数”时需要付出的额外代价。
标准库事先定义了一组function object,分为算术、关系、逻辑运算:
plus<type>,minus<type>,negate<type>......
less<type>,less_equal<type>......
logical_and<type>......
当然这些都是一个个模板类,我看了一下plus<type>的定义:

  template <class _Tp>

    struct plus : public binary_function<_Tp, _Tp, _Tp>

    {

      _Tp

      operator()(const _Tp& __x, const _Tp& __y) const

      { return __x + __y; }

    };

虽然叫function object ,只是在传递的时候定义的对象。
书中例,sort()默认时是用地步元素的<运算符排序的,而当这样:
sort(vec.begin(),vec.end(),greater<int>());
这第三个实参才是一个对象,不过它没有名字,没用名字不怕,形参有就行。
以前有个问题一直困扰着我,比如算法sort(),可以传递两个参数表示一段范围,可以传递三个参数,第三个参数表示排序算法(我一直以为sort()就一个版本),我就疑惑了,当传递两个实参时,第三个参数难道有默认值实参?内部又怎样判断的?真是太费劲了。那天看了下标准库,发现的确是两个重载的sort()。
对于function object 理解就好,后面会讲如何设计function object。
Function Object Adapters
记得看《c++ primer》时,第一次接触适配器,容器适配器。当时真是不理解啊,就是不明白为啥叫适配器,不知道它内部是如何工作的,渐渐地也就明白了。比如queue,适配器的内部是基于deque实现的,为什么是基于deque呢,因为deque的特性——双端队列可以满足queue的要求,删除前面的增加队尾的,而且高效,这样遍不必再重新定义一个queue类的容器,只需在deque上加些运算就行了。
前面的find_if1:

template<typename InputIterator,typename T>

InputIterator find_if1(InputIterator first,InputIterator last,T p);

若p传递的是less<type>,那么p是个函数对象,应该接受两个参数,而find_if1的内部是逐一遍历元素的,所以只传递给p一个参数,你可能会说,我可以在find_if1内部定义一个变量,将这个变量也传给p,这不就两个了么,但是,这样违背了一个原则,那就是泛型,想一想的确如此嘛,不过,可以在传递p的时候,将p的一个参数绑定到一个变量或是常量上。为了解决这一问题,适配器就来了。

第一种adaptor是binder adapter(绑定配接器),其会将function object的参数绑定至特定值上,使二元function object转化为一元。标准库仅提供了两个binder adapter:bind1st、bind2nd。看字面意思就懂,绑到第一个和绑到第二个。

这样便解决了上面find_if1()的参数问题,相对于find(),find_if1(),仅需修改if中的判断条件仅可,而其它的不用变。

template<typename InputIterator,typename T>

InputIterator find_if1(InputIterator first,InputIterator last,T p){

    for(;first!=last;++first)

        if(p(*first))

            return first;

    return 0;

}

假如我们想找到第一个比a大的元素,如下:

find_if1(vec.begin(),vec.end(),bind2nd(greater<int>(),a));

或:

find_if1(vec.begin(),vec.end(),bind1st(less<int>(),a));

这样就很方便了,若是使用带默认参数的函数,是无法达到这样的灵活度的,注意,带默认实参的函数不论在函数声明还是函数调用都是有限制的,并不如这样方便。

写了段代码试了下:

#include<iostream>

#include<functional>

#include<vector>

using namespace std;

template<typename InputIterator,typename T>

InputIterator find_if1(InputIterator first,InputIterator last,T p){

    for(;first!=last;++first)

        if(p(*first))

            return first;

    return last;

}

int main(){

    vector<int> ivec;

    for(int i=0;i!=10;++i)

        ivec.push_back(i);

    vector<int>::iterator it=find_if1(ivec.begin(),ivec.end(),bind2nd(greater<int>(), 5));

    cout<<*it<<endl;

}

输出6,对了。

另一种adaptor是所谓的negator,它会逆转function object的真伪值,not1可逆转unary function object的真伪值,not2可逆转binary function object。

如上例,如果我想找到第一个小于等于5的元素,可这样:

find_if1(ivec.begin(),ivec.end(),not1(bind2nd(greater<int>(),5)));

关于泛型的设计就这样了。

3.7使用map & 3.8使用set

终于到关联容器了,本书对map和set的讲解仅仅3页,都没讲multimap和multiset,真是简略啊!

对于map需要注意的是其下标操作,和pair,不过本书并未提到pair。

对于set注意什么呢?书中说set元素皆依据其所属型别默认的<运算符进行排列,我试了下,的确如此,我又试了下map,发现它是依key值的默认<运算符排序的。


3.9如何使用Iterator Inserters

本节开头回顾了一下3.6节中的例子,filter()
while((first=find_if(first,last,bind2nd(pred,val)))!=last)
       *at++=*first++;
这里就提出了一个问题,at所指容器的容量必须足够大,以存储指定的元素,但是一般情况下,我们并不知道存储多少个元素。若是使其等于来源端容器的大小,未免有些太大了。所以我们应该想一种方法将其改改。难道要把
*at++=*first++改为push_back()或push_front()操作?这样便失去了其泛型的性质。而且书上也说了,所有会对元素进行复制行为的泛型,皆和filter()的实现极为相似。谨记STL的精神,泛型!算法是泛型的,将特定型别六个传入的实参吧。
由此引出了,insertion adapters,这些adapter让我们得以避免使用容器的assignment运算符。
back_inserter()会以容器的push_back()函数取代assignment运算符,其参数为所需传值的容器本身。
inserter()以容器的insert()函数取代assignment运算符。接受两个参数,一个是容器,一个是迭代器,指向容器的安插操作起始点。
front_inserter()以push_front()函数取代assignment运算符。
记得含如头文件iterator。书中只提了这些,这些比较容易理解,不过总是有些疑问。不知到底是个什么机制。既然*at++=*first++不变,那么一定有类,一定重载了*、++、==运算符,然后我就看了下代码,以back_inserter()为例:

template<typename _Container>

    inline back_insert_iterator<_Container>

    back_inserter(_Container& __x)

    { return back_insert_iterator<_Container>(__x); }


该函数挺简单,然后我又看了下back_insert_iterator<>,如下:

template<typename _Container>

    class back_insert_iterator

    : public iterator<output_iterator_tag, void, void, void, void>

    {

    protected:

      _Container* container;


    public:

      typedef _Container          container_type;

      


      

      explicit

      back_insert_iterator(_Container& __x) : container(&__x) { }


      //后面才是重点

      back_insert_iterator&

      operator=(typename _Container::const_reference __value)

      {

container->push_back(__value);

return *this;

      }


     

      back_insert_iterator&

      operator*()

      { return *this; }


      

      back_insert_iterator&

      operator++()

      { return *this; }


      

      back_insert_iterator

      operator++(int)

      { return *this; }

    };


可以看出“++”并不改变指针的指向。这段代码看懂了,以上的就明了了。而对于其它两个adaptor都差不多,不过inserter()的有点不一样。

3.10使用iostream iterator
这个不了解,它的定义看不懂,唉,以后再看吧。
它是标准库提供的输入输出用的iostream iterator类,要先#include<iteratro>
istream_iterator<>
ostream_iterator<>
如istream_iterator<string> is(cin);is为连接至标准输入装置的istream_iterator读取的是字符。istream_iterator<string> eof; 不指定istream对象时,代表end-of-file。
使用方法  copy(is,eof,back_inserter(text));
ostream_iterator<string> os(cout," ");输出字符串到标准输出装置,空格为指定的分隔符,默认情况下无分隔。
使用copy(text.begin(),text.end(),os);
两个例子:

#include<vector>

#include<iostream>

#include<iterator>

#include<algorithm>

#include<string>

using namespace std;

int main(){

    istream_iterator<string> is(cin);

    istream_iterator<string> eof;

    vector<string> text;

    copy(is,eof,back_inserter(text));

    sort(text.begin(),text.end());

    ostream_iterator<string> os(cout," ");

    copy(text.begin(),text.end(),os);

}


#include<vector>

#include<iostream>

#include<iterator>

#include<algorithm>

#include<string>

#include<fstream>

using namespace std;

int main(){

    ifstream in_file("input_file.txt");

    ofstream out_file("out_file.txt");

    if(!in_file||!out_file){

        cerr<<"!!unable to open the necessary file.\n";

        return -1;

    }

    istream_iterator<string> is(in_file);

    istream_iterator<string> eof;

    vector<string> text;

    copy(is,eof,back_inserter(text));

    sort(text.begin(),text.end());

    ostream_iterator<string> os(out_file," ");

    copy(text.begin(),text.end(),os);

}




原创粉丝点击