1. 泛型算法是独立于容器的;泛型算法只通过迭代器与容器联系,所以泛型算法不执行容器操作,也绝对不会改变容器的大小。泛型算法通过插入迭代器向容器中插入内容;通过流迭代器从输入流读取数据或者向输出流写入数据。
2. 泛型算法可分为三类:
3. 谓词(谓词函数):一些泛型算法允许用户自定义函数取代默认的比较操作符来判断迭代器范围内的值是否满足条件,这种函数被称为谓词。谓词包含接受一个参数的一元谓词和接受两个参数的二元谓词。
4. 可调用对象:对于一个对象,如果可以对它使用调用操作符(也就是一对括号),那么就称为“可调用对象”,包括:
5. lambda表达式:
[捕获列表] (参数列表) mutable throw() -> 尾置返回类型 { 函数体 }
void func() { int i = 0, j = 1; auto func1 = [i, &j]{return i+j;} //ok,值捕获i,引用捕获j i++, j++; int k = func1(); //k等于2,值捕获的i在lambda函数定义时被确定为0,引用捕获的j在调用时确定为2 auto func2 = [i]{return ++i;} //错误,i不可修改 auto func3 = [i]mutable{return ++i;} //ok,有mutable声明可以修改值捕获的i auto func4 = [&j]{return ++j;} //ok,引用捕获的j可以修改,并且调用时会影响到外侧 int kk = func4(); //lambda内部引用被修改,j现在是3 auto func5 = [=,&j]{return i+j;} //ok,自动值捕获i auto func6 = [i, &]{return i+j;} //错误,自动捕获必须在前 auto func7 = [&,&j]{return i+j;} //错误,混合使用时,自动捕获必须和命名捕获类型不一致 auto func8 = [i,&j]{cout<<i+j;} //ok,可以使用全局的cout对象,不需要捕获 int *p = new int[10]; p[0] = 1; auto func9 = [p]{cout<<p[0];} //ok delete p; func9(); //错误,p已经被释放 }
auto func1 = []{return 18;} //返回类型自动推断为int auto func2 = []{cout<<"Hello lambda!";} //返回类型自动推测为void auto func3 = []{cout<<"Hello lambda!";return 18;} //错误,自动推断返回类型为void auto func3 = []->int{cout<<"Hello lambda!";return 18;} //ok
size_t v1 = 42; auto f1 = [v1]{return v1;}; //值捕获v1,此时lambda内部的值为42 v1 = 0; //修改了v1的值,但是由于已经初始化过,所以对lambda对象f1里面的v1没有影响 size_t v2 = f1(); //v2是42 size_t v3 = 42; auto f2 = [&v3]{return v3;}; //引用捕获v3,将v3的地址传入 v3 = 0; //修改了v3的值,由于传入的是地址,所以lambda对象f2内部的值也被修改了 size_t v4 = f2(); //v4是0
注意:参数列表为空时可以省略,mutable和throw()不需要都可以省略,返回类型自动推断时也可以省略,捕获列表和函数体无论如何都不能省略,最简lambda函数:[ ]{ }
6. 参数绑定:c++11中标准库增加了一个bind函数,它返回一个可调用对象。bind是一个函数适配器,它可以改变一个函数的调用方式(比如改变参数的个数或者参数的顺序),来适应原对象的参数列表(例如:某些泛型算法要求传入单参函数)。bind使用占位符“_n”代表调用时的第n个参数,_1第一个参数、_2第二个参数....... (这些占位符在std::placeholders中定义)把引用参数传递给bind函数时,要使用标准库的 ref 或者 cref 函数。
bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); } auto func1 = bind(isShorter, _1, "test"); //ok,func1是个单参函数对象,它的行为就是使用自己的第一个参数和“test”调用isShort bool bRet = func1("abc"); //ok auto func2 = bind(isShorter, _2, _1); //ok,交换两个参数的传入次序 void swap(int &a, int &b) { int temp = a; a = b; b = temp; } auto func3 = bind(swap, ref(_1), ref(_2)); //ok,使用ref 保持了引用特性的传入
7. 插入迭代器:插入迭代器不同于普通迭代器,为插入迭代器的解引用赋值(*it = val)具有插入的效果,会为容器插入一个指定值。back_inserter函数会产生一个效果等同push_back的插入迭代器,需要目标容器支持push_back函数;front_inserter函数会产生一个效果等同push_front的插入迭代器,需要目标容器支持push_front函数;inserter函数会产生一个效果等同insert插入迭代器,需要目标容器支持insert函数。
8 .流迭代器:iostream也具有迭代器istream_iterator和ostream_iterator,其中istream_iterator只能用于读取数据,ostream_iterator只能用于写数据。流迭代器是模板,只要某一类型支持>>就可以定义istream_iterator,支持<<就可以定义ostream_iterator。对istream_iterator使用++就会持续读出数据;对ostream_iterator使用=(赋值)就会向输出流写数据,ostream_iterator支持++但没有什么作用。istream_iterator定义时不提供参数则生成流末尾下一位的迭代器(流结束的eof),ostream_iterator没有末尾可无限输出。
9. 逆序迭代器:逆序迭代器与正常的迭代器正好相反,适合从尾到头访问一个容器。特别需要注意的是,逆序迭代器是右闭合,所以与正常迭代器的转换为:rbegin = end - 1,rend = begin - 1,也可以通过riter.base()来取得相对应的正常迭代器的值。
10. 五种迭代器:迭代器可以分为五种
名称 | 支持操作 | 举例 |
输入迭代器 | == != ++ * ->,单向只读(不可比较大小,不能后退) | istream_iterator |
输出迭代器 | ++ *,单向只写(不可比较大小,不能后退,不能判断相等) | ostream_iterator |
单向迭代器 | == != ++ * ->,单向读写(不可比较大小,不能后退) | forward_list |
双向迭代器 | == != ++ -- * ->,双向读写(不可比较大小) | list map set |
随机访问迭代器 | == != ++ -- * -> + += - -= > >= < <= [],随机读写(全部运算) | vector deque string |
除了输出迭代器之外,其余四个排成一个由低到高的序列,算法要求的迭代器类型由低向高兼容。
11. forward_list和list容器的迭代器不是随机访问迭代器,不可随机访问,所以对于它们使用泛型函数效率很低。在forward_list和list中,定义了很多成员函数来补充这一空缺,比如说sort、unique 、reverse、merge、remove等等,forward_list还特有splice函数实现元素的“搬家”,使用时需要注意。
12. 关联容器分为有序容器(基于红黑树实现)和无序容器(基于hash表实现),包含存储key-value的map(a.k.a. 关联数组)和只存储key的set,map和set分为允许key重复的multimap、multiset和不允许key重复的map、set。2×2×2共计8种:map、set、multimap、multiset、unorder_map、unorder_set、unorder_multimap和unorder_multiset。
13. 有序关联容器的键(key)要求严格弱排序,即:对于任意两个key的对象k1和k2,他们之间要么属于<(小于),要么属于=(等于),不可能存在第三种关系。所以使用迭代器访问有序关联容器中的内容时,是按照key的顺序访问的;但是对于无序关联容器,使用迭代器访问时key是无序的。有序容器默认使用<操作符,我们指定使用自己定义的操作。
class A{...} bool compareA(const A& a1, const A& a2){...} multiset<A, decltype(compareA)*> someset(compareA); //ok,使用自定义比较操作
14. map容器的迭代器解引用后得到pair类型,pair类型在utility头文件中定义,pair的两个数据成员(first和second)是public的,可以使用构造函数、makepair调用以及列表初始化来创建一个pair对象。
map<string, int> word_counter; auto it = word_counter.begin(); //*it的类型是pair<string, int> auto p1 = make_pair("abc", 3); //ok,make_pair返回pair<string, int>类型 pair<string, int> p2("abc", 3); //ok,直接调用构造函数 pair<string, int> p3 = {"abc", 3}; //ok,列表初始化
15. 关联容器中定义了key_type、mapped_type和value_type三种类型,key_type是关联容器的关键字的类型,mapped_type是map容器中值的类型,value_type是关联容器中保存的元素的类型(set中等于key_type,map中是pair<const key_type, mapped_type>类型)。注意:map的value_type中的关键词都是const的,不允许修改。另外,使用迭代器访问map和set时,不允许修改key内容(对于map只允许修改value,对于set则是完全不能修改)。
16. 下标操作符:对map使用下标操作符时应当填入key值,该操作符返回一个value的左值(mapped_type的引用),如果该key不存在,那么map会自动添加该key-value对,并将value默认初始化。所以,为了避免使用下标操作符导致的插入行为,可以使用count 或者find 函数确定一个key是否存在map中。set容器不支持下标操作符,multimap也不支持下标操作符。
17. 在有序multimap和multiset中,允许key出现多次,同一个key将被连续的存储。使用upper_bound和lower_bound能够获得其中一个key的迭代器范围,使用equal_range也可以达到同样的目的。
multimap<string, string> somemultimap; ........... //为multimap填充数据,省略 //使用lower_bound和upper_bound for(auto beg=somemultimap.lower_bound(key), end=somemultimap.upper_bound(key); beg!=end; ++beg) { //do some thing } //使用equal_range,等价于使用lower_bound和upper_bound for(auto pos=somemultimap.equal_range(key); pos.first!=pos.second; ++pos.first) { //do some thing }
18. 无序容器使用hash表实现,一个hash值对应一个桶,相同hash值的元素保存在同一个桶之中。unorder容器提供了一系列桶管理函数,我们可以使用它们提升unorder容器的性能。
19. 无序容器对关键字类型有两个要求: