介绍
c++Primer第十章学习心得
一、泛型算法概述
通过对迭代器进行操作,而非容器本身,使得算法的实现不再依赖于容器,这是其优点。大部分在中
1.只读算法
find(iter1,iter2,val);找到第一个val的位置
count(iter1,iter2,val);统计val出现次数
accumulate(iter1,iter2,val);计算总和,val为初值,该函数在numeric中,要求元素类型和val匹配
equal(iter1,iter2,iter3),将iter1与iter3比较至iter2,若不同返回false,但是若2比1长,前面相同仍然返回true;
vector<double> nums{1,1,1,2,2,2,3,3,4,4,5,6};
vector<double> n{1,1,1,2,2};
bool flag = false;
flag = equal(n.begin(),n.end(),nums.begin());//1
cout<<flag;
2.写容器的算法
由于算法不执行容器操作,所以写入时不能修改容器本身的大小!
且算法不检查写操作,哪怕对未定义空间写入,普通迭代器是通过赋值。可以通过插入迭代器保证有足够的元素空间,它是通过push_back将值插入到容器中。
fill(vec.begin(),vec.end(),val);//将每个元素置为val
fill_n(vec.begin(),10,0);//若vec的值少于10个,该行为是未定义的
fill_n(back_insert(vec),10,0);//安全
auto it = back_insert(vec);
*it = 15;//容器增加一个元素15
copy(it1,it2,it3)将it1和it2之间的元素拷贝到it3后,返回拷贝的最后一个元素之后,同样地,需要保证it3之后的空间与it1、it2之间的空间大小保持一致
3.重排容器元素的算法
sort()排序,unique()将相邻的重复元素“删除”,返回不重复元素后的迭代器,可以通过容器本身的删除操作完整实现
vector<int> num{1,1,2,3,4,5,5,6,2,3}];
sort(num.begin(),num.end());
auto it = unique(num.begin(),num.end());
num.erase(it,num.end());
二、定制操作
可以通过自定义的操作来代替默认运算符,类似sort中的自定义函数,是一个谓词
谓词分为一元谓词和二元谓词
1.lambda表达式
[capture list](parameter list) -> return type { function body }
capture list是一个局部变量的列表,通常为空但必须存在,其他和普通函数一样,但必须使用尾置返回类型。可以忽略参数列表和返回类型,但必须有捕获列表和函数体。当函数类型被忽略时,会根据函数体中的代码推断返回类型,如果函数体仅含一个return语句,则返回类型由返回表达式的类型推断,否则返回类型为void。
lambda函数不能有默认参数,所以实参数量和形参数量相同。
关于捕获列表的理解:
[]形式意为不捕获任意外部变量,[&]是以引用形式捕获所有外部变量,允许改变,[=]是以拷贝形式捕获,且即使是拷贝也不能改变大小.[var]是捕获具体的某个变量,也可用=、&修饰它。还可以捕获this指针,用于访问类中的成员。当捕获值时,该值在创建lambda函数时被确定,而非调用时;当捕获引用时,局部变量必须存在。
int val = 42;
auto f() = [val] -> {return val;};
val = 0;
auto j = f();//j=42
关于可变lambda
使用值捕获时,lambda函数默认值不可改变,因此可以在参数列表后加上关键词mutable。引用捕获是否可变取决于引用本身是否为const。
关于返回类型
如果lambda函数中只有一个return语句,可以省略返回类型,依靠编译器自己判断;否则为void类型,无法返回值。需要通过尾置类型主动控制返回类型。
2.bind函数
定义在头文件functional中,通常与占位符结合。使用占位符可以加命名空间,也可以用限定符。
#include <functional>
using namespace std::placeholders;
auto f = bind(fun,_1);//_1就就是占位符,表示该参数为fun中第一个形参
auto f = bind(fun,std::placeholders::_1);//此方式也可
bind函数的主要目的是绑定参数,可以直接绑定值,也可以绑定形参,还可以用于重排参数顺序。在使用自定义函数做一元谓词或二元谓词时,多余的参数可以通过bind绑定。
三、再谈迭代器
除了常规迭代器外,还存在一些特殊迭代器,被定义在iterator头文件中。包括:插入迭代器、流迭代器、反向迭代器、移动迭代器。
1.插入迭代器
迭代器不是容器操作,因此不能直接改变容器。而插入迭代器可以调用容器操作,向容器的指定位置插入一个元素。
back_inserter:创建一个调用push_back的迭代器
front_inserter:调用push_front
inserter:调用insert
不同迭代器由于调用的不同,所以效果是不同的,对于inserter而言,插入位置固定,而front_inserter可以保证每次都是插入到容器第一个元素前。
2.iostream迭代器
无iostream容器,但存在可以用于该对象的迭代器。istream_iterator通常使用时会创建两个迭代器,一个指向对象,一个指向空。
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof_iter;
while(in_iter!=eof_iter){
vec.emplace_back(*in_iter++);
}
//另一种写法
istream_iterator<int> in_iter(cin),eof_iter;
vector<int> vec(in_iter,eof_iter);
ostream_iterator需要指定对象,可附加一个c字符串,用于每次输出之后额外输出一个该字符串。使用时,通过赋值即可自动输出。
ostream_iterator<int> out_iter(cout," ");
for(auto e : vec){
*out_iter++ = e;//实际无效,等价于out_iter = e,此处为了与其他迭代器保持一致
}
//另一种写法
copy(vec.begin(),vec.end(),out_iter);
//使用流迭代器读取输入,并将其从大到小排列后输出
istream_iterator<int> in_iter(cin), eof_iter;
vector<int> vec(in_iter, eof_iter);
sort(vec.begin(), vec.end(), [](const int &a, const int &b)
{ return a > b; });
ostream_iterator<int> out_iter(cout, " ");
copy(vec.begin(), vec.end(), out_iter);
3.反向迭代器
使用rbegin()和rend()获取反向,此时的++实际是向第一个元素移动,–为向最后一个元素移动,使用时需要格外注意。此外,反向与对应的普通迭代器(.base())所表示的范围实际是不同的相邻,因为是左闭合区间。
四、泛型算法结构
设计泛型算法时,应当指定使用迭代器的类型。根据操作不同,将迭代器分为五类,分别是:
迭代器类型 | 特点 |
---|---|
输入迭代器 | 只读不写,单次扫描,只能递增 |
输出迭代器 | 只写不读,单次扫描,只能递增 |
前向迭代器 | 可读写,多次扫描,只能递增 |
双向迭代器 | 可读写,多次扫描,可递增递减 |
随机访问迭代器 | 支持所有操作 |
由上往下等级递增,高层迭代器支持底层迭代器所有操作(输出不支持输入)
算法应当指定每个迭代器参数的最小类别
算法通过重载传递一个谓词或者通过重命名方式,例如
sort(vec.begin(),vec.end(),[](const int &a,const int &b){return a>b;});
sort(vec.begin(),vec.end());//重载
find(vec.begin(),vec.end(),val);//val为特定值
find_if(vec.begin(),vec.end(),谓词);//重命名
部分算法需要区分拷贝和不拷贝的版本,通常通过_copy进行区别
reverse(beg,end);
reverse_copy(beg,end,dest);//将beg和end反转后拷贝到dest
五、特定容器算法
对于list和forward_list而言,不能使用双向迭代器,因此需要使用特定的成员函数,部分通用算法可以用,但性能低于特定容器本身的成员函数。