算法不依赖与容器(使用迭代器),但大多数依赖于元素类型。如find需要==运算符,其他算法可能要求支持<运算符。
算法永远不会执行容器的操作,永远不会改变底层容器的大小(添加或删除元素)。
accumulate(v.cbegin(), v.cend(), string(“ ”)) 算法累加运算符,第3个参数的类型决定了使用哪个+号运算符。
equal(v1.cbegin(), v1.cend(), v2.cbegin()),逐个比较两个序列。第二个序列至少与第一个序列一样长。
容器和元素类型都不必一样,只要支持==符号比较两个元素类型。
那些只接受单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个等长。
确保算法不会访问第二个序列中不存在的元素是程序员的责任。
算法不检查写操作面,向目的位置迭代器写入数据的算法
auto it = back_inserter(vec);//返回插入迭代器,使用它赋值时会调用push_back将元素添加到容器。
fill_n(it,10,0); //添加10个元素到vec。
很多算法提供拷贝版本,返回的是一份拷贝,原序列并未改变。
sort(words.begin(), words.end());//排序
auto end_unique = unique(words.begin(), words.end());//去重,返回不重复区域后面一个位置
words.erase(end_unique,words.end());//算法不能直接增删,调用erase删除多余元素
即时words没有重复元素,此操作也是安全的
谓词(predicate)
可调用的表达式,返回结果是一个能用作条件的值。
用谓词替代算法默认的操作,如定义二元isShorter函数,替代sort默认的<比较元素。
sort(words.begin(), words.end(), isShorter);
stable_sort(words.begin(), words.end(), isShorter);//维持相等元素的原有排序
partition将容器划分成两部分,true的在前。partition_stable 维持原有元素的顺序
可调用对象
函数、函数指针、函数对象,lambda表达式(可理解为一个未命名的内联函数)
[capture list] (parameter list) -> return type {function body}
其中参数列表和返回类型可以省略,但是捕获列表和函数体必须永远包含。
忽略参数列表等价于空参数列表
忽略返回类型,如果函数体只有一个return语句则从表达式的类型判断;如包含其他任意语句,则返回void。
lambda
不能有默认参数
lambda只有在其捕获列表中的捕获一个它所在函数的局部变量,才能再函数体中使用该变量。仅限局部非static变量,局部static和所在函数外声明的变量可以直接使用。
make_plural(count, “word”, “s”) //区分单复数,count为1则单数
find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >=sz;});
for_each(wc,words.end(),[](const string &s){cout<<s<<” ”;});
排序-去重-删除多余-按长度排序-找到长度大于sz的起点,打印
值捕获
前提是变量可拷贝,被捕获的变量的值在lambda创建时拷贝,而不是调用时。
引用捕获
需要保证捕获的局部变量的有效性,也可以从函数返回lambda,不能包含局部变量的引用捕获。如果可能,尽量避免捕获指针或引用。
lambda捕获列表 |
|
[] |
空捕获列表 |
[names] |
名字列表,默认都是被拷贝 |
[&] |
隐式捕获列表,都采用引用捕获 |
[=] |
隐式捕获列表,都采用值捕获 |
[&,identifier_list] |
除个别值捕获 |
[=, identifier_list] |
除个别引用捕获 |
可变lambda
默认不会改变被捕获的变量的值,如希望改变,使用mutable。
对于只在一两个地方使用的简单操作,lambda表达式是最有用的。
对于在很多地方使用的相同操作,或者一个操作需要很多语句才能完成,通常定义一个函数更好。
捕获列表为空的lambda,通常可以用函数替代;
而对于有捕获变量的,就不那么容易了。会面临谓词参数个数不一致的问题。
bind函数(函数适配器)
接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
auto newCallable = bind(callable, arg_list);
arg_list对应给callable的参数。其中可能包含_n形式的名字,表示占据了传递给newCallable参数的第n个位置。
bool check_size(const string& s, string::size_type sz) {return s.size() >= sz;}
auto check6 = bind(check_size, _1, 6);//
bind调用只有一个占位符,便是check6只接收一个参数,占位符出现在第一个arg_list的第一个位置,表示check6此参数对应check_size的第一个参数const string&。
auto g =bind(f, a, b, _2, c, _1);//f有5个参数,其中_1和_2分别对应g的第1个和第2个参数
g(_1,_2)映射为f(a, b, _2, c, _1)
需要包含命名空间using std::placeholders::_1;
using namespace std::placeholders;
对于不是占位符的参数,默认是拷贝到bind返回的可调用对象中的,有时候需要用引用方式传递ref(os),cref()生成const引用。
bind ref cref都在头文件functinal里。
注:bind1st和bind2nd,分别只能绑定第一个或第二个参数,由于局限性强已经在新标准中弃用。binder1st和binder2nd也类似,只不过他们是模板类需要指定op的类型
bind1st(op, value)(x) -> op(value,x)
bind2nd(op,value)(x) -> op(x,value)
其他迭代器
插入迭代器:绑定到容器上,可以向容器插入元素
流迭代器:绑定到输入输出流,可以遍历关联的IO流,只要定义了<<或>>运算符的对象
反向迭代器:向后移动,forward_list没有
移动迭代器:不是拷贝其中的元素,而是移动move它们。
插入迭代器
*it, ++it, it++ 不会对it做任何事情,返回it
back_inserter push_back
front_inserter push_front
inserter insert
如inserter生成的迭代器做如下赋值操作 *it = val;
效果相当于以下代码
it = c.insert(it, val);//it指向新加入的首元素
++it;
使用不同的插入迭代器,最后生成的序列顺序会不同。
流迭代器
创建时必须制定将要读写的对象类型,可以在创建时将其绑定到一个流上,也可以默认初始化迭代器当尾后迭代器使用。
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while(in_iter!=eof)
vec.push_back(*in_iter++);
可以改成成如下形式
istream_iterator<int> in_iter(cin), eof;
vector<int> vec(in_iter, eof); //从迭代器范围构造vec
从流中读取的数据来构造vec
与某些算法结合,来处理流数据。输入流迭代器可以作为源迭代器,输入作为目标迭代器
如cout<<accumulate(in, eof, 0)<<endl;
istream_iterator允许懒惰求值,推迟中流中读取数据。标准库保证在第一次解引用前,从流中读取数据的操作已经完成。
如果迭代器可能销毁,或者两个对象同步使用一个流,那么何时读取就很重要了。
ostream_iterator允许第二个参数,是C风格的字符串,在输出每个元素之后都会打印此字符串。必须绑定到一个流。
*out, ++out, out++提供形式支持,不会做任何事情
ostream_iterator<int> out_iter (cout, “ “);
for (auto e : vec)
*out_iter++ = e;
cout<<endl;
其中*和++都可以忽略,但是推荐这种写法,可以保持与其他迭代器的使用保持一致,修改容易,而且对读者来说也更为清晰。
反向迭代器
使我们可以使用算法透明地向前或向后处理容器。
需要既支持++也支持--,forward_list和流迭代器不能创建。
当使用反向迭代器,在打印的时候会反向,需要使用base成员来转换。
[line.crbegin(), rcomma)和[rcomma.base(), line.end())指向line中相同的元素范围。
另外反向迭代器表示的范围是不对称的,当从普通迭代器初始化反向迭代器或是给其赋值的时候,其指向并不是相同的。
5种迭代器类型
迭代器类别 |
|
输入迭代器 |
只读,不写;单遍扫描,只能递增 |
输出迭代器 |
只写,不读;单遍扫描,只能递增 |
前向迭代器 |
可读写;多遍扫描,只能递增 |
双向迭代器 |
可读写;多遍扫描,可递增递减 |
随机访问迭代器 |
可读写;多遍扫描,支持全部迭代器运算 |
算法指明了迭代器参数的最小类别,如传递的迭代器参数基本更低会产生错误。
输入迭代器:
至少支持==, !=, 前后置++, 解引用*(赋值的右侧), ->
*it++保证是有效的,但是递增可能导致其他迭代器失效,不能保证输入迭代器的状态可以保存下来并用来访问元素。因此只能用于单遍扫描算法。
如find和accumulate支持istream_iterator
输出迭代器:(可看做输入迭代器的补集)
至少支持前后置++, 解引用*(赋值的左侧),只能向一个输出迭代器赋值一次,单遍扫描。
如copy第3个参数,ostream_iterator
前向迭代器:
replace, forward_list
双向迭代器:(--)
reverse
随机访问迭代器:
< <= > >= + += - -= 两个迭代器相减 下标运算符
sort array deque string vector 内置数组元素的指针
常见算法形参类型
alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);
向输出迭代器接入数据的算法都是假定目标空间足够。
单个beg2的算法假定从beg2开始的序列至少与序列1一样大。
算法命名规范
重载传递一个谓词
_if版本 将接受一个元素值版本改成接受一个谓词版本,使用不同的函数名避免可能的重载歧义。
copy版本
默认重排元素的算法写回输入序列,copy版本写到一个指定的输出目的位置。
链表类型特有算法
其他通用算法也可以用于链表,但是代价过高,需要交换元素。链表可以通过改变链接来完成交换,而不用真正交换元素。所以优先使用成员函数版本。
list forward_list成员函数版本算法(都返回void) |
|
lst.merge(lst2) |
合并,两个都必须是有序的,合并之后lst2为空 |
lst.merge(lst2,comp) |
同上,使用谓词给定的比较操作 |
lst.remove(val) |
erase删除元素 |
lst.remove_if(pred) |
同上 |
lst.reverse() |
反转序列 |
lst.sort() |
使用<或给定比较操作排序 |
lst.sort(comp) |
|
lst.unique() |
删除同一个值得连续拷贝 |
lst.unique(pred) |
同上,使用二元谓词 |
splice成员
lst.splice(args)或flst.splice_after(args)
(p, lst2) 将lst2中所有元素移动到lst中p之前或flst中p之后,元素将从lst2删除,不能为同一个链表
(p, lst2, p2) 将p2指向的元素移动到lst中,可以是相同的链表
(p, lst2, b, e) 将b和e之间指向的元素移动到lst中,可以是相同的链表,可以是同链表但不能包含p。
链表特有版本一个重要的区别是会改变底层的容器。