C++容器泛型算法

1.引子

  标准容器定义的操作通常包含添加和删除元素;访问第一个和最后一个元素;获取容器大小,并在某些情况下重设容器大小;获取指向第一个和最后一个容器的下一位置的迭代器。
  用户通常希望对容器元素有更多有用操作,比如排序、查找等。标准库没有为每种容器定义相关成员函数,而是定义了一组泛型算法。

2.只读算法

  1. 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函数使用两个迭代器和一个值,检查两个迭代器之间所有元素,找到和值相等的元素就会返回该元素迭代器,找不到就会返回第二个迭代器实参,只要比较返回值是否等于第二个迭代器实参就知道有没有找到。

  1. 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字符串对比》。

  1. 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.写容器元素的算法

  1. fill函数
      fill函数用第三个输入参数填充容器:
fill(vec.begin(),vec.end(),0);
fill(vec.begin(),vec.begin()+vec.size()/2,10);

本质上是安全的,只会写入与输入范围同样数量的元素,哪怕是空容器,该函数只会对输入范围中已存在元素进行写操作。

  1. fill_n函数
      该函数假定对指定数量的元素写操作是安全的,初学者常犯错误是对没有元素的空容器用fill_n函数。
vector<int> vec;                    //空容器
fill_n(vec.begin(),10,0);           //灾难性错误,无法写入空容器
  1. back_inserter
      该函数可以给容器添加元素,使用该函数之后fill_n就可以对空串操作了:
fill_n(back_inserter(vec),10,0);

效果相当于在vec上调用push_back。

  1. 写入到目标迭代器的算法
      要创建一个vector的副本,可以用copy函数:
vector<int> ivec;
copy(list.begin(),list.end(),back_inserter(ivec));

相当于把list复制到ivec后面。
这个方式效率较低,要创建一个已存在容器的副本可以用下面的方式:

vector<int> ivec(list.begin(),list.end());
  1. 算法的_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

  1. 整理
sort(words.begin(),words.end());

上述代码作用后,vector的元素按字典序排列:
fox jumps over quick red red slow the the turtle
其中red和the重复。

  1. 去除重复
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()。

  1. 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. 插入迭代器
    有三种:(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
  1. 流迭代器
    尽管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中的内容去重复制到输出流

在这、里插入图片描述

  1. 反向迭代器
    一个既支持++也支持–的迭代器就可以定义反向迭代器,但是流迭代器由于不能反向遍历,因此没有反向迭代器。
    类似迭代器,反向迭代器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. 容器特有的算法
    先定义随机访问迭代器:除了有双向迭代器所有功能以外,还支持以下操作:
    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中的一部分
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值