查找元素、替换或移除值、重新排序……都属于这一范畴。
10.1 Overview
----------------------------------------------------------------
使用这些算法时,请包含下面的头文件:
#include <algorithm>
#include <numeric>
算法并不直接作用于容器,而是作用于容器的迭代器,比如:
int val = 42;
auto result = find(vec.begin(), vec.end(), val);
(???此处使用cbegin和cend会报错。)
有了迭代器,独立于容器的算法才成为可能。
算法虽然独立于容器,但是它依赖于容器的元素类型。
算法不会执行容器的operations,它不会增加或删除容器的元素。
10.2 A First Look at the Algorithms
----------------------------------------------------------------
虽然库提供了100多种算法,但是它们和容器类似,具备一致的结构,易于理解和使用。
1. 有一些算法是只读的,比如find、count和accumulate。
auto result3 = accumulate(vch.begin(), vch.end(), 0);
string sum = accumulate(v.begin(), v.end(), string(""));
(accumulate的第三个参数类型,确定了相加之和的类型。)
还有一种只读的算法是equal:
equal = (roster1.begin(), roster1.end(), roster2.begin());
roster1和roster2的容器类型可以不一样,只要它们的元素可以比较即可。
2. 有一些算法是可写的,比如fill或fill_n:
fill(vec.begin(), vec.end(), 10);
一般来说,算法无法添加或者删除元素,但是使用insert iterator可以。
back_inserter使用容器的引用作为参数,返回insert iterator,从而调用push_back方法:
auto it = back_inserter(vch);
*it = 5;
我们可以使用back_inserter来创建迭代器,用作算法的destination:
fill_n(back_inserter(vch), 5, 7);
for (auto &r : vch)
cout << r << endl;
copy、replace也是可写的算法。
3. 有一些算法用来重新排序,比如sort:
vector<string> words = {"hello", "world", "hello", "maria"};
sort(words.begin(), words.end());
for (auto &r : words)
cout << "a: " << r << endl;
虽然算法不能直接操作容器(不能添加或删除元素),但它可以调用容器的方法。
“算法基于迭代器来操作以实现泛型,而当需要在容器中添加或删除元素时, 不知道容器和容器元素的具体
类型,就不可能知道需要增加或减少多少空间,就无法实现容器添加或删除元素的目的。 添加或删除容器元
素的操作是与类型强相关的,而泛型的算法做不到这点。” —— (知乎)南friend
10.3 Customizing Operations
----------------------------------------------------------------
我们可以定制自己的算法。
重载的一种sort函数,使用了第三个参数作为predicate(谓词),它是一个表达式,返回conditon值。
predicates可以有一个参数(unary predicates),或两个参数(binary predicates)。
比如:
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
int main(int argc, char *argv[])
{
vector<string> words = {"hello", "world1", "hello111", "maria1111", "apple"};
sort(words.begin(), words.end(), isShorter);
for (auto &r : words)
cout << "a: " << r << endl;
return 0;
}
执行的结果是:
a: hello
a: apple
a: world1
a: hello111
a: maria1111
使用stable_sort算法,可以在根据predicates排序的基础上,再根据alphabetical排序。
有时候我们需要给predicates输入更多的参数,此时可以使用Lambda表达式。
predicates可以是任何能够使用call方法的表达式,目前我们使用的是functions,此外还有两种:
* classes that overload the function-call operator
* lambda expressions
lambda表达式可以被认为是,一种未命名的inline函数:
[capture list] (parameter list) -> return type { function body }
capture list:(定义此lambda表达式的)函数的局部变量。
parameter list:参数的参数列表。
return type:使用trailing return来指定返回值的类型。
比如下面这段代码:
auto f = [] {return 42;};
cout << f() << endl;
char c = ' ';
ostream &os = cout;
auto ff = [&os, c](const string &s) {os << s << c;};
ff("hello");
ff("world");
lambda表达式不可以有默认的参数。
现在我们就可以使用lambda表达式来解决predicates的问题了:
vector<string>::size_type sz;
sz = 5;
stable_sort(words.begin(), words.end(),
[sz](const string &a, const string &b) {return a.size() >= sz;});
lambda表达式的创建,实际上是一个(未命名的)新类的创建。
它即创建类的type,又创建了一个类的实体,lambda的capture就是此类的变量成员。
capture list传递的不是变量地址,而是变量的值,因此它的值在lambda表达式创建时就确定了,
如果要传递变量的话,使用引用即可。
lamdba表达式可以作为函数的返回值。
由于lambda表达式的特点,我们最好将它的capture list保持得简单直接一些。
如何告诉编译器,此lambda表达式通通使用变量的值:
auto sz = s.size();
auto wc = find_if(words.begin(), words.end(),
[=](const string &s) {return s.size() >= sz;});
如何告诉编译器,此lambda表达式通通使用变量的引用:
auto sz = s.size();
auto wc = find_if(words.begin(), words.end(),
[&](const string &s) {return s.size() >= sz;});
另外,它们可以混合使用:
[&, c]
[=, &os]
实际上,由于算法经常需要使用predicates,所以它和lambda表达式经常紧密的结合。
虽然lambda表达式在需要使用local变量的时候有着优势,但我们仍然可以通过函数来实现它:
auto newCallable = bind(callable, arg_list);
比如:
#include <functional>
using namespace std;
using namespace std::placeholders;
bool check_size(const string &s, string::size_type sz)
{
return s.size() >= sz;
}
int main(int argc, char *argv[])
{
auto check6 = bind(check_size, _1, 6);
//auto check6 = bind(check_size, std::placeholders::_1, 6);
}
(???由于GCC版本问题,bind未能解析。)
10.4 Revisting Iterators
----------------------------------------------------------------
插入迭代器:
*it = val;
等于:
it = c.insert(it, val);
++it;
流迭代器:
vector<int> vec;
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while (in_iter != eof)
vec.push_back(*in_iter++);
for (auto &a : vec)
cout << a << endl;
还可以更简单的写成:
istream_iterator<int> in_iter(cin), eof;
vector<int> vec(in_iter, eof);
(* 为什么要把流写成迭代器的形式呢?因为算法需要迭代器啊!)
(* 更复杂的用法,以后用到的时候再详细学习。)
反向迭代器:
for (auto r_iter = words.rbegin(); r_iter != words.rend(); ++r_iter)
cout << *r_iter << endl;
反向迭代器便于算法的反向操作。
反向迭代器不支持forward_list和流迭代器。
另外,反向迭代器的base()和它指向的是相邻的元素,而不是同一个。
10.5 Structure of Generic Algorithms
----------------------------------------------------------------
迭代器可以分为5类。
算法的命名是遵循一定规律的。
10.6 Container-Specific Algorithms
----------------------------------------------------------------
list和forward_list定义了自己的算法,比如sort、merge、remove、reverse和unique。
它们对于自身的效率更高。
它们会改变容器本身。