版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79223273
C++ 学习笔记(10)泛型算法、lambda表达式、bind函数、迭代器
10.1 概述
- 算法永远不不会改变底层容器的大小。
10.2 初识泛型算法
10.2.1 只读算法
- accumulate,积累。第三个参数类型决定了函数中使用哪个加法运算符以及返回值类型。序列中元素必须与第三个参数匹配。
std::vector<int> v{ 1, 2, 3, 4 };
std::vector<string> vs{ "i", "am","eveything" };
// 累加 1+2+3+4 = 10
int sum = accumulate(v.begin(), v.end(), 0);
// 累成 1*2*3*4 = 24
int product = accumulate(v.begin(), v.end(), 1, std::multiplies<int>());
// 连接字符串 输出 -i-am-everything
string str = accumulate(vs.begin(), vs.end(), string(""), [](string a,string b) { return a + "-" + b; });
- 那些只接受一个单一迭代器表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
10.2.2 写容器元素的算法
- 如果第二个序列是第一个序列的子集,则程序会产生严重错误:equal会试图访问第二个序列列表末尾之后的元素。
10.2.3 重排容器元素的算法
- unique将所有重复的元素放到列表后面,返回指向第一个重复元素的迭代器。
10.3 定制操作
10.3.1 向算法传递函数
stable_sort:稳定排序,可以维持相等元素的原有顺序。
lambda表达式:未命名的内联函数。
[捕获列表] (参数列表) -> 返回类型 { 函数体 }
- 可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
- 不能有默认参数。
- 捕获列表只用于非static变量。
- 可以直接使用static变量和所在函数之外声明的名字。
- 捕获变量,可以是值或引用。
- 隐式捕获:
&
引用方式捕获所有局部变量,=
值的方式捕获。
- 可以用mutable改变一个被捕获的变量的值。
int a = 4;
auto f = [a] () mutable { return ++a; }; // 不加mutable将无法修改a
cout << f(); // 输出5
cout << a; // 输出4
10.3.4 参数绑定
std::bind:通用的函数适配器。
auto newCallable = bind(callable, arg_list);
- newCallable就是一个可调用对象。
- arg_list,callable的参数列表。
placeholders命名空间:如占位符
_1
,实际是std::placeholders::_1
。
using namespace std::placeholders;
void f(int a, int b, int c) { cout << a << " " << b << " " << c << endl; }
auto g = bind(f, 666, _2, _1);
g(10,20); // 实际调用 f(666, 20, 10);
- bind参数不能传递引用,可以使用标准库的
ref
函数。
bind(print, ref(os)); // os是ostream对象,输出流不能拷贝,所以用引用。
10.4 再探迭代器
迭代器 (Iterator)
- 插入迭代器(insert iterator):绑定到容器,可以向迭代器插入元素。
- 流迭代器(stream iterator):绑定到输入输出流,可以遍历IO流。
- 反向迭代器(reverse iterator):往后移动的迭代器,forward_list没有这个迭代器。
- 移动迭代器(move iterator):用于移动对象。
10.4.1 插入迭代器
- back_inserter:使用
push_back
的迭代器。 - front_inserter:使用
push_front
的迭代器。 - inserter:使用
insert
的迭代器,第二个参数为指定迭代器位置。
std::vector<int> v{1, 2, 3, 4};
std::fill_n(std::back_inserter(v), 3, -1); // 在列表后面插入三个-1
for (int n : v)
std::cout << n << ' '; // 输出:1 2 3 4 -1 -1 -1
auto it = std::inserter(v,v.begin()); // 前向迭代器。下面几种的输入都一样(*it、++it、it++)。
it = 66;
*it = 22;
++it = 44;
it++ = 55;
for (int n : v)
std::cout << n << ' '; // 输出:66 22 44 55 1 2 3 4 -1 -1 -1
10.4.2 iostream迭代器
- istream_itertor:读取输入流。标准库不保证迭代器立即从流中读数据,但可以保证使用前,已经完成读取流的数据。
- 创建时绑定到流中,作为输入。
- 创建时使用默认初始化,则可以当作尾后值。
- ostream_itertor:向输出流写数据。
- 必须绑定到流中,不允许空的或表示尾后位置的ostream_iterator。
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl; // 输出结果为:计算输入的整形的和,直到文件末端或错误字符。
std::vector<int> v{ 1, 2, 3, 4 };
ostream_iterator<int> out(cout, " "); // 每次写入后加一个空格=
for (auto e : v) // 将列表的所有数值直接写入cout。
out = e;
cout << endl;
10.4.3 反向迭代器
- 反向迭代器需要递减运算符。
10.5 泛型算法结构
10.5.1 5类迭代器
- 输入迭代器要求:
- 用于比较两个迭代器的相等和不相等运算符(==、!=)
- 用于推进迭代器的前置和后置递增运算(++)
- 用读取元素的解引用运算符(*);只出现在右侧
- 箭头运算符(->),等价 (*it).member,即解引用迭代器
- 输出迭代器要求:
- 用于推进迭代器的前置和后置递增运算(++)
- 解引用运算符(*),只出现在左侧
- 前向迭代器要求:
- ==, !=, ++, *, ->
- 双向迭代器要求:
- ==, !=, ++, –, *, ->
- 随机访问迭代器要求:
- 用于比较两个迭代器相对位置的运算符(<、<=、>、>=)
- 迭代器和整数值的加减运算(+、+=、-、-=),计算结果是迭代器位置的移动。
- 用于两个迭代器上的减法运算符(-),得到两个迭代器的距离
- 下标运算符(iter[n]),与*(iter[n])等价
10.6 特定容器算法
- list 和 forward_list,应该优先使用成员函数版本的算法,而不是通用算法。
- 链表版本的算法与通用的区别:会改变底层的容器结构,如会删除一些元素。