Boolan C++ STL与泛型编程_4

主要内容:

本节主要讲解STL六大部件中剩下的4大部件,包括算法、迭代器、泛函数和适配器。分类器和容器在前两节已经讲解。算法与其他STL部件的区别之一在于算法是函数模板,其他的是类模板。这几大部件也是相互联系的。算法虽然对容器一无所知,但是它通过问答迭代器,通过迭代器实现了对容器的操作。当迭代器无法回答迭代器的问题时,编译就会报错。算法也是泛函数的应用场合之一。适配器则是在容器、迭代器、泛函数的基础上再次封装,用这三大部件实现所需功能。

1. 迭代器

1.1 迭代器的种类
  • 使用随机访问迭代器的容器:array, vector,deque
    使用双向迭代器的容器:list,红黑树作为底层支撑的容器
    使用单向迭代器的容器:forward_list
    hash_table作为底层支撑的容器的迭代器取决于其中的链表采用的迭代器类型。
    还有两个特殊的迭代器类型:
    input迭代器:istream_iterator
    output迭代器:ostream_iterator
  • 共有五种迭代器类型,其中四种是相互继承的关系input(父类)<-farward<-bidirectional<-random_access(子类). 还有一种output_iterator是单独的。
  • 5种迭代器作为类的原因之一:通过迭代器的萃取器得到类型。typename iterator_traits<T>::iterator_category category;
  • typeid(itr).name()输出取决于不同的library,返回值由不同的实现定义的。

1.2 迭代器对算法的影响

  • eg. template <class InputIterator> inline iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last)计算两个迭代器间的距离,内部首先会识别iterator的类型,之后根据不同的迭代器类型调用不同的__distance(first, second, category())函数计算距离,category()是个临时对象,表示iterator的类型。

  • eg. template<class InputIterator, class Distance> inline void advance(InputIterator &i, Distance n) 是将iterator移动n个元素的位置,其内部会调用__advance(i, n, iterator_category(i)), iterator_category函数用于获取iterator的类型。根据不同的迭代器类型调用不同的__advance函数。如果是input迭代器,那么会将iterator逐个移动n个位置;如果是双向迭代器,那么会先判断n是正整数还是负整数,再决定是向前还是向后逐个移动。如果是随机访问迭代器,那么处理起来就简单了,直接将指针加n个位置。

  • 由于不同迭代器间有继承的关系,在调用__distance, __advance等这类重载的函数时,如果没有特别对这种迭代器类型的实现函数,那么会根据迭代器类型的父类找到重载的函数。(这是迭代器是类且相互继承的意义之二)

  • eg. template<class InputIterator, class OutputIterator> OutputInterator copy(InputIterator first, InputIterator last, OutputIterator result)内部有多出检查,首先会检查first和last的类型,如果是const char类型,那么会调用memmove();如果是const wchar_t类型,那么也会调用memmove(). 如果是InputIterator类型,那么就调用__copy_dispatch(),再去判断是const T, 还是T,还是迭代器。如果是迭代器,那么会再去检查是随机的还是其他类型的迭代器。如果是指针类型,那么会利用type traits询问它的拷贝构造重不重要(has trivial op=(), has non-trivial op=(),对于不需要自己定义,可以使用编译器给的默认拷贝构造函数,才叫做不重要的拷贝构造).

  • eg. destroy函数的内部实现和copy类似,都是会将传入的类型做判断,不同的类型采用不同的处理方式。如果传入的是forward_iterator或其派生的迭代器,那么会再利用type_traits判断析构函数是否是重要的,如果是重要的,那么会调用用户定义的~dtor.

  • eg. __unique_copy(first, last, result)函数中result如果是outputIterator,由于output iterator无法read, 无法执行*result != *first, 对于这种情况的处理就不同于result传入的是forward iterator.

  • 算法中模板参数的命名具有暗示作用,是给调用者看的,对于编译来说,只有编译到内部,出现传入的迭代器无法进行的操作时,才会报错。eg. template<class InputIterator> inline iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last) { ... }

2. 算法源码剖析

  • qsort()和bsearch()是c的函数,不是c++标准库的算法。
  • c++标准库的算法有标准格式
template<typename Iterator>
std::Algorithm(Iterator itr1, Iterator itr2, ...)
{ ... }
  • accumulate(first, last, init);
    accumulate(first, last, init, binary_op); //binary_op可以传自定义的函数,或是泛函数,binary_op(init, first)这个函数定义了初值init和first之间的计算方法。
  • for_each(first, last, func); 内部会调用func(*first)
  • replace, replace_if, replace_copy.
    replace(first, last, old_value, new_value)是将first到last内等于old_value的元素替换为new_value.
    replace_if(first, last, pred, new_value)这里predicate表示要传一个能返回真假的判断函数pred(*first), 返回真的话,会做替换。
    replace_copy(first, last, res, old_value, new_value)是将将first到last区间内的值拷贝给res, 如果是old_value,替换为new_value再拷贝给res,原有的值不做替换。
  • count, count_if
    count(first, last, value) 对于其中等于value的元素进行计数。
    count_if(first, last, pred) 对于其中符合pred(*first)的元素进行计数.
    如果容器中有自己的版本,要用自己的,效率更高。
  • find, find_if 是循序搜索。
  • sort(first, last);
    sort(first, last, op); op可以是自定义比较大小的函数,也可以是泛函(就是传入泛函的对象或临时对象, eg.less<int>())。
  • binary_search() 内部调用lower_bound()进行查找。

3. 泛函数/函数对象

  • 泛函数主要用于配合算法使用。标准库中已经定义了多种泛函数,分为算术类(plus, minus...),逻辑运算类(logical_and...),相对关系类(equal_to, less...)等。他们的共同点在于继承自unary_function(一个操作数)或是binary_function(两个操作数)类,他们内部定义了一些typedef, 共适配器询问,他们的内部并没有数据成员,对于自定义的泛函数只有遵循stl的体系架构,继承于unary_function或是binary_function才能作为泛函数适配器。
  • gnu c++独有的泛函。下面是G2.9下的泛函。G4.9的名称改了。
    identify传入什么元素就传出什么元素。
    select1st传入pair, 传出pair中第一个元素。
    select2nd传入pair, 传出pair中第二个元素。

4. 多种适配器

  • 适配器可以改造泛函数、迭代器和容器。适配器中可以拥有泛函数、迭代器或容器。
4.1 泛函数适配器
  • 泛函数适配器:binder2nd

  • eg. bind2nd(less<int>(), 40)给less函数绑定第二个参数为40,使得第一个参数和40进行比较,这里less<int>()是一个临时对象,并没有被调用 。bind2nd内部调用binder2nd, 重载()(这里会询问less第一个参数类型和返回值类型),将操作的第二个参数传入绑定的值,再以返回值的形式传出操作。对于需要绑定的值在调用binder2nd的时候会检查,首先bind2nd会向operation询问他的第二个参数的类型,之后创建一个这个类型的临时对象,并将需要绑定的值赋给它。这时会检查类型是否匹配。typename是因为编译器在编译时还不知道其要传入的类型,所以要告诉编译器这是一个类型。

  • <functional> bind可以替代bind1st、bind2nd、binder1st、binder2nd.

  • 泛函数适配器: not1
    not1(pred), 对pred的结果取非。

  • bind (C++11) 可以绑定函数、泛函、成员函数(_1必须是某个object地址)、数据成员(_1必须是某个object地址)。

auto fn_five = bind(my_divide, 10, 2);  // 将10和2绑定给my_divide函数。
double my_divide(double x, double y) { return x/y; }
fn_five();  // 调用这个函数。
auto fn_half = bind(my_divide, _1, 2);  // 只绑定第二个参数,_1表示占位符。
fn_half(10);
auto fn_invert = bind(my_divide, _2, _1); _1和_2表示占位符。
fn_half(10, 2);  // 2/10
auto fn_rounding = bind<int>(my_divide, _1, _2);
fn_rounding(10, 3);  // 返回int类型
  • 对象赋初值c++11的新写法:eg. MyObj obj{1,2};可以使用大括号。
4.2 迭代器适配器
  • rend() begin()......rbegin() end()
  • reverse_iterator类中有一个正向迭代器,都是由这个正向迭代器实现的。
  • inserter是个函数模板,将元素插入到另一个容器中。copy(bar.begin(), bar.end(), inserter(foo, it)); 将bar的元素全部插入到foo容器的it位置,并不覆盖foo原有的元素。如果是copy(bar.begin(), bar.end(), foo.begin());这样写会覆盖foo中原有的元素。
template<class _Container> inline
    insert_iterator<_Container> inserter(_Container& _Cont,
        typename _Container::iterator _Where)
    {   // return insert_iterator
    return (insert_iterator<_Container>(_Cont, _Where));
    }
  • insert_iterator内部定义了拷贝赋值函数,其内部调用了容器的insert函数。这样外部使用inserter,在copy中会调用insert_interator的拷贝赋值函数。
4.3 X适配器
  • X适配器:ostream_iterator
    ostream_iterator内部定义了拷贝赋值函数,将value赋值给标准输出。所以copy会调用ostream_iterator的拷贝赋值,输出各个元素。
    std::ostream_iterator<int> out_it(std::cout, ",");
    copy(vec.begin(), vec.end(), out_it);

  • X适配器:istream_iterator
    重载operator++作为输入数据。
    istream_iterator<int> iit(cin); 在构造的时其内部就会调用operator++。这里会等待输入数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容