1.泛型算法:
定义:标准库没有给每个容器定义一套增删改查的算法,而是定义了一组算法。这些算法是通用的(泛型的),大多是都独立于 任何容器。
一般情况,这些算法不不直接操作容器,而是遍历两个迭代器指定的一个元素范围来进行操作。
大多数算法都定义在algorithm中,标准库还在numeric.h中定义了一组数值泛型算法。
- 标准库算法find auto result = find(vec.begin(), vec.end(), val); // 查找值为val的,找到返回结果指向它,没找到结果为vec.cend()。
使用迭代器递增运算,可以使算法不依赖于容器。
1.只读算法:
- accumulate(累加函数)在numeric.h中定义 int nSum = accumulate(vec.begin(), vec.end(), 0); // 第三个参数为初始值,同时也决定了函数使用哪个加法运算符以及返回类型。
- equal(比较两个序列是否保存相同的值) equal(vec1.cbegin, vec1.cend(), vec2.cbegin()); // 只接受单一迭代器表示第二个序列的算法,都假定第二个序列的长度小于等于第一个序列。vec1和vec2不必一定是相同类型的容器,只需要满足元素可以使用==操作符即可(因此string元素和char*元素也可以一起比较)。
2.写容器元素的算法:
- fill(写入元素) fill(vec.begin, vec.begin+vec,size()/2, 10); // 将前一部分的元素赋值为1
3.插入迭代器:
一种向容器中添加元素的迭代器。
通常,通过一个迭代器向容器元素赋值时,赋值迭代器指向的元素被赋予该值。通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中。
- back_inserter(创建一个插入迭代器,定义在iterator.h) auto it = back_inserter(vec); *it = 42; // vec添加了一个值为42的新元素
- copy(拷贝算法) copy(begin(a1), end(a1), a2); // 将a1中的元素拷贝给a2(目的序列的起始位置) 返回目的位置迭代器(递增后)的值。即为尾元素之后的位置
- replace(所有等于定值得元素都改为另一个值) replace(lst.begin(), lst.end(), nFindValue, nNewValue) 前两个表示迭代器序列,第三个是要找的值,第四个为替换的值。
- replace_copy(改值+拷贝) replace_copy(lst.begin(), lst.end, back_inserter(vec), nFind, nNew); // lst中的元素不会被改值,而vec为lst的值得拷贝,但是已经被替换过了。
- sort(重排) sort(words.begin(), words.end());
- unique(去重) auto end_unique = unique(words.begin(), words.end()); // 将不重复的元素挪到前面。大小未改变,只是覆盖相邻的重复元素。
4.谓词:
一个可调用的表达式,返回结果是能用作条件的值,如Bool。
谓词:只接受单一参数。 二元谓词:有两个参数。
sort有一个版本,接受第三个参数。这个参数为谓词,该谓词可替代<来比较元素。
5.lambda表达式:
- find_if(查找满足谓词的元素) find_if(vec.begin(), vec.end(), 谓词); // 如果有一个元素试谓词不为0,则返回该元素,否则返回尾迭代器。
对于一个对象或一个表达式,如果可以对其使用调用运算符(.)则称它为可调用的。
四种可调用对象:函数、函数指针、重载了函数调用运算符的类和lambda表达式。
可以向一个算法传递任何类别的可调用对象。
lambda表达式表示一个可调用的代码单元,可以理解为未命名的内联函数。
[捕获列表] (参数列表) -> return type {函数体} // 参数不能有默认参数 必须使用尾置返回
捕获列表:lambda所在函数中定义的局部变量的列表,用来指出lambda表达式中会用到的变量。
如果忽略返回类型,会根据函数体重的代码推断出返回类型,函数体只是一个return,则返回return后的类型,多条语句返回void。
6.lambda捕获与返回:
当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。
可以这样理解:向一个函数A传递一个lambda时,同时定义了一个新类型和该类型的一个对象。向A中传递的参数就是生成的类类型 的未命名对象。 注意:类 类型的对象!!!
从lambda生成的类,都包含一个数据成员为lambda所捕获的变量,类似普通类的数据成员。lambda的数据成员在lambda对象创建时被初始化。
值捕获 | 拷贝变量。与函数的参数是调用时拷贝,而被捕获的变量的值是在lambda创建时拷贝。 如果想改变这个局部变量的值,加上关键字mutable即可在lambda函数体内改变局部变量的值。 |
引用捕获 | 确保被引用的对象在lambda执行的时候是存在的。 可能的话,应避免捕获指针或引用。 |
隐式捕获:可以让编译器根据lambda中的代码来推断需要哪些变量。在捕获列表中写一个&或=,&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。
7.参数绑定:
bind标准库函数,定义在functionnal.h中。
auto newCallable = bind(callable, 参数1, 参数2) ;
bind函数可看作一个通用的函数适配器,可以接受一个可调用对象(callable),生成一个新的可调用对象(newCallable)来“适应”原对象的参数列表。
newCallable:新的可调用对象
callable:可调用对象
参数列表:对应callable的参数。也可以这么表示 _1, 6, _2, 9 _n为占位符 表示第n参数,传递给newCallable的参数的“位置”。6和9表示newCallable传递的值给callable的。
名字_n都定义在placeholders的命名空间中(该命名空间本身定义在std命名空间),该命名空间定义在functionnal.h(和bind函数一样)。
auto g = bind(f, a, b, _2, c, _1);
当调用g时,如果第一个参数为X,第二个参数为Y(即_1 = X, _2 = Y)。 实际调用g(X, Y),隐射到f为:f(a, b, Y, c, X);
bind使用时会拷贝参数,如果希望传递给bind一个对象而不拷贝它,则使用标准库的ref函数。
osstream &print(ostream &os, const string &s, char c){
return os << s << c;
}
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ‘ ’)); // 挨个打印words中的元素,并以空格分隔开。因为ostream不能拷贝,所以使用引用。
8.迭代器:
插入迭代器 | 被绑定到一个容器上,可用来向容器插入元素 |
流迭代器 | 被绑定到输入输出流上,可用来遍历有关联的IO流 |
反向迭代器 | 从尾元素到首元素反向移动的迭代器,除forward_list外都有反向迭代器 |
移动迭代器 | 不是拷贝其中的元素,而是移动它们 |
9.插入迭代器:
插入器是一种迭代器适配器,传入两个参数:容器和迭代器,向给定容器添加元素。有三种类型:
- back_inserter:创建一个使用push_back的迭代器。
- front_inserter:创建一个使用push_front的迭代器。
- inserter:创建一个使用insert的迭代器,必须接受第二个参数(迭代器),插入该迭代器之前 。
it = t; // it为插入迭代器, t为元素值
10.iostream迭代器:
虽然iostream类型不是容器,但标准库定义了可以用于这些IO类型对象的迭代器。
- istream_iterator:读取输入流。使用>>读取流,因此istream_iterator要读取的内容必须定义了>>输入运算符。
istream_iterator<int> int_in(cin); // 绑定到流上,用cin读取int
istream_iterator<int> int_eof; // 默认初始化迭代器,这样就创建了可以当尾后使用的迭代器
一旦其关联的流遇到文件尾后遇到IO错误,迭代器的值就与尾后迭代器相等。
++in,in++ 迭代器累加,使用元素类型所定义的>>从输入流中读取下一个值。
istream_iterator绑定到一个流时,标准库不保证立即读取数据,但是保证在使用前从流中读取数据的操作能完成。
- ostream_iterator:向一个输出流写数据。可以对任何具有输出运算符(<<)的类型定义ostream_iterator。
*out,++out,out++ 不对out做任何事,每个运算符都返回out。
11.反向迭代器:
有反向迭代器的前提是支持--运算,所以forward_list和流迭代器不能创建反向迭代器。
- rIter.base() 将反向迭代器转换成正向。由于迭代器表示的范围是左闭右开,所以转换后的正向迭代器的指向不同于转换前的反向迭代器去的指向,向后移了一个。
12.五类迭代器:
输入迭代器 | 只读,不写,单遍扫描,只能递增 |
输出迭代器 | 只写,不读,单遍扫描,只能递增 |
前向迭代器 | 可读写,多遍扫描,只能递增 |
双向迭代器 | 可读写,多遍扫描,可递增递减 |
随机访问迭代器 | 可读写,多遍扫描,支持全部迭代器运算 |
12.算法形参模型:
alg(begin, end, args) | |
alg(begin, end, dest, args) | 算法假定按需求写入数据,不管多少都是安全的。 dest是一个指向容器的迭代器,则写到dest容器中,若dest绑定插入迭代器则插入新元素,dest为ostream_iterator则将数据写入流中。 |
alg(begin, end, begin2, args) | |
alg(begin, end, begin2, end2, args) |
list和forward链表可以通过改变元素间的链接来快速“交换”元素,而不是真正的交换它们的值。这些链表版本的算法的性能要比对应的通用算法要好的多。