一、概述
泛型算法可以用于不同类型的元素和多种容器类型,大多数算法定义在头文件algorithm中,还有一些数值泛型算法定义在头文件numeric中。
迭代器让算法不依赖于容器,但是算法依赖于元素类型的操作(算法永远不会执行容器的操作)。
1、find和count:
#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>
#include <list>
using namespace std;
using v_int = vector<int>;
using v_str = vector<string>;
using l_char = list<const char *>;
int main(int argc, char const *argv[])
{
/********************
*概述(find、count)。
*********************/
v_int vec = {1,2,3,4,5,6};
int val = 5;
//find将范围内每个元素与定值比较,返回第一个等于定值的迭代器,如果找不到就返回第二个元素。
auto result = find(vec.cbegin(),vec.cend(),val);
cout << *result << endl;
//count返回给定值在序列中出现的次数。
auto cnt = count(begin(vec),end(vec),val);
cout << cnt << endl;
return 0;
}
上述代码运行结果如下:
二、初识泛型算法
1、只读算法(accumulate、equal)。
v_int vec = {1,2,3,4,5,6};
v_str vec_str1{"A","B","C","D"};
//accumulate:元素求和,前两个参数指定求和元素的范围,第三个参数是和的初值。
auto sum_int = accumulate(vec.cbegin(),vec.cend(),0);
cout << sum_int << endl;
auto sum_str = accumulate(vec_str1.cbegin(),vec_str1.cend(),string(""));//string初始化为空串。
cout << sum_str << endl;
l_char lst_char{"A","B","C","D"};
//equal:用于确定两个序列是否保存相同的值,返回布尔值。第二个序列至少应该和第一个序列一样长。
cout << equal(vec_str1.cbegin(),vec_str1.cend(),lst_char.cbegin()) << endl;
2、写容器元素的算法:(fill(常用)、fill_n、back_inserter、copy(常用)、replace(常用)、replace_copy、sort(常用)、unique(常用))。
fill(vec.begin(),vec.begin() + vec.size()/2,0);//fill:接受一对迭代器表示范围,用指定值替代范围内的元素。
fill_n(vec.begin()+3,3,1);//fill_n:用指定值替代从指定元素开始的多个元素。
for (const auto s : vec)
{
cout << s << " ";
}
cout << endl;
//back_inserter:通过插入迭代器将元素添加到容器中。
v_int vec2;
auto it = back_inserter(vec2);
*it = 10;
fill_n(back_inserter(vec2),5,0);//向vec2的末尾添加五个元素。
for (const auto s : vec2)
{
cout << s << " ";
}
cout << endl;
//copy:此算法将输入范围中的元素拷贝到新的序列中,三个参数都是迭代器,返回值为尾迭代器。
auto ret = copy(begin(vec),end(vec),begin(vec2));
for (const auto s : vec2)
{
cout << s << " ";
}
cout << endl;
//replace:将序列中某个元素全部替换为指定的元素(最后一个参数)。
replace(begin(vec2),end(vec2),0,1);
for (const auto s : vec2)
{
cout << s << " ";
}
cout << endl;
//replace_copy:原序列不变,新序列是原序列的拷贝,只是替换了原序列中某个元素为新的元素。
replace_copy(begin(vec2),end(vec2),back_inserter(vec),1,0);
for (const auto s : vec)
{
cout << s << " ";
}
cout << endl;
v_int vec3 = {1,2,1,6,3,4,4,5};
//sort:默认使用升序排序,如果要使用降序则用反向迭代器。
sort(vec3.begin(),vec3.end());
for (const auto s : vec3)
{
cout << s << " ";
}
cout << endl;
//unique:重排输入范围,使得每个元素只出现一次,返回指向不重复区域之后一个位置的迭代器。
auto end_unique = unique(vec3.begin(),vec3.end());
vec3.erase(end_unique,vec3.end());//删除重复元素。
for (const auto s : vec3)
{
cout << s << " ";
}
cout << endl;
三、定制操作
1、向算法传递函数(sort(常用))。
bool IsShorter(const string &s1,const string &s2)
{
return s1.size() < s2.size();
}
v_str vec = {"abc","fox","over","fox","jumps","slow"};
//将元素按大小重排,相同长度的元素按字典序排列,第三个参数叫谓词。
sort(begin(vec),end(vec),IsShorter);//谓词指定了某种排序规则。
for (const auto s : vec)
{
cout << s << " ";
}
cout << endl;
2、lambda表达式【C++11】(介绍lambda、向lambda传递参数、使用捕获列表,调用find_if和for_each)。
【Note】:
1)一个lambda表达式表示一个可调用的代码单元,lambda必须使用尾置返回来指定返回类型,捕获列表和函数体是不可缺少的。
2)一个lambda只有在捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
3)捕获列表只能使用局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。
//去掉重复元素
auto end_unique = unique(begin(vec),end(vec));
vec.erase(end_unique,vec.end());
//按长度排序(与上面的带谓词sort类似),只是此处使用了lambda表达式。
//lambda表达式的结构:[捕获列表] (参数列表) -> 返回类型 {函数体},可以忽略参数列表和返回类型。
stable_sort(vec.begin(),vec.end(),
[] (const string &s1,const string &s2) -> bool
{ return s1.size() < s2.size(); });//stable_sort维持相等元素的原有顺序,是一种稳定排序。
for (const auto s : vec)
{
cout << s << " ";
}
cout << endl;
vec_str_type sz = 3;
//find_if:查找第一个具有特定大小的元素,返回其位置。
auto wc = find_if(vec.begin(),vec.end(),
[sz] (const string &a) -> bool
{ return a.size() > sz; });
cout << (vec.end() - wc) << endl;
//for_each:对输入范围内的所有元素执行可调用对象。
for_each(wc,vec.end(), [] (const string &s) { cout << s << " "; });
cout << endl;
3、lambda捕获和返回(值捕获、引用捕获、隐式
捕获、可变lambda、指定lambda的返回类型)。
【Note】:
1)当以引用方式捕获变量时,必须保证在lambda执行时变量时存在的。
2)一般来说,应该尽量减少捕获的数据量,避免捕获引用和指针。
3)对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。
//1、值捕获。
size_t v1 = 42;
auto f = [v1] { return v1; };//将v1拷贝到名为f的可调用对象。
v1 = 0;
auto v1_1 = f();//f保存了我们创建它时v1的拷贝,因此随后的修改不会影响lambda内对应的值。
cout << v1_1 << endl;
//2、引用捕获。
auto f2 = [&v1] { return v1; };
auto v2_1 = f2();
cout << v2_1 << endl;//f2保存的是v1的引用,而非拷贝,是,所以随后的修改就会影响lambda内对应的值。
//3、隐式捕获。
auto f3 = [=] { return v1; };//让编译器推断。
auto v3_1 = f3();
cout << v3_1 << endl;
//4、混合捕获。
const char c = ' ';
ostream &os = cout;//ostream对象不能拷贝,因此只能采用引用捕获。
//第一个元素必须是&或者=,os采用隐式捕获,引用捕获方式;c显式捕获,值捕获方式。
for_each(wc,vec.end(), [& , c] (const string &s) { os << s << c; });
cout << endl;
//5、可变lambda,改变捕获变量的值(加上mutable关键字)。
size_t v2 = 1;
auto f4 = [&v2] () mutable { --v2; if(!v2) return v2; else return v2; };
auto v4_1 = f4();
cout << v4_1 << endl;
//6、指定lambda返回类型。
v_int vi = {-1,5,4,-7,-2,-3};
//transform:接受三个迭代器,前两个表示输入序列,第三个表示迭代器目的位置,
//将输入序列的每个元素都替换为可调用对象操作该元素所得到的值。
//使用尾置返回类型定义lambda的返回类型。
transform(begin(vi),end(vi),begin(vi),[] (int i) -> int { return (i > 0) ? i : -i; });
for (const auto s : vi)
{
cout << s << " ";
}
cout << endl;
4、参数绑定
【C++11】(
bind、bind重排参数顺序、绑定引用参数)。
bind定义在functional头文件中,接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
bool Check_Size(const string &s,string::size_type sz)
{
return s.size() > sz;
}
ostream &print(ostream &os,const string &s,char c)
{
return os << s << c;
}
//bind调用生成一个可调用对象,将vec每个元素绑定到—_1上。
auto wa = find_if(begin(vec),end(vec),bind(Check_Size,_1,3));
for_each(wa,vec.end(), [] (const string &s) { cout << s << " "; });
cout << endl;
//bind重排参数顺序。
sort(begin(vec),end(vec),bind(IsShorter,_2,_1));//长度由长到短排序。
//绑定引用参数,使用ref标准库函数00。
for_each(begin(vec),end(vec),bind(print,ref(os),_1,' '));
上述代码运行结果如下:
四、再探迭代器
1、插入迭代器(back_inserter、front_inserter、inserter)。
【Note】:
1)只有在容器支持push_front的情况下,我们才可以使用front_inserter。类似的,只有在容器支持push_back的情况下,我们才可以使用back_inserter。
#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <vector>
#include <deque>
using namespace std;
using v_int = vector<int>;
using d_int = deque<int>;
using in_iter_int = istream_iterator<int>;
using out_iter_int = ostream_iterator<int>;
v_int vec1_1 = {1,2,3,4,5,6,7,8,9};
v_int vec1_2,vec1_3;
d_int deq1_1;
//创建一个使用push_back的迭代器。
copy(begin(vec1_1),end(vec1_1),back_inserter(vec1_2));
//创建一个使用push_front的迭代器。
copy(begin(vec1_1),end(vec1_1),front_inserter(deq1_1));
//创建一个使用insert的迭代器。第一个参数为容器,第二个元素为指向该容器的迭代器,
//元素将被插到给定迭代器所表示的元素之前。
copy(begin(vec1_1),end(vec1_1),inserter(vec1_3,begin(vec1_3)));
for (const auto s : vec1_2)
{
cout << s << " ";
}
cout << endl;
for (const auto s : vec1_3)
{
cout << s << " ";
}
cout << endl;
for (const auto s : deq1_1)
{
cout << s << " ";
}
cout << endl;
2、iostream迭代器(istream_iterator、ostream_iterator)。
//下面的这条语句使得文件路径可以使用中文名。
locale::global(locale(""));
//文件操作。
ifstream InFile("E:\\ML_and_DL\\C++_further_study\\c++_STL\\泛型算法\\in.txt");
ofstream OutFile1;
ofstream OutFile2;
OutFile1.open("E:\\ML_and_DL\\C++_further_study\\c++_STL\\泛型算法\\out1.txt");
OutFile2.open("E:\\ML_and_DL\\C++_further_study\\c++_STL\\泛型算法\\out2.txt");
//流迭代器in_iter从输入流InFile读取某种类型的数据,eof表示输入流的末尾。
in_iter_int in_iter(InFile),eof;
//流迭代器out_iter将某种类型的数据写到输出流out_iter中,每个值后面都输出某个字符(空格,换行等)。
out_iter_int out_iter(OutFile1," ");
out_iter_int out_iter2(OutFile2,"\n");
//从迭代器的范围构造容器。
v_int vec2_1(in_iter,eof);
if(InFile)
{
for (auto s : vec2_1)
{
if(!(s % 2))
{
out_iter2 = s;//奇数输出到out2.txt中。
}
else
{
out_iter = s;//偶数输出到out1.txt中。
}
}
}
3、反向迭代器(crbegin、crend)。
v_int vec3_1 = {1,2,3,4,5};
for (auto r_iter = vec3_1.crbegin();r_iter != vec3_1.crend();++r_iter)
{
cout << *r_iter << " ";
}
cout << endl;
//反向排序,从大到小排列。
sort(vec3_1.rbegin(),vec3_1.rend());
//通过流迭代器结合copy打印元素。
out_iter_int out_iter3(cout," ");
copy(begin(vec3_1),end(vec3_1),out_iter3);
cout << endl;
string str("first,middle,last");
//找到第一个逗号的位置。
auto comma = find(str.cbegin(),str.cend(),',');
//打印str.cbegin()到comma之间的内容。
cout << string(str.cbegin(),comma) << endl;
//反向找到第一个逗号的位置。
auto rcomma = find(str.crbegin(),str.crend(),',');
//rcomma.base()得到一个正向迭代器,从逗号开始读取字符到str.cend()。
cout << string(rcomma.base(),str.cend()) <<endl;
五、算法结构和特定容器算法
1、迭代器类别:
2、特定容器算法:
对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法。
#include <iostream>
#include <algorithm>
#include <list>
#include <forward_list>
using namespace std;
using l_int = list<int>;
using fl_int = forward_list<int>;
int main(int argc, char const *argv[])
{
l_int lst1 = {1,2,3,4,4,5,6};
l_int lst1_2 = {7,8,9};
//merge:将lst1_2的元素合并到lst1。
lst1.merge(lst1_2);
//remove_if:给定条件下,删除某些元素,这里采用一元谓词来判断。
lst1.remove_if([] (int i) { return i % 2 ; });//删除奇数元素。
//reverse:反转容器中的元素。
lst1.reverse();
//sort:排序(默认升序)。
lst1.sort();
//unique:删除重复元素。
lst1.unique();
for (const auto s : lst1)
{
cout << s << " ";
}
cout << endl;
return 0;
}