泛型算法与 Lambda 表达式
1.泛型算法
可以支持多种类型的算法
(1)重点讨论 C++ 标准库中定义的算法
(2)为什么要引入泛型算法而不采用方法的形式
内建数据类型不支持方法
计算逻辑存在相似性,避免重复定义
(3)如何实现支持多种类型
使用迭代器作为算法与数据的桥梁
(4)一些泛型算法与方法同名,实现功能类似,此时建议调用方法而非算法
std::find V.S. std::map::find
(5)分类(查资料,自己理解)
1读算法:给定迭代区间,读取其中的元素并进行计算
accumulate / find / count
2写算法:向一个迭代区间中写入元素
单纯写操作: fill / fill_n
读 + 写操作: transform / copy
注意:写算法一定要保证目标区间足够大
3排序算法:改变输入序列中元素的顺序
sort / unique
(6)泛型算法使用迭代器实现元素访问
迭代器的分类
1输入迭代器:可读,可递增 典型应用为 find 算法
2输出迭代器:可写,可递增 典型应用为 copy 算法
3前向迭代器:可读写,可递增 典型应用为 replace 算法
4双向迭代器:可读写,可递增递减 典型应用为 reverse 算法
5随机访问迭代器:可读写,可增减一个整数 典型应用为 sort 算法
(7)一些特殊的迭代器
1插入迭代器: back_insert_iterator / front_insert_iterator / insert_iterator
#include <iostream>
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> x;
std::fill_n(std::back_insert_iterator<std::vector<int>>(x), 10, 3);
//std::fill_n(std::back_inserter(x), 10, 3);
for (auto i : x)
{
std::cout << i << ' ';
}
std::cout << std::endl;
}
2流迭代器: istream_iterator / ostream_iterator
#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>
int main()
{
std::istringstream str("1 2 3 4 5");
std::istream_iterator<int> x(str);
std::cout << *x << std::endl;
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>
int main()
{
std::istringstream str("1 2 3 4 5");
std::istream_iterator<int> x(str);
std::istream_iterator<int> y{};
for (; x != y; ++x)
{
std::cout << *x << std::endl;
}
}
3反向迭代器
#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <numeric>
int main()
{
std::vector<int> x{1, 2, 3, 4, 5};
std::copy(x.begin(), x.end(), std::ostream_iterator<int>(std::cout, " "));
}
4移动迭代器: move_iterator
(8)并发算法( C++17 / C++20 )
1std::execution::seq
2std::execution::par
3std::execution::par_unseq
4std::execution::unseq
2.bind 与 lambda 表达式
(1)可调用对象
1函数指针:概念直观,但定义位置受限
2类:功能强大,但书写麻烦
3bind :基于已有的逻辑灵活适配,但描述复杂逻辑时语法可能会比较复杂难懂
4lambda 表达式:小巧灵活,功能强大
(2)bind:通过绑定的方式修改可调用对象的调用方式
1早期的 bind 雏形: std::bind1st / std::bind2nd
具有了 bind 的基本思想,但功能有限
2std::bind ( C++11 引入):用于修改可调用对象的调用方式
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <functional>
bool MyPredict(int val)
{
return val > 3;
}
bool MyPredict2(int val1, int val2)
{
return val1 > val2;
}
int main()
{
using namespace std::placeholders;
std::vector<int> x{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> y;
std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind(MyPredict2, _1, 3));
for (auto p : y)
{
std::cout << p << ' ';
}
std::cout << std::endl;
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
bool MyPredict2(int val1, int val2)
{
return val1 > val2;
}
int main()
{
using namespace std::placeholders;
auto x = std::bind(MyPredict2, _1, 3); //调用时第1个参数与3比较
x(50);
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
bool MyPredict2(int val1, int val2)
{
return val1 > val2;
}
bool MyAnd(bool val1, bool val2)
{
return val1 && val2;
}
int main()
{
using namespace std::placeholders;
auto x1 = std::bind(MyPredict2, _1, 3);
auto x2 = std::bind(MyPredict2, 10, _1);
auto x3 = std::bind(MyAnd, x1, x2);
std::cout << x3(5) << std::endl;
}
调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险
可以使用 std::ref 或 std::cref 避免复制的行为
3std::bind_front ( C++20 引入): std::bind 的简化形式
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
bool MyPredict2(int val1, int val2)
{
return val1 > val2;
}
int main()
{
using namespace std::placeholders;
auto y = std::bind_front(MyPredict2, 3); //3是第1个参数
std::cout << y(2) << std::endl;
}
(3)lambda 表达式
为了更灵活地实现可调用对象而引入
C++11 ~ C++20 持续更新
C++11 引入 lambda 表达式
C++14 支持初始化捕获、泛型 lambda
C++17 引入 constexpr lambda , *this 捕获
C++20 引入 concepts ,模板 lambda
lambda 表达式会被编译器翻译成类进行处理
1lambda 表达式的基本组成部分
1)参数与函数体
#include <iostream>
int main()
{
//auto x = [](int val) {return val > 3;};
auto x = [](int val)
{
return val > 3 && val < 10;
};
std::cout << x(5) << std::endl;
}
2)返回类型
自动推导,但是类型要相同
#include <iostream>
int main()
{
auto x = [](int val)
{
if (val > 3)
{
return 3.0;
}
else
{
return 1.5;
}
};
std::cout << x(5) << std::endl;
}
如果不相同,需显式写出返回类型
#include <iostream>
int main()
{
auto x = [](int val) -> float
{
if (val > 3)
{
return 3.0;
}
else
{
return 1.5f;
}
};
std::cout << x(5) << std::endl;
}
3)捕获:针对函数体中使用的局部自动对象进行捕获
#include <iostream>
int main()
{
int y = 10;
auto x = [y](int val) //[]目的是捕获
{
return val > y;
};
std::cout << x(5) << std::endl;
}
值捕获、引用捕获与混合捕获
#include <iostream>
int main()
{
int y = 10;
auto x = [y] (int val) mutable //[]目的是捕获
{
++y;
return val > y;
};
std::cout << x(5) << std::endl;
std::cout << y << std::endl; //Lambda表达式内部对y的修改不会传到外部,y是值捕获
}
#include <iostream>
int main()
{
int y = 10;
auto x = [&y] (int val) //[]目的是捕获
{
++y;
return val > y;
};
std::cout << x(5) << std::endl;
std::cout << y << std::endl; //引用捕获修改了y的值
}
#include <iostream>
int main()
{
int y = 10;
int z = 4;
auto x = [&y, z] (int val) //混合捕获
{
++y;
return val > z;
};
std::cout << x(5) << std::endl;
std::cout << y << std::endl; //引用捕获修改了y的值
}
#include <iostream>
int main()
{
int y = 10;
int z = 4;
auto x = [=] (int val) // = 代表值捕获, & 代表引用捕获
{
return val > z;
};
std::cout << x(5) << std::endl;
std::cout << y << std::endl; //引用捕获修改了y的值
}
this 捕获
#include <iostream>
struct Str
{
auto fun()
{
int val = 3;
auto lam = [val, this]()
{
return val > x;
};
return lam();
}
int x;
};
int main()
{
Str s;
s.fun();
}
初始化捕获( C++14 )
#include <iostream>
int main()
{
int x = 3;
auto lam = [y = x](int val)
{
return val > y;
};
std::cout << lam(100) << std::endl;
}
#include <iostream>
#include <string>
int main()
{
std::string a = "hello";
auto lam = [y = std::move(a)]()
{
std::cout << y << std::endl;
};
lam();
std::cout << a << std::endl;
}
#include <iostream>
#include <string>
int main()
{
int x = 3;
int y = 8;
auto lam = [z = x + y](int val)
{
return val > z;
};
}
*this 捕获( C++17 )
#include <iostream>
struct Str
{
auto fun()
{
int val = 3;
auto lam = [val, *this]()
{
return val > x;
};
return lam;
}
int x;
};
auto wrapper()
{
Str s;
return s.fun();
}
int main()
{
auto lam = wrapper();
lam();
}
4)说明符
mutable / constexpr (C++17) / consteval (C++20)…
#include <iostream>
int main()
{
int y = 10;
auto x = [y] (int val) mutable //mutable使得y可修改
{
++y;
return val > y;
};
std::cout << x(5) << std::endl;
std::cout << y << std::endl;
}
#include <iostream>
int main()
{
auto lam = [] (int val) constexpr
{
return val + 1;
};
constexpr val = lam(100);
std::cout << val << std::endl;
}
5)模板形参( C++20 )
#include <iostream>
int main()
{
auto lam = []<typename T> (T val)
{
return val + 1;
};
constexpr val = lam(100);
std::cout << val << std::endl;
}
2lambda 表达式的深入应用
1)捕获时计算( C++14 )
#include <iostream>
#include <string>
int main()
{
int x = 3;
int y = 8;
auto lam = [z = x + y]()
{
return z;
};
lam();
}
2)即调用函数表达式( Immediately-Invoked Function Expression, IIFE )
#include <iostream>
int main()
{
int x = 3;
int y = 8;
const auto lam = [z = x + y]()
{
return z;
}();
std::cout << lam << std::endl;
}
3)使用 auto 避免复制( C++14 )
#include <iostream>
#include <map>
int main()
{
std::map<int, int> m{{2, 3}};
/*
auto lam = [](const std::pair<int, int>& p)
{
return p.first + p.second;
};
*/
auto lam = [](const auto& p)
{
return p.first + p.second;
};
std::cout << lam(*m.begin()) << std::endl;
}
4)Lifting ( C++14 )
#include <iostream>
auto fun(int val)
{
return val + 1;
}
auto fun(double val)
{
return val + 1;
}
int main()
{
auto lam = [](auto x)
{
return fun(x);
}
std::cout << lam(3) << std::endl;
std::cout << lam(3.5) << std::endl;
}
5)递归调用( C++14 )有点难。。。
#include <iostream>
int factorial(int n)
{
return n > 1 ? n * factorial(n - 1) : 1;
}
int main()
{
std::cout << factorial(5) << std::endl;
}
#include <iostream>
int main()
{
auto factorial = [](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);
};
std::cout << factorial(5) << std::endl;
}
3.泛型算法的改进—— ranges
(1)可以使用容器而非迭代器作为输入
通过 std::ranges::dangling 避免返回无效的迭代器
#include <iostream>
#include <algorithm>
#include <vector>
#include <ranges>
int main()
{
std::vector<int> x{1, 2, 3, 4, 5};
auto it = std::find(x.begin(), x.end(), 3);
std::cout << *it << std::endl;
auto it2 = std::ranges::find(x, 3);
std::cout << *it2 << std::endl;
}
#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};
auto it = std::ranges::find(fun(), 3);
std::cout << *it << std::endl;
}
(2)从类型上区分迭代器与哨兵
(3)引入映射概念,简化代码编写
(4)引入 view ,灵活组织程序逻辑