您的位置:首页 > 编程语言 > C语言/C++

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

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

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

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

函数名所在库所有形式返回以及解释补充知识
find()algorithmfind(b,e,val)//b和e为迭代器,val为值寻找(b,e)中是否有val。返回第一个等于给定元素的迭代器,若没有,则返回第二个参数(e迭代器)可以在子序列中查找,b和e不必须是begin和end
count()algorithmcount(b,e,val)//b和e为迭代器,val为值寻找(b,e)val出现的次数。返回第一个等于给定元素的迭代器,若没有,则返回第二个参数(e迭代器)泛型算法不依赖容器,它不会改变容器大小。它依赖于迭代器和元素类型,要保证元素类型可以使用此算法(比如int支持<,>等)
accumulate()numericaccumulate(b,e,val)//b和e为迭代器,val为和的初始值对b和e中元素求和,val是和的初始值。返回类型与第三个(val)参数一致。需要元素对象支持“+”。比如accumulate(v.begin(),v.end(),"")就是错的,因为""默认为char*类型,char*没有+运算符。所以要写为accumulate(v.begin(),v.end(),string(""))
equal()algorithmequal(v1.b,v1.e,v2.b)比较序列1和从b开始的序列2,

返回true(相等)或false(不等),假设序列2至少与序列1长度相等。因为要处理序列1中的所有元素
v1和v2对象不一定类型相同,但至少支持==,如string和char*
fill()algorithmfill(b,e,val)将b和e之间的用val填充确保序列有效
fill_n()algorithmfill_n(v.b,v.size,val)将b开始的size个元素用val填充不能在空容器上调用,因为空容器没有指定大小;b+size<总大小
back_inserter(迭代器介绍)iteratorfill_n(back_inserter(v),v.size,val)接受指向容器的引用,返回与该容器绑定的插入迭代器实际是调用push_back(),可以实现对容器添加元素
copy()algorithmcopy(a1.b,a1.e,a2)将a1的b至e元素拷贝到a2,返回目的位置迭代器后的值a2至少和e-b一样长
replace()algorithmreplace(b,e,val1,val2)将b至e的值从val1改为val2 
replace_copy()algorithmreplace_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()algorithmunique(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<int> in_iter(cin); istream_iterator<int> eof;while(in_iter!=eof)v.push_back(*in_iter++);eof为空迭代器,可以代替尾后迭代器。操作:istream_iterator<T> in(is);istream_iterator<T> end;表示尾后迭代器。in1==in2;in1!=in2;*in,in->iter,++in,in++。注意,istream_iterator是允许懒惰求值,即在第一次解引用之前读取输入,所以要警惕没有使用就销毁的情况。

ostream_iterator:操作:ostream_iterator<T> out(os),ostream_iterator<T> out(os,d);将输出值后面写入os,每个值后面都输出一个d,d指向空字符结尾的字符数组。out=val用<<将val写入out。*out,++out,out++无效操作,无错。举例:ostream_iterator<int> out_iter(cout," ");for(auto e:vec)*out_iter++=e;
cout<<endl;就会把vec中的每个元素写入iter,不过这个++没有作用是为了看起来很清晰,也为了和其他迭代器保持一致。还可以使用:copy(vec.begin(),vec.end(),out_iter);更加好用。

反向迭代器:除了forward_list,其他容器都支持反向迭代器,通过调用rbegin,rend,crbegin,crend获得。反向迭代器会反向处理数据,我们通过reverse_iterator的base来完成转换,把成员函数返回正向迭代器:cout<<string(line.crbegin(),rcomma)<<endl;//输出反向的string。cout<<string(rcomma.base(),line.cend())<<endl;//将迭代器变为正常的。

除了上述分类,还有一种:输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器。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,课后题一定要做···如果你没有别的项目去练手,这些题真的不错。

希望我们一起进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: