泛型算法与容器

本文详细介绍了C++中的泛型算法,包括只读算法、写入元素、元素重排,强调算法不会改变底层容器大小。讨论了谓词、lambda表达式和参数绑定bind的应用,以及迭代器的种类如插入迭代器、流迭代器和反向迭代器。通过实例展示了如何使用这些工具在容器中进行高效操作。
摘要由CSDN通过智能技术生成

容器中,我们对容器中元素的操作有限,多数情况下,我们可以进行插入、删除、访问首尾元素、确定容器大小以及获得指向容器的iterator。那如果我们想要查找特定元素、替换或删除一个特定值、给元素排序,等等这些操作是容器中未进行定义的。泛型算法(generic algorithm)给我们提供了这些容器没有的操作。 使用泛型算法我们需要添加头文件 #include ,这里有100多个算法哦。另外标准库还在头文件numeric中定义了一组数值泛型算法。

概念


* 算法永远不会执行容器的操作*
泛型算法本身不会执行容器的操作,只会运行于iterator之上,执行iterator的操作。算法的这一特性带来的一个惊喜,亦或者可以说是一个非常必要的程序假定:算法永远不会改变底层容器的大小。算法可能改变的有:容器中元素的值、元素的位置(移动元素),但永远不会直接添加/删除元素。

算法理解


看看上面说的,哇,100多个算法,那我怎么记住呢,平时要用的时候怎么b办呢。机智的小伙伴回答:查文档啊,nonono,现在还有更机智的方法:来理解之后再查文档,我也是对自己够够的了。废话不多说,正题开始,理解算法。

只读算法

–只会读取输入范围内的元素,而不对其进行改变
find 查找给定值是否在容器中,是返回true。
count() 返回给定值出现的次数。
accumulate 求和函数,第三个参数的类型决定了加法中使用那种加法运算符以及返回类型。
equal 操作两个序列,要求两个序列中的元素数目一样多,该算法用于确保两个序列是否保存相同的值。

写容器中的元素

–给容器中的元素赋新值
给容器中的元素赋新值要求:序列元大小>=要写入元素的数目
fill (begin,end,val) 向给定输入序列中写入数据,将给定值val复制给输入序列范围[begin,end)内的每一个元素
fill_n(dest,n,val) 将给定值val赋值给iterator指向的元素开始dest的指定个元素n。
copy(begin(a1),end(a1),a2) 参数为三个iterator,把a1的元素拷贝给a2
back_inserter(vec) 插入迭代器,将一个元素添加到迭代器vec中

vector<int> vec;
fill_n(back_inserter(vec),10,100); //向vec中添加10个值为100的元素

容器中的元素重排

—改变元素位置而不改变元素的

谓词


一元谓词:返回布尔值的一元函数是谓词。这种函数可供STL算法进行判断。常用于find_if、remove_if以满足相应的情况来查找或者删除。

二元谓词:接受两个参数并返回一个布尔值的函数是二元谓词。这种函数用于诸如std::sort等STL函数中,如下使用二元谓词对存储std::string 值的容器进行不区分大小写的排序。二元谓词::在判断两个对象时经常使用到。

重载之后的sort函数形如:sort(begin,end,predicate)。

lambda表达式


这里写图片描述

如图,lambda表达式由下面几个部分构成:
1. lambda-introducer (捕获字段):
2. lambda-parameter-declaration-list (变量列表)
3. mutable-specification (捕获的变量可否修改)
4. exception-specification (异常设定)
5. lambda-return-type-clause (返回类型)
6. compound-statement (函数体)

[lambda-introducer](parameters) mutable exception_specification->return_type{ statement }

外部变量的捕获规则:
默认情况下,即捕获字段为 [] 时,lambda表达式是不能访问任何外部变量的,即表达式的函数体内无法访问当前作用域下的变量。

如果要设定表达式能够访问外部变量,可以在 [] 内写入 & 或者 = 加上变量名,其中 & 表示按引用访问,= 表示按值访问,变量之间用逗号分隔,比如 [=factor, &total] 表示按值访问变量 factor,而按引用访问 total。

不加变量名时表示设置默认捕获字段,外部变量将按照默认字段获取,后面在书写变量名时不加符号表示按默认字段设置,比如下面三条字段都是同一含义:

[&total, factor] // 按值访问变量 factor,而按引用访问 total。
[&, factor]
[=, &total]

参数列表(可选)
lambda表达式的参数列表基本和函数的一致,不过有如下限制:

1. 参数列表不能有默认参数
2. 不能是可变参数列表
3. 所有的参数必须有个变量名

lambda表达式使用的参数列表。只有在不使用任何参数,并且没有自定mutable、一个exception_specification 和一个return_type的情况下可以忽略该列表,返回类型在某些情况下也是可以忽略的,详见对return_type的说明:eg: [] {return 10;}

能否修改捕获的变量(可选)
如果所在作用域的变量是通过值捕捉到,那么lambda表达式主体中可以使用这些变量的副本。这些副本默认标记为const,因此lambda表达式的主体不能修改这些副本的值。如果lambda表达式标记为mutable,那么这些副本则不是const,因此主体可以修改这些本地副本。

如果在参数列表后加上了 mutable,则表示表达式可以修改按值捕获的外部变量的拷贝。

异常设置(可选)
和函数一样,可以用 throw 来限定表达式能够抛出哪些异常。

返回类型(可选)
如果设置返回类型,你需要在类型名前面加上 ->。如果你只有一个返回语句的话,返回类型可以省略,编译器将会为你做出判断。如果忽略了return_type,那么编译器会根据以下原则判断返回类型:

如果lambda表达式主体的形式为{return expression;}那么表达式return_type的类型为expression的类型。其他情况下的return_type为void。

函数体
lambda表达式的函数体和普通函数大致相同。如果lambda的函数体包含任一return语句之外的内容,且未指定返回类型,则返回void。

 //将上图的lambda表达式补充完整:
 z = [=]()mutable throw() -> int { 
     int n = x + y; x = y ; y = n; return n;}();
//这行代码定义了一个没有返回值也没有任何参数的lambda表达式。
//注意:尾部的(),这对括号使得这个lambda表达式立即执行: 
[] {cout << "Hello from Lambda" << endl; } ();  

可以使用lambda表达式调用stable_sort(),fing_if(),for_each()。

lambda应用

将lambda表达式用作返回值

定义在头文件中的std::function是多态的函数对象包装,类似函数指针。它可以绑定至任何可以被调用的对象(仿函数、成员函数指针、函数指针和lambda表达式),只要参数和返回类型符合包装的类型即可。返回一个double、接受两个整数参数的函数包装定义如下:

function<int(void)> multiplyBy2Lambda(int x)  
{  
    return [=]()->int{return 2 * x; };  
}  
//在这个例子中,lambda表达式的返回类型和空参数列表可以忽略,可改写为:
return[=] {return 2 * x; };  

将lambda表达式用作参数:

你可以编写lambda表达式作为参数的函数。例如,可通过这种方式实现回调函数。例如

generate(vec.begin(), vec.end(), [&index] {return ++index; });  
for each (vec.begin(), vec.end(),[](int i){cout << i << " "; }); 
{  
    cout << endl;  
    testCallback(vec, [](int i){return i < 6; });  
}  

参数绑定bind

这里从what,why,how三个方面讲解一下bind的简单用法。
- what
这里直接看宏定义吧,看完之后迷糊的话就看后面的哦!

// new callbacks based on C++11
#define CC_CALLBACK_0(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, ##__VA_ARGS__)

#define CC_CALLBACK_1(__selector__,__target__, ...)
std::bind(&__selector__,__target__, std::placeholders::_1,
##__VA_ARGS__)

#define CC_CALLBACK_2(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, std::placeholders::_1, 
std::placeholders::_2, ##__VA_ARGS__)

#define CC_CALLBACK_3(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, std::placeholders::_1, 
std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)
  • why

    C++98中,有两个函数bind1st和bind2nd,它们分别可以用来绑定functor的第 一个和第二个参数,它们都是只可以绑定一个参数。各种限制,使得bind1st和bind2nd的可用性大大降低。
    而绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,这个bind才是真正意义上的绑定。bind绑定的可调用对象可以是Lambda表达式或者类成员函数等可调用对象。

  • how
    在c++11之前,要绑定某个函数、函数对象或者成员函数的不同参数值需要用到不同的转换器,如bind1st、bind2nd、fun_ptr、mem_fun和mem_fun_ref等.在c++11中,绑定参数的方法得以简化.c++11提供了”一站式”绑定模板bind,其用法为:

#include <functional>
std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定     值2,...,参数绑定值n);

先看看waht部分的代码:std::placeholders是一个占位符。当使用bind生成一个新的可调用对象时,std::placeholders表示新的可调用对象的第 几个参数和原函数的第几个参数进行匹配,看一个实例:

auto g = bind(f,a,b,_2,c,_1);
g(X,Y); //调用g(X,Y)会调用f(a,b,Y,c,X)

可以看到在bind的时候,第一个位置是f,除了这个,参数的第3个和第5个位置为占位符,表示可调用对象g有两个参数,在调用g时,它的第2个参数与f的第三个参数匹配,g的第1个参数与f的第5个参数匹配。如调用g(X,Y)会调用f(a,b,Y,c,X)。

使用std::bind的一些需要注意的地方:

  • bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的;
  • 对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的;
  • bind的返回值是可调用实体,可以直接赋给std::function对象;
  • 对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的;
  • 类的this可以通过对象或者指针来绑定。

再探迭代器


除了为每个容器定义的迭代器外,标准库在头文件iterator中还定义了额外集中迭代器,包括:

  • 插入迭代器—insert iterator

    插入迭代器提供了以下几种操作:*itr,itr++,++itr,itr = value。但实际上,前三种操作为“空操作”(no-op),仅仅返回itr。第四种操作itr = value才是插入迭代器的核心,这个操作通过调用容器的成员函数(push_back(),push_front(),insert(),取决于插入器类型)把value插入到插入器对应容器的相应的位置上。

    插入器有三种,差别在于元素插入的位置:
    back_inserter(),通过调用容器的push_back成员函数来插入元素,因此这种插入器只对vector,list,deque和string有效。
    front_insert (), 通过调用容器的push_front成员函数来插入元素,因此它只对list和deque有效。
    inserter(),通过调用insert成员函数来插入元素,并由用户指定插入位置,它对所有标准的容器类型都有效,因为所有容器都定义了insert成员函数。例如:

itr = value;
//等价于
pos = container.insert(pos,value);  
++pos;  //即在指定位置插入元素,同时更新下次插入位置。 
  • 流迭代器—stream iterator

    Iterator头文件定义了两个流迭代器模板,其中istream_iterator用于输入流,ostream_iterator用于输出流,T是要从流中提取的或者写入流中的对象类型。
    输入流迭代器:istream_iterator<int> input(cin);创建了一个istream_iterator类型的迭代器 input,可以指向流中int类型的对象。这个构造函数的实指定与迭代器相关的实际流,因此它是一个可以从标准输入流cin中读取整数的迭代器。默认的istream_iterator构造函数创建一个end-of-stream迭代器。它等价于通过调用容器的end()函数获得容器的end迭代器。下面代码说明了如何创建cin的end-of-stream迭代器来补充input迭代器:istream_iterator<int> inputEnd; 具体使用如下:

vector<int> numbers;
istream_iterator<int> input(cin),inputEnd;
while(input != inputEnd)
{
    numbers.push_back(*input++);
}

输出流迭代器:ostream_iterator<int> out(cout);该模板的实参int指定要处理的数据类型,构造函数实参cout指定将作为数据的目的地的流,以便cout迭代器能将int类型的值写到标准输出流中。如:

ostream_iterator<int> out(cout);//输出没有空格
//ostream_iterator<int> out(cout,” ”);//输出空格
int data [] = {1,2,3,4,5,6,7,8,9};
vector<int> numbers(data,data+9);
copy(numbers.begin(),numbers.end(),out);

这里需要注意的是:流迭代器不支持自减操作。

  • 反向迭代器—reverse iterator

    反向迭代器是一种反向遍历容器的迭代器。也就是,从最后一个元素到第一个元素遍历容器。反向迭代器将自增(和自减)的含义反过来了:对于反向迭代
    器,++ 运算将访问前一个元素,而 – 运算则访问下一个元素。

    回想一下,所有容器都定义了 begin 和 end 成员,分别返回指向容器首元素和尾元素下一位置的迭代器。容器还定义了 rbegin 和 rend 成员,分别返回指向容器尾元素和首元素前一位置的反向迭代器。与普通迭代器一样,反向迭代器也有常量(const)和非常量(nonconst)类型。图 11.1 使用一个假设名为 vec 的 vector 类型对象阐明了这四种迭代器之间的关系。
    

    这里写图片描述
    图 1 比较 begin/end 和 rbegin/rend 迭代器

vector<int> vec={0,12,4,2,53,5,8};  

vector<int>::reverse_iterator r_iter;  
for (r_iter = vec.rbegin(); // binds r_iter to last element  
      r_iter != vec.rend(); // rend refers 1 before 1st element  
      ++r_iter) // decrements iterator one element  
    cout << *r_iter << endl;  

虽然颠倒自增和自减这两个操作符的意义似乎容易使人迷惑,但是它让程序员可以透明地向前或向后处理容器。例如,为了以降序排列 vector,只需向 sort传递一对反向迭代器:sort(vec.rbegin(), vec.rend());

啊,终于写完,这个笔记写的可谓是困难重重,中间遇到csdn博客罢工,导致有些东西写了好几遍。额,安慰自己说这是熟能生巧的一个过程,还是不错的呢。发现一个好玩的事就是用Markdown记录东西,还得会点基础的HTML呢,表格什么的直接就可以上手了,不用其他转换,这样速度也会很快。期待有一天Markdown功能更强大。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值