
🎉作者简介:👓 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢 c + + , g o , p y t h o n , 目前熟悉 c + + , g o 语言,数据库,网络编程,了解分布式等相关内容 \textcolor{orange}{博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容} 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容
📃 个人主页: \textcolor{gray}{个人主页:} 个人主页: 小呆鸟_coding
🔎 支持 : \textcolor{gray}{支持:} 支持: 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦 \textcolor{green}{如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦} 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦👍 就是给予我最大的支持! \textcolor{green}{就是给予我最大的支持!} 就是给予我最大的支持!🎁
💛本文摘要💛
本专栏主要是对c++ primer这本圣经的总结,以及每章的相关笔记。目前正在复习这本书。同时希望能够帮助大家一起,学完这本书。 本文主要讲解第10章 泛型算法
大多数算法定义在头文件 algorithm 中,部分在 numeric 中部分算法从两个序列中读取元素,两个序列不必相同(如vector和list),序列中的元素也不必相同(如double和int),要求是只要能比较两种元素即可。俩种假设
三个迭代器时,前俩个迭代器表示第一个序列范围,第三个迭代器表示第二个序列元素范围。这种情况假定第二个序列至少与第一个一样长。cbegin()和cend()//对输入范围内的元素在 val 的基础上求和。可以对字符串“+”。注意元素是加在val上,如果元素是double,val是int,和会被转换成int
int sum = accumulate(vec.cbegin(),vec.cend(),val);
//比较两个序列的元素是否全部相等(假定第二个序列至少与第一个序列一样长
bool F = equal(vec1.cbegin(),vec1,end(),vec2.cbegin());
//查找符合某条件的元素,返回迭代器,如果没有就返回尾迭代器
auto iter = find_if(vec.begin(),vec.end(),'一元谓词');
fill(vec.begin(),vec.end(),val);//将 val 赋予输入序列中的每一个元素
fill(vec.begin(),vec.begin() + vec.size()/2,10);//将一个容器的前一半的值写为10
fill_n(dest,n,val);//将 val 赋予从 dest 开始的连续 n 个元素。假定dest开始的序列有至少 n 个元素
fill_n(vec.begin(),vec.size(),0);//将 vec 的所有元素重置为0
for_each(vec.begin(),vec.end(),'可调用对象');
//对输入范围内的每一个元素执行可调用对象的内容。注意:可调用对象是一元谓词,参数类型是迭代器指向的类型
插入迭代器
back_inserter 定义在头文件iterator中。back_inserter接收一个指向容器的引用,返回一个与该容器绑定的插入迭代器。vector<int> vec;
auto it = back_inserter(vec);//it 即为vec新加的插入迭代器
*it = 24;//给 it 赋值后,vec 现在有一个元素为 24
back_inserter() 函数的返回值作为算法的目的位置
fill_n(back_inserter(vec),10,0);//添加 10 个元素到 vec
拷贝算法
copy时必须保证目标目的序列至少要包含与输入序列一样多的元素。auto ret = copy(begin(a1),end(a1),begin(a2));//把数组 a1 的内容拷贝给 a2,返回的是目的位置迭代器(递增后)的值
replace(ilst.begin(),ilst.end(),0,42);//把输入范围内所有值为 0 的元素改为 42
replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42);//把输入范围内所有值为 0 的元素改为 42 并拷给 dest 开头的序列。
不保留原序列,将原序列的值全部换成新的保留原序列,ivec包含ilst的一份拷贝,原来的ilst中的值为0的元素在ivec中都变为42.//默认按字典序从小到达排列输入范围内的元素。重复的元素会相邻
sort(words.begin(),words.end());
//将输入范围内的元素重排,将重复的元素里不是第一次出现的元素都放到序列的后端。返回指向序列后端重复元素中的第一个重复元素的迭代器。
auto end_unique = unique(words.begin(),words.end());
//删除重复的元素。erase()函数可以接受两个相等的迭代器作为参数
words.erase(end_unique,words.end());
自定义操作来代替默认运算符一个可调用的表达式,返回结果是一个可以作为条件的值,如返回一个 bool 值。一元谓词和二元谓词,分别接收一个参数和俩个参数元素类型必须能转换为谓词参数的类型。接收谓词参数的算法对输入序列中的元素调用谓词'sort'
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
sort(words.begin(), words.end(), isShorter);
'stable_sort 稳定排序'
//会按照元素长度大小排序,而长度相同的单词会保持字典序
stable_sort(words.begin(), words.end(), isShort);
四种可调用对象:函数、函数指针、重载了函数调用运算符的类、lambda表达式形式:
[capture list](parameter list) -> return type {function body}。
capture list捕获列表是一个lambda所在函数定义的局部变量的列表(通常为空)。不可忽略。return type是返回类型。可忽略。parameter是参数列表。可忽略。function body是函数体。不可忽略。注意
函数内部尾置返回来指定返回类型捕获列表只用于局部非 static 变量。参数列表和返回类型都可以省略。如果忽略返回类型,则根据 return 语句推断返回类型(此时函数体必须只有 return 语句)。 auto f = [] {return 42;}
例子
find_if
auto wc = find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >= sz;});for_each
for_each(wc, words.end(), [](const string &s){cout << s << " ";})新的类类型和该类型的一个对象。(传递的参数实际上就是此编译器生成的类类型的未命名对象。)auto f = [ ]{ return 42; } 实际上定义了一个类类型的对象。
值捕获和引用捕获,类似参数传递。值捕获
被值捕获的变量的值是在 lambda 创建时拷贝,而非调用时拷贝。因此在 lambda 创建后改变被捕获的变量不会影响 lambda 中对应的值。void func()
{
size_t v1 = 42 //局部变量
auto f = [v1]{return v1;};//将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f(); //j为42;f保存了创建它时v1的拷贝
}
不能拷贝ostream对象,因此想要捕获os对象唯一方法就是捕获其引用(或指向os的指针)引用捕获
确保被引用的对象在 lambda 执行时是存在的。上层函数的局部变量,在函数结束后就都不复存在了。引用捕获可以用于捕获变量是 IO 类型时。void func()
{
size_t v1 = 42 //局部变量
auto f2 = [&v1]{return v1;};//将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f2(); //j为0;f2保存了v1的引用,而非拷贝
}
隐式捕获
&(引用方式)或=(值方式)。auto f3 = [=] {return v1;}wc = find_if(words.begin(), words.end(), [=](const string& s){ return s.size()>=sz; });
混合显示捕获与隐式捕获
值捕获,另一部分采用隐式捕获。采用混合捕获,捕获列表中的第一个元素必须是一个&或=
for_each(words.begin(), words.end(), [&,c](const string& s){ os<<s<<c; }); // 这里的 os 是通过隐式的引用捕获得到的。
for_each(words.begin(), words.end(), [=, &os](cosnt string& s){ os<<s<<c;}); // 这里的 c 是通过隐式的值捕获得到的。
lambda捕获列表
[],[参数序列] // 空列表与显式捕获
[=],[&] // 隐式值捕获与隐式引用捕获
[=,参数列表],[&,参数列表] // 混合捕获
可变lambda
关键字muitablevoid func()
{
size_t v1 = 42 //局部变量
//f可以改变他所捕获的变量的值
auto f = [v1]() mutable{return ++v1;};
v1 = 0;
auto j = f(); //j为43
}
void func()
{
size_t v1 = 42 //局部变量
//v1是一个非const变量的引用
//可以通过v2中的引用改变它
auto f = [&v1]() mutable{return ++v1;};
v1 = 0;
auto j = f(); //j为1
}
指定lambda返回类型
lambda 中除了 return 还有其他语句,此时应该指明返回类型。lambda定义返回类型时,必须使用尾置返回类型lambda表达式更适合在一两个地方使用的简单操作。如果是很多地方使用相同的操作,还是需要定义函数。对于捕获列表为空的 lambda,通常可以用函数来代替。对于捕获列表不为空的 lambda,不容易使用函数替换。标准库bind函数
bind 函数接受一个可调用对象,生成一个新的调用对象来“适应”原对象的参数列表。auto newCallable = bind(allable, arg_list);
我们再调用newCallable的时候,newCallable会调用callable并传递给它arg_list中的参数。
bind占位符
_1,_2,_n 等占位符分别表示新可调用对象的第 1,2,n 个参数,将 _n 放在 bind 中不同的参数位置,表示将新可调用对象的第 n 个参数和旧可调用对象在该位置的参数绑定在了一起。placeholders中,placeholders 这个名字定义在 std 中。placeholders 的实现定义在 functional 头文件中。using namespace std::placeholders::_1;//使用 _1,_2 前要先声明使用命名空间 placeholders。
bind的参数
可以减少参数数目。(减少掉的参数被设为一个固定值)auto g = bind(f, a, b, _2, c, _1); // g 只有两个参数,两个参数分别传递给 f 的第 5 个和第 3 个参数
调用g(X, Y),会调用f(a, b, Y, c, X);
将 bind 用于算法的谓词
sort(words.beegin(), words.end(), isShorter); //长度由短到长排序
sort(words.begin(), word.end(), isShorter(, _2, _1));//长度由长到短排序
绑定引用参数
值传递,被拷贝到 bind 返回的可调用对象中。ref函数和cref函数[&os, c](const string &s){os << s << c;}; //正确
bind(print, os, _1, ' '); // 错误,os 不能拷贝
bind(print, ref(os), _1, ' '); // 正确
绑定到容器上,可以来向容器插入元素。绑定到输入或输出流上,可以用来遍历所关联的 IO 流。这些迭代器向后移动,不能向前移动除了 forward_list 外的标准库容器都有反向迭代器。不拷贝其中的元素,而是移动他们。接受一个容器,生成一个迭代器,能实现向给定容器添加元素。注意
back_inserter 是插入器,back_insert_iterator> 是插入迭代器类型。back_inserter(v) 返回绑定到容器 v 的 back_insert_iterator,并实现其自增。插入迭代器的三种类型
即一个指向给定容器的迭代器,元素会被查到迭代器所指向的元素之前。| 操作 | 解释 |
|---|---|
it=t | 在it指定的当前位置插入值t。假定c是it绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用c.push_back(t)、c.push_front(t)、c.insert(t, p),其中p是传递给inserter的迭代器位置 |
*it, ++it, it++ | 这些操作虽然存在,但不会对it做任何事情,每个操作都返回it |
插入迭代器的定义
vector<int> v;
'使用插入器生成'
auto bIn = back_inserter(v); // bIn 是一个绑定到 v 的插入迭代器。
'直接定义'
back_insert_iterator<vector<int>> bIn(v); // bIn 是一个绑定到 v 的插入迭代器。
insert_iterator<vector<int>> iIn(v, v.begin()); // iIn 是一个绑定到 v 的插入迭代器
插入迭代器的操作
插入迭代器操作只有一种,就是赋值操作。*bIn = 30;//在 bIn 所绑定的容器的尾元素之后插入一个 30
*iIn = 10;//在 iIn 所绑定的容器的相应元素之前插入一个 10
插入器 inserter 与函数 insert 的不同
恒定指向同一个元素的。而 insert 返回的是指向所插入元素的迭代器'1'
auto f1 = inserter(v, v.begin());
*f1 = 10;
'2'
iter = v.insert(v.begin(), 10);
++iter;
插入迭代器的使用
vector<int> v1,v2;
*back_inserter(v1) = 2; // 在 v1 的尾元素之后插入一个 2
*inserter(v1, v1.begin()) = 6; // 在 v1 的首元素之前插入一个 6
copy(v1.begin(),v1.end(),back_inserter(v2)); // 将 v1 的所有元素按顺序拷贝到 v2 的尾元素之后。
copy(v1.begin(),v1.end(),inserter(v2,v2.begin())); // 将 v1 的所有元素按顺序拷贝到 v2的首元素之前
虽然iostream类型不是迭代器,但定义了可以使用这些IO类型对象的迭代器。iostream有俩类
istream_iterator:读取输入流。ostream_iterator:向输出流写数据。流迭代器作用
我们可以用泛型算法从流对象中读取数据以及向其写入数据。创建流迭代器时,必须指定迭代器将要读写的对象类型。流迭代器可以绑定到 iostream,fstream,stringstream。它将对应的流当作一个元素序列来处理。可以为任何具有输入运算符的(>>)类型定义istream_iterator,即所有内置类型和重置了 >> 的类。创建流迭代器
1. 绑定到一个流
2. 默认初始化:采用这种方式创建的迭代器可以当作尾后值使用,可以在 for 循环中作为终止条件。
istream_iterator<int> int_it(cin);//创建了一个流迭代器 iInt,iInt 从 cin 读取 int
istream_iterator<int> int_eof;//尾后迭代器。当关联的流遇到文件尾或遇到错误,迭代器的值就与尾后迭代器相等。
while(in_it != eof)
{
vec.push_back(*in_iter ++);
}
istream_iterator使用
istream_iterator<T> in(is); //in从输入流is读取类型为T的值
istream_iterator<T> end; //读取类型是T的值的istream_iterator迭代器,表示尾后位置
in1 == in2; in1 != int2; //首先 in1 和 in2 必须读取相同类型。如果 in1 和 in2 都是尾后迭代器或绑定到相同的输入,则两者相等。
*in; //返回从流中读取的值
++in;in++; //递增操作
'1'
istream_iterator<int> inInt(cin),eof;
while(inInt != eof)
vec.push_back(*inInt++);
'2'
vector<int> vec(inInt,eof);//和上面效果相同,当遇到文件尾或遇到第一个不是 int 的数据停止。
'3'
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;
ostream_iterator 定义
可以为任何具有输出运算符的(<<)类型定义 ostream_iterator,即所有内置类型和重置了 << 的类。ostream_iterator<int> out(os);//out 将类型为 int 的值写到输出流 os 中
ostream_iterator<int> out(os,str);//out 将类型为 int 的值写到 os 中,每个值后面都跟着一个C风格字符串 str。(字符数组)
ostream_iterator操作
ostream_iterator 的赋值操作等价于输出流的输出操作。每赋一次值,输出一次。ostream_iterator<T> out(os); //out将类型为T的值写到输出流os中
ostream_iterator<T> out(os, d); //out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组。
out = 6; //等价于 os<<6。将 6 写入到 out 绑定的输出流中。
*out; ++out; out++; //这些运算符存在但没有意义。
ostream_iterator 的使用
for(auto e:vec)
out = e; //赋值语句将元素写到 cout,每赋一次值,写操作就会被提交一次。
'* 和 ++可以省略'
for(auto e:vec)
*out++ = e;//与上面的语句实际效果相同,看起来更清晰。
'使用 copy 来打印 vec 中的元素,更为简单。'
copy(vec.begin(),vec.end(),out);//将 vec 中的元素写入到了 out 绑定的输出流中
操作含义会颠倒。除了 forward_list 外,其他容器都支持反向迭代器。流迭代器不支持递减操作。rbegin和rend。函数 riter.base() 返回相应的正向迭代器,正向迭代器指向靠后一个元素。
auto riter = string.rbegin()//反向迭代器 riter 指向 string 的尾元素
auto iter = riter.base();//正向迭代器 iter 指向 string 的尾后元素
| 迭代器类别 | 解释 | 支持的操作 |
|---|---|---|
| 输入迭代器 | 只读,不写;单遍扫描,只能递增 | ==,!=,++,*,-> |
| 输出迭代器 | 只写,不读;单遍扫描,只能递增 | ++,* |
| 前向迭代器 | 可读写;多遍扫描,只能递增 | ==,!=,++,*,-> |
| 双向迭代器 | 可读写;多遍扫描,可递增递减 | ==,!=,++,--,*,-> |
| 随机访问迭代器 | 可读写,多遍扫描,支持全部迭代器运算 | ==,!=,<,<=,>,>=,++,--,+,+=,-,-=,*,->,iter[n]==*(iter[n]) |
输入迭代器
输入迭代器只用于顺序访问,只能用于单遍扫描算法,如算法 find 和 accumulate。
Istream_iterator 是一种输入迭代器。
输出迭代器
只能向一个输出迭代器赋值一次,只能用于顺序访问的单遍扫描算法。如 copy 函数的第三个参数。
ostream_iterator 是一种输出迭代器。
前向迭代器
可以读写元素。只能在序列中沿一个方向移动,可以多次读写同一个元素。
可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列多遍扫描。
算法 replace 要求前向迭代器,forward_list 的迭代器是前向迭代器。
双向迭代器
可以正向/反向读写序列中的元素。支持递增递减运算符。
算法 reverse 要求双向迭代器。list 的迭代器是双向迭代器。
随机访问迭代器
提供在常量时间内访问序列中任意元素的能力。
支持以下操作:
支持 <, <=, >, >= 等关系运算符。支持迭代器与整数的加减。支持两个迭代器之间的相减。支持下标运算符。iter[n] 和 *(iter[n]) 含义相同。算法 sort 要求随机访问迭代器。array, deque, vector, string 的迭代器是随机访问迭代器。
大多数算法一般有以下几种形式
alg(beg, end, other args);alg(beg, end, dest, other args);alg(beg, end, beg2, other args);alg(beg, end, beg2, end2, other args);其中,alg是算法名称,beg和end表示算法所操作的输入范围。dest、beg2、end2都是迭代器参数,是否使用要依赖于执行的操作。
dest 经常是一个插入迭代器或一个 ostream_iterator,他们都能确保不管写多少元素都肯定是安全的。
来代替 < 或 ==。unique(beg, end); // 使用 == 比较元素
unique(beg, end, comp); // 使用 comp 比较元素
_if版本算法
接受一个元素值的算法通常有一个不同名的版本:加_if,接受一个谓词代替元素值。find(beg, end, val); // 查找范围内 val 第一次出现的版本。
find_if(beg, end, pred); // 查找第一个令 pred 为真的元素。
拷贝版本算法
reverse(beg, end); // 反转序列中的元素。
reverse_copy(beg, end, dest); // 将元素按逆序拷贝到 dest。
remove_if(v1.begin(), v1.end(), [](int i){return i%2;}); // 从 v1 中删除奇数元素。
remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i){return i%2;}); // 将去掉了奇数元素的 v1 序列拷到 v2 中。v1当中的元素不变
sort, merge, remove, reverse 和 unique。通用版本的 sort 要求随机访问迭代器,而 list 和 forward_list 分别提供双向迭代器和前向迭代器,因此不能用于 list 和 forward_list。优先使用成员函数版本的算法。lst.merge(lst2); // 将 lst2 的元素合并入 lst。两者必须都是有序的,合并后 lst2 将变为空。使用 < 比较元素。
lst.merge(lst2,comp); // 上面的 merge 的重载版本,用给定的 comp 代替 <
lst.remove(val); // 调用 erase 删除掉与给定值相等的每个元素。
lst.remove_if(pred); // pred 是个一元谓词,删除掉 lst 中使 pred 为真的每个元素。
lst.reverse(); // 反转 lst 中元素的顺序。
lst.sort(); // 使用 < 给元素排序
lst.sord(comp); // 重载版本
lst.unique(); // 调用 erase 删除同一个值的连续拷贝。
lst.unique(pred); // pred 是个二元谓词,删除使 pred 为真的连续拷贝。
voidlist和forward_list的splice成员函数版本的参数:
链表类型定义了 splice 算法,此算法是链表特有的,没有通用版本。两个链表间移动元素或在一个链表中移动元素的位置。lst.splice(p, lst2); flst.splice_after(p, lst2); // p 是一个指向 lst 中元素的迭代器,或一个指向 flst 首前位置的迭代器。
// 函数将 lst2 中的所有元素移动到 lst 中 p 之前的位置或 flst 中 p 之后的位置。
lst.splice(p, lst2, p2); flst.splice_after(p, lst2, p2); // p2 是一个指向 lst2 中位置的迭代器。
// 函数将 p2 指向的元素移动到 lst 中,或将 p2 之后的元素移动到 flst 中,lst2 可以是与 lst 或 flst 相同的链表。
lst.splice(p, lst2, b, e); flst.splice_after (p, lst2, b, e); // b 和 e 表示 lst2 中的合法范围。
// 将给定范围中的元素从 lst2 移动到 lst 中。lst2 可以是与 lst 或 flst 相同的链表,但是 p 不能指向 b 和 e 之间的元素。
lst.splice(args)或flst.splice_after(args)