1.引子
标准容器定义的操作通常包含添加和删除元素;访问第一个和最后一个元素;获取容器大小,并在某些情况下重设容器大小;获取指向第一个和最后一个容器的下一位置的迭代器。
用户通常希望对容器元素有更多有用操作,比如排序、查找等。标准库没有为每种容器定义相关成员函数,而是定义了一组泛型算法。
2.只读算法
- find函数
假设有一个int型的vector对象,名为vec,想知道里面是否包含value就可以用find函数
int value=41;
vector<int>::const_iterator result =
find(vec.begin(),vec.end(),search_value);
cout<<"The value"<<value
<<(result==vec.end()
?"is not present" //找不到就返回最后一个元素下一个位置迭代器
:"is present") //找到就返回元素位置迭代器
<<endl
find函数使用两个迭代器和一个值,检查两个迭代器之间所有元素,找到和值相等的元素就会返回该元素迭代器,找不到就会返回第二个迭代器实参,只要比较返回值是否等于第二个迭代器实参就知道有没有找到。
- accumulate函数
accumulate函数在numeric头文件中定义,假设有一个int型的vector对象,名为vec,下面的代码
int sum = accumulate(vec.begin(),vec.end(),41);
得到的sum是将vec所有元素求和再加41。
这个函数也可以用来连接string型vector容器中元素:
string sum=accumulate(v.begin(),v.end(),string(""));
注意这时第三个变量一定是string类型,不能是字符串字面值,原因可以参考我的另一个博客《C++与C字符串对比》。
- find_first_of函数
该函数输入两个迭代器范围,在第一个范围中依次查找和第二个范围中相匹配的元素,如果没找到,就返回第一个范围最后一个迭代器:
size_t cnt=0;
list<string>::iterator it=roster1.begin();
while((it=find_first_of(it,roster1.end(),
roster2.begin(),roster2.end()))
!=roster1.end()){
++cnt;
++it;
}
cout << "Found" << cnt
<< "names on both rosters" <<endl
3.写容器元素的算法
- fill函数
fill函数用第三个输入参数填充容器:
fill(vec.begin(),vec.end(),0);
fill(vec.begin(),vec.begin()+vec.size()/2,10);
本质上是安全的,只会写入与输入范围同样数量的元素,哪怕是空容器,该函数只会对输入范围中已存在元素进行写操作。
- fill_n函数
该函数假定对指定数量的元素写操作是安全的,初学者常犯错误是对没有元素的空容器用fill_n函数。
vector<int> vec; //空容器
fill_n(vec.begin(),10,0); //灾难性错误,无法写入空容器
- back_inserter
该函数可以给容器添加元素,使用该函数之后fill_n就可以对空串操作了:
fill_n(back_inserter(vec),10,0);
效果相当于在vec上调用push_back。
- 写入到目标迭代器的算法
要创建一个vector的副本,可以用copy函数:
vector<int> ivec;
copy(list.begin(),list.end(),back_inserter(ivec));
相当于把list复制到ivec后面。
这个方式效率较低,要创建一个已存在容器的副本可以用下面的方式:
vector<int> ivec(list.begin(),list.end());
- 算法的_copy版本
使用算法的_copy版本对原来的容器做出处理但是不修改原来的元素,只是创建一个新序列来存储处理结果。
比如replace函数,该算法将序列中等于第一个值的元素替换成第二个值:
但如果我们不想改变原来的序列,可以用replace_copy,该函数接收第三个实参,指定保存到调整后序列的目标位置。
vector<int> ivec;
replace(ilist.begin(),ilist.end(),0,41);
replace_copy(list.begin(),list.end(),back_inserter(ivec),0,41)
4.对容器元素重新排序的算法
举个例子, words是字符串容器:
the quick red fox jumps over the slow red turtle
- 整理
sort(words.begin(),words.end());
上述代码作用后,vector的元素按字典序排列:
fox jumps over quick red red slow the the turtle
其中red和the重复。
- 去除重复
vector<string>::iterator end_unique=unique(words.begin(),words.end());
使用unique函数后并未删除重复,只是把重复的元素放到最后,此处返回end_unique。
此时原来的vector变成了:
fox jumps over quick red slow the turtle red the
最后使用erase删除重复:
words.erase(end_unique,words.end());
得到:
fox jumps over quick red slow the turtle
即使没有重复的元素,调用erase也是安全的,因为如果不存在重复,unique返回的是words.end(),也就是说需要删除的范围为空。类似的函数还有.remove(val)和.reverse()。
- stable_sort和count_if函数
同样以解决一个小问题为例,以统计长度不小于6的单词个数为例,为解决此问题需要用到另外两个泛型算法:stable_sort和count_if,使用这些算法需要配套函数谓词(predicate):
bool isShorter(const string &s1, const string &s2)
{
return s1.size()<s2.size();
}
bool GT6(const string &s)
{
return s.size()>=6;
}
对上面的排序去重的字符串容器使用stable_sort:
stable_sort(words.begin(),words.end(),GT6);
调用后words中元素按长度排列,但是长度相同的单词仍保持字典序。
用count_if统计长度不小于6的单词:
vector<string>::size_type wc =
count_if(words.begin(),words.end(),GT6);
类似的还有find_if、.remove_if(pred)函数,这类函数通常多一个参数,需要输入一个谓词。
5.迭代器
- 插入迭代器
有三种:(1)back_inserter使用push_back实现插入;
(2)front_inserter使用push_front实现插入;
(3)inserter使用insert实现插入,和前两个相比,多了一个实参也就是指向插入位置的迭代器,实现指定位置之前插入。
下面用例子说明inserter:
list<int> ilist1,ilist2,ilist3;
for(list<int>::size_type i=0;i!=4;++i)
ilist1.push_fornt(i); //ilist1为3 2 1 0
copy(ilist1.begin(),ilist1.end(),front_inserter(ilist2)); //ilist2为0 1 2 3
copy(ilist1.begin(),ilist1.end(),inserter(ilist3,ilist3.begin())); //每次在固定位置插入,ilist3结果是3 2 1 0
- 流迭代器
尽管iostream不是容器,STL同样提供了iostream使用的迭代器。
下面用istream_iterator创建vector来说明用法:
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while(in_iter!=eof)
vec.push_back(*in_iter++);
上面的代码可以用下面的更简洁代码代替:
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
vector<int> vec(in_iter,eof);
使用ostream_iterator的例子:
ostream_iterator<string> out_iter(cout,"\n");
istream_iterator<string> in_iter(cin),eof;
while(in_iter!=eof)
*out_iter++=*in_iter++;
该段程序读cin,将结果输出到cout不同行中。
与算法一起使用流迭代器:
istream_iterator<int> cin_it(cin);
istream_iterator<int> end_of_stream;
vector<int> vec(cin_it,end_of_stream);
sort(vec.begin(),vec.end());
ostream_iterator<int> output(cout," "); //定义输出流迭代器
unique_copy(vec.begin(),vec.end(),output); //将vector中的内容去重复制到输出流
- 反向迭代器
一个既支持++也支持–的迭代器就可以定义反向迭代器,但是流迭代器由于不能反向遍历,因此没有反向迭代器。
类似迭代器,反向迭代器rend指向头一个元素之前的位置。
使用迭代器输出逗号分隔的单词中第一个单词可以这样写:
string::iterator comma=find(line.begin(),line.end(),',');
cout<<string(line.begin(),comma)<<endl;
但是如果用反向迭代器想要输出最后一个单词,类似的代码输出将是倒序的,想要输出正序的最后一个单词,可以这样写:
string::iterator comma=find(line.rbegin(),line.rend(),',');
cout<<string(comma.base(),line.end())<<endl; //.base()返回逗号后一个元素位置,此处line.end()不能换成line.rbegin(),因为指向的位置不同
- 容器特有的算法
先定义随机访问迭代器:除了有双向迭代器所有功能以外,还支持以下操作:
1关系操作符如>等,比较两个迭代器位置;
2可以通过迭代器和整型数值n的加减实现迭代器在容器中向前或向后退回n个元素;
3通过两个迭代器之间减法操作符得到两个迭代器之间的距离;
4下标操作符iter[n],等价于*(iter+n);
输入/输出迭代器-前向迭代器-双向迭代器-随机访问迭代器 层级依次变高,高级迭代器拥有低级迭代器所有功能,map,set,list提供双向迭代器,然而 string,vector,deque提供随机访问迭代器。list不提供随机访问迭代器,因此merge,remove,reverse,unique等算法虽然可以用在list上却会付出性能代价。
下面是list容器特有操作。
lst.merge(lst2);
lst.merge(lst2,comp);//将lst2的元素合并到lst中,两个容器都必须排序,用前一个函数必须是顺序,后一个使用comp谓词进行比较
lst.remove(val);
lst.remove_if(pred);//使用lst.erase删除所有等于指定值或者谓词返回不为1的值
lst.reverse();//反向排列
lst.splice(iter,lst2);
lst.splice(iter,lst2,iter2);
lst.splice(iter,beg,end);//第一个版本将lst2中所有元素移到lst的iter前,第二个版本只移动iter2指向的元素,这种情况下lst2和lst可以是同一个list,第三个版本移动beg和end范围内的元素,这个范围也可以是lst中的一部分