[C++学习笔记] 泛型算法

C++标准库定义了一组泛型算法实现了一些经典算法的公共接口 如排序 搜索等 它们可以适用于不同类型的元素和多种容器类型

这些算法大多定义在algorithm头文件中 并在numeric头文件中定义了一组数值算法

一般情况下这些算法并不直接操作容器 而是通过迭代器来遍历范围 处理元素

泛型算法永远不会改变底层容器的大小 只会运行在迭代器上 执行迭代器的操作

泛型算法的结构类似 除了少数例外 都对一个范围内的元素进行操作 通过两个迭代器来确定输入范围
接受输入范围的算法总是使用前两个参数来表示此范围

Find(vec.begin(),vec.end(), 'a'); //查找 若未找到 返回尾后迭代器
accumulate(vec.begin(),vec.end(), 0); // 求和 和的初值为0 
accumulate(vec.begin(),vec.end(), string("")); 
//求和 初值为空串 注意不可直接用字面值空串 其类型为const char* 无对应+操作

注意第三个参数和初值必须与求和的类型相容 也决定了最终返回类型

比较两个序列中元素是否相同

Equal(vec1.cbegin(),vec1.cend(), vec2.cbegin()); // 序列2至少和序列1一样长

接受谓词的算法

谓词是一个可调用的表达式 其结果是一个能用作条件的值
标准库算法所使用的谓词分为两类

  • 一元谓词(只接受单一参数)
  • 二元谓词(两个参数)

接受谓词参数的算法会对输入序列中元素调用谓词 因此这些元素必须能转换成谓词的参数类型

Bool isshorter(const string&s1, const string&s2){ 
	return s1.size() < s2.size();
}
Sort(vec.begin(),vec.end(), isshorter);

注意 这个sort传入的比较函数 以你返回true的类型排序
即想升序排序 就使小于时为true 反之 大于时为true

lambda表达式

一个lambda表达式表示一个可调用的代码单元
可以理解为是一个未命名的内联函数 本质上是函数对象

与函数的不同在于

  • lambda表达式可以定义在函数内部
  • 不能使用默认参数
  • 且必须使用尾置返回来指定返回类型

其形式为

[capture list] (parameter list) -> return type {function body}

参数列表和返回类型为可选项们,可以忽略

若忽略返回类型
如果函数只包含单一一条return语句 返回return语句对应类型
否则返回void

auto f = [] {return 42;}; 
cout<<f()<<endl;

通过捕获列表 lambda表达式可以使用除了参数以外的值

int sz=5; 
auto wc = find_if(vec.begin(),vec.end(), [sz](const string &a){return a.size()>= sz;}); 
//返回第一个长度不小于sz的元素

当定义一个lambda时 编译器生成一个与lambda对应的未命名的类类型
默认情况下 从lambda生成的类都包含对应该lambda所捕获的变量的数据成员 并在其对象创建时被初始化

捕获列表默认是值传递 也可以使用引用传递
但以引用方式捕获一个变量时 必须保证在lambda执行时变量存在
因此 尽可能避免捕获引用或指针以防止潜在的问题

int v1 = 4; 
auto f = [&v1] {return v1;}; 
v1=0; 
cout<<f()<<endl; //结果为 0 因为传递的是引用 实际使用的是v1的值

捕获列表中 =表示值传递 &表示引用传递
lambda可以通过隐式方式自动捕获所需变量 即只指出捕获方式 当函数内使用时会自动捕获外部变量

[&](){ sz = 1;} // 自动以引用传递方式捕获到变量sz
[=](){ int a = sz;} // 自动以值传递方式捕获到变量sz

同时也可以通过混用显式和隐式方式来对某一部分采用值传递捕获 另一部分采用默认方式隐式捕获

[=, reference_list](){} //reference list里的变量按引用捕获,其余的变量按值捕获,比如 [=, &a, &b] 表示 a 和 b 按引用,其余按值捕获
[&, identifier_list]( ){ } // identifier list里的变量按值捕获,其余的变量按引用捕获,比如 [&, a, b] 表示 a 和 b 按值,其余按引用捕获

其他标准库算法

for_each()接受一个可调用对象 并对输入序列中的每个元素调用此对象

for_each(vec.begin(), vec.end(), [](const string &a){cout<<a<<endl;});

通过bind()函数可以接受一个可调用对象 生成一个新的可调用对象来适应原对象的参数列表
bind定义在functional头文件中
通过形如_n的占位符来表示传递给新对象的参数 n表示参数的位置

bool check(const string &a, int sz){
	return a.size()>= sz;
} 
auto check2= bind(check, _1, 6); 
String s= "hi"; 
check2(s);

更一般地 还可以通过bind设置调用对象参数 或重新安排参数顺序
假设f是一个具有5个参数的函数

auto g= bind(f,a,b,_1,c,_2); 
//即g为一个两个参数的函数 其第一个参数输入到f的第3个参数 
//第二个参数输入到f的第5个参数 f的1 2 4参数分别绑定为a,b,c

bind是通过拷贝来传递参数
若无法拷贝(如IO类)或需使用引用时 必须通过ref()函数返回对应对象的引用
同样该函数也定义在functional头文件中

特殊迭代器

插入迭代器是一种向容器中添加元素的迭代器 如back_inserter
对该迭代器的赋值会调用push_back将一个具有该值的元素添加到容器中

back_inserter() front_inserter() 分别插入元素到头尾
inserter() 接受第二个参数 将元素插入到给定的迭代器之前

流迭代器可以从流对象中读取数据

istream_iterator<int> int_in(cin), eof;

同样 可以使用泛型算法操作流迭代器

cout<< accumulate(int_in, eof, 0) <<endl;

输出流迭代器的赋值语句事实上将值写入到流中

使用反向迭代器搜索后需要通过.base()转换成正向 否则内容也会反向处理
如 "last" 会变为 "tsal"