您的位置:首页 > 编程语言 > C语言/C++

C++Primer笔记——泛型算法

2014-03-01 21:04 134 查看
时间:2014.03.01

地点:基地

————————————————————————————————————

一、简述

泛型算法并不直接操作容器,而是遍历两个迭代器指定的一个元素范围,如此将算法是作用容器分离,实现通用性。泛型算法多数定义在头文件algorithm中。比如我们想在vector<int>下找到一个特定值,可实现如下:

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
int main(){
	vector < int > vec = { 1, 2, 3, 4, 5, 6, 7 };
	int val = 2;
	auto result = find(vec.cbegin(), vec.cend(), val);
	cout << "The value" << val << (result == vec.cend() ? " is not present" : " is present") << endl;
	return 0;
}
可见,我们传递find函数的前两个参数描述的是查询范围,第三个参数是查询目标,find将目标值在查询范围里逐一比较,一相等就返回该元素的迭代器,如果一直不相等,则匹配到了最后的迭代器,find函数不得不返回该迭代器,获得迭代器之后,我们稍微做处理,判断这个迭代器是不是最后一个迭代器,即end位置来决定是不是查询成功。

正如前面所说,find迭代器操作的是迭代器,任何可提供迭代器的容器都可用fand算法,另外,由于指针在很多地方与迭代器行为相似,因此我们还可以将find作用于数组,找到返回对于元素指针,没找到返回尾元素之后的指针。比如:

#include<algorithm>
#include<iostream>
using namespace std;
int main(){
	int ia[] = { 1, 2, 3, 4, 5, 6 };
	int val = 8;
	auto result = find(begin(ia),end(ia),val);
	cout << "The value" << val << (result == end(ia) ? " is not present" : " is present") << endl;
	return 0;
}
当然,既然说find操作的是范围,那么它在序列的子范围中查找也是OK的。比如:

auto result=find(ia+1,ia+4,val);//在从数组ia[1]到ia[4](不包活ia[4]的子范围内查找目标
注意:find作用的范围始终是一个半包闭区间 [ ),且find返回尾后迭代器用来表示未找到给定元素。

类似于find算法,我们还有个count函数用于计算给定范围类特定值出现的次数。

而accumulate也是一样,用于计算序列的和,第三个参数是和的初始值,它在头文件numeric中,当然这种求和以第三个参数类型为基础,序列中的元素要能匹配到它的类型,当做字符串加法(即字符串连接)时,第三个参数应该显示给出,比如:string(""),我们不能给一个字符串字面值,这样传递的类型实际上是一个const char * ,而它并没有定义+运算符。对于只读算法,我们最好使用cbegin()和cend(),若计划想用算法返回的迭代器来改变元素的值,就需要使用begin()和end()的结果作为参数。

————————————————————————————————————

二、泛型算法永远不会执行容器的操作(摘自Premier)

泛型算法本身是不会执行容器的操作的,它们只会运行于迭代器之上,执行对迭代器的操作,这样,算法永远都不会改变底层容器的大小,注意只是不能改变大小,因为算法可以改变容器中的值,或者移动元素,但永远不会直接增加或删除元素。基于此,泛型算法又分为只读算法和写算法。诸如find,count,accumulate,equal等算法都是只读的。而写容器元素的算法必须注意确保序列原大小至少不小于我们要求算法写入的元素数目,因为我们的算法是不执行容器操作的,即容器的大小不会变化。比如fill算法,也是接受一对迭代器表示范围,还接受第三个参数,fill将给定的这个值赋予输入序列中的每个元素。

fill(vec.begin(),vec.end(),0);//将每个元素都重置为0,
fill_n(vec.begin(),n,val);//向给定赋值迭代器指向的元素开始处给n个元素赋新值val


————————————————————————————————————

三、几个常用算法

使用fill_n时,要注意向目的位置迭代器写入数据的算法假定目的位置会足够大,能容纳要写入的元素。比如我们不能用fill_n向一个空向量写入元素。但我们可以使用插入迭代器解决这个问题,插入迭代器可以保证算法有足够的元素空间来来容纳数据,它是一种向容器中添加数据的迭代器。通常,通过迭代器向容器赋值时,值被赋予迭代器指向的元素,而通过插入迭代器则是添加元素到容器中。插入迭代器back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,比如:
vector<int> vec;//空向量
fill_n(back_inserter(vec),10,0);//添加十个元素到vec
接下来还学习一个个拷贝算法

拷贝算法copy接受三个迭代器,前两个表范围,第三个表目的序列的起始位置。即我们希望将给定范围内的元素拷贝到目的序列中,传递给copy的目的序列至少要包含与输入范围序列一样多的元素。例如:

int a1[]={0,1,2,3,4,5,6};
int a2[sizeof(a1)/sizeof(*a1)];//a2与a1大小一样
auto ret=copy(begin(a1),end(a1),a2);//把a1的内容拷贝给a2


还有一个替换算法replace,读入一个序列范围,将序列中某个值全部用一个新值来替换

replace(ilst.begin(),ilst.end(),0,42);


当然如果你并不希望源数据被改变,而只是想替换后放在另外一个序列中,我们可以使用replace的拷贝版:replace_copy

replace_copy(ilst.cbegin(),ilst.cend(),back_inserter(ivec),0,42);
————————————————————————————————————

四、总结

今天学到此吧,总结一下,一是泛型算法都作用于迭代器上,实现了与具体容器相分离。二是各泛型算法并不改变容器大小,当然有个back_insetter的家伙,它可以绑定容器返回一个插入迭代器,使用该迭代器赋值时会调用push_back,所以元素能追加到容器后,容器大小变化。三是几个泛型算法在用法上的相似性,即大多接受一对迭代器表示的范围,针对这个范围进行相关操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: