第三讲

算法的形式

  • C++标准库的算法,是什么东西?

说明:

  1. 算法Algorithm 是个 function template,标准库中的算法都长成如下这样:

    template<typename Iterator>
    Algorithm(Iterator itr1, Iterator itr2) {...
    }template<typename Iterator, typename Cmp>
    Algorithm(Iterator itr1, Iterator itr2, Cmp comp) {...
    }
    
  2. 算法看不见容器,对其一无所知;所以,它所需要的一切信息都必须从Iterators取得,而Iterators(由Containers供应)必须能够回答Algorithm的所有提问,才能搭配该 Algorithm 的所有操作;

  3. 如果算法提问,迭代器无法回答,那么编译到那一行就会报错;

迭代器的分类

各种容器的iteratorsiterator_category

说明:

  1. 迭代器是由容器提供的

    • ArrayVector都是连续空间,所以它们的迭代器应该是Random_Access
    • Deque是分段连续的,是假的连续,但是给使用者的感觉是真的连续,所以它的迭代器也应该是Random_Access
    • list是双向链表,空间不是连续的,所以它的迭代器是bidirection, 可以往前,也可以往后,但是不能跳跃;
    • forward_list是单向链表,所以它的迭代器是forward_iterator
    • set/map/multiset/multimap底层结构都是红黑树,红黑节点之间是双向的,所以它们的迭代器都是bidirection这种类型;
    • unordered系列的容器底层是Hashtable,每个bucket下是一个链表,所以要看这个链表是单向还是双向的,才能决定hashtable是forward 还是 bidirection类型;
  2. 容器提供的迭代器的分类不是1,2,3,4,5这样的整数,而是如上所示的这种对象。五种iterator category,都是类,且存在继承关系:
    • input_iterator_tag
    • forward_iterator_tag
    • bidirectional_iterator_tag
    • random_access_iterator_tag
    • output_iterator_tag
  3. 如下程序打印出各种容器的迭代器的分类的形式:

程序说明:

  1. 传入各种容器的迭代器给dispaly_category(I itr)函数,其中类似array<int,10>::iterator()的形式是个临时对象,没有名称;
  2. iterator_traits<I>::iterator_category就是向iterator_traits提问迭代器的类型;
  3. display_category(xx_iterator_tag)是重载函数,这里就说明了为什么分类不用整数,而是用对象来表示,因为category不论是什么,往上调用display_category函数自然而然编译器就会找到是哪个,如果是整数,就无法写得这么漂亮;
#include <iostream>
#include <array>
#include <vector>
#include <list>
#include <forward_list>
#include <deque>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <iterator>
using namespace std;void display_category(random_access_iterator_tag) { cout << "random_access_iterator" << endl; }
void display_category(bidirectional_iterator_tag) { cout << "bidirectional_iterator" << endl; }
void display_category(forward_iterator_tag) { cout << "bidirectional_iterator" << endl; }
void display_category(output_iterator_tag) { cout << "output_iterator" << endl; }
void display_category(input_iterator_tag) { cout << "input_iterator" << endl; }template <typename I>
void display_category(I itr) {typename iterator_traits<I>::iterator_category cagy;display_category(cagy);
}int main() {cout << "\ntest_iterator_category().....\n";display_category(array<int, 10>::iterator());display_category(vector<int>::iterator());display_category(list<int>::iterator());display_category(forward_list<int>::iterator());display_category(deque<int>::iterator());display_category(set<int>::iterator());display_category(map<int, int>::iterator());display_category(multiset<int>::iterator());display_category(multimap<int, int>::iterator());display_category(unordered_set<int>::iterator());display_category(unordered_map<int, int>::iterator());display_category(unordered_multiset<int>::iterator());display_category(unordered_multimap<int, int>::iterator());//istream_iterator和ostream_iterator在iterator头文件中display_category(istream_iterator<int>());display_category(ostream_iterator<int>(cout, ""));return 0;
}
#linux实测运行结果
test_iterator_category().....
random_access_iterator
random_access_iterator
bidirectional_iterator
bidirectional_iterator
random_access_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
bidirectional_iterator
input_iterator
output_iterator

各种容器的iteratorsiterator_categorytypeid

上面的程序是人为进行了些加工,指定了要打印的字符串,但是通过typeid可以直接打印出iterator_category

#include <iostream>
#include <array>
#include <vector>
#include <list>
#include <forward_list>
#include <deque>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <iterator>
#include <typeinfo>
using namespace std;void display_category(random_access_iterator_tag) { cout << "random_access_iterator" << endl; }
void display_category(bidirectional_iterator_tag) { cout << "bidirectional_iterator" << endl; }
void display_category(forward_iterator_tag) { cout << "bidirectional_iterator" << endl; }
void display_category(output_iterator_tag) { cout << "output_iterator" << endl; }
void display_category(input_iterator_tag) { cout << "input_iterator" << endl; }template <typename I>
void display_category(I itr) {typename iterator_traits<I>::iterator_category cagy;display_category(cagy);cout << "typeid(itr).name() = " << typeid(itr).name() << endl << endl;//The output depends on library implementation//The particular representation pointed by the//returned values is implementation-defined, 返回值由实现的版本定义//and may or may not be different for different types.
}int main() {cout << "\ntest_iterator_category().....\n";display_category(array<int, 10>::iterator());display_category(vector<int>::iterator());display_category(list<int>::iterator());display_category(forward_list<int>::iterator());display_category(deque<int>::iterator());display_category(set<int>::iterator());display_category(map<int, int>::iterator());display_category(multiset<int>::iterator());display_category(multimap<int, int>::iterator());display_category(unordered_set<int>::iterator());display_category(unordered_map<int, int>::iterator());display_category(unordered_multiset<int>::iterator());display_category(unordered_multimap<int, int>::iterator());display_category(istream_iterator<int>());display_category(ostream_iterator<int>(cout, ""));return 0;
}

运行结果:

test_iterator_category().....
random_access_iterator
typeid(itr).name() = Pi random_access_iterator
typeid(itr).name() = N9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE bidirectional_iterator
typeid(itr).name() = St14_List_iteratorIiE # 编译器在编译的时候会在名称前后添加一些东西bidirectional_iterator
typeid(itr).name() = St18_Fwd_list_iteratorIiErandom_access_iterator
typeid(itr).name() = St15_Deque_iteratorIiRiPiEbidirectional_iterator
typeid(itr).name() = St23_Rb_tree_const_iteratorIiEbidirectional_iterator
typeid(itr).name() = St17_Rb_tree_iteratorISt4pairIKiiEEbidirectional_iterator
typeid(itr).name() = St23_Rb_tree_const_iteratorIiEbidirectional_iterator
typeid(itr).name() = St17_Rb_tree_iteratorISt4pairIKiiEEbidirectional_iterator
typeid(itr).name() = NSt8__detail14_Node_iteratorIiLb1ELb0EEEbidirectional_iterator
typeid(itr).name() = NSt8__detail14_Node_iteratorISt4pairIKiiELb0ELb0EEEbidirectional_iterator
typeid(itr).name() = NSt8__detail14_Node_iteratorIiLb1ELb0EEEbidirectional_iterator
typeid(itr).name() = NSt8__detail14_Node_iteratorISt4pairIKiiELb0ELb0EEEinput_iterator
typeid(itr).name() = St16istream_iteratorIicSt11char_traitsIcElEoutput_iterator
typeid(itr).name() = St16ostream_iteratorIicSt11char_traitsIcEE

istream_iterator的iterator_category

说明:

  1. 接口必须相同,实现可以不同
  2. G2.9版本的istream_iterator有两个模板参数,G3.3版本有4个模板参数,但是后面两个都有默认值;G4.9版本也是4个模板参数;
  3. 各个版本都定义了自己的iterator_categoryinput_iterator_tag

ostream_iterator的iterator_category

说明:

  1. istream_iterator类似,各个版本都是在定义自己的iterator_categoryoutput_iterator_tag

迭代器分类对算法的影响

distance算法

说明:

  1. distance算法是计算两个指针之间的距离:

    • 如果是连续空间,可以直接相减,调用__distance(RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag)函数;
    • 如果不是连续空间,则将first一直往后走直到和last重合,这个往后走的步数,就是两个迭代器之间的距离,调用的是__distance(InputIterator first, InputIterator last, input_iterator_tag)函数;
  2. distance算法的返回值要通过询问iteartor_traits获得difference_type,传入的Iteratorcategory也要通过询问iterator_traits获得;category()是个临时对象,记住这种写法typename()是创建临时对象;
  3. 如果两个迭代器之间有100万个元素,那么这两个__distance函数的效率截然不同;

advance算法

说明:

  1. advance算法,根据iterator_category的不同调用不同的__advance函数;
  2. iterator_category(const Iterator&)函数就是将传入的迭代器参数丢给iterator_traits,询问其对应的iterator_category是什么,即如图中的解释:协助取出iteratorcategory并以此创建一个临时对象;
  3. 迭代器的分类之所以没有使用整数来表示,而是使用对象,是因为这些分类之间有继承关系,如果问出来的分类是forward_iterator_tag,代码中并没有专门为其设计的版本,因为forward_iterator_taginput_iterator_tag的子类,那么就会进入input_iteartor_tag的版本;

copy算法

说明:

  1. copy算法就是复制,需要知道来源端的起点、终点以及目的端的起点;
  2. copy中不断地在检查它所接收到的迭代器是不是特属于某种类型而决定是否要做特别的动作来加快速度;
    • 如果迭代器firstlastconst char*类型,则调用memmove(),直接传到目的端;
    • 如果迭代器是const wchar_t*类型,也是直接做memmove()
    • 如果迭代器是InputIterator类型,则调用__copy_dispatch();在__copy_dispatch()函数中又去检查迭代器的类型:
      • 如果是指针类型,则调用__copy_t()函数,判断是否有重要的拷贝构造函数,如果有不重要的拷贝构造函数,就调用memmove()函数;如果有重要的拷贝构造函数,就调用__copy_d()函数,用尾减头确定要拷贝的元素个数,减少了循环次数,速度较快;
      • 如果是<InputIterator,InputIterator>类型,调用__copy()函数,并在该函数中继续判断迭代器的类型:
        • 如果是InputIterator类型,则从头开始一个个拷贝,直到拷贝到尾,速度很慢;
        • 如果是RandomAccessIterator类型,则调用__copy_d()函数,用尾减头确定要拷贝的元素个数,减少了循环次数,速度较快;
  3. 图中涉及到的Type Traits,将在第四讲中讲解;

destroy算法

说明:

  1. 和前面讲解的算法一样,根据传入的迭代器的类型做不同的操作,以使得速度变快;
  2. 如果要销毁的元素的析构函数不重要,则什么都不做;否则,调用析构函数;

unique_copy算法

算法源码中对iterator_category的”暗示“

算法是模板函数,即可以接受各种类型。

说明:

  1. sort算法的typename命名为RandomAccessIterator就是暗示接受的迭代器为RandomAccessIterator,此处的这个typename可以命名为其他的,之所以命名为当前这个,就是一种”暗示“;
  2. distance算法暗示接受的迭代器是InputIterator都可以;
  3. find算法暗示接受InputIterator;
  4. rotate算法暗示接受ForwardIterator;
  5. reverse_copy算法暗示接受BidirectionalIterator

算法源码剖析

算法要满足模板函数的类型才是C++标准库提供的算法。qsortbsearch算法是C中的函数,不是标准库中提供的算法。而count_iffindsort算法是C++标准库中提供的算法。

算法accumulate

说明:

  1. accumulate算法的功能是累计,图上有两个版本:

    • 三个参数的情况,进行累加,初值加上每个元素;
    • 四个参数的情况,每次都将初值和元素进行binary_op的操作;
  2. 测试代码:

    #include <iostream>     // std::cout
    #include <functional>   // std::minus
    #include <numeric>      // std::accumulate
    namespace jj34
    {int myfunc (int x, int y) {return x+2*y;}struct myclass {int operator()(int x, int y) {return x+3*y;}
    } myobj;void test_accumulate()
    {cout << "\ntest_accumulate().......... \n";    int init = 100;int nums[] = {10,20,30};cout << "using default accumulate: ";cout << accumulate(nums,nums+3,init);  //160 使用指针作为迭代器,且前闭后开cout << '\n';cout << "using functional's minus: ";//minus<int>是C++标准库提供的functor,减法cout << accumulate(nums, nums+3, init, minus<int>()); //40 cout << '\n';cout << "using custom function: ";//myfunc就是个函数cout << accumulate(nums, nums+3, init, myfunc);    //220 cout << '\n';cout << "using custom object: ";//myobj是函数对象(function object/functor)/仿函数,是个类或结构体,重载()cout << accumulate(nums, nums+3, init, myobj);  //280cout << '\n';
    }
    }
    

算法for_each

说明:

  1. for_each算法对firstlast范围的每个元素做f操作,参数f可以是任何东西,只要能被()调用起来;

  2. 测试代码:

    #include <iostream>     // std::cout
    #include <algorithm>    // std::for_each
    #include <vector>       // std::vector
    namespace jj35
    {void myfunc (int i) {  //functioncout << ' ' << i;
    }struct myclass {       //function objectvoid operator() (int i) { cout << ' ' << i; }
    } myobj;void test_for_each()
    {cout << "\ntest_for_each().......... \n";  vector<int> myvec;myvec.push_back(10);myvec.push_back(20);myvec.push_back(30);for_each (myvec.begin(), myvec.end(), myfunc);cout << endl;       //output: 10 20 30for_each (myvec.begin(), myvec.end(), myobj);cout << endl;      //output: 10 20 30//since C++11, range-based for- statementfor (auto& elem : myvec)elem += 5;for (auto elem : myvec)cout << ' ' << elem ;     //output: 15 25 35
    }
    }
    
  3. C++11开始新的for循环形式:

    for (decl : coll) {statement
    }
    

算法replace,replace_if,replace_copy

说明:

  1. replace函数的参数:头、尾、旧值、新值;条件是元素值和旧值相同;
  2. replace_if函数表示需要给定一个条件,参数:头、尾、条件、新值;符合pred(*first)作为条件;
  3. replace_copy函数功能是将旧区间的等于旧值的都以新值的形式放到新区间中;

算法count,count_if

说明:

  1. 左边是标准库的算法,全局的,泛化的;右边是说明容器中是否有该同名的成员函数;
  2. 容器不带成员函数count():
    • array
    • vector
    • list
    • forward_list
    • deque
  3. 容器带有成员函数count(): 使用的时候就要使用容器中的count,针对这种类型的容器做了特别的处理
    • set/multiset
    • map/multimap
    • unordered_set/unordered_multiset
    • unordered_map/unordered_multimap
  4. 容器带有成员函数count()的这8个,是associated container,可以用key找到data,小型的数据库,有严谨的结构,内部使用红黑树或哈希表实现,有自己比较快的计数方式;

算法find,find_if

说明:

  1. find查找,循序查找;
  2. find_if查找符合条件的元素;
  3. set/multisetmap/multimapunordered_set/unordered_multisetunordered_map/unordered_multimap因为有自己严谨的结构,所以使用这些结构,不需要调用全局的find

算法sort

说明:

  1. set/multisetmap/multimapunordered_set/unordered_multisetunordered_map/unordered_multimap不带sort,因为遍历自然就形成了sorted状态;

  2. 默认的排序方法也是从小到大排序;

  3. 测试代码:

    #include <iostream>     // std::cout
    #include <algorithm>    // std::sort
    #include <vector>       // std::vector
    namespace jj36
    {bool myfunc (int i,int j) { return (i<j); }struct myclass {bool operator() (int i,int j) { return (i<j);}
    } myobj;bool test_sort()
    {   cout << "\ntest_sort().......... \n";   int myints[] = {32,71,12,45,26,80,53,33};vector<int> myvec(myints, myints+8);          // 32 71 12 45 26 80 53 33// using default comparison (operator <):sort(myvec.begin(), myvec.begin()+4);         //(12 32 45 71)26 80 53 33// using function as compsort(myvec.begin()+4, myvec.end(), myfunc);     // 12 32 45 71(26 33 53 80)// using object as compsort(myvec.begin(), myvec.end(), myobj);      //(12 26 32 33 45 53 71 80)// print out content:cout << "\nmyvec contains:";for (auto elem : myvec)     //C++11 range-based for statementcout << ' ' << elem ;  //output: 12 26 32 33 45 53 71 80// using reverse iterators and default comparison (operator <):sort(myvec.rbegin(), myvec.rend());     // print out content:cout << "\nmyvec contains:";for (auto elem : myvec)     //C++11 range-based for statementcout << ' ' << elem ;  //output: 80 71 53 45 33 32 26 12    // using explicitly default comparison (operator <):sort(myvec.begin(), myvec.end(), less<int>());  // print out content:cout << "\nmyvec contains:";for (auto elem : myvec)      //C++11 range-based for statementcout << ' ' << elem ;  //output: 12 26 32 33 45 53 71 80   // using another comparision criteria (operator >):sort(myvec.begin(), myvec.end(), greater<int>());  // print out content:cout << "\nmyvec contains:";for (auto elem : myvec)     //C++11 range-based for statementcout << ' ' << elem ;  //output: 80 71 53 45 33 32 26 12
    }
    }
    
  4. listforward_list中带有成员函数sort(),因为全局的sort要求是RandomAccessIterator,可以跳跃,但是listforward_list容器都不能跳跃;

关于reverse iterator,rbegin(),rend()

说明:

  1. 图中的绿色小箭头表示如果对迭代器取值得到的元素;
  2. rbegin()对应的就是end()的指针,rend()对应的就是begin()的指针,但是需要改变它们的行为,所以加了一个iterator adapter——reverse_iterator,使得取值的时候和原来的指针取值方式变得不同;

算法binary_search

说明:

  1. binary_search算法一定是在排序的序列中使用;
  2. binary_search就是通过调用lower_bound函数来完成查找;
  3. lower_bound找到的是在不违反排序的情况下,找到的能安插要查找的数的最低点,upper_bound是最高点,即返回的是能安插的位置;

仿函数和函数对象

仿函数functors

说明:

  1. 仿函数只为算法服务;
  2. 仿函数就是要模仿函数,必须重载(),图中是标准库提供的三大分类的functor;
  3. 之所以要将这些操作设计成函数,是为了将这些动操作传到算法中,所以要写成函数或仿函数;
  4. 每个标准库提供的仿函数都继承了binary_function

说明:

  1. identity/select1st/select2nd 是GNU C++独有的,C++标准库的规格里并没有规定必须有这些;
  2. G4.9中这三个仿函数的名称变了,变成了_Identity/_Select1st/_Select2nd;

说明:

  1.  // using explicitly default comparison (operator <):sort(myvec.begin(), myvec.end(), less<int>());
    

    sort(myvec.begin(), myvec.end());是一样的,从小到大排序,只是将默认的比较方法写出来了,less<int>是类型,less<int>()产生了一个临时对象;

  2.  // using another comparision criteria (operator >):sort(myvec.begin(), myvec.end(), greater<int>());
    

    使用greater<int>()就变成了逆向排序,从大到小进行排序;

  3. 此处自己编写的仿函数struct myclass没有继承binary_function,就没有融入STL中,那么未来就没有被改造的机会;如果希望自己写的functor能够和STL的体系结构如何的话,就必须选择”适当者“(unary_functionbinary_function)继承。。

仿函数functors的可适配(adaptable)条件

说明:

  1. binary_function就是两个操作数的操作(+,-,…),unary_function就是一个操作数;
  2. binary_functionunary_function的相同点是接受模板参数并将参数换了个名称,没有data,没有function,所以它们的大小都是0(理论值,实际上可能是1),如果作为父类,那它的大小一定是0;
  3. STL规定每个Adaptable Function都应挑选适当者继承之(因为Function Adapter将会提问)。所谓的"可适配(adaptable)"的意思就是可以被修改、适配或调整。如果你希望你写的functor是适配的,那么就要挑选一个”适当者“继承,因为当你被Adapter改造的时候,Adapter可能会来问这些问题,类似算法问迭代器问题。例如less是两个操作的数的操作,所以它要继承binary_function,这里只是继承了typedef,并没有增加额外的开销(没有数据和方法)。
  4. 仿函数如果要可适配的条件是能Adapter回答问题,为了能回答Adapter的问题就必须继承”适当者“;
  5. 小结:仿函数就是一个类中重载了(),这样的类创建出来的对象就叫做函数对象或者仿函数,之所以叫做函数对象,是因为创建出来的对象是一个对象,但是它像一个函数;

存在多种Adapter

说明:

  1. 根据Adapter需要修饰或改造的东西的名称则将其命名为什么:

    • Function Adapters:改造Functor;
    • Iterator Adapters: 改造Iterator;
    • Container Adapters: 改造Container;
  2. 所有的Adapter都是用复合而非继承的方式来使用所要改造的东西的功能;
  3. 回顾:算法不知道容器,能够看到的只有迭代器,而迭代器是由容器提供,所以算法想要知道的一切问题都要通过5种typedef来询问迭代器,所以迭代器必须有这5种type;
  4. Adapter是仿函数和算法之间的桥梁,Function Adapter可能想要知道Functors的一些特性,也是通过问和答来实现的,通过3或2个typedef来询问;

容器Adapter

说明:

  1. stackqueue都内含了一个Sequence,默认是个deque,隐藏了某些deque的操作,这是一种改造,修改了函数名称也是一种改造:

    • stack:push_back -> push; pop_back->pop;
    • queue: push_back -> push; pop_front->pop;

函数Adapter

函数适配器:binder2nd

说明:

  1. binder2nd类中的typename Operation::second_argument_type value;就是在提问:binder2nd这个Adapter在修饰改造一个动作Operation(如之前的less),询问Operationsecond_argument_type是什么,就是Adapter向Functor提问,Functor要回答;

  2.  count_if(vi.begin(), vi.end(), not1(bind2nd(less<int>(), 40)));//bind2nd是将第二个参数绑定为特定值,此处是40//less<int>():less<int>是类型,加上()产生了临时对象,并不是调用 要准确区分()是创建对象还是函数调用
    
  3. bind2nd实际上调用了binder2nd

  4. binder2ndop会记录less<int>(),value会记录40,修饰functor,最后也要表现为functor,所以重载operator(),修饰完成后传给count_if

  5. 调用流程就是:count_if->bind2nd->binder2nd->operator()

  6. 辅助函数bind2nd的作用是根据实参推导出Opeartion的类型;

  7. binder2nd<Operation>(op, arg2_type(x))也是一个临时对象,其中binder2nd<Operation>是类型,而创建临时对象会调用binder2nd的构造函数;这个对象作为第三参数传入count_if,即pred这个参数,然后pred(*first)就会调用operator()

  8. 函数对象(图中是less<int>())必须能够回答second_argument_typefirst_argument_typeresult_type才能和binder2nd这个Adapter完美搭配使用,如果能回答则说这个functor是可适配(adaptable)的,可适配的条件就是要继承binary_function;

  9. typename的作用是帮助编译器通过编译,在编译的时候,还没有被使用,所以Operation是什么不知道,因此也不知道Operation::second_argument_type是什么,它到底是什么东西,将来是否会出错,都是编译器会考虑的,所以编译器会犹豫不决,正常来讲编译到这里的时候不应该通过编译,而加了typename就是坚定编译器的决心,这是个问答形式,告诉编译器Operation::second_argument_type就是个typename,让它编译通过吧;这种很长的提问前面一般都会加typename;

  10. 如果binder2nd这个functor要可适配的话,就要继承unary_function,而它的操作数就是bind2nd这个结果;

函数适配器:not1

说明:

  1. not1(bind2nd(less<int>(), 40))就是不小于40的数;
  2. bind2nd(less<int>(), 40)会作为实参传入not1函数中,函数模板会进行实参推导,推导出Predicate是什么类型,推导出来之后创建临时对象,即调用unary_negate类的构造函数;

新型适配器,bind

说明:

  1. 右侧的已经过时了,现在使用左侧的替代;

  2. 如下是bind的使用示例:

  1. bind是从C++11开始提供的适配器,可以绑定:

    • 函数
    • 函数对象
    • 成员函数:_1必须是某个object地址,其中的_1是占位符(placeholder)
    • 数据成员:_1必须是某个object地址
  2.  //binding functions:auto fn_five = bind(my_divide, 10, 2); //绑定my_divide函数,绑定它的两个参数为10和2auto fn_half = bind(my_divide, _1, 2); //绑定my_divide函数第二参数为2,第一参数留着cout << fn_half(10) << '\n'; //因为只绑定了一个参数,所以调用的时候还需要传入一个参数,传入的参数落在_1位置上auto fn_invert = bind(my_divide, _2, _1); //绑定my_divide函数,两个参数都空置cout << fn_invert(10, 2) << '\n'; //10是第一实参,会落在_1的位置上;2是第二实参,会落在_2这个位置上;auto fn_rounding = bind<int>(my_divide, _1, _2); //指定了模板参数,就改变return type为intcout << fn_rounding(10,3) << '\n';
    
  3.  //binding members:MyPair ten_two{10,2}; //设定对象并给定初值//成员函数其实有个参数:thisauto bound_memfn = bind(&MyPair::multiply, _1); //绑定MyPair的成员函数multiply,_1空闲,占位this参数cout << bound_memfn(ten_two) << '\n'; //调用的时候指定auto bound_memdata = bind(&MyPair::a, ten_two); //调用的时候绑定了this参数cout << bound_memdata() << '\n'; //取得aauto bound_memdata2 = bind(&MyPair::b, _1); //调用的时候绑定了this参数cout << bound_memdata2(ten_two) << '\n'; //取得b
    
  4.  vector<int> v{15, 37, 94, 50, 73, 58, 28, 98};//cbegin()表示返回的迭代器是不可以更改的,c是const的意思int n = count_if(v.cbegin(), v.cend(), not1(bind2nd(less<int>(), 50)));//用bind替换bind2ndauto fn_ = bind(less<int>(), _1, 50);cout << count_if(v.cbegin(), v.cend(), fn_) << endl;cout << count_if(v.begin(), v.end(), bind(less<int>(), _1, 50)) << endl;
    

迭代器Adapter

迭代器适配器:reverse_iterator

说明:

  1. reverse_iterator的关键就是operator*,对逆向迭代器取值,就是对「对应的正向迭代器」退一位取值;
  2. 注意其他的操作,都是和正向迭代器的操作是相反的;

迭代器适配器:inserter

说明:

  1. copy操作是将源端的数据拷贝到目的端,firstlast分别是源端的迭代器的头和尾,result是目的端的起始位置,所以将数据拷贝到目的端的时候,目的端的空间是否足够、是否合法,copy操作是不知道的,所以copy和一般的迭代器的搭配的时候拷贝动作是进行”赋值“,有隐患;
  2. 我们希望在要插入的位置自己准备空间,放一个元素则创建一个新的空间,而不是进行”赋值“;而copy中的实现是”赋值“,如何变成我们希望的创建新空间呢?可以使用inserter,将目的端的迭代器改成是插入的动作,insert_iterator中进行了operator=函数的重载,所以使得copy中调用的*result = *first操作变成了调用容器的insert操作,而容器的insert操作就会自己开辟一块空间存放数据;

X Adapter

之所以叫做X Adapter,因为不知道要归到哪一类中。

ostream_iterator

说明:

  1. std::ostream_iterator<int> out_it(std::cout, ","); 是将迭代器的第一参数绑定到cout上,即放入的数据都显示到屏幕上;第二参数绑定为字符串,作为分隔符号使用;
  2. copy*=++还有返回时会调用拷贝构造都作用在result上,这四个动作要如何改变,才能使得满足我们的需求——元素打印到屏幕上?
  3. ostream_iterator 中通过操作符重载operator=使其能够使用cout输出出来;
  4. ostream_iteratorostream的适配器;

istream_iterator

说明:

  1. std::istream_iterator<double> eos;调用无参构造函数,std::istream_iterator<double> iit(std::cin); 调用有参构造函数,istream_iteratorcin记录到in_stream中;
  2. ++iit调用istream_iteratoroperator++()函数,读入数据;
  3. *iit调用istream_iteratoroperator*函数,返回读入的数据;

说明:

  1. iit在创建的时候就已经读入了值,所以*first的时候可以取出值了;while循环中一边读入数据,一边返回数据;

图中是写错一行代码报出的错误信息,所以读源码的意义就体现在这里,看到繁杂的错误信息,要有能力分析它们出现的原因。

【侯捷】C++STL标准库与泛型编程(第三讲)相关推荐

  1. 侯捷C++课程笔记03: STL标准库与泛型编程

    本笔记根据侯捷老师的课程整理而来:STL标准库与泛型编程 pdf版本笔记的下载地址: 笔记03_STL标准库与泛型编程,排版更美观一点(访问密码:3834) 侯捷C++课程笔记03: STL标准库与泛 ...

  2. STL标准库及泛型编程

    1-认识headers.版本.重要资源 C++ Standard Library Standard Template Library 标准库 > STL 标准库以header files形式呈现 ...

  3. 【侯捷】C++STL标准库与泛型编程(第二讲)

    第二讲 应具备的基础 C++基本语法 模板(Template)基础 令你事半功倍 数据结构(Data Structures)和算法(Algorithms)概念 令你如鱼得水 书籍: <Algor ...

  4. 【侯捷】C++STL标准库与泛型编程(第一讲)

    前言 所谓Generic Programming(GP,泛型编程),就是使用 template(模板)为主要工具来编写程序.本课程第二讲开宗明义阐述了 GP 与 OOP(Object Oriented ...

  5. 【侯捷】C++STL标准库与泛型编程(第四讲)

    第四讲 1.一个万用的Hash Function 说明: 1.1 针对自定义类型的哈希函数的编写方式 自定义类型: #include <functional> class Customer ...

  6. C++拾取——使用stl标准库实现排序算法及评测

    今天看了一篇文章,讲各种语言的优势和劣势.其中一个观点:haskell非常适合写算法,因为使用者不用去关心具体的计算机实现,而只要关注于操作语义.这让它在专心研究算法的人中非常受欢迎.所以很多时候,语 ...

  7. STL标准库-容器-set与map

    STL标准库-容器-set与multiset C++的set https://www.cnblogs.com/LearningTheLoad/p/7456024.html STL标准库-容器-map和 ...

  8. C++的STL标准库学习(queue)队列(第四篇)

    queue容器基本概念 Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素.  也就是说输入的数据要 ...

  9. C++的STL标准库学习(stack)栈

    stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示.stack容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法 ...

最新文章

  1. 拆解交易系统--模块拆解与服务化
  2. Spark - 大数据Big Data处理框架
  3. git 常用的撤销操作
  4. 跳出内层循环 使用 for of 代替 map
  5. 【Express】—post传递参数
  6. 使用mingw编译log4cpp--问题整理
  7. 二进制包20分钟快速安装部署 Kubernetes v1.14.0 集群
  8. win10家庭中文版安装win7虚拟机
  9. 哈佛幸福课--部分笔记
  10. 玛雅日历和2012年12月,whats的新政
  11. Udacity机器学习入门笔记——数据集与问题
  12. Air Jordan 32 Performance Review
  13. docker安装和一些基本操作
  14. 翻转课堂学习总结集—2015级
  15. 骞云科技 DevOps 实践
  16. Android 流量球效果的WaveView
  17. android 7.0 手机拍照裁剪问题
  18. 射雕英雄传》中的爱情-孔庆东
  19. Costco最全新零售指南
  20. Lasso算法理论介绍

热门文章

  1. scrapy框架使用piplines、items进行提取项目并保存数据
  2. Python爬取豌豆荚软件分类
  3. python写网站和java写网站的区别-做Web开发,编程语言用Python还是Java好呢?
  4. VnlnHub hacksudo-Thor
  5. MySQL查询某段日期内某段时间内的数据
  6. 【PIL】图像分割竞赛中8位深调色板模式的mask读取与保存(PASCAL VOC)
  7. 从零设计开发快递自动填写收发货地址功能
  8. HashMap面试题
  9. 知道很多大道理,却依旧无法过上想要的生活?
  10. 【机器视觉】教你选择工业机器人视觉系统!