泛型算法:可以用于不同类型的元素和多种容器类型的实现了一些经典算法的公共接口。
泛型算法本身不会执行容器的操作,只会运行于迭代器之上,执行迭代器(iterator)的操作。故 算法永远不会改变底层容器的大小。算法可能会改变容器中元素的值,可能在容器内移动元素,但永远不会直接添加或删除元素。有一类特殊的迭代器,称为插入器(inserter),能在底层容器上执行插入操作,算法可以操作这样的迭代器来达成向容器内添加元素的效果,但算法自身永远不会执行这样的操作。
泛型算法结构:
除少数例外,标准库算法都对一个范围内的元素操作,我们将此范围称为“输入范围”。接受输入范围的算法总是使用前两个参数来表示此范围,分别是指向首元素和尾元素之后位置的迭代器。
对于只读取而不改变元素的算法,迭代器最好使用cbegin()和cend()。若计划使用算法返回的迭代器来改变元素的值就需要使用begin()和end()。
输入迭代器 | 只读,不写;单遍扫描,只能递增。 必须支持: 比较两个迭代器相等和不相等的运算符(==、!=); 迭代器的前置和后置递增运算(++); 读取元素的解引用运算符(*),只作右值; 箭头运算符(->),等价于(*it).member,即解引用迭代器,并提取对象的成员。 |
输出迭代器 | 只写,不读;单遍扫描,只能递增。 必须支持: 迭代器的前置和后置递增运算(++); 解引用运算符(*)只作左值。 |
前向迭代器 | 可读写;多遍扫描,只能递增。 支持所有输入和输出迭代器的操作。 |
双向迭代器 | 可读写;多遍扫描,可递增可递减。 支持所有前向迭代器的操作; 前置和后置递减运算符(--)。 |
随机访问迭代器 | 可读写;多遍扫描,支持全部迭代器运算。 支持所有双向迭代器的操作; 比较两个迭代器相对位置的关系运算符(<、<=、>、>=); 迭代器和一个整数值的加减运算(+、+=、-、-=); 两个迭代器的减法运算符(-)得到其距离;下标运算符(iter[n])。 |
大多数算法具有如下4种形式之一:
alg (beg, end, other args);
alg (beg, end, dest, other args);
alg (beg, end, beg2, other args);
alg (beg, end, beg2, end2, other args);
其中,alg是算法名字,beg和end表示算法的输入范围,是否有other args依赖于要执行的操作。dest、beg2和end2都是迭代器参数,分别承担指定目的位置和第二个范围的角色。
dest若是直接指向容器的迭代器那么算法将输出数据写到容器中已存在的位置上,更常见的情况是dest被绑定到一个插入迭代器或输出流迭代器,会将新元素添加到容器中或写入到一个输出流。
向输出迭代器写入数据的算法都假定目标空间足够容纳写入数据。
算法命名:
一些算法使用重载形式传递谓词:接受谓词参数来代替<或==运算符的算法和不接受额外参数的算法通常都是重载的函数。
unique (beg, end); //使用==比较元素
unique (beg, end, comp); //使用comp比较元素
_if版本的算法:接受一个元素值的算法通常都有一个不同的接受一个谓词代替元素值的版本(非重载),接受谓词参数的算法都有附加_f的前缀。
find (beg, end, val); //查找范围中val第一次出现的位置
find (beg, end, pred); //查找第一个满足pred为真的元素
_copy版本的算法:将元素写到一个指定的输出目的位置而非输入范围。
reverse (beg, end); //反转范围内元素
reverse (beg, end, dest); //将元素逆序拷贝到dest
也有同时提供_copy和_if版本的算法
remove_if (beg, end, pred); //删除范围内满足pred的元素
remove_copy_if (beg, end, dest, pred); //将删除后的元素拷贝到dest
只读算法:
find (iterator1, iterator2, val) | 查找算法,iterator1和iterator2两个迭代器表示元素范围,find将范围中每个元素和给定值val进行比较,返回指向第一个等于给定值的元素的迭代器,若是无匹配元素,则返回第二个参数来表示搜索失败。 |
find_if (iterator1, iterator2, predicate) | 查找第一个具有指定条件的元素,对每个元素调用第三个参数给定的谓词,返回第一个使谓词返回非0值的元素,若不存在,返回尾迭代器。 |
count (iterator1, iterator2, val) | 计数算法,算法count与find类似,返回值是val在元素范围内出现的次数。 |
accumulate (iterator1, iterator2, val) | 求和算法,前两个参数表示元素范围,第三个参数是和的值,第三个参数的类型决定了函数中使用那个加法运算符及返回值的类型。 |
equal (iterator1, iterator2, iterator3) | 如果两个序列所有对应元素都相等返回true,否则false前两个参数表示第一个序列中的元素范围,第三个参数表示第二个序列的首元素。第二个序列至少与第一个序列一样长 |
写容器元素的算法:
fill (iterator1, iterator2, val) | 写入算法,前两个参数表示元素范围,将第三个参数的值赋给输入序列的每个元素。 |
fill_n (iterator, n, val) | 三个参数分别是单迭代器、计数值、值,将val赋给iterator指向的元素开始的n个元素。iterator开始的序列至少包含n个元素,否则结果是未定义的。 |
copy (iterator1, iterator2, iterator3) | 拷贝算法,将前两个参数表示的输入序列中的元素写入目的序列,第三个参数表示的目的序列的起始位置。函数返回目的位置迭代器的值(即指向拷贝到目的序列的尾元素之后的位置的迭代器)。传递给copy的目的序列至少要包含与输入序列一样多的元素。 |
replace (iterator1, iterator2, val1, val2) | 改写算法,将前两个参数表示的元素范围内所有等于val1的元素替换为val2。 |
replace_copy (iterator1, iterator2, iterator3, val1, val2) | 原序列不变,将repalce后的序列保存在第三个参数指向的序列。 |
sort (iterator1, iterator2); | 排序算法,将前两个参数表示的元素范围内元素从小到大的顺序重新排序。可通过增加第三个参数改变排序方式。 |
stable_sort (iterator1, iterator2) | 稳定排序算法,维持相等元素的原有顺序。 |
unique (iterator1, iterator2) | 将前两个参数表示的元素范围内的重复元素“消除”,返回指向无重复值范围末尾的迭代器。原序列大小未变,但无重复元素的位置之后的元素仍然存在,但不知道它们的值是什么。 |
for_each (iterator1, iterator2, predicate) | 此算法接受一个可调用对象,并对输入序列中每个元素调用此对象。 |
transform (iterator1, iterator2, iterator3, predicate) | 算法对输入序列中每个元素调用可调用对象,并将结果写到目的位置。 |
lambda表达式
谓词:可调用的表达式,其返回结果是一个能用作条件的值。
标准库算法所使用的谓词分为两类:一元谓词(只接受单一参数)和二元谓词(有两个参数)。根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但是有时我们希望进行的操作需要更多参数,超出了算法对谓词的限制。
我们可以向一个算法传递任何类别的可调用对象(可以对其使用调用运算符的对象或表达式)。可调用对象有四种:函数、函数指针、重载了函数调用运算符的类、lambda表达式。
一个lambda表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数。与函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。
lambda表达式的形式:
[capture list] (parameter list) -> return type { function body }
capture list(捕获列表)使一个lambda所在函数中定义的局部变量的列表;parameter list、return type、function body分别表示参数列表、返回类型、函数体。但lambda必须使用尾置返回类型(-> return type)来指定返回类型。
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
auto f = [] { return 42};
cout << f() << endl; //打印42
在lambda中忽略括号和参数列表等价于指定一个空参数列表。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果lambda的函数体包含任何return语句之外的内容,且未指定返回类型,则返回void。
1、parameter list
lambda不能有默认参数,实参数目永远与形参数目相等。
下面一个带参数的lambda与isShorter功能相同
bool isShorter (const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
[] (const string &s1, const string &s2) { return s1.size() < s2.size(); }
2、capture list
一个lambda通过在捕获列表中列出来指出将会使用的局部变量。
捕获方式:
[] | 空捕获:捕获列表若为空,说明lambda不使用他所在函数中的任何局部变量。 |
[names] | namse是一个逗号分隔的名字列表,名字都是lambda所在函数的局部变量。默认情况下是值捕获(与传值参数类似),名字前使用&则是引用捕获(与其他类型的引用的行为类似) |
[=] / [&] | 隐式捕获:通过在捕获列表中写一个&或=,指示编译器推断捕获列表。=表示值捕获,&表示引用捕获 |
[=, identifier_list] | 任何隐式捕获的变量都采用值捕获方式捕获,identifier_list中不能包括this且必须是引用捕获。 |
[&, identifier_list] | 任何隐式捕获的变量都采用引用捕获方式捕获,identifier_list必须是值捕获。 |
void func()
{
size_t val = 42; //定义一个局部变量
auto f1 = [] { return val; }; //错误:val未捕获
//值捕获,将val拷贝到名为f2的可调用对象
auto f2 = [val] { return val; };
//引用捕获,对象f2包含val的引用
auto f3 = [&val] { return val; };
//隐式捕获,为值捕获方式
auto f4 = [=] { return val; };
val = 0;
auto ans = f2(); //ans为42,f2保存了创建时val的拷贝
ans = f3(); //ans为0,f3保存val的引用而非拷贝
ans = f4(); //ans为42,f2保存了创建时val的拷贝
}
3、可变lambda
若是希望能改变被值捕获的变量,必须在参数列表后加上关键字mutable,故可变lambda不能省略参数列表。一个引用捕获的变量是否可以修改依赖于此引用指向的是否是const类型。
void func()
{
size_t val = 42;
//f可改变它所捕获的变量的值
auto f = [val] () mutable { return ++val; };
//val是一个非cosnt变量
auto f2 = [&val] { return ++val; };
val = 0;
auto ans = f(); //ans为43
ans = f2(); //ans为1
}
4、指定返回类型
默认情况下,如果lambda的函数体包含任何return语句之外的内容,则返回void。
当我们需要为lambda定义返回类型时,必须使用使用尾置返回类型。
//使用transform算法和lambda将序列v1中每个元素的绝对值保存在v2中
transform (v1.begin(), v1.end(), v2.begin(),
[] (int i) { return i < 0 ? -i : i; });
//错误:不能推断lambda的返回类型
transform (v1.begin(), v1.end(), v2.begin(),
[] (int i) { if (i < 0) return -i; else return i; });
//正确:使用尾置返回类型
transform (v1.begin(), v1.end(), v2.begin(),
[] (int i) -> int { if (i < 0) return -i; else return i; });
下面是关于泛型算法和lambda表达式的例子:
目的:简化一个保存了多个单词的vector,按照单词长度由短至长、相同长度按字典序排序,且每个单词只出现一次。
void elimDumps (vector<string> &words)
{
//按字典序排序words,以便查找重复单词
sort (words.begin(), words.end());
//unique重排输入范围,使每个单词只出现一次
//返回指向无重复区域之后一个位置的迭代器
auto end_unique = unique (words.begin(), words.end());
//使用erase删除重复单词
words.erase (end_unique, words.end());
}
//比较函数,用来按长度排序单词
bool isShorter (const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
main()
{
vector<string> words{the, quick, red, fox, jumps, over, the, slow, red, turtle};
//将words按字典序重排,并消除重复单词
elimDumps (words);
//按长度重新排序,长度相同的单词维持字典序
stable_sort (words.begin(), words.end(), isShorter);
for (const auto &s : words) //无需拷贝字符串
cout << s << " "; //打印每个元素,以空格分隔
cout << endl;
}
输出为:
fox red the over slow jumps quick turtle
目的:修改上面的程序,求大于等于一个给定长度的单词的数量并将这些单词打印。
void biggies (vector<string> &words, vector<string>::size_type sz)
{
elimDumps (words);
stable_sort (words.begin(), words.end(),
[] (const string &s1, const string &s2) { return s1.size() < s2.size(); });
//获取一个迭代器,指向第一个满足size() >= sz 的元素
auto wc = find_if (words.begin(), words.end(),
[sz] (cosnt string &s) { return s.size() >= sz; });
//计算满足size() >= sz 的元素的数目
auto count = words.end() - wc;
//打印长度大于等于给定值的单词,每个单词后接一个空格
cout << count << " " << make_plural (count, "word", "s")
<< " of lenth " << sz << " or longer" << endl;
for_each (wc, words.end(),
[] (const string &s) {cout << s << " "; });
cout << endl;
}
参数绑定:
标准库bind函数
定义在头文件functional中。可将bind函数看作一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
bind一般形式为:
auto newCallable = bind (callable, arg_list);
其中,newCallable本身是一个可调用对象,callable是被newCallable调用的对象,arg_list是一个逗号分隔的参数列表,对应给callable的参数。
arg_list中的参数可能包含形如_n的名字,称之为占位符,表示newCallable中的参数在callable中的位置,其中n是一个整数,_1表示newCallable的第一个参数,_2表示newCallable的第二个参数,依此类推。
名字_n都定义在一个名为placeholders的命名空间中,则会个命名空间本身定义在std命名空间,为了使用这些名字,两个命名空间都要写上。
如_1对应的using声明为:
using std::placeholders::_1;
或
using namespace std::placeholders;。
后面对bind的调用代码认为之前已经恰当地使用了using声明。
当调用newCallable时,newCallable会调用callable并传递给它arg_list中的参数。
auto check6 = bind (check_size, _1, 6);
string s = "hello";
bool b1 = check6(s); //check6(s)会调用check_size (s, 6)
//下面两条语句是等价的
auto wc = find_if (words.begin(), words.end(), [sz] (const string &a) { check_size (a, sz); };
auto wc = find_if (words.begin(), words.end(), bind (check_size, _1, sz));check_size (a, sz);
auto g = bind (f, a, b, _2, c, _1); //调用g (X, Y)时会调用f (a, b, Y, c, X);
sort (words.begin(), words.end(), isShorter); //按长度由短至长排序
sort (words.begin(), words.end(), bind (isShorter, _2, _1)); //按长度由长至短排序
//os是一个局部变量,引用一个数据流
//c是一个局部变量,类型为char
for_each (words.begin(), words.end(), [&os, c] (const string &s) { os << s <<c; });
//下面的函数与上语句等价
ostream &print (ostream &os, const string &s, char c)
return os << s << c;
//下面用bind代替对os的值捕获,是错误的
for_each (words.begin(), words.end(), bind (print, os, _1, ' '));
//下面用ref函数返回给定的引用,是正确的
for_each (words.begin(), words.end(), bind (print, ref(os), _1, ' '));