ch10.泛型算法与Lambda表达式目录
- 泛型算法
- bind 与 lambda 表达式
- 泛型算法的改进—— ranges
一.泛型算法

- 泛型算法:可以支持多种类型的算法
- 这里重点讨论 C++ 标准库中定义的算法
<algorithm> <numeric> <ranges>
- 为什么要引入泛型算法而不采用方法的形式
- 内建数据类型不支持方法
- 计算逻辑存在相似性,避免重复定义
- 如何实现支持多种类型:使用迭代器作为算法与数据的桥梁
- 这里重点讨论 C++ 标准库中定义的算法
- 泛型算法通常来说都不复杂,但优化足够好
- 一些泛型算法与方法同名,实现功能类似,此时建议调用方法而非算法
- std::find V.S. std::map::find
```c
#include
#include //包含了C++一些基本的泛型函数 #include //包含了一些数值的算法 #include //C++20的一个库,包含了一些算法
- std::find V.S. std::map::find
```c
#include
int main(){
std::vector
std::vector
## 1.1.泛型算法的分类
- 读算法:给定迭代区间,读取其中的元素并进行计算
- [accumulate](https://zh.cppreference.com/w/cpp/algorithm/accumulate) / [find](https://zh.cppreference.com/w/cpp/algorithm/find) / [count](https://zh.cppreference.com/w/cpp/algorithm/count)
- #include<algorithm>
- 只读
```c
/*----accumulate:累积----*/
//版本一:init累加的初始值,first-last,累加的区间
template<class InputIt, class T>
constexpr // C++20 起
T accumulate(InputIt first, InputIt last, T init)
{
for (; first != last; ++first) {
init = std::move(init) + *first; // C++20 起有 std::move
}
return init;
}
//版本二
/* op - 被使用的二元函数对象。接收当前积累值 a (初始化为 init )
和当前元素 b 的二元运算符。该函数的签名应当等价于:
Ret fun(const Type1 &a, const Type2 &b);
自定义op,可以实现其他广义的累积:减法,乘法等等*/
template<class InputIt, class T, class BinaryOperation>
constexpr // C++20 起
T accumulate(InputIt first, InputIt last, T init,
BinaryOperation op)
{
for (; first != last; ++first) {
init = op(std::move(init), *first); // C++20 起有 std::move
}
return init;
}
/*----find----*/
//指向首个满足条件的迭代器,或若找不到这种元素则为 last 。
//版本一
template<class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value)
{
for (; first != last; ++first) {
if (*first == value) {
return first;
}
}
return last;
}
//版本二
template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
{
for (; first != last; ++first) {
if (p(*first)) {
return first;
}
}
return last;
}
/*----count----*/
https://zh.cppreference.com/w/cpp/algorithm/count
- 写算法:向一个迭代区间中写入元素
- 单纯写操作: fill / fill_n
- 读 + 写操作: transform / copy
- 注意:写算法一定要保证目标区间足够大
/*----fill----*/ //first, last - 要修改的元素范围 //value - 要赋的值 //policy - 所用的执行策略。细节见执行策略。 template< class ForwardIt, class T > void fill(ForwardIt first, ForwardIt last, const T& value) { for (; first != last; ++first) { *first = value; } } /*----fill_n----*/ template<class OutputIt, class Size, class T> OutputIt fill_n(OutputIt first, Size count, const T& value) { for (Size i = 0; i < count; ++i) { *first++ = value; } return first; }/*----transform----*/ https://zh.cppreference.com/w/cpp/algorithm/transform //返回值:指向最后一个变换的元素的输出迭代器。 //InputIt first1, InputIt last1:用来读的参数,输入 //OutputIt d_first:写入区间的开头位置,输出 //UnaryOperation unary_op:一元操作符 //版本一 template<class InputIt, class OutputIt, class UnaryOperation> OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op) { while (first1 != last1) { //对first1解引用,然后进行unary_op一元操作, //最后写到d_first中 *d_first++ = unary_op(*first1++); } return d_first; } //版本二 template<class InputIt1, class InputIt2, class OutputIt, class BinaryOperation> OutputIt transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op) { while (first1 != last1) { *d_first++ = binary_op(*first1++, *first2++); } return d_first; }/*----copy----*/ //https://zh.cppreference.com/w/cpp/algorithm/copy //返回值:指向目标范围中最后复制元素的下个元素的输出迭代器。 //InputIt first1, InputIt last1:用来读的参数,输入 //OutputIt d_first:写入区间的开头位置,输出 //版本一 template<class InputIt, class OutputIt> OutputIt copy(InputIt first, InputIt last, OutputIt d_first) { while (first != last) { *d_first++ = *first++; } return d_first; }
- 排序算法:改变输入序列中元素的顺序
/*----sort----*/ https://zh.cppreference.com/w/cpp/algorithm/sort //RandomIt first, RandomIt last:排序的范围 //缺省情况使用<操作符排序,既是从小到大排序/*----unique----*/ //https://zh.cppreference.com/w/cpp/algorithm/unique //要求:传入的是已经排序好的元素,然后将重复的元素删除,只保留一个 //版本一 template<class ForwardIt> ForwardIt unique(ForwardIt first, ForwardIt last) { if (first == last) return last; ForwardIt result = first; while (++first != last) { if (!(*result == *first) && ++result != first) { *result = std::move(*first); } } return ++result; }//exp #include<iostream> #include<algorithm> int main(){ //1 2 2 3 4 //unique操作后 //1 2 3 4 //实际上内存中保存的是 //1 2 3 4 (返回迭代器的位置last)4 最后一个4是之前的,没有修改 v.erase(last, v.end()); //从返回的位置开始到结束,进行删除操作 }
泛型算法使用迭代器实现元素访问
迭代器的分类:
- 输入迭代器:可读,可递增 —— 典型应用为 find 算法
- 输出迭代器:可写,可递增 —— 典型应用为 copy 算法
- 前向迭代器:可读写,可递增 —— 典型应用为 replace 算法
- 双向迭代器:可读写,可递增递减 —— 典型应用为 reverse 算法
- 随机访问迭代器:可读写,可增减一个整数 —— 典型应用为 sort 算法
一些算法会根据迭代器类型的不同引入相应的优化:如 distance 算法
1.2.一些特殊的迭代器
-
插入迭代器: back_insert_iterator / front_insert_iterator / insert_iterator
- back_insert_iterator底层:push_back
- front_insert_iterator底层:push_front
#include <iostream> #include <iterator> #include <deque> #include <vector> #include <list> int main() { std::deque<int> q; std::back_insert_iterator< std::deque<int> > it(q); for (int i=0; i<10; ++i) it = i; // 调用 q.push_back(i) // 0 1 2 3 4 5 6 7 8 9 for (auto& elem : q) std::cout << elem << ' '; -------- std::vector<int> x; std::fill_n(std::back_insert_iterator<std::vector<int>>(x), 10, 3); //简化写法 //std::fill_n(std::back_insert<std::vector<int>>(x), 10, 3); //3 3 3 3 3 3 3 3 3 3 for(auto i:x) std::cout<< i <<' '; ---- std::list<int> x;//list才支持push_front std::fill_n(std::front_insert<std::vector<int>>(x), 10, 3); //3 3 3 3 3 3 3 3 3 3 for(auto i:x) std::cout<< i <<' '; } - 流迭代器: istream_iterator / ostream_iterator
/*----istream_iterator----*/ #include<iostream> #include<algorithm> #include<vector> #include<list> #include<iterator> #include<sstream> #include<numeric> int main(){ std::istringstream Str{"1 2 3 4 5"}; std::istream_iterator<int> x{Str}; //1 ==>int tmp; Str>>tmp;//输入流迭代器 std::cout<< *x <<std::endl; ++x;//istream_iterator维护一个对象,Str>>tmp;//输入流迭代器 std::cout<< *x <<std::endl;//2 //缺省初始化,可以表示流迭代器的结束位置 std::istream_iterator<int> y{}; for (x!=y){ std::cout<< *x <<std::endl;//1 2 3 4 5 } //x为迭代器的开始.y为迭代器的结束,累加结果为15 int res = std::accumulate(x,y,0); std::cout<<res<<std::endl;//15 }```c /—-ostream_iterator—-/ #include
#include #include #include
int main()
{
std::istringstream str(“0.1 0.2 0.3 0.4”);
std::partial_sum(std::istream_iterator
- 反向迭代器:从后向前遍历

```c
/*----ostream_iterator----*/
#include <iostream>
#include <sstream>
#include <iterator>
#include <numeric>
#include <vector>
int main()
{
std::vector<int> x{1,2,3,4,5};
std::copy(x.rbegin(),x.rend(),
std::ostream_iterator<int>(std::cout, " "));//54321
}
//输出: 0.1 0.3 0.6 1
- 移动迭代器: move_iterator
```c
/—-ostream_iterator—-/
#include
#include #include #include #include #include #include
int main() { std::vector<std::string> v{“this”, “”, “is”, “”, “an”, “_”, “example”}; //lambda表达式,打印出v的元素 auto print_v = & { std::cout « rem; for (const auto& s : v) std::cout « std::quoted(s) « ’ ‘; std::cout « ‘\n’; };
print_v("Old contents of the vector: ");
//按照字符串的相加方式进行累加(字符串拼接)
std::string concat = std::accumulate(std::make_move_iterator(v.begin()),
std::make_move_iterator(v.end()),
std::string());
/* 使用 std::move_iterator 的替代方式可能是:
using moviter_t = std::move_iterator<std::vector<std::string>::iterator>;
//moviter_t 性能同move会移动变量
std::string concat = std::accumulate(moviter_t(v.begin()),
moviter_t(v.end()),
std::string()); */
print_v("New contents of the vector: ");
std::cout << "Concatenated as string: " << quoted(concat) << '\n'; } 可能的输出: Old contents of the vector: "this" "_" "is" "_" "an" "_" "example" New contents of the vector: "" "" "" "" "" "" "" Concatenated as string: "this_is_an_example" ``` ```c #include <iostream> #include <iomanip> #include <algorithm> #include <vector> #include <iterator> #include <numeric> #include <string>
int main(){ std::string x = “abc”; auto y = x; std::cout« x « std::endl;//abc
autu a = std::move(x);
//空,因为x被移动了,将亡值小结的知识点
std::cout<< x << std::endl; } ```
1.3.迭代器与哨兵( Sentinel )
哨兵:通常用来标记迭代器区间结尾的地方,迭代不断的递增,终有一刻和结尾的迭代器相等,哨兵就是判断是否与迭代器相等,以find函数为例,其输入的迭代器区间是同一种类型的InputIt first, InputIt last;但是ranges算法输入的迭代器类型可以不相同,只要可以进行比较判断即可,此时last就称为一个哨兵类型.
1.4.并发算法( C++17 / C++20 )
std::execution::seq/par/par_unseq/unseq四种执行的方式.
- std::execution::seq:顺序执行
- std::execution::par:并发执行,多线程
- std::execution::par_unseq:并发非顺序执行,一个线程处理多条命令
- std::execution::unseq(C++20):
#include<iostream>
#include<algorithm>
#include<chrono>
#include<random>
#include<ratio>
#include<vector>
#include<execution>
int main(){
std::random_device rd;
std::vector<double> vals(10000000);
for(auto& d:vals){
d = static_cast<double>(rd());
}
for(int i = 0; i<5; ++i){
using namespace std::chrono;
std::vector<double> sorted(vals);
const auto startTime = high_resolution_clock::now();
//原始
// std::sort(sorted,begin(), sorted.end());
// std::execution::unseq(C++20)
std::sort(std::execution::unseq,\
sorted,begin(), sorted.end());
//std::execution::par:并发执行,多线程,有性能提升
std::sort(std::execution::par,\
sorted,begin(), sorted.end());
const auto endTime = high_resolution_clock::now();
std::cout<<"Latency: "
<<duration_cast<duration<double,std::milli>>(endTime - startTime).count()\
<<std::endl;
}
}
二.bind 与 lambda 表达式
- 很多算法允许通过可调用对象自定义计算逻辑的细节
- transform / copy_if / sort…
sort自定义比较的方式等
#include<functional> #include<iostream> bool MyPredict(int val){ return val > 3; } int main(){ std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; std::vector<int> y; // back_inserter对y进行push_back操作 std::copy_if(x.begin().x.end(),std::back_inserter(y),MyPredict); // 输出:4,5,6,7,8,9,10 for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; }
- transform / copy_if / sort…
sort自定义比较的方式等
- 如何定义可调用对象
- 函数指针:概念直观,但定义位置受限:不能在函数内部定义函数
#include<functional> #include<iostream> #include<vector> int main(){ std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; std::vector<int> y; //在main函数内部定义函数,非法 bool MyPredict(int val){ return val > 3; } // back_inserter对y进行push_back操作 // 函数指针调用的一种缺陷 std::copy_if(x.begin().x.end(),std::back_inserter(y),MyPredict); // 输出:4,5,6,7,8,9,10 for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; } - 类:功能强大,但书写麻烦:对函数重载,操作符重载,对类引入一些方法
- bind :基于已有的逻辑灵活适配,但描述复杂逻辑时语法可能会比较复杂难懂
- lambda 表达式:小巧灵活,功能强大
- 函数指针:概念直观,但定义位置受限:不能在函数内部定义函数
2.1.bind
- 通过绑定的方式修改可调用对象的调用方式
- 早期的 bind 雏形: std::bind1st / std::bind2nd
- 具有了 bind 的基本思想,但功能有限(基本已经移出标准了)
std::greater
template< class T > struct greater; // 检查第一参数是否大于第二个 constexpr bool operator()(const T &lhs, const T &rhs) const { return lhs > rhs; // 假定实现使用平坦地址空间 }#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> int main(){ std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; // back_inserter对y进行push_back操作 // std::bind2nd(std::greater<int>(),3) // std::bind2nd,表示把第二个参数固定为3, // 所以此时,只要第一个参数比3大,就返回true std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind2nd(std::greater<int>(),3)); // 输出:4,5,6,7,8,9,10 for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; ---- // std::bind1st,对greater的第一个参数进行绑定,第一个参数为3 // 表示,3比输入的数字大就为true,所以此时为1,2 std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind1st(std::greater<int>(),3)); // 输出:1,2 for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; }功能还是比较受限
#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> bool MyPredict(int val1, int val2){ return val1 > val2; } int main(){ std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; //非法,只有部分标准库的函数才能使用bind1st,bind2nd std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind1st(MyPredict,3)); for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; }
- 具有了 bind 的基本思想,但功能有限(基本已经移出标准了)
std::greater
- std::bind ( C++11 引入):用于修改可调用对象的调用方式
#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> using namespace std::placeholders;//bind的命名空间 bool MyPredict(int val1, int val2){ return val1 > val2; } int main(){ std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind(MyPredict,_1,3)); //输出4 5 6 7 8 9 10 for(auto p:y){ std::cout<< p <<' '; } std::cout<<std::endl; ---单独讨论--- auto k = std::bind(MyPredict,_1,3); // 输出 1 std::cout<< k(50);//50会被作为MyPredict的第一个参数 // 注意:这里的_1正确理解是调用时输入的第一个参数 // 不是固定的指MyPredict的第一个参数,如下: auto k = std::bind(MyPredict,_1,3);//MyPredict(50 ,3) auto k = std::bind(MyPredict,3,-1);//MyPredict(3, 50) //函数指针后面的参数与函数参数一一对应 auto k = std::bind(MyPredict, _2, 3); std::cout<< k("dad", 50, 1);//1:50>3 // 所以这里的_1, _2, _3,是指调用时的第几个参数 auto k = std::bind(MyPredict, _2, _1); std::cout<< k(3, 4);//MyPredict(4,3): 输出1 }#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> using namespace std::placeholders;//bind的命名空间 bool MyPredict(int val1, int val2){ return val1 > val2; } bool MyAnd(int val1, int val2){ return val1 && val2; } int main(){ auto x1 = std::bind(MyPredict,_1,3);//MyPredict(_1 ,3) auto x2 = std::bind(MyAnd, 10, _1); auto x3 = std::bind(MyAnd, x1, x2); std::cout<< x3(5);//输出1 //5是bind的参数,5作为x1(5>3),x2(10>5)的_1进行计算,MyAnd为真 auto x4 = std::bind(MyPredict, _1, _1); std::cout<< x4(5);//输出0(5>5) }- 调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险
#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> #include<memory> using namespace std::placeholders;//bind的命名空间 void MyProc(int* ptr){ } auto fun(){ int x; //&x是复制到MyProc里面的,当fun调用完,x就不存在了 return std::bind(MyProc, &x); } int main(){ auto ptr = fun(); ptr();//调用指针就很危险,未定义 } ====== auto fun(){ int x; //使用智能指针可以解决这个问题 std::shared_ptr<int> x(new int()); return std::bind(MyProc,x); } int main(){ auto ptr = fun(); ptr(); } ====== void Proc(int& x){ ++x; } auto fun(){ int x; //使用智能指针可以解决这个问题 std::shared_ptr<int> x(new int()); return std::bind(MyProc,x); } int main(){ int x = 0; Proc(x); std::cout<< x <<std::endl;//1,正常逻辑 ----- int x = 0; auto b = std::bind(Proc, x); b();//将x拷贝给b,进行++,操作的是b内部的成员 std::cout<< x <<std::endl;//0,小心有坑 //可以使用 std::ref 或 std::cref 避免复制的行为 ----- int x = 0; auto b = std::bind(Proc, std::ref(x));//传引用 b();//避免拷贝 std::cout<< x <<std::endl;//1 } - 可以使用 std::ref 或 std::cref 避免复制的行为
- 调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险
- std::bind_front ( C++20 引入): std::bind 的简化形式,类似与std::bind1st的功能
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 bool MyPredict(int val1, int val2){ return val1 > val2; } int main(){ //bind_front固定第一个参数为3 auto y = std::bind_front(MyPredict, 3); std::cout<< y(2) <<std::endl;//1, 3>2 }2.2.lambda表达式
2.2.1.lambda 表达式
lambda 表达式( https://leanpub.com/cpplambda )
- 为了更灵活地实现可调用对象而引入
- C++11 ~ C++20 持续更新
- C++11 引入 lambda 表达式
- C++14 支持初始化捕获、泛型 lambda
- C++17 引入 constexpr lambda , *this 捕获
- C++20 引入 concepts ,模板 lambda
- lambda 表达式会被编译器翻译成类进行处理
2.2.2.lambda 表达式的基本组成部分
- 参数与函数体
-
返回类型
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 //lambda构造的是一个对象 int main(){ auto x = [](int val){return val > 3;};//返回类型为bool:x为bool std::cout<< x(5);//1, 5>3 ---- auto x = (int val) {//此时无法自动推到x的类型 if(val > 3) return 3.0;//double else return 1.5f;//float }; ---- auto x = (int val) -> float//显式指定返回类型 { if(val > 3) return 3.0;//double else return 1.5f;//float }; } - 捕获:针对函数体中使用的局部自动对象进行捕获
- 值捕获、引用捕获与混合捕获
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 //lambda构造的是一个对象 int main(){ int y = 10;//局部自动对象 int z = 3; //这种y不能捕获,static静态和全局对象不需要捕获,可以直接使用 //static int y = 10; auto x= [y](int val){//利用[]进行y值的捕获,然后复制到lambda内部 return val>y; }; std::cout<<x(5);//0; 5>10; ---- //值捕获 auto x= [y](int val) mutable//值捕获,将y的值复制到lambda内部 { ++y;//这个y不是外部的y return val>y; }; std::cout<<x(5)<<std::endl;//0 //10,lambda内部y的计算不会传到外部 std::cout<<y<<std::endl;//10 ----- //引用捕获 auto x= [&y](int val) //引用捕获 { ++y;//这个y就是外部的y return val>y; }; std::cout<<x(5)<<std::endl;//0 //11,lambda引用捕获,传入引用到内部y的计算会传到外部 std::cout<<y<<std::endl;//11 ----- //混合捕获 auto x= [&y, z](int val) //y为引用捕获,z为值捕获 { ++y;//这个y就是外部的y return val>z; }; std::cout<<x(5)<<std::endl;//5>3:1 //11,lambda引用捕获,传入引用到内部y的计算会传到外部 std::cout<<y<<std::endl;//11 ----- //自动分析,lambda里面用到了哪些局部对象,[=]全部默认为值捕获 //[=]代表值捕获 auto x= [=](int val) //y,z为值捕获 { ++y;//值捕获 return val>z; }; std::cout<<x(5)<<std::endl;//5>3:1 std::cout<<y<<std::endl;//10 ----- //自动分析,lambda里面用到了哪些局部对象,[&]全部默认为引用捕获 //[&]代表引用捕获 auto x= [&](int val) //y,z为引用捕获 { ++y;//引用捕获 return val>z; }; std::cout<<x(5)<<std::endl;//5>3:1 //11,lambda引用捕获,传入引用到内部y的计算会传到外部 std::cout<<y<<std::endl;//11 ---- //除了z,其他全部默认为引用捕获 auto x= [&, z](int val) //z为值捕获 { ++y; return val>y; }; std::cout<<x(5)<<std::endl;//0 std::cout<<y<<std::endl;//11 ---- //y为引用捕获,其他全部默认为值捕获 auto x= [=, &y](int val) { ++y; return val>y; }; std::cout<<x(5)<<std::endl;//0 std::cout<<y<<std::endl;//11 } - this 捕获
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 //lambda构造的是一个对象 struct Str{ void fun() { int val = 3; //this指向当前的Str对象,这样一来就相当于对x声明过了 auto lam = [val,this]() { return val > x; }; return lam(); } int x; }; int main(){ Str s; s.fun();//此时this对应的就是s的地址 } - 初始化捕获( C++14 )
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 int main(){ int x = 3; auto lam = [](int val){ return val>3; } std::cout<<lam(100);//100>3 ---- //初始化捕获,给lambda内部定义一个y并赋值 auto lam = [y=x](int val){ return val>y; } std::cout<<lam(100);//100>3 ---- //初始化捕获 std::string a = "hello"; //把a移动给y,此后a就为空 auto lam = [y=std::move(a)](){ std::cout<<y<<std::endl; } lam();//hello std::cout<<a<<std::endl;//空 ---- //初始化捕获 int x = 3; int y = 10; auto lam = [z = x+y](int val){ return val > z; } ---- //初始化捕获 int x = 3; int y = 10; //左边的x为初始化给lambda内部使用的x //右边的x为外部的x作为右值对内部x进行赋值 auto lam = [x=x](int val){ return val > x; } } - *this 捕获( C++17 )
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 //this 捕获,可能存在风险 struct Str{ void fun() { int val = 3; //this指向当前的Str对象,这样一来就相当于对x声明过了 auto lam = [val,this]() //auto lam = [val,*this]()//会把内容复制一份到里面,就是安全的 { return val > x; }; return lam; } int x; }; auto wrapper(){ Str s;//this指向的是这一个指针 //此时的s也是wrapper的局部自动对象,调用结束会被销毁 return s.fun(); } int main(){ //wrapper()返回的是lambda表达式 auto lam = wrapper(); lam();//未定义的使用,Str s在这一步就被销毁了 }
- 值捕获、引用捕获与混合捕获
- 说明符
- mutable / constexpr (C++17) / consteval (C++20)……
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 int main(){ int y = 3; auto lam = [y](int val){//默认;lambda内部是const的,不能改变 //++y;//不能通过编译 return val > y; }; ------- int y = 3; auto lam = [y](int val) mutable //去掉const { //++y;//mutable后就可以更改 return val > y; }; ------- int y = 3; //可以在编译期(根据实际情况也可在运行期)进行运算 auto lam = [y](int val) constexpr { retun val + 1; }; constexpr int val = lam(100); std::cout<< val<<std::endl;//101 ------- int y = 3; //consteval只能在编译期进行运算 auto lam = [y](int val) consteval { retun val + 1; }; constexpr int val = lam(100); std::cout<< val<<std::endl;//101 }
- mutable / constexpr (C++17) / consteval (C++20)……
- 模板形参( C++20 )
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 int main(){ int y = 3; auto lam = [y]<typename T>(T val) { retun val + 1; }; constexpr int val = lam(100); std::cout<< val<<std::endl;//101 }
2.2.3.lambda 表达式的深入应用
- 捕获时计算( C++14 )
- 即调用函数表达式(Immediately-Invoked Function Expression,IIFE)
#include<functional>//bind #include<iostream> #include<algorithm> using namespace std::placeholders;//bind的命名空间 int main(){ int x = 3; int y = 5; //捕获时计算( C++14 ) auto lam = [z = x+y](int val) { return z > val; }; //前面的都是先构造lambda表达式,后进行执行,分两步 ---- //IIFE:即调用函数表达式 const auto lam = [z = x+y](int val) { return z > val; }();//立即执行,避免重新定义一个函数 } -
使用 auto 避免复制( C++14 )
#include<functional>//bind #include<iostream> #include<algorithm> #include<map> int main(){ auto lam = [](auto x)//内部是函数模板的实现 { return x+1; }; ------ std::map<int,int> m{{2,3}}; auto lam = [](const std::pair<int,int>& p)//会复制 //auto lam = [](const std::pair<const int,int>& p)//不会复制 //auto lam = [](const auto& p)//不会复制 { return p.first + p.second; } std::cout<<lam(*m.begin())<<std::endl; } -
Lifting ( C++14 )
#include<functional>//bind #include<iostream> #include<algorithm> #include<map> auto fun(int val) { return val+1; } auto fun(double val) { return val+1; } int main(){ auto b = std::bind(fun, 3);//这行报错,对函数重载无法区分 std::cout<<b()<<std::endl; ------ //Lifting ( C++14 ) auto lam = [](auto x)//auto:内部实现为函数模板 { return fun(x); }; lam(3); lam(3.5) } - 递归调用( C++14 )
#include<functional>//bind #include<iostream> #include<algorithm> #include<map> //函数不一样,看到函数这一行就能解析是什么对象了 int factorial(int n) { return n>1 ? n*factorial(n-1) : 1; } int main(){ factorial(5);//典型应用 ---- //无法解析,只能将后面执行了才能解析auto auto factorial2 = [](int n) {//factorial2还没确定,所以这条语句不确定,判定为不合法 return n>1 ? n*factorial2(n-1) : 1;//出错 } ----- //解决 auto factorial2 = [](int n) { auto f_impl = [](int n, const auto& impl) -> int { return n>1 ? n*impl(n-1 ,impl) : 1; }; return f_impl(n, f_impl); } ----- }三.泛型算法的改进—— ranges
- 可以使用容器而非迭代器作为输入
- 通过 std::ranges::dangling 避免返回无效的迭代器
#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> #include<ranges> auto fun() { return std::vector<int> {1,2,3,4,5}; } int main(){ std::vector<int> x{1,2,3,4,5}; //返回ptr为迭代器 auto ptr = std::find(x.begin(), x.end(), 3); std::cout<< *ptr<<std::endl;//3 ------ std::vector<int> x{1,2,3,4,5}; auto ptr = std::ranges::find(x.begin(), x.end(), 3); std::cout<< *ptr<<std::endl;//3 ------ std::vector<int> x{1,2,3,4,5}; //ranges自动解析为x.begin(), x.end() auto ptr = std::ranges::find(x, 3); std::cout<< *ptr<<std::endl;//3 ------ //通过 std::ranges:dangling 避免返回无效的迭代器 //dangling悬挂(意会,就是已经被销毁的东西) //有问题:fun()调用完后,vector被销毁,ptr指向右值的一个位置 auto ptr = std::ranges::find(fun(), 3); std::cout<< *ptr<<std::endl;//这里解引用就出错 }
- 通过 std::ranges::dangling 避免返回无效的迭代器
-
引入映射概念,简化代码编写
#include<functional>//bind #include<iostream> #include<algorithm> #include<vector> #include<ranges> #include<map> int main(){ std::map<int,int> m{{2,3}}; //查找第二个元素为3的迭代器 auro it = std::ranges::find(m, 3 &(std::pair<const int, int>::second)); //2 3 st d::cout<<it->first<<' '<<it->second<<std::endl; } - 从类型上区分迭代器与哨兵
- 引入 view ,灵活组织程序逻辑
