泛型算法
因容器的方法比较少, 为了扩展容器的方法, 标准库搞了一套算法,来实现排序/查找等高级功能, 因为是容器通用的所以称为泛型.
一般通过两个迭代器指定的元素范围来操作. 多数定义在头文件algorithm中.
算法可能会改变容器内元素的值或者移动容器内的元素, 但是不会改变容器的大小,即不会添加或删除元素
只读算法, 如find, count, accumulate(求和, numeric头文件中), equal
accumulate 在操作string时, 原书中将不可以使用字符串的字面值常量, 14中可以使用,但是结果不符合预期.
accumulate(v.cbegin(), v.cend(), string(" "));
accumulate(v.cbegin(),v.cend(), " "); // c++ 14 未报错, 但结果不如何预期
equal(first.begin(), first.end(), secord.begin()) // 判断first和second包含的元素是否相等
只接受一个单一迭代器来表示第二个序列的算法, 都假定第二个序列至少与第一个序列一样长.由此引申, equal以第一个序列循环.
- 如果第二个序列短于第一个, 则运行时报严重错误.
- 即使是第二个比第一个长,比第一个多的部分也不会参与比较.
写容器的算法: fill, replace
fill(v.begin(),v.end(),value)
fill_n(b.begin, n, value) 不能写空序列, 只能是修改,否则 错误
replace(v.begin(), v.end(), valueForSearch, newValue)
replace_copy(v.begin(), v.end(), back_insert(ivec), 0, 42) 替换后的序列保存到ivec中
介绍back_inserter 插入迭代器 在iterator头文件中, 用于插入元素
拷贝算法 copy
copy(v.begin(), v.end(), target.begin()) 返回copy后目的位置迭代器的值
重排容器元素算法 sort, unique
sort(v.begin(), v.end())
auto ret = unique(v.begin(), v.end()) // 返回一个指向不重复值 范围末尾的迭代器
定制操作 -- 自定义比较方法
谓词: 一个可调用的表达式, 其返回结果是一个能用作条件的值.
一元谓词: 只接受单一参数
二元谓词: 接收两个参数
向算法传递函数
排序算法
void elimDups(vector<string>& words) {
sort(words.begin(), words.end());
auto end_unique = unique(words.begin(), words.end());
words.erase(end_unique, words.end());
}
bool isShorter(const string& s1, const string& s2) { return s1.size() < s2.size(); }
elimDups(words); // 将words按照字典重排, 并消除重复单词
stable_sort(words.begin(), words.end(), isShorter); // 按照长度重排, 长度相同的单词维持字典序
lambda表达式 理解为一个未命名的内联函数,
与函数不同的地方是:
- 可以定义在函数内部
- 必须使用尾置返回来指定返回类型.
- 参数不能有默认值
[capture list](parameter list) -> return type {function body}
capture list 捕获列表, lambda所在函数中定义的局部变量
参数列表和返回类型可忽略, 捕获列表和函数体必须得有
lambda
值捕获: 赋值, 拷贝
引用捕获 赋值 引用 [&v1]
隐式捕获 自己推断, [=] 值捕获方式, [&] 引用捕获方式
混合方式 [&, parm], 当使用混合模式时, 捕获列表中第一个元素必须是一个&或者=
如果lambda中不是单一的return, 如果不指定返回值类型则默认为void返回类型.
int v1 =10;
auto f=[v1]() mutable {return ++v1;}
mutable 不可以改变v1的值, 其返回值是v1+1的副本, 并非v1 真正+1了, 如果不使用mutable, 则++v1, 会编译失败.
bind函数 (functional) auto newCallable = bind(callable, arg_list)
当调用newCallable时, newCallable会调用callable 并传递arg_list中的参数
auto check6 = bind(check_size, std::placeholders::_1, 6);
std::placeholders::_1 参数占位符
bind 函数 可以 改变目标函数的参数 个数, 顺序
当bind需要传递一个引用类型的参数时 需要使用ref/cref函数
再探迭代器
- 插入迭代器: 用于容器插入元素 *it, ++it, it++ 不会做任何事情
- 流迭代器: 输入输出流上,可以遍历关联的io流
- 反向迭代器 除了forward_list 之外都有反向迭代器, 向后移动而不是向前移动.
- 移动迭代器 只为移动容器中的元素.
vector<int> vec = { 1,2,3,4,5,6,7,8,9 };
ostream_iterator<int> out_iter(cout, " d ");
for (auto e : vec) *out_iter++ = e;
cout << endl;
copy(vec.begin(), vec.end(), out_iter); // copy 也可以输
反向迭代器会导致string的反向输出, 需要使用反向迭代器中的base()方法转换为普通迭代器.
string s = "First, middle, last";
auto rcomma = find(s.crbegin(), s.crend(), ',');
cout << string(s.crbegin(), rcomma) << endl; // 输出tsal
cout << string(rcomma.base(), s.cend()) << endl; // 输出last
cout << string(rcomma, s.cend()) << endl; // 编译错误
上面代码中的rcomma 和recomma.base() 指向的并不是一个位置
泛型算法结构
- 输入迭代器 只读不写, 单遍扫描, 只能递增
- 输出迭代器 只写不读, 单遍扫描, 只能递增
- 前向迭代器 可读写, 多遍扫描, 只能递增
- 双向迭代器 可读写, 多遍扫描, 可递增递减
- 随机访问迭代器 可读写, 多遍扫描, 支持全部迭代器运算
输入迭代器 如find, accumulate 要求输入迭代器. istream_iterator 是输入迭代器:
- 支持 == !=
- 支持++
- 支持 * 解引用
- 箭头运算符 -> 等价于(*it).member
输出迭代器 如copy的第三个参数, ostream_iterator 是输出迭代器:
- 支持 ++
- 支持 *, 只出现在赋值运算符的左侧
- 只能向一个输出迭代器赋值一次
前向迭代器 replace要求前向迭代器 , forward_list 上的迭代器是前向迭代器
双向迭代器 除了forward_list之外, 其余的都能提供双向迭代器. reverse要求双向迭代器.
随机访问迭代器
- 支持 < <= > >=
- 支持 迭代器与整数 +, +=, -, -=
- 支持 两个迭代器上的减法运算符
- 支持 下标运算符
高级别迭代器支持低层类别迭代器的所有操作
array, deque, string, vector 都是随机访问迭代器
算法的形参模式
- alg(beg, end, other args);
- alg(beg, end, dest, other args);
- alg(beg, end, beg2, other args);
- alg(beg, end, beg2, end2, other args);
beg, end,表示操作范围, dest 一般表示可以写入的目的位置的迭代器. beg2, end2 表示第二个输入范围. 如果只有beg2, 则beg2的范围至少比beg, end指定的范围大.
算法命名规范
一般都会接受一个第三个参数作为谓词用于替换<, ==.
_if 谓词版本的函数.
_copy, 一般算法的操作都是直接作用的第一对迭代器代表的范围上, 而_copy版本 则会返回一个新的容器.
特定容器的算法
链表类型: list forward_list
- lst.merge(lst2) lst2 抽取lst1, 抽取后lst2 变空
- lst.merge(lst2, comp)
- lst.remove(val) 调用erase删除与给定值相等的每个元素
- lst.remove_if(pred) 上述的谓词版本
- lst.reverse() 反转
- lst.sort() 升序排序
- lst.sort(comp)
- lst.unique() 去重
- lst.unique(pred)
链表特有的操作会改变容器. 如通用版本的merge, 不会改变参数中的容器, 而链表版本的merge会销毁参数中容器.
splice 实现list拼接的功能。将源list的内容部分或全部元素删除,拼插入到目的list
- void splice ( iterator position, list<T,Allocator>& x );
- void splice ( iterator position, list<T,Allocator>& x, iterator it );
- void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );
position 是要操作的list对象的迭代器
list&x 被剪的对象
对于一:会在position后把list&x所有的元素到剪接到要操作的list对象
对于二:只会把it的值剪接到要操作的list对象中
对于三:把first 到 last 剪接到要操作的list对象中
list<int> a = { 1,2,3 };
list<int> b = { 4,5,6 };
// a.splice(a.end(), b); // a: 123456
// a.splice(a.end(), b, (++b.begin())); // a: 1235
a.splice(a.end(), b, ++b.begin(), b.end()); // a: 12356
ostream_iterator<int> out_iter(cout," ");
copy(a.cbegin(), a.cend(), out_iter);