标准库未给每个容器都定义成员函数来实现诸如查找特定元素、替换和删除一个特定值、重排元素顺序等操作,而是定义了一组泛型算法:
- 称为“算法”,是因为其实现了一些经典算法的公共接口,如排序和搜索
- 称为“泛型”,是因为他们可以用于不同类型的元素和多种容器类型
#include <algorithm>
#include <numeric>
auto result = find( lst.cbegin(), lst.cend(), val);
int sum = accumulate(vec.cbegin(), vec.cend(), 0); //正确
string sum = accumulate(vec.cbegin(), vec.cend(), string("")); //正确
string sum = accumulate(vec.cbegin(), vec.cend(), ""); //错误:const char * 没有定义+运算符
accumulate 定义在numeric头文件中,前两个参数指出求和元素范围,第三个参数是和的初始值 ,序列中的元素类型必须和第三个参数匹配 。第三个参数的类型决定函数中使用哪种加法运算以及返回值的类型
向算法传递函数
谓词
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法使用的谓词分为两类:一元谓词(unary predicate,意味着他们只接收单一参数)和二元谓词(binary predicate,意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换成谓词的参数类型。
接受一个二元谓词参数的sort版本用这个谓词代替<来比较函数:
lambda表达式
void elimDups(vector<string> &words)
{
sort(words.begin(), words.end());
auto end_unique = unique(words.begin(), words.end());
words.erase( end_unique, words.end() );
}
void biggies(vector<string> &words, vector<string>::size_type sz)
{
elimDups(words); //将word按字典序排序,删除重复单词
//按长度排序,长度相同的单词维持字典序
stable_sort(sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size() < b.size();));
//获取一个指向第一个满足size() >= sz的元素的迭代器
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz;});
//获取一个迭代器,指向第一个满足size() >= sz的元素
auto count = words.end() - wc;
cout << count <<" " << make_plural(count, "word", "s")
<< " of length" << sz << "or longer" << endl;
//打印长度大于等于给定值的单词,每个单词后面接一个空格
for_each(wc, words.end(),
[](const string &s){cout << s << "";});
cout << endl;
}
lambda捕获和返回
当定义一个lambda时,编译器生成一个与lambda对于的新的(未命名)类类型。类似参数传递,变量的捕获也可以是值或引用。
值捕获
void fcn1()
{
size_t v1 = 42;
auto f2 = [v1]{return v1};
v1 = 0;
auto j = f2(); //j为42
}
引用捕获
void fcn1()
{
size_t v1 = 42;
auto f2 = [&v1]{return v1};
v1 = 0;
auto j = f2(); //j为0
}
引用捕获和返回引用有着同样的问题和限制。如果采用引用方式捕获一个变量,就必须确保被引用对象在lambda执行的时候是存在的。
void biggies(vector<string> &words,
vector<string>::size_type sz,
ostream &os = cout, char c = " ")
{
elimDups(words);
stable_sort(sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size() < b.size();));
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz;});
auto count = words.end() - wc;
cout << count <<" " << make_plural(count, "word", "s")
<< " of length" << sz << "or longer" << endl;
for_each(wc, words.end(),
[&os,c](const string &s){ os << s << c;});
cout << endl;
}
隐式捕获
除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体重的代码来推断我们要使用那些变量:在捕获列表中写一个&(引用捕获方式)或=(值捕获方式).
void biggies(vector<string> &words,
vector<string>::size_type sz,
ostream &os = cout, char c = " ")
{
elimDups(words);
stable_sort(sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size() < b.size();));
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz;});
auto count = words.end() - wc;
cout << count <<" " << make_plural(count, "word", "s")
<< " of length" << sz << "or longer" << endl;
for_each(wc, words.end(),
[&, c](const string &s){ os << s << c;});
for_each(wc, words.end(),
[=, &os](const string &s){ os << s << c;});
cout << endl;
}
当混合使用显示捕获和隐式捕获时,捕获列表的第一个元素必须是&或=中的一个,此符号指定默认捕获方式。且非默认捕获方式必须采用与默认捕获方式不同的方式,即隐式捕获为引用方式,则显示捕获命名变量必须采用值方式,因此不能在名字前使用&。反之,如果隐式捕获采用值方式,则显示捕获命名变量必须采用引用方式,即使用&
[] | 空捕获列表。lambda不能使用所在函数体重的变量。一个lambda只有捕获变量后才能使用它们 |
[names] | names是一个逗号分隔的名字列表。这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。可以采用&修改为引用捕获方式 |
[&] | 隐式捕获列表,采用引用捕获方法。lambda体中所使用的而来自所在函数的实体都采用引用方式使用 |
[=] | 隐式捕获列表,采用值捕获方法。lambda体中所使用的而来自所在函数的实体的值 |
[&, identifier_list] | identifier_list是一个逗号分隔的名字列表,包含0个或多个来自所在函数的变量,均采用值捕获,任何隐式捕获的变量都采用引用方式捕获。identifier_list中的名字前不能使用& |
[=, identifier_list] | identifier_list是一个逗号分隔的名字列表,包含0个或多个来自所在函数的变量,均采用引用捕获,任何隐式捕获的变量都采用值方式捕获。identifier_list中的名字不能包括this,且这些名字必须使用& |
可变lambda
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果希望改变其值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表
void fcn1()
{
size_t v1 = 42;
auto f2 = [v1]() mutable {return ++v1};
v1 = 0;
auto j = f2(); //j为43
}
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型
void fcn1()
{
size_t v1 = 42;
auto f2 = [&v1]{return ++v1};
v1 = 0;
auto j = f2(); //j为1
}
指定lambda返回类型
默认情况,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。
//正确
transform(vi.begin(), vi.end(), vi.begin(),
[](int i){return i< 0? -i: i;});
//错误
transform(vi.begin(), vi.end(), vi.begin(),
[](int i){if(i < 0)return -i;else return i;};
当必须为一个lambda定义返回类型时,必须使用尾置返回类型
transform(vi.begin(), vi.end(), vi.begin(),
[](int i)->int
{if(i < 0)return -i;else return i;};
参数绑定
对于需要在很多地方使用相同操作的情况,通常应该定义一个函数。对于只接收一个一元谓词的函数,可以使用bind函数来解决:
标准库bind函数
bind函数可以看做一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,调用的一般形式为:
#include <functional>
auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对于给定callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,表示newCallable的参数,他们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1是newCallable的第一个参数。
#include <functional>
using std::placeholders::_1;
auto check6 = bind(check_size, _1, 6);
string s = "hello";
bool b1 = check6(s); //check6(s)会调用check_size(s, 6)
bool b1 = check_size(s, 6);
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a));
auto wc = find_if(words.begin(), words.end(),
bind(check_size, _1, sz));
使用placeholders名字
名字_n都定义在一个名叫placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。为了使用这些名字,两个命名空间都要写上:
using std::placeholders::_1;
对于每个占位符都需要提供一个单独的using申明太烦人,可以使用如下形式的using语句表明希望使用所有来自namespace_name的名字都可以在我们的程序中直接使用:
using namespace namesplace_name;
using namespace std::placeholders;
bind的参数
auto g = bind( f, a, b, _2, c, _1);
g(_1, _2)
f(a, b, _2, c, _1)
用bind重排参数序列
//按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
//按单词长度由长至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
绑定引用参数
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但对于不能使用拷贝方式的需绑定参数的类型会出错(如ostream、istream)。如果希望传递的是一个对象而又不拷贝它,就必须使用标准库ref函数。
#include <functional>
ostream &print( ostream &os, const string &s, char c)
{
return os << s <<c;
}
for_each(words.begin(), words.end(),
bind(print, ref(os), _1, ' ');
函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类。均定义在头文件functional中
插入迭代器(insert iterator) | 被绑定到一个容器上后,可用于向容器插入元素 |
流迭代器(stream iterator) | 被绑定到输入或输出流上,可用来遍历所关联的IO流 |
反向迭代器(reverse iterator) | 向后而不是向前移动。除了forward_list之外的标准库容器都有 |
移动迭代器(move iterator) | 移动 |
插入迭代器
back_iterator | 创建一个使用push_back的迭代器 |
front_iterator | 创建一个使用push_front的迭代器 |
inserter | 创建一个使用insert的迭代器。次函数接受第二个参数,这个参数必须是一个指向指定容器的迭代器。元素将被掺入到给定迭代器所表示的元素之前。 |
对插入迭代器进行*it,++it,it++均不对it做任何事情,每个操作都返回it
it = inserter(c, iter);
*it = val;
it = c.inserter(it, val);
++it;
iostream迭代器
istream_iterator操作
当创建一个流迭代器时,必须指定迭代器将要读取的对象类型。通过流迭代器,可以使用泛型算法从流对象读取数据以及向其写入数据。默认初始化流迭代器为尾后迭代器。
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while(in_iter ! = eof)
vec.push_back( *in_iter++ );
istream_iterator<int> in_iter(cin), eof;
vector<int> vec(in_iter, eof);
由于算法使用迭代器操作来处理数据,而流迭代器又至少支持某些迭代器操作,因此至少可以用某些算法来操作流迭代器。
istream_iterator<int> in_iter(cin), eof;
cout << accumulate( in_iter, eof, 0) << endl;
ostream_iterator操作
ostream_iterator<T> out(os); | out将类型为T的值写到输出流os中 |
ostream_iterator<T> out(os,d); | out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组 |
out = val; | 通过<<运算符将val写入到out所绑定的ostream中,val的类型必须与out可写的类型兼容 |
*out, ++out, out++ | 运算符存在,但不对out做任何操作。每个运算符返回out |
使用流迭代器处理类类型
可以为任何定义了输入运算符(<<)的类型创建istream_iterator对象。类似,可以为任何定义了输出运算符(>>)的类型创建ostream_iterator对象。
istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "\n");
Sales_item sum = *item_iter++;
while( item_iter != eof)
{
if(item_iter->isbn() == sum.isbn())
sum += *item_iter++;
else
{
out_iter = sum;
sum = *item_iter++;
}
}
out_iter = sum;
反向迭代器
反向迭代器是在容器中从尾元素向首元素反向移动的迭代器。递增(递减)操作的含义会颠倒过来(string容器的字符串也会反过来,可以通过base()函数)