[C++] C++11新特性介绍 分析(2): lambda表达式、function包装器、bind()接口
C++11
lambda
表达式
std::sort()
接口对自定义类型进行排序 或者 设置降序排列, 需要使用 仿函数, 就像这样:#include <iostream>
#include <iterator>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
using std::cout;
using std::endl;
using std::greater;
using std::string;
using std::vector;
struct Goods {
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
: _name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
// 价格升序仿函数
struct ComparePriceLess {
bool operator()(const Goods& gl, const Goods& gr) {
return gl._price < gr._price;
}
};
// 价格降序仿函数
struct ComparePriceGreater {
bool operator()(const Goods& gl, const Goods& gr) {
return gl._price > gr._price;
}
};
int main() {
int array[] = {4, 1, 8, 5, 3, 7, 0, 9, 2, 6};
// 默认按照小于比较,排出来结果是升序
std::sort(array, array + sizeof(array) / sizeof(array[0]));
cout << "升序" << endl;
for (auto e : array) {
cout << e << " ";
}
cout << endl << endl;
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
cout << "降序" << endl;
for (auto e : array) {
cout << e << " ";
}
cout << endl << endl;
vector<Goods> v = {
{"苹果", 2.1, 5},
{"香蕉", 3.1, 4},
{"橙子", 2.2, 3},
{"菠萝", 1.5, 4}
};
sort(v.begin(), v.end(), ComparePriceLess());
cout << "价格升序" << endl;
cout << "物品 价格 评价" << endl;
for (auto e : v) {
cout << e._name << " " << e._price << " " << e._evaluate << endl;
}
cout << endl;
sort(v.begin(), v.end(), ComparePriceGreater());
cout << "价格降序" << endl;
cout << "物品 价格 评价" << endl;
for (auto e : v) {
cout << e._name << " " << e._price << " " << e._evaluate << endl;
}
cout << endl;
return 0;
}
std::sort()
针对标准类型, 默认是按照降序排序的.std::greater
可以让 std::sort
对数组升序排序.std::sort()
然后才可以正确的排序.lambda
表达式lambda
表达式
lambda
表达式呢?// 其他部分还是 上面代码中的部分
void printGoods(vector<Goods>& v) {
cout << "物品 价格 评价" << endl;
for (auto e : v) {
cout << e._name << " " << e._price << " " << e._evaluate << endl;
}
cout << endl;
}
int main() {
vector<Goods> v = {
{"苹果", 2.1, 5},
{"香蕉", 3.1, 4},
{"橙子", 2.2, 3},
{"菠萝", 1.5, 4}
};
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price < g2._price; });
cout << "价格升序" << endl;
printGoods(v);
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price; });
cout << "价格降序" << endl;
printGoods(v);
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate < g2._evaluate; });
cout << "评价升序" << endl;
printGoods(v);
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate > g2._evaluate; });
cout << "评价降序" << endl;
printGoods(v);
return 0;
}
lambda
表达式std::sort()
的第三个参数:lambda
表达式. 不过 并不完整, lambda
表达式的完整格式为:[captureList] (parameters) mutable -> returnType { statement }
[]()mutable->xxx{ }
-
[capture-list]
:捕捉列表
该列表总是出现在
lambda
表达式的开始位置, 编译器根据[]
来判断接下来的代码是否为lambda
表达式, 捕捉列表能够捕捉上下文中的变量供lambda
表达式使用 -
(parameters)
:参数列表
与 普通函数的参数列表 一致. 如果不需要参数传递, 则可以连同
()
一起省略 -
mutable
默认情况下,
lambda
函数总是一个const
函数, 即 它的捕捉的 值对象 可以被看作默认被const
修饰.mutable
可以取消其常量性. 使用该修饰符时,参数列表不可省略(即使参数为空)
-
-->returnType
:返回值类型
用 追踪返回类型形式 声明函数的返回值类型, 没有返回值时此部分可省略. 返回值类型明确情况下. 也可省略. 由编译器对返回类型进行推导
什么是返回值类型明确的情况下?
看一看上面的例子, 结果一定是一个
bool
所以可以省略返回值声明类似的情况就可以省略
-
{ statement }
:函数体
与普通函数的编写方式相同. 但是 在此作用域内, 只能使用传入的参数 或 捕捉到的父级作用域对象.
参数列表
、mutable
、返回值类型
在特定情况下都可以省略
lambda
表达式的组成来看, 与函数非常的相似. 而实际上, lambda
表达式就是一个 匿名函数对象
, 可以被称为 匿名函数
, 因为它没有函数名.lambda
表达式 除了可以直接匿名使用之外, 还可以这样:int main() {
vector<Goods> v = {
{"苹果", 2.1, 5},
{"香蕉", 3.1, 4},
{"橙子", 2.2, 3},
{"菠萝", 1.5, 4}
};
auto priceUp = [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price; };
sort(v.begin(), v.end(), priceUp);
cout << "价格升序" << endl;
printGoods(v);
return 0;
}
auto
来对 lambda
表达式 定义一个变量. 这样 相应的 lambda
表达式 就可以通过此变量被调用.lambda
表达式之后, 有关于 lambda
表达式的使用还有一些关于 捕捉列表
细节需要介绍.lambda
表达式, 尝试实现交换对象的值:int main() {
int a = 10, b = 20;
cout << a << " " << b << endl;
auto lamSwap1 = [](int x, int y){
int tmp = x;
x = y;
y = tmp;
};
auto lamSwap2 = [](int& x, int& y){
int tmp = x;
x = y;
y = tmp;
};
lamSwap1(a, b);
cout << "lamSwap1:: "<< a << " " << b << endl;
lamSwap2(a, b);
cout << "lamSwap2:: "<< a << " " << b << endl;
return 0;
}
lambda
表达式, 哪一个可以成功让 a
和 b
交换值?lamSwap2
, 因为 传入函数内的参数 如果是临时变量改变 不会影响到原数据. 而 lamSwap2
使用的是 左值引用.捕捉列表
捕捉列表
描述了上下文中哪些数据可以在 lambda
表达式作用域内 使用, 以及使用的方式传值还是传引用.[var]
:表示值传递方式捕捉变量var
[=]
:表示值传递方式捕获所有父作用域中的变量(包括this
)[&var]
:表示引用传递捕捉变量var
[&]
:表示引用传递捕捉所有父作用域中的变量(包括this
)[this]
:表示值传递方式捕捉当前的this
指针
int main() {
int a = 10, b = 20;
cout << a << " " << b << endl;
auto lamSwap1 = [a, b](){
int tmp = a;
a = b;
b = tmp;
};
auto lamSwap2 = [&a, &b](){
int tmp = a;
a = b;
b = tmp;
};
lamSwap1();
cout << "lamSwap1:: "<< a << " " << b << endl;
lamSwap2();
cout << "lamSwap2:: "<< a << " " << b << endl;
return 0;
}
lambda
表达式, 哪一个可以成功让 a
和 b
交换值?lamSwap2
肯定可以, 不过 lamSwap1
可以吗?lambda
表达式上.lamSwap1
. 我们在介绍 lambda
表达式的结构时 介绍过一个内容:默认情况下,
lambda
函数总是一个const
函数, 即 它的捕捉的 值对象 可以被看作默认被const
修饰.mutable
可以取消其常量性. 使用该修饰符时,参数列表不可省略(即使参数为空)
lamSwap1
中, 我们捕捉的 [a, b]
就是 值对象. 所以, 此时 lamSwap1
内的 a
和 b
都是被 const
修饰的值.mutable
:auto lamSwap1 = [a, b]()mutable{
int tmp = a;
a = b;
b = tmp;
};
lamSwap1
是 传值捕捉
.mutable
这个关键词, 显得很没有用.const
修饰, 想要修改需要使用mutable
. 但是使用之后, 又因为传值捕捉, 导致无法修改实际内容. 所以 mutable
好像没有什么太大作用.[]捕捉列表
的用法. 可以捕捉 父级作用域的对象, 让其可以在 lambda
表达式内部使用.使用 [&var] 就是传引用捕捉, [var] 就是传值捕捉
.使用 [&] 就是传引用捕捉所有父级作用域内的对象, 使用 [=] 就是传值捕捉所有父级作用域内的对象
[&, a] // a对象传值捕捉, 其他父级作用域内的所有对象 传引用捕捉
[=, &a] // a对象传引用捕捉, 其他父级作用域的所有对象 传值捕捉
lambda
表达式的使用, 实际上是非常灵活的.lambda
表达式 禁止重复捕捉
:[&, &a]
[&a, &a]
[=, a]
[a, a]
GCC不会报错, 只会报出警告:
lambda
表达式 只能捕捉父级作用域中的对象
. 即 包含此 lambda
表达式的作用域lambda
表达式底层
lambda
表达式 还有一个非常重要的内容是 关于它的类型.auto
接收一个 lambda
表达式. 但是他究竟是什么呢?lambda
表达式能不能相互赋值呢?lambda
表达式, 也不能相互赋值:int main() {
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
f1 = f2;
return 0;
}
lambda
表达式的副本.lambda
表达式赋值给一个 参数和返回值类型都相同的函数指针void (*pF)();
int main() {
auto f1 = []{cout << "hello world" << endl; };
auto f2(f1);
f2();
pF = f1;
pF();
return 0;
}
lambda
表达式.lambda
的底层实现 是 仿函数
.operator()运算符
的类对象包装器 function
lambda
表达式之后.函数指针
, 仿函数
, lambda
template<class F, class T>
T useF(F f, T x) {
static int count = 0; // static 用来记录实例化出的同一个函数被执行多少次
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double{ return d / 4; }, 11.11) << endl;
return 0;
}
useF()
模板中, 第一个模板参数是用来接收 可调用对象类型的.函数名
、函数对象
、lambda
之后, 此模板会被实例化三个不同的函数:useF()
内定义的 static
每次执行都是1, 且不同的地址.useF()
函数.包装器
function
包装普通可调用对象
function包装器
也叫作 适配器
.std::function类模板
, 也是一个包装器.function
类模板:-
第一个模板参数
Ret
需要传入可调用对象的返回值类型 -
后面则是
模板可变参数包
, 需要传入 可调用对象的参数类型列表 -
不过,
function
类模板传参 不需要向其他类模板一样, 将参数用逗号隔开可以直接 这样传参
function< retType(paraType1, paraType2, paraType3, ...)>
std::function
类模板 将 不同类型的可调用对象, 包装成一个类型 即 function
类型使用时, 需要使用
#include <functional>
包含functional
头文件
#include <functional>
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名
std::function<double(double)> func1 = f;
cout << "未使用包装器 f 类型" << typeid(f).name() << endl;
cout << "包装器后 类型" << typeid(func1).name() << endl << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << "未使用包装器 Functor() 类型" << typeid(Functor()).name() << endl;
cout << "包装器后 类型" << typeid(func2).name() << endl << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double{ return d / 4; };
auto lam1 = [](double d)->double{ return d / 4; };
cout << "未使用包装器 lambda 类型" << typeid(lam1).name() << endl;
cout << "包装器后 类型" << typeid(func3).name() << endl;
return 0;
}
function
对象. 都是同一个类模板, 肯定是相同的类型.function
对象来执行 可调用对象的功能:#include <functional>
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名(函数指针)
std::function<double(double)> func1 = f;
cout << func1(1) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << func2(4) << endl;
// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b){ return a + b; };
cout << func3(1, 2) << endl;
return 0;
}
function
对象, 传入给其他模板共其使用:#include <iostream>
#include <functional>
using std::cout;
using std::endl;
template<class F, class T>
T useF(F f, T x) {
static int count = 0; // static 用来记录实例化出的同一个函数被执行多少次
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double{ return d / 4; };
cout << useF(func3, 11.11) << endl;
}
useF
函数模板 只实例化出了一个函数. 因为, 同一个 count
被 ++
、输出了 3 次function
包装类内成员函数
function
包装器, 除了可以包装这三种基础的可调用对象之外. 还可以包装 类内的成员函数:class Plus{
public:
static int plusi(int a, int b) {
return a + b;
}
double plusd(double a, double b) {
return a + b;
}
};
int main() {
// 包装静态成员变量
std::function<int(int, int)> func1 = Plus::plusi;
cout << func1(1, 2) << endl;
// 包赚非静态成员变量
std::function<double(Plus, double, double)> func2 = &Plus::plusd;
cout << func2(Plus(), 3.3, 4.4) << endl;
return 0;
}
包装静态成员函数 和 包装非静态成员函数 的方式是 存在不同的
静态成员函数
, 因为 不存在隐含的this
指针, 本身就可以直接通过类名调用, 比如 Plus::plusi(1, 2)
. 所以在 实例化 function
对象 进行包装时, 不用在参数中 传入一个 Plus
对象. 并且, 不需要取地址包装.非静态成员函数
, 由于是类内函数且非静态 第一个参数是隐含的this
指针, 所以需要 通过对象调用. 所以 在进行包装时, 第一个参数是需要指定类的, 并且在使用时, 需要传入一个对象, 例子中使用了 Plus()
来传递匿名对象. 并且, 需要取地址包装.bind()
bind()
?bind()
是一个函数模板,它可以 接受一个可调用对象, 生成一个新的可调用对象来“适应”原对象的参数列表#include <functional>
int Sub1(int a, int b) {
return a - b;
}
int main() {
std::function<int(int, int)> func1 = std::bind(Sub1, std::placeholders::_1, std::placeholders::_2);
cout << func1(4, 8) << endl;
std::function<int(int, int)> func2 = std::bind(Sub1, std::placeholders::_2, std::placeholders::_1);
cout << func2(4, 8) << endl;
return 0;
}
std::bind()
绑定了 Sub1()
函数, 并将其包装.std::bind()
的参数传递, 有一些差别. 导致 包装之后的 函数的执行结果不同.bind()
使用 及 功能
bind()
的功能究竟是什么呢?1. 调整参数位置
std::placeholders::_1
和 std::placeholders::_2
. 第一个参数容易理解, 就是需要 bind()
的可调用对象.std::placeholders::_n
是什么意思呢?bind()
的作用了.bind()
的第一个参数, 就是接受的可调用对象. 而 后面的 std::placeholders::_n
其实表示的是 可调用对象对应的参数std::placeholders::_1
, 表示 可调用对象的第一个参数std::placeholders::_2
, 表示 可调用对象的第二个参数std::placeholders::_3
, 表示 可调用对象的第三个参数, 以此类推.-
std::bind(Sub1, std::placeholders::_1, std::placeholders::_2)
_1
表示Sub1()
的第一个参数,_2
表示Sub1()
的第一个参数那么,
bind()
执行之后, 获得的函数就是Sub1(int a, int b)
, 函数体不变. -
std::bind(Sub1, std::placeholders::_2, std::placeholders::_1)
同样的,
_1
表示Sub1()
的第一个参数,_2
表示Sub1()
的第一个参数那么, 此次
bind()
执行之后, 获得的函数就是Sub1(int b, int a)
, 函数体不变.
Sub1()
的函数体是 reutn b - a;
func1(4, 8)
, func1() <==> Sub1(int a, int b)
, 所以 结果是 8 - 4 = 4
func2(4, 8)
, func2() <==> Sub2(int b, int a)
, 所以 结果是 4 - 8 = -4
std::placeholders::_n
强绑定了 所接受的可调用对象的参数的位置. bind()
后面的参数, 表示了 bind()
执行之后的可调用对象的参数位置2. 绑定参数
this
指针 且无法更改. 所以 在包装时, 需要将包装的函数的第一个参数类型 指明为类. 且在使用时需要传入一个对象.bind()
的作用是 接受一个可调用对象, 生成一个新的可调用对象来“适应”原对象的参数列表bind()
, 将 类的对象 绑定在类的成员函数上, 以便之后使用时 不在手动传入对象:#include <functional>
class Sub {
public:
int sub(int a, int b) {
return a - b;
}
};
int main() {
Sub s;
// 绑定成员函数
std::function<int(int, int)> func = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
cout << func(3, 4) << endl;
return 0;
}
std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2)
通过 在 this
指针的位置 直接传入一个对应类对象.bind()
真正常用的功能. 调整参数位置 相应的没有那么常用.std::placeholders::_n
占位符 是不包括 this
指针的.作者: 哈米d1ch 发表日期:2023 年 7 月 6 日