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() | 保证等长元素字典序 |
find_if() | 接受谓词版本的find,第3个参数是一个谓词 |
调用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
ostream_iterator:操作:ostream_iterator 反向迭代器:除了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,课后题一定要做···如果你没有别的项目去练手,这些题真的不错。 希望我们一起进步。