泛型算法
大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组泛型算法。
它们可以用于不同类型的元素和多种容器类型(不仅包括标准库类型,如vector或list,还包括内置的数组类型)
只读算法
find(beg,end,val); //前两个参数是迭代器范围,第三个参数是要查找的值
返回第一个等于给定值的元素的迭代器。如果范围中无匹配元素,则返回第二个参数。
所需的头文件是algorithm。
accumulate(beg,end,iniSum) //前两个参数是迭代器范围,第三个参数是和的初始值
注意,元素类型与和初始值的类型不必相同,但是它们之间必须能够执行“+”运算。
所需的头文件是numeric。
对于只读而不改变元素的算法,通常用cbegin()和cend()指定迭代器范围
equal(v1.cbegin(),v1.cend(),v2.cbegin());
v1和v2中元素的类型不必相同,只要我们能用==来比较这两种元素类型即可。
注意,equal基于一种非常重要的假设:它假定第二个序列至少与第一个序列一样长。用单一迭借口代器表示第二个序列的算法都有这个假设。
写容器元素的算法
使用这类算法时,必须确保原序列大小至少不小于我们要求算法写入的元素数目。(算法不会执行容器操作,因此它自身不可能改变容器大小)
fill(v.begin(),v.end(),val) //将迭代器范围内的元素都置为val
fill_n(v.begin(),n,val) //将给定值赋予迭代器指向的元素开始的指定个元素。
back_iterator
back_iterator接受一个指向容器的引用,,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算会调用push_back将一个具有给定值的元素插入到容器中
用法:
vector<int> vec; //空容器
auto it=back_iterator(vec);
*it=42; //容器中现在有一个元素
将back_iterator作为算法的目的位置:
vector<int> vec;
fill_n(back_iterator(vec),10,0) //添加10个元素到vec中(不能用vec.begin()作为迭代器写入元素。因为此时vec中没有元素,是空的。)
copy(v1.begin(),v1.end(),v2.begin()); //前两个参数是一对迭代器范围,第三个参数是目的序列的起始位置
其返回值是指向v2尾元素之后的位置的迭代器
注意,目的序列至少要包含与输入序列一样多的元素。这一点很重要。
replace(v.begin(),v.end(),oldValue,newValue)//前两个参数是一对迭代器范围,算法将该范围内所有等于oldValue的元素的值替换为newValue
replace(v.begin(),v.end(),v2.begin(),oldValue,newValue) //原序列不变,v2包含v1的拷贝,且v2中所有等于oldValue的元素的值替换为newValue
sort(v.begin(),v.end()) //利用元素的<运算符实现从小到大的排序
unique(v.begin(),v.end()) //假如某个元素第一次出现的,那么它的位置保持不变。之后重复出现的该元素都被放在序列末尾。算法返回指向不重复区域之后一个位置的迭代器
定制操作
谓词
谓词是一个函数,接受一个或两个参数。返回bool类型值。接受谓词的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换成谓词的参数类型
例
bool isShorter(const string &s1,const string &s2)
{
return s1.size()<s2.size();
}
stable_sort(v.begin(),v.end(),isShorter); //isShorter使得算法用长度大小排序。而stable_sort与sort不太一样。它能维持相等元素的原有顺序。
lambda表达式
有些算法只能接受一元谓词,有些可以接受二元谓词。有时我们希望的操作需要更多参数,超出了算法对谓词的限制。因此,引用lambda表达式。lambda表达式可能定义在函数内部
lambda表达式具有以下形式
[捕获列表](参数列表)->返回类型{函数体} 例 []{return 42} //该lambda表达式的捕获列表为空,省略了参数列表和返回类型
捕获列表是由若干个定义在lambda所在函数中的局部变量组成的列表(将局部变量包含在其捕获列表中,lambda表达式能够在函数体使用这些变量)
调用lambda表达式
例:
auto f=[]{return 42}; 用lambda表达式定义可调用对象
cout<<f();//调用lambda表达式。输出42
例:
vector<string> words
...
stable_sort(v.begin(),b.end(),[](const string &a,const string &b){return a.size()<b.size();}); //按长度排序
使用lambda表达式代替谓词时,它的参数个数谓词的参数个数一样。但是它可以访问自己所在函数中的局部变量(要在捕获列表中指出)。还可以直接使用定义在当前函数之外的名字。
lambda表达式的值捕获
注意被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝
void f(){
int n=1;
auto f=[n]{return n;};
n=2;
auto j=f();//j 是1
}
lambda表达式的引用捕获
void f(){
int n=1;
auto f=[&n]{return n;};
n=2;
auto j=f();//j 是2
}
注意,使用引用捕获时,一定要确保被引用的对象在lambda执行的时候是存在的。若函数返回lambda,则lambda不能包含引用捕获。(因为捕获的引用指向的是局部变量,而局部变量在函数结束后就不存在了)
捕获指针,迭代器也是一样,必须确保lambda执行时指针或迭代器指向的对象存在。
隐式捕获
可以让编译器根据lambda的代码来推断我们要用哪些变量
用法:在捕获列表中写一个&或者=.&表示采用引用方式,=表示采用值捕获方式。
void f(){
int n=1;
auto f=[=]{return n;}; //隐式捕获,值捕获方式
n=2;
auto j=f();//j 是1
}
void f(){
int n=1;
auto f=[&]{return n;}; //隐式捕获,引用捕获方式
n=2;
auto j=f();//j 是2
}
还可以混合使用隐式捕获和显示捕获
void f(){
int n1=1;
int n2=3;
auto f=[=,&n2]{return n1+n2;}; //n1隐式捕获,值捕获方式;n2显式捕获,引用捕获方式
n1=2;
n2=5;
auto j=f();//j 是1+5=6
}
void f(){
int n1=1;
int n2=3;
auto f=[&,n2]{return n1+n2;}; //n1隐式捕获,引用捕获方式;n2显式捕获,值捕获方式
n1=2;
n2=5;
auto j=f();//j 是2+3=5
}
可变lambda
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果想要改变值拷贝的变量,需要在参数列表后面加上关键字mutable.
int _tmain(int argc, _TCHAR* argv[]){
int m = 0, n = 0;
// 不加mutable会报错
//[=] (int a){ m = ++n + a; }(4);
//[m,n] (int a){ m = ++n + a; }(4);
int sum=[=] (int a) { return m = ++n + a; }(4);
//
// [=] (int m, int n, int a){m=++n+a; }(m, n, 4);
// 下面这个函数m,n的值依然会被修改,因为m,n是按引用传入的
// [=] (int &m, int &n, int a){m=++n+a; }(m, n, 4);
cout<<sum<<endl; //输出5
cout << m << endl ;//输出0
cout<< n << endl; //输出0
return 0;
}
即一个值被拷贝的变量,它的值 依然没有被修改。这维持按值引用的特性。
若lambda函数体中只有单一的return语句,那么无须指定返回类型,编译器会根据return 后面的表达式推断返回类型
若lambda函数体中有多个return语句,那么需要指定返回类型。定义返回类型时,必须使用尾置返回类型
[](int i)->int {
if(i<0)
return -i;
else
return i;
}
bind接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表
用法: auto newCallable=bind(callable,arg_list);
arg_list中可能含有形如_n的名字。n指代传递给新生成的可调用对象的参数表中的第n 个参数。例,
bool check_size(const string&,string::size_type sz){
return s.size()>=sz;
}
auto check=bind(check_size,_1,6);
string s="hello,you";
所以bind用来
1.将一个多参数的可调用对象,转换成较少参数的新的可调用对象。如上面的例子
2能实现重排参数顺序
例:
sort(words.begin(),words.end(),bind(ishorter,_2,_1)); //实现单词长度由长到短排列
注意,_1,_2,...等名字在命名空间placeholders中。假如要使用_1,要在程序中加入using std::placeholders::_1
绑定引用参数
有些对象不能拷贝,可以用cref函数。ref(对象)返回一个新对象,包含原对象的引用。新对象是可以拷贝的。
cref(对象)生成一个保存const引用的类。
插入迭代器
有三种:
back_inserter(c) //创建一个使用push_back的迭代器(c是支持push_back的容器)
front_inserter(c) //创建一个使用push_front的迭代器(c是支持push_front的容器)
inserter(c,p) //p是迭代器,创建一个使用insert的迭代器,将元素插入到给定迭代器所表示的元素之前
工作过程:
若it是inserter生成的迭代器,则*it=val;等效于
it=c.insert(it,val); //it指向新加入的元素
++it; //递增it,使其指向原来的元素
若使用front_inserter.
list<int> lst={1,2,3,4};
list<int> lst2,lst3;
copy(lst.cbegin(),lst.cend(),front_inserter(lst2)); //拷贝完成后,lst2包含4 3 2 1
copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin())); //拷贝完成后,lst3包含1 2 3 4
iostream迭代器
istream_iterator
例:
istream_iterator<int> in(cin); //in从输入流cin记取类型为int的值
istream_iterator<int> end; //尾后迭代器
cout<<accumulate(in,end,0); //使用算法操作流迭代器。若输入1 2 3 4 5 ctl+z则输出15
ostream_iterator
ostream_iterator<T> out(os); //out将类型为T的值写到输出流os
ostream_iterator<T> out(os,d); //out将类型为T的值写到输出流os,每个值后面都输出一个d.d是一个空字符结尾的字符数组
例
ostream_iterator<int> out(cout," ");
for(auto e:vec){
*out++=e; //赋值语句实际上将元素写到cout
}
cout<<endl;
可以为任何定义了输入运算符的类型创建istream_iterator对象。可以为任何定义了输出运算符的类型创建ostream_iterator对象
反向迭代器