泛型算法:
称为算法:因为它们实现了一些经典算法的,公共接口。
称为泛型:它们可用于,不同类型的元素和多种容器类型。
概述:
大部分算法,都定义在头文件,algorithm中;在头文件,numeric中,还定义了数值泛型算法。
标准库算法find:
查找范围,[ vec.cbegin(),vec.cend() ],vec.cend()表示尾元素的后一个位置。
对于向量vector vec的查找:
find(vec.cbegin(),vec.cend(),val);//查找失败返回第二个参数迭代器,成功的话,返回val对应的迭代器。
对于数组int ia[] = {27,210}:
find(ia.begin(),ia.end(),val);
迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。
泛型算法本身不会执行容器的操作。
编程假定:算法永远不会改变底层容器的大小。但算法可能改变容器中保存的元素的值,也可能在容器内移动元素。
若迭代器是插入器,当算法给这个迭代器赋值时,则它们会在底层容器上执行插入操作。但并不是算法插入的。
初识泛型算法:
只读算法:
accumulate(vec.cbegin(),vec.cend(),0),定义在numeric中,第三个参数是和的初值。
string定义了+运算符,可以用它来实现,vec中的string元素的拼接。
但是因为const char *没有定义+运算符,则第三个参数不能为空串""。
操作两个序列的算法:
equal(roster1.cbegin(),roster1.cend(),roster2.cbegin());
roster1和roster2可以是不同类型的容器。
编程假定:第二个序列至少与第一个序列一样长。
写容器元素的算法:
fill(vec.begin(),vec.end(),0); //将所有元素重置为0
fill_n(dest,n,val); //从dest开始的序列,至少包含n个元素。
常犯错误:对一个空向量,插入值。
介绍back_inserter:
插入迭代器:保证算法,有足够元素空间来容纳输出数据的方法。
对插入迭代器赋值时,会将元素拷贝进容器。
back_inserter,定义在iterator头文件中。
auto it=back_inserter(vec);//输入容器引用,返回一个迭代器
*it=42;//调用底层容器的push_back
常用搭配:
fill_n(back_inserter(vec),10,0);//添加10个元素到vec
拷贝算法:
int a1[] = {0,1,2,3,4};
int a2[sizeof(a1)/sizeof(*a1)];
auto ret = copy(begin(a1),end(a1),a2); // 把a1的内容拷贝给a2
ps:ret指向拷贝到a2的尾元素之后的位置。
replace(ilst.begin(),ilst.end(),0,42); // 对给定范围内的序列,将0值替换为42
replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42); //ivec包含ilst的一份拷贝,但此拷贝中,0都替换为42了。
重排容器元素的算法:
消除重复元素
words是含有重复元素的的vector。
先通过,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();
}
sort(words.begin(),words.end(),isShorter);
稳定排序:
stable_sort(words.begin(),words.end(),isShorter); //保持原有相等元素的顺序不变。
lambda表达式:
find_if:接受一个迭代器表示的范围,和一个谓词;返回第一个使谓词返回非0值的元素,若不存在,则返回尾迭代器。
C++四种可调用对象:
函数、函数指针、重载了函数调用运算符的类、lambda表达式。
一个lambda表达式就有的形式:
[capture list](parameter list) -> return type {function body}
捕获列表:函数中局部变量列表。要永远包含捕获列表和函数体。
与普通函数不同,lambda必须使用尾置返回,来制定返回类型。
auto f = []{ return 42;};f();
若未指定返回类型,则要么根据函数体代码,推断出返回类型;要么返回void。
向lambda传递参数:
与普通函数不同,lambda不能有默认参数,调用的实参数目永远与形参数目相等。
空捕获列表,表明lambda不使用它所在函数中的任何局部变量。
使用捕获列表:
一个lambda只有在其捕获列表中,捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
调用find_if:
auto wc=find_if(words.begin(),words.end(),[sz](const string &a){return a.size() >= sz};
ps:返回指向,第一个满足条件的元素所在的迭代器。
计算从wc到words的末尾一共有多少个元素:
auto count = words.end() - wc;
可根据count是否为1,输出单复数:make_plural(count, ”word“,"s);
for_each算法:
for_each(wc, words.end(), [] (const string &s){cout << s << " ";} ); // 每个单词后面接一个空格
ps: lambda可以直接使用局部static变量和在它所在函数之外声明的名字。使用局部非static变量,需先捕获。
lambda捕获和返回:
当定义一个lambda时,编译器生成一个,与lambda对应的新的,没名字的类类型。
在函数中,用lambda传递参数时,被编译器生成为,没名字的对象。
捕获的变量,相当于lambda生成的类,的数据成员,在lambda对象创建时被初始化。
值捕获:
采用值捕获的前提是,变量可以拷贝。
引用捕获:
auto f = [&v1] {return v1};
必须确保,lambda执行时,这个引用捕获的变量是存在的。
引用捕获是必要的:
因为不能拷贝ostream对象,因此捕获os的唯一方法就是,捕获其引用。
[&os ,c ](const string &s){ os <<s << c; };
ps:确保lambda每次执行的时候,捕获的变量都有预期的意义,是程序员的承认。
捕获一个普通变量,如int、string或其他非指针类型,采用简单的值捕获方式即可。
隐式捕获:
在捕获列表中写一个&(引用捕获)或=(值捕获)
混合使用:
捕获列表的第一个元素必须是一个&或=,显示捕获必须使用与隐式捕获不同的方式。
可变lambda:
对于一个值捕获的变量,lambda不会改变其值,若要改变,需这样写:
auto f = [v1] () mutable { return ++v1};
指定lambda返回类型:
若一个lambda既没有指定返回类型,体内又包含了return之外的语句,则编译器假定此lambda返回void。
若被假定返回void,但实质上又返回类值,则产生编译错误。
因此,最好声明一下返回值:
[] (int i) -> int {if(i<0) return -i; else return i; };
参数绑定:
标准库bind函数,它定义在头文件functional中,可看作是,通用的适配器。
bind函数返回一个,可调用对象。
例如,函数,bool check_size(const string &s, string::size_type sz);
如不想使用lambda的捕获列表,若传递函数check_size(),则find_if又只能传一个参数,匹配不上check_size的参数列表。则,auto check6 = bind(check_size, _1, 6);
所以又,auto wc = find_if(words.begin(), words.end(), check6);
使用placeholders名字:
名字_n定义在名为placeholders的命名空间,placeholders又定义在std命名空间。
placeholders命名空间定义在functional头文件中。
bind的参数:
auto g = bind(f, a, b, _2, c, _1);
g的第一个参数绑定到_1,g的第二个参数绑定到_2。
栗子://按单词长度由长至短排序:
sort(words.begin(), words.end(),bind(isShorter, _2, _1));
绑定引用参数:
因为不能拷贝一个ostream,因此需使用标准库,ref函数:返回一个对象,包含给定引用,可拷贝:
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
//还有一个cref函数,生成一个保存const引用的类。
再探迭代器:
(1)、插入迭代器:能实现向给定容器添加元素。直接向迭代器it = t,这样赋值即可。
*it、++it,it++这些操作存在,但不做任何事情,只返回it
插入迭代器:back_inserter,front_inserter,inserter。
使用inserter时,需要给定第2个参数,指向给定容器的迭代器,插入的元素在该迭代器所指的元素之前。
list<int> lst = {1,2,3,4};
list<int> lst2;
copy(lst.cbegin(), lst.cend(), inserter(lst2,lst2.begin())); // 给定的迭代器是lst2.begin() 记为x,插入元素之后变为,1,2,3,4,x
因此,插入元素序列的顺序没被颠倒。
(2)、iostream迭代器:
istream_iterator、ostream_iterator,这些迭代器将它们对应的流,当作一个特定类型的元素序列来处理。
istream_iterator:
创建流迭代器时,必须制定读写的对象类型;读取的类型也必须定义了,输入运算符。
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof; // 可当作尾后迭代器
vector<int> vec(in_iter,eof); // 从流中读取的数据被用来构造vec
accumulate(in, eof ,0); // 从标准输入读取的值的和
使用懒惰求值:
istream_iterator绑定一个流时,直到我们使用迭代器时才真正读取。
ostream_iterator:
该迭代器可接受第2个参数,该参数必须是C语言风格的字符串。
ostream_iterator<int> out_iter(cout," ");
*和++对ostream_iterator对象不做任何事情。
通过调用copy来打印vec中的元素:copy(vec.begin(),vec.end(),out_iter);
ps:若某种类型,定义了>>和<<,即可定义,istream_iterator和ostream_iterator。