c++ primer学习(八) 泛型算法

c++之所以叫c++,就是因为他相对于c有两个提升,一个是面向对象,一个是泛型。虽然泛型主要讲的是模板,但是泛型算法确实给我们了极大的便利。

这些泛型算法一般都是定义在algorithm库中的(绝大多数算法都是定义在algorithm中的),还有一部分泛型算法是在numeric库中的。

其实我感觉这一章的前半部分只需要画一张表就可以了,这一章前一部分就是一些算法:


函数名 所在库 所有形式 返回以及解释 补充知识
find() algorithm find(b,e,val)//b和e为迭代器,val为值 寻找(b,e)中是否有val。返回第一个等于给定元素的迭代器,若没有,则返回第二个参数(e迭代器) 可以在子序列中查找,b和e不必须是begin和end
count() algorithm count(b,e,val)//b和e为迭代器,val为值 寻找(b,e)val出现的次数。返回第一个等于给定元素的迭代器,若没有,则返回第二个参数(e迭代器) 泛型算法不依赖容器,它不会改变容器大小。它依赖于迭代器和元素类型,要保证元素类型可以使用此算法(比如int支持<,>等)
accumulate() numeric accumulate(b,e,val)//b和e为迭代器,val为和的初始值 对b和e中元素求和,val是和的初始值。返回类型与第三个(val)参数一致。 需要元素对象支持“+”。比如accumulate(v.begin(),v.end(),"")就是错的,因为""默认为char*类型,char*没有+运算符。所以要写为accumulate(v.begin(),v.end(),string(""))
equal() algorithm equal(v1.b,v1.e,v2.b) 比较序列1和从b开始的序列2,
返回true(相等)或false(不等),假设序列2至少与序列1长度相等。因为要处理序列1中的所有元素
v1和v2对象不一定类型相同,但至少支持==,如string和char*
fill() algorithm fill(b,e,val) 将b和e之间的用val填充 确保序列有效
fill_n() algorithm fill_n(v.b,v.size,val) 将b开始的size个元素用val填充 不能在空容器上调用,因为空容器没有指定大小;b+size<总大小
back_inserter(迭代器介绍) iterator fill_n(back_inserter(v),v.size,val) 接受指向容器的引用,返回与该容器绑定的插入迭代器 实际是调用push_back(),可以实现对容器添加元素
copy() algorithm copy(a1.b,a1.e,a2) 将a1的b至e元素拷贝到a2,返回目的位置迭代器后的值 a2至少和e-b一样长
replace() algorithm replace(b,e,val1,val2) 将b至e的值从val1改为val2  
replace_copy() algorithm replace_copy(b,e,v2.b,val1,val2) 将v1的b至e中的val1改为val2并添加到v2中去,v1不变 v2至少和e-b一样长,若没有定义v2,则可使用:replace_copy(v1.begin(),v1.end(),back_inserter(v2),val1,val2);
unique() algorithm unique(b,e) 返回指向不重复区域的尾后迭代器 不会删除元素,而是将重复元素放入返回迭代器之后的位置,删除元素则要使用成员函数,如erase
试了一下发现效果不好,因为超过了边缘,可是网站自带的表格功能有太弱,没办法把画好的表格完美拷贝过来,下一张表就直接在网站上画了吧。

第二部分:定制操作(lambda表达式)

谓词:一个可调用表达式,返回结果是可以用作条件的值,只有一元谓词和二元谓词,即接受一个/2个参数的谓词。可以将谓词作用于算法。

sort() 按字典排序  
stable_sort() 保证等长元素字典序  
stable_sort会按长度拍序。

find_if() 接受谓词版本的find,第3个参数是一个谓词

lambda()表达式:形式:[捕获列表](参数列表)->返回类型{函数体}。参数列表和返回可以没有。捕获列表是lambda所在函数中的所需要的局部变量列表(所在函数包括main)。

调用lambda举例:find_if(words.begin(),words.end(),[sz](const string & s){return s.size()>=sz;});假设函数中有sz,调用find_if去获得第一个长度>=sz的迭代器,没有则返回end()。

for_each(b,e,lambda):对输入序列中(b,e)的每一个元素调用可调用对象,这里我写作lambda。

关于捕获,分为值捕获和引用捕获,比如我们需要一个ostream对象,而它不能被拷贝,所以只能用引用捕获。而隐式捕获则是在捕获列表中写:[&]或者[=],前者是捕获引用,后者则是捕获值,对象很多时可:[&,args]或者[=,args],前者args全为值,后者则是引用。

对于捕获列表中是值捕获的,lambda不能改变其值,我们可以加一个mutable让其可以改变:int i=0;auto f=[i]()mutable{return ++i;}而引用捕获则依赖此引用是const还是不是。

lambda返回值:若函数体是单一return语句则无需返回类型;若为多条语句需要显式指出返回类型。

绑定参数:目的是将接受多个参数的函数改为一个或2个(当然也可以是其他数目,不过在这里当然是1至2个),用法:auto newcallable=bind(callable,arg_list);,arg_list是占位符,表示这里是一个newcallable的参数,_1是第一个参数,_2是第二个参数,等等。如:bool check_size(const string &s,string::size_type sz){return s.size()>=sz;}

auto check6=bind(check_size,_1,6);,这样,就锁定了check_size的第一个参数为check6的形参,check_size()的第二个参数为6。_n在库placeholders中。注意,newcallable的调用顺序完全决定于_n。如auto f=g(a,b,_2,_1);则f(x,y)为:g(a,b,y,x)。

以引用的方式使用bind:必须使用ref()或者cref(),不能直接把如ostream类型直接bind到_n上,因为bind的实质是拷贝。

这一部分看总结里似乎很短,但当时确实花了很大力气,尤其是在bind这里。

第三部分:迭代器

除了基础迭代器,如begin,end外,还有额外几种:

插入迭代器:绑定到容器上,可用来向容器插入元素。支持操作:it=t。在it位置插入t。但是*it,++it,it++都没有效果(但没有错),返回it。插入迭代器有以下几个:back_inserter,front_inserter,inserter。前2个是迭代器,创建一个使用push_back或push_front的迭代器。后一个是函数,接受2个参数:inserter(c,iter);会将后边的元素插入到iter之前的位置,并返回这个位置。如:*iter=val;就是:iter=c.insert(iter,val);++iter;。

流迭代器:迭代器绑定到io流上。istream_iterator读取输入流,ostream_iterator写入输出流。如:

istream_iterator in_iter(cin); istream_iterator eof;while(in_iter!=eof)v.push_back(*in_iter++);eof为空迭代器,可以代替尾后迭代器。操作:istream_iterator in(is);istream_iterator end;表示尾后迭代器。in1==in2;in1!=in2;*in,in->iter,++in,in++。注意,istream_iterator是允许懒惰求值,即在第一次解引用之前读取输入,所以要警惕没有使用就销毁的情况。

ostream_iterator:操作:ostream_iterator out(os),ostream_iterator out(os,d);将输出值后面写入os,每个值后面都输出一个d,d指向空字符结尾的字符数组。out=val用<<将val写入out。*out,++out,out++无效操作,无错。举例:ostream_iterator out_iter(cout," ");for(auto e:vec)*out_iter++=e; cout<

反向迭代器:除了forward_list,其他容器都支持反向迭代器,通过调用rbegin,rend,crbegin,crend获得。反向迭代器会反向处理数据,我们通过reverse_iterator的base来完成转换,把成员函数返回正向迭代器:cout<

除了上述分类,还有一种:输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器。c++标准指明了每个算法的最小迭代器要求。可以看stl来了解。

算法的形参模式:alg(b,e,args);

alg(b,e,dest,args);

alg(b,e,b2,args);

alg(b,e,b2,e2,args);

dest是算法可以写入的目的位置的迭代器。所有的算法都遵循此模式。

命名上,有使用_if来表示接受一个谓词,它和原算法都可以有相同数目的形参。如find和find_if。

使用_copy表示是将元素写入(拷贝到)一个新输出目的位置,如reverse(b,e)和reverse_copy(b,e,dest),将反转后的b,e拷贝到dest。当然也有同时使用_if和_copy的:remove_copy_if:remove_copy_if(v1.begin(),v1.end(),back_iterator(v2),[](int i)[return i%2;});将偶数从v1拷贝到v2.

对链表的特定算法:lst.merge(lst2)

lst.merge(lst2,comp)//将lst2合并入lst,lst2变为空。第一个版本用<运算符,第二个用给定的。

lst.remove(val)

lst.remove_if(pred)//调用erase删除是val或令pred为真的元素。

lst.reverse()

lst.sort()

lst.sort(comp)

lst.unique()

lst.unique(pred)

我们对链表(list,forward_list)形式的有这些成员函数形式的算法是因为只用交换链接,而通用算法(也是可以用的)会交换元素,对于链表来说效率太低。

splice算法:链表独有。

lst.splice(args),flst.splice_after(args)。args有如下形式:

(p,lst2)p是指向lst或flst首前位置的迭代器,将lst2移入lst的p之前或flst的p之后。元素从lst2中删除。

(p,lst2,p2)把p2指向的lst2的元素移入p位置。lst2和lst可以相同。

(p,lst2,b,e)把lst2的b,e移入p,lst2和lst可以相同,但p不能在[b,e)中。

这一章到此结束,其实每次整理的过程都非常痛苦,因为基本上就是用很短的时间(不到一天)把一章重新学一遍,经常会有的感觉是“学完了一小节还有一小节,翻过了一页还有一页”,没办法,大部头就是这个样子,不过好处也是明显的,很多第一次匆匆看过的内容终于仔细看了一遍,第一次没理解的概念有了一点认识。

而且,计算机领域的核心大部头就那么几本,看完一本就少一本,等都看的差不多的时候,也就可以出师了。

最后,其实看这种大部头本身也是一种学习,你要知道,你哪怕完整地学过一本,也领先于中国起码70%的cs学生了。btw,课后题一定要做···如果你没有别的项目去练手,这些题真的不错。

希望我们一起进步。

你可能感兴趣的:(c++,primer学习整理)