概述
在前一篇我们介绍了容器的基本概念以及使用其成员函数进行增删改查,但有的时候我们还希望对容器进行更多的操作,比如:查找特定元素,替换元素等。而标准库并未给出此类成员函数,
此时需要引入algorithm头文件,其中定义了一系列的操作算法。
这些算法不直接操作容器,而是遍历有两个迭代器指定的范围。
算法永远不会执行容器的操作
泛型算法本身不会执行容器的操作,他们只会运行于迭代器之上,执行迭代器的操作;因此,算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会添加或删除元素。
只读算法
一些算法只会读取其输入范围内的元素,而从不更改元素,find,count就是如此
int sum = accumulate(vec.begin(),vec.end(),0);
对vec进行求和,和的初值为0;
算法和元素类型
accmulate的第三个参数的类型决定了函数中使用哪种加法运算符,以及返回值类型;
同时也意味着将元素类型加到和的类型上的操作必须是可行的
由于string定义了+运算符,所以可以通过调用
string sum = accumulate(v.begin(),v.end(),string(""));//正确
但const char * 上没有定义+运算符
string sum = accumulate(v.begin(),v.end(),"");//错误
操作两个序列的算法
另一个只读算法是equal,用于确定两个序列是否保存相同的值。接受三个迭代器,前两个迭代器表示第一个序列的范围,第三个表示第二个序列中的首元素
//v1应至少与v2中的元素一样多
equal(v1.begin(),v1.end(),v2.begin());
由于equal利用迭代器进行操作,所以可以用equal来比较两个不同类型的容器中的元素,而且元素类型也不必相同,只要我们能用==来比较两个元素类型即可。
写容器元素的算法
一些算法将新值赋予序列中的元素,当我们使用这类元素时必须确保序列原大小不小于我们要求算法写入的数目,因为算法不会执行容器的操作,因此他们自身可能不会改变容器的大小。
算法fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数。fill将这个给定的值赋予输入序列中的每个元素。
fill(v.begin(),v.end(),0);//将每个元素置0;
算法不检查写操作
fill_n(dest,n,val);
fill_n假定dest指向一个元素,而从dest开始的序列至少包含n个元素,否则将会出现未定义行为。
back_inserter
back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加容器中
vector<int> vec;//空向量
auto it = back_inserter(vec);//通过它将赋值会将元素添加到vec中
*it = 42;//vec中现有一个元素,值为42
常常使用back_inserter来创建一个迭代器,作为算法的目的位置使用
vector vec;
fill_n(back_inserter(vec),10,0);
每个元素的值都将为0;
拷贝算法
auto ret = copy(begin(a1),end(a1),a2);//把a1内容拷贝给a2;
copy返回的是其目的位置迭代器的值。即,ret恰好指向拷贝到a2的尾元素之后的位置。
replace(v.begin(),v.end(),0,42);//将v中所有值为0的元素替换为42,如果希望原序列保持变可以调用replace_copy;
replace_copy(ilst.cbegin(),ilst.cend(),back_inserter(ivec),0,42);
调用后ivec保存了了ilst的拷贝,并且把其中值为0的元素替换为42;
消除重复元素
利用sort和earse来消除容器中的重复元素
sort(v.begin(),v.end());//对容器进行排序
//unique重排输入范围,使得每个单词只出现一次
//排列在范围的前部分,返回指向不重复区域之后的一个位置
auto end_unique = unique(v.begin(),v.end());
//使用earse删除重复元素
v.earse(end_unique,words.end());
unique使得不重复元素出现在容器的前部分,返回迭代器指向最后一个不重复元素之后的位置,此部分依然存在,但我们不知道值是什么
注意:unique作用于有序容器才有效!!!!
向算法传递参数
可以向sort传递一个谓词,自定义我们想要的排序方式
bool isShort(const string &s1,const string s2)
{
return s1.size()<s2.size();
}
sort(v.begin(),v.end(),isShort())
排序算法
在我们将容器按大小排序时,我们还希望具有相同长度的元素进行字典排序。为了保持具有相同长度的元素按字典排序可以使用stable_sort算法
elimDups(v);//进行字典排序
//按照长度排序长度相同的单词维持字典序
stable(v.begin(),v.end(),isShort);
lambda表达式
一个lambda表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数。
表达式形式如下所示
[capture list](parameter list) ->return type{function body};
可以忽略参数列表和返回类型但永远包含捕获列表和函数体
auto f = []{return 0;}
如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回值类型,则返回void
向lambda传递参数
使用lambda来维持stable_sort:
//按长度排序,长度相同的单词维持字典序
stable_sort(v.begin(),v.end(),[](string a,string b)
{return a.size<b.size;}
)
使用捕获列表
一个lambda通过将局部变量包含在其捕获列表中来指出将会使用那些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
[sz](const string &a)
{return a.size() >= sz;};
一个lambda只有在其捕获列表中捕获一个它所在函数的局部变量,才能在函数体中使用该变量。
值捕获
与传值参数类似,采用值捕获的前提是变量可以拷贝,被捕获的值是在lambda创建时拷贝,而不是调用时拷贝
void func1()
{
size_t v1 = 42;
auto f = [v1]{return v1;}
v1 = 0;
auto j = f();//j为42
}
引用捕获
一个与引用方式捕获的变量与其他类型的引用行为类似,改变引用就会改变其值。
void func2()
{
size_t v1 = 42;
auto f = [&v1]{return 42;}
v1 = 0;
auto j = f();//j = 0;
}
如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失了;
隐式捕获
为了指示编译器推断捕获列表,应在列表中写一个&或=
[=](const string &s)
{
return s.size()>=sz;
}
当混合使用默认捕获和显式捕获时,捕获列表中的第一个元素必须为一个&或=,第一个必须是隐式捕获,且第二个元素的捕获方式必须与第一个不同,如果第一个使用引用,则第二个必须使用值
可变lambda
如果我们希望能改变一个捕获变量的值,就必须在参数列表首加上关键字mutable
void func3()
{
size_t v1 = 42;
auto f = [v1]()mutable {return v1++;}
v1 = 0;
auto j = f()//j = 43;
}
**stable_sort()**按长度排序,同时使得长度相同的元素具有字典序。
partition()对容器划分,使得谓词为真的元素排在前,返回最后一个使谓词为真的元素后一个位置的迭代器
**stable_partition()首先按长度排序,将谓词为真的元素排在前,返回最后一个使谓词为真的元素的后一个位置的迭代器,**长度相同的元素符合字典序
find_if()返回一个迭代器,指向第一个使谓词为真的元素,如果这样的元素不存在返回end()的拷贝
cout_if()返回一个计数值,表示谓词有多少次为真。
remove_if()将容器中所有使谓词为真的元素放在尾端,返回一个迭代器指向第一个使谓词为真的元素
标准库bind函数实现参数绑定
如何使用函数来替换lambda,有的时候我们的谓词只能接受一个参数,lambda帮我们解决了这个难题,其实当使用函数作为谓词时,我们可以使用bind进行参数绑定,来接受多个参数,bind会对参数进行拷贝,定义在头文件functional中。
bind的一般形式为:
auto newCallable = bind(callable,arg_list);
其中newCallable是一个可调用的对象,arg_list是一个逗号分隔的参数列表,对应给定的callable参数。当我们调用newCallable时,会调用callable并传递给它arg_list中的参数
arg_list中,_1表示newCallable中的第一个参数,_2表示第二个,以此类推
bool check_size(const string &s,string::size_type sz)
{
return s.size()>=sz;
}
//check6是一个可调用对象,接受一个string类型的参数
//并用此string和值6来调用check_size()
auto check6 = bind(check_size,_1,6);
string s;
bool b1 = check6(s);//check(s)会调用check_size(s,6);
绑定引用参数
默认情况下,bind那些不是占位符的参数被拷贝到bind返回的可调用函数中。但是,与lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定的参数的类型无法拷贝,如果我们希望传递给bind一个对象而不拷贝他,就必须使用标准库函数ref函数。
bind(print,ref(os),_1);
使用占位符_1,_2时要声明命名空间
using namespace std::placeholders;
再探迭代器
- back_inserter 创建一个使用push_back的迭代器
- front_inserter 创建一个使用push_front的迭代器
- inserter 创建一个使用inserter的迭代器。此函数接受第二个参数,参数必须是指向容器的迭代器,元素将被插入到给定迭代器锁所表示的元素之前,定义在头文件iterator中
只有在容器支持相应的成员函数时才能使用以上迭代器。
iostream迭代器
istream_iterator in(is) in从输入流is中读取类型为T的值
istream_iterator end 表示尾后位置
istream_iterator<int> in(cin);
istream_iterator<int> end;
while(in!=end)
{
v.push_back(*in++);
}
//等价于vector<int> v(in,end);
ostream_iterator out(os) out将类型为T的值写到输入流os中
ostream_iterator out(os,d) out将类型为T的值写到输入流中,每个值后面都加一个d,d指向一个空字符串结尾的字符数组;
ostream_iterator<int> out(cout," ");
*out++ = v[i];
cout<<endl;
对于list和forward_list应该优先使用成员函数版本的算法而不是通用算法。