C++中的泛型算法相关概述及要点总结

C++之 泛型算法

1.概述

  • 大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。
  • find(第一个迭代器,第二个迭代器,要查找的值)由于find操作的是迭代器,因此我们可以用同样的find函数在任何容器中查找值。find可以返回尾后迭代器来表示未找到给定元素。

算法永远不会执行容器的操作:
泛型算法本身不会执行容器的操作,他们只会运行与迭代器之上,执行迭代器的操作——算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器中移动元素,但永远不会添加和删除元素。此外,标准库定义了一类特殊的迭代器,叫做插入器(inserter),与普通迭代器相比,插入器能够做更多的事情。

  • algorithm 中定义了一个名为count的函数,它类似于find函数,接受一对迭代器和一个值作为参数,count返回给定值在序列中出现的次数。
  • 在头文件numeric中,accumulate函数接受三个参数,前两个指出了需求和的范围,第三个参数是和的初值。

accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。此外string中定义了+运算符。可以通过调用此函数将string元素连接起来。但是需要注意的是,不能讲字符串字面值传递给第三个参数,因为const char* 并没有+运算符。

  • equal是另一个只读算法,用于确定两个序列是否保存相同的值。此算法接受三个迭代器。前两个表示第一个序列范围,第三个表示第二个序列的首元素。

记住算法不会执行容器操作,因此他们自身不可能改变容器的大小。
fill算法接受一对迭代器表示一个范围,还接受一个值作为第三个参数。fill算法是写容器元素的算法,不是只读算法。

  • 一个初学者非常容易犯的错误是在一个空容器上调用fill_n (或者类似的写元素的算法)。
  • back_inserter 接受一个指向容器的引用,返回一个与容器绑定给定插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back 将一个具有给定值的元素添加到容器中。
vector <int> vec;
auto it=back_inserter(vec);
*it=42; //vec 中现在有一个元素,值为42

我们还可以使用back_inserter 来创建一个迭代器,作为算法的目的位置来使用。

vector <int> vec;//空向量
fill_n(back_inserter(vec),10,0);//添加10个0元素到vec
  • copy()算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。此算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围的元素拷贝到目的序列中。
  • replace() 算法读入一个序列,并将其中给所有等于给定值的元素都改为另一个值。接受4个参数,前两个表示输入范围的迭代器,后面两个分别是要搜索的值和另一个新值。
  • sort()按照字典序排序words,接收两个迭代器,此外sort还有其他形式的重载版本,其中第二个版本就是重载过的,它接受第三个参数,此参数就是一个谓词。谓词是一个可以调用的表达式,其返回结果是一个能用作条件的值。
  • unique算法重排输入序列,将相邻的重复项消除,并返回一个不重复范围末尾的迭代器。

标准库算法对迭代器而不是容器进行操作,因此,算法不能直接添加或删除元素。

2.定制操作

  • 向算法传递函数,可以使用谓词
  • stable_sort ()这种稳定排序算法维持相等元素的原有顺序
  • 可以使用标准库find_if 算法来查找第一个具有特定大小的元素。与find的不同的是find_if的第三个参数是一个谓词。find_if 算法对输入序列中的每一个元素都调用这个给定的谓词。当参数较多时可以利用lambda表达式来代替谓词。
  • 可调用对象:函数和函数指针,以及冲在了函数调用运算符的类以及lambda表达式
  • lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。
  • 在lambda表达式中我们可以忽略参数列表和返回类型,但是必须包含捕获列表和函数体。lambda必须使用尾置返回类型来指定返回类型。
lambda表达式引用示例
[捕获列表](参数列表)返回类型 {函数体} 空捕获列表表示此lambda不使用它所在函数中的任何局部变量

lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。

  • 注意:一个lambda只有在其捕获列表中捕获一个他所在函数中的局部变量,才能在函数体中使用该变量;捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字
  • for_each 算法接受一个可调用对象,并对输入序列中给每一个元素调用此对象。
  • 与参数传递类似,变量的捕获方式也可以是值或者引用。

当以引用方式捕获一个变量时,必须保证在lambda执行时变量时是存在的。lambda捕获的都是局部变量,这些变量在函数结束时就不存在了,如果lambda可能是在函数结束后执行,捕获的引用指向的局部变量已经消失。

  • 如果一个lambda体包含return之外的任何语句,则编译器都假定此lambda返回void。与其他void的函数类似,被推断返回void的lambda不能返回值。
  • transform算法将输入序列中的每一个元素替换为可调用对象操作该元素得到的结果。transform接受三个迭代器和一个可调用对象,前两个迭代器表示输入序列,第三个迭代器表示目的位置,可调用对象可以是lambda或者谓词,或者其他。
  • 当我们需要为一个lamda定义返回类型是,必须使用尾置返回类型。例如:
transform(vi.begin(),vi.end(),vi.begin(),[](int i)-> int {if (i<0) return -i; else return i;});
  • bind的标准库函数定义在头文件functional中,可以将bind函数看做一个通用的函数适配器,它可以接受一个可调用对象,并且生成一个新的可调用对象来适应原对象的参数列表,例如调用形式如下:
auto newcallable =bind (callable, arg_list);
//其中callable是一个可调用对象,newcallable本身也是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable函数,即当我们调用newcallable时,newcallable会首先调用callable,并传递给他arg_list 中的参数。

名字_n都定义在一个名为placeholders 的命名空间中,而这个命名空间本身又定义在std命名空间using std::palcehoders::_1.对于每一个占位符名字我们都必须提供一个单独的using声明,编写这样的声明很麻烦,因而我们可以使用:using namespace namespace_name//这种形式说明希望所有来自namespace_name的名字都可以在我们的程序中使用比如using namespace std::placehlders

  • 与bind函数一样,placeholders命名空间也定义在functional中。
  • 标准库中还有一个cref函数,生成一个保存const引用的类,与bind一样,函数ref和cref也定义在头文件functional中。

3.再探迭代器

  • 标准库在头文件iterator中还定义了额外几种迭代器:
    (1)插入迭代器-这些迭代器被绑定到一个容器上,可用来向容器插入元素。
    (2)流迭代器-这些迭代器被绑定到输入和输出流上,可用来遍历所关联的IO流
    istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据
    (3)反向迭代器-这些迭代器向后而不是向前移动。除了forward_list 之外的标准库都有反向迭代器
    (4)移动迭代器-这些专用的迭代器不是拷贝其中的元素,而是移动他们。
istream_iterator 操作
istream_iterator<T> in(is) ;in从输入流is读取类型为T的值
istream_iterator<T> end; 读取类型为T的值的istream_iterator迭代器表示尾后位置
++in,in++
in->men 与(*in).men的含义类似
ostream_iterator操作
ostream_iterator<T> out (os);out将类型为T的值写到os中
ostream_iterator<T> out(os,d);out将类型为T的值写到输出流os中,每个之后面都输出一个d。d指向一个空字符串结尾的字符数组
out=val 用<<运算符将val写入到out所绑定的ostream中。val的类型必须与out可写的类型兼容
out,++out,out++ 这些运算符是存在的,但不对out做任何事情,每个运算符都返回out
//ostream_iterator 只支持递增,解引用和赋值操作
  • 当我们为out_iter赋值时,可以忽略解引用和递增运算符,但是仍然推荐保留,因为利于读者理解,同时利于修改为操作其他迭代器类型

eof 被定义为空的istream_iterator 从而可以被当做尾后迭代器来使用。

  • rbegin,rend,crbegin,crend成员函数来获得反向迭代器,这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器。

反向迭代器的目的是表示元素范围,而这些范围是不对称的,这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素。

4.泛型算法结构

  • 迭代器分为5种类别
    其中随机访问迭代器提供在常量时间内访问序列中任意元素的能力,此类型的迭代器支持双向迭代器的所有功能,此外还支持很多其他的操作。算法sort要求随机访问迭代器,array,deque,string和vector的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是。
  • 算法的规范
    (1)接受谓词参数的算法都有附加的_if前缀。比如:find算法查找一个指定值,而find_if算法查找一个使得谓词非0的元素
    (2)区分拷贝元素和不拷贝元素的版本:写到额外目的空间的算法都在名字后面附加一个_copy
reverse(beg,end);//反转输入范围中的元素顺序
reverse_copy(beg,end,dest);//将元素按逆序拷贝到dest

(3)一些算法还同时提供了_copy和_if 版本,这些版本接受一个目的位置迭代器和一个谓词。

5.特定容器算法

对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法,此外通用版本的sort要求随机访问迭代器,因此不能用于list和forward_list,因为这两个类型分别提供双向迭代器和前向迭代器。

  • list和forward_list成员函数版本的算法
lst.merge(lst2)和lst.merge(lst2,comp) 将来自lst2的元素合并入lst。lst和lst2必须都是有序的,元素将从lst2中删除,在合并以后,lst2变为空。第一个版本使用<运算符;第二个版本使用给定的比较操作
lst.remove(val)和lst.remove_if(pred)调用erase删除掉与给定值相等(==)或令一元谓词为真的每一个元素。
lst.reverse() 反转lst中元素的顺序
lst.sort()和lst.sort(comp)使用<或给定的比较操作排序元素
lst.unique()和lst.unique(pred)调用erase删除同一个值的连续拷贝。第一个版本使用==,第二个版本使用给定的二元谓词。
  • lst.splice(args)或者flst.splice_after(args)此算法是链表结构所特有的,因此不需要通用版本。C++P370参考
(p,lst2) 函数将lst2的所有元素移动到lst中p之前的位置或者是flst中p之后的位置,将元素从lst2中删除。lst2的类型必须与lst或者flst相同且不能是同一个链表。
(p,lst2,p2)
(p,lst2,b,e) b和e必须是lst2中合法的范围,将给定范围的元素从lst2中移到lst或flst中

链表特有版本和通用版本间的一个重要区别是链表版本会改变底层的容器。

如果大家觉得有用,希望大家能点个赞支持一下!!!有兴趣的话可以关注我的博客,我会持续更新有关机器学习,深度学习,人工智能和算法相关内容的博客!!!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值