【C++进阶篇】C++11新特性(下篇)

一. lambda表达式

lambda可以像仿函数一样传给函数,像sort,它的优势在于代码清晰度高,且简便,它的本质是一个匿名对象。它没有类型,可以使用auto接收推导类型。

1.1 仿函数使用

仿函数作为对象,控制sort排升序还是降序。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

struct cmpGreater
{
	bool operator()(int X, int Y)
	{
		return X > Y;
	}
};

struct cmpLess
{
	bool operator()(int X, int Y)
	{
		return X < Y;
	}
};
int main()
{
	vector<int> arr{ 10,4,2,6,7,3,8,0,1,5,9 };

	//仿函数传递对象
	sort(arr.begin(), arr.end(), cmpLess());//降序
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	sort(arr.begin(), arr.end(), cmpGreater());//降序
	for (auto e : arr)
		cout << e << " ";
	return 0;
}

创建vector对象,通过sort排序,仿函数控制升序行为。

  • 输出结果:

在这里插入图片描述

上面已经说过lambda也可以完成仿函数的行为,下面展示一下。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
	vector<int> arr{ 10,4,2,6,7,3,8,0,1,5,9 };

	//lambda表达式
	//升序
	sort(arr.begin(), arr.end(), [](int x, int y)-> int {return x > y; });//升序
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	//降序
	sort(arr.begin(), arr.end(), [](int x, int y)-> int {return x < y; });//降序
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
	return 0;
}

输出结果:
在这里插入图片描述
sort函数使用lambda代替仿函数,从结果可以看出一样完成一样的功能。

1.2 lambda表达式的语法

C/C++ lambda 语法格式:

[capture-list] (parameters)-> return type { function boby };

解释:

  1. [Capture List] :捕捉列表,用于定义Lambda如何访问外部作用域的变量。
  2. (Parameters): 参数列表定义 Lambda的输入参数。
  3. ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此
    部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
  4. {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以
    使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。

在这里插入图片描述
其中,参数列表,mutable,-> returntype可以省略。

  • 省略参数列表,表示当前是一个无参的参数对象。
  • 省略mutable,表示捕捉的对象保持其常性。
  • 省略->returntype,返回值类型由编译器自动推导。

注意:捕捉列表和函数体不可省略,很容易理解。也就是说最基本的 lambda 表达式 只需要写 [ ]{ }

int main()
{
	//最简单的lambda表达式
	[] {};
	return 0;
}

此时表达式相当于返回值为空,参数为空,函数体为空的匿名对象。

void func()  {  };

lambda表达式本质是匿名对象,而func是一个有名对象,直接调用即可。

1.3 lambda表达式使用

上面说过lambda是匿名对象,该对象出了作用域(当前行)就销毁了,其它作用域无法使用它。
lambda只允许使用函数体参数中的变量,要想使用外层变量,此时就需要捕捉
捕捉分为,值捕捉,引用捕捉,隐式捕捉,全捕捉,即混合捕捉等。

1.3.1 传值和传引用捕捉

下面通过写代码,看看编译器现象。
在lambda表达式外定义几个变量,看看lambda表达式是否能使用???
在这里插入图片描述
需要捕捉,先进行值捕捉,看看现象。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
	int x = 10, y = 20;
	[x](int a, int b)//当前作用域调用该匿名对象
		{
			a++;
			b++;
			x;
			cout << "x = " << x << endl;
		}(10,20);

	auto ret1 = [x](int a, int b)//使用对象构建lambda表达式
		{
			a++;
			b++;
			x;
			cout << "x = " << x << endl;
		};
	ret1(20, 30);
	return 0;
}

输出结果:
在这里插入图片描述
下面我们对捕捉的对象进行修改,看看行不行???
在这里插入图片描述
从上图可以看出编译器报错,不支持,我们就需要修改呢,有没有办法???
有的兄弟,有的,可以是用mutable关键字,加在参数列表即可。默认捕捉的值具有常性。

  • 功能:捕捉的值可以去除该值的常性。
int main()
{
	int x = 10, y = 20;
	[x](int a, int b)//当前作用域调用该匿名对象
	mutable	{
			a++;
			b++;
			x++;
			cout << "x = " << x << endl;
		}(10, 20);
		return 0;
}

在这里插入图片描述
从结果可以看出该值进行了自增。且不是该对象的拷贝对象。
还有其它的方法也可以完成该功能:传引用捕捉。

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
	int x = 10, y = 20;
	//传引用捕捉
	[&x](int a, int b)//当前作用域调用该匿名对象
	{
		a++;
		b++;
		x++;
		cout << "x = " << x << endl;
	}(10, 20);

	return 0;
}

一样的输出结果。
在这里插入图片描述

1.3.2 隐式捕捉

在捕捉列表显示写个 = ,表示为隐式捕捉,我们用了哪些变量就捕捉哪些变量,该行为编译器会自动推导。

#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
	int x = 10, y = 20,a=30,b=40,z=50;
	//隐式引用捕捉
	[=]()
	{
		int ret = x + y + a;
		cout << "ret = " << ret << endl;
	}();

	//等价于
	auto ret = [=](){ int ret = x + y + a; cout << "ret = " << ret << endl;};
	ret();
	return 0;
}

输出结果:
在这里插入图片描述
隐式捕捉好处在于代码简洁,提升代码表达力的利器。

1.3.3 混合捕捉

显示捕捉分为传值捕捉和传引用捕捉。混合捕捉是隐式捕捉和显示捕捉的混合体。
规则:

  1. 当使用混合捕捉时,第一个元素必须是&或=。
  2. &混合捕捉时,后面的捕捉变量必须是值捕捉。
  3. =混合捕捉时,后面的捕捉变量必须是引用捕捉。
#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
	int a = 10, b = 20, c = 30, d = 90, x = 1, y = 2, z = 3;
	//混合捕捉,第1个为隐式捕捉
	auto ret = [=,&a,&b](){
		a++;
		b++;
		int ret = a + b + x + y;
		cout << "ret= " << ret << endl;
		return ret;
		};
	ret();

	cout << "之前: x = " << x << ", y = " << y << endl;
	//混合捕捉,第1个为隐式捕捉
	auto ret1 = [&, a, b]() {
		x++;
		y++;
		int ret = a + b + x + y;
		cout << "之后: ";
		cout << "x = " << x <<", y = " << y << endl;
		return ret;
		};
	ret1();

	return 0;
}

输出结果:
在这里插入图片描述
从结果可以看出x和y都进行自增,符合预期行为。
注意:局部静态变量和全局变量不需要捕捉,强行捕捉会报错。

  • 报错示例:

在这里插入图片描述
默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
	int x = 10;
	[x]() mutable{
		x++;
		cout << "lambda表达式里的x = " << x << endl;
		return x; }();


	cout << "lambda表达式外的x = " << x << endl;
}

在这里插入图片描述
从结果可以看出x并没有改变,捕捉的对象确实是存储在lambda表达式中临时对象。
当lambda表达式定义在全局变量捕捉列表必须为空,因为没有其它的变量可以被捉到。

1.4 lambda表达式原理

lambda对象的大小。

#include<iostream>
#include<algorithm>
using namespace std;

//普通函数
int add(int x, int y)
{
	return x + y;
}

//仿函数
struct ADD
{
	int operator()(int x, int y)
	{
		return x + y;
	};
};

int main()
{
	auto typec = add;
	ADD add1;
	auto ret = [](int x, int y) {return x + y; };

	cout << "普通函数: " << sizeof(typec) << endl;
	cout << "仿函数: " << sizeof(add1) << endl;
	cout << "lambda表达式: " << sizeof(ret) << endl;

	return 0;
}

在这里插入图片描述
可以看出lambda表达式对象与仿函数一样都是大小都是1字节。空类(没有成员变量)独占1个字节。
VS2022两者汇编代码:
在这里插入图片描述
可以看出汇编代码完全一致,所以编译器就是把lambda表达式当做仿函数。
结论:lambda 表达式 本质上就是一个 仿函数

1.5 lambda优点及建议

  1. 代码简洁性革命
  • 减少模板代码:替代单方法接口的匿名类(如Java的Runnable),代码量减少50%-70%
  • 内联逻辑封装:在算法中间件(如std::sort的比较函数)中直接嵌入逻辑,避免上下文切换
  1. 函数式编程加速器
  • 高阶函数支持:天然适配map/filter/reduce等操作,实现链式数据处理流水线
  • 不可变数据:强制以表达式形式返回值,天然支持无副作用编程
  1. 延迟执行专家
  • 回调封装:在GUI事件处理、异步编程中封装待执行逻辑
  • 策略模式实现:动态替换算法逻辑(如Java的Comparator接口)
  1. 并发编程利器
  • 无状态共享:天然适配并行流(如Java的parallelStream())
  • 线程安全默认:值捕获([=])创建的副本天然隔离线程间数据

最佳实践:Lambda表达式是现代编程的瑞士军刀,但需遵循"3行法则":超过3行的逻辑应重构为具名函数。合理使用可显著提升代码表现力,但需在简洁性与可维护性间找到平衡点。建议从简单场景开始实践,逐步掌握其高级特性,并始终关注变量作用域和线程安全性。

二. 包装器

包装器分为函数包装器和类包装器。

2.1 function

一个结构一致的函数,lambda表达式基本相同。

// 普通函数
void func(int n)
{
	cout << "void func(int n): " << n << endl;
}

// 仿函数
struct Func
{
public:
	void operator()(int n)
	{
		cout << "void operator()(int n): " << n << endl;
	}
};

// lambda 表达式
auto lambda = [](int n)->void
	{
		cout << "[](int n)->void: " << n << endl;
	};

int main()
{
	void(*pf)(int);//函数指针

	pf = func;//函数名本身就是函数的地址

	pf(10);

	//Func f;//类实例化出对象
	//pf = f;//error

	pf = lambda;
	pf(20);
	return 0;
}

输出结果:
在这里插入图片描述

可以看出函数指针无法指向类对象仿函数对象
有没有什么东西可以完成该功能。包装器就是解药。
function就是一个类模版,可以包装任何对象,包括函数指针、仿函数、lambda、宾得表达式等。
function是基于 可变模版参数 实现的,原型如下:

template <class Ret, class… Args>
class function<Ret(Args…)>;

其中Ret是返回值类型,Args为模版参数包,表示传给函数的参数,function 模板类通过 模板特化 指明了包装的函数对象类型。
注意:使用function前,需要包含头文件

int main()
{
	// 包装器
	function<void(int)> f;

	f = func;
	f(10);

	f = Func();
	f(20);

	f = lambda;
	f(30);

	return 0;
}

在这里插入图片描述
function还可以包装 类内成员函数
包装静态成员函数相对较简单,只需指明类域即可。

#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;


class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
		int _n;
};

int main()
{
	function<int(int,int)> f = Plus::plusi;
	//function<int(int,int)> f = &Plus::plusi;//correct

	cout << "f(10, 20) = " << f(10, 20) << endl;
	return 0;
}

在这里插入图片描述
下面进行包装静态成员函数,相对复杂一点。使用上述方法会报错
在这里插入图片描述
解决办法:在包装时,指定第一个参数为类,并且需要取地址。

function<double(Plus,double, double)> f = &Plus::plusd;//因为成员函数还多了个this指针,这个&不可省略

//匿名对象
f(Plus(), 10, 20);

//普通对象
Plus p;
f(p, 10, 20);
return 0;

在这里插入图片描述

  • 细节:为什么包装静态成员函数不需要取地址,而非静态成员函数需要取地址???

非静态成员函数必须通过&Plus::plusd、显式获取成员函数指针,并通过std::bind、Lambda或std::mem_fn绑定对象实例,否则无法直接调用。

  • 本质区别:

在这里插入图片描述
当第一参数设置为类的指针时,看看现象。
在这里插入图片描述
第一个参数设置为指针,普通函数可以,对于匿名对象就不可以。
再将第一参数设置为引用版本看看,能不能解决问题。
在这里插入图片描述
可能有人会想第一个参数用const修饰,但是会导致普通的对象无法使用,因为涉及权限放大,从非const到const的放大。

最佳实践:将第一个参数设置为普通的类即可。

2.2 bind绑定

bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收
fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。
bind 也在这个头⽂件中。
原型:

template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);

fn 是传递的 函数对象,args 是传给函数的 可变参数包,这里使用了 万能引用(引用折叠),使其在进行模板类型推导时,既能引用左值,也能引用右值。
使用bind改变参数顺序。

void func(int x, int y)
{
	cout << "void func(int x, int y)" << x << " " << y << endl;
}

int main()
{
	//正常调用
	func(10, 20);

	auto ret = bind(func, -2,-1);
	ret(10, 20);
	return 0;
}

在这里插入图片描述
输出结果好像不对,因为没有指定类域,导致-2,-1传递给函数。现在指定一下。

 placeholders::_2, placeholders::_1

在这里插入图片描述
结果好像是对的。

bind的底层是仿函数,通过bind绑定的函数将参数根据用户指定的行为传递给函数,函数做出行为。
bind主要作用用于指定参数的个数。
下面我们将第一个参数指定。

int main()
{
	auto f = bind(func, 100, placeholders::_1);
	f(10);
	f(10, 20);
	return 0;
}

此时如果坚持传递参数,会优先使用绑定的参数,再从函数参数列表中,从左到右选择参数进行传递,直到参数数量符合,比如这里第二次调用虽然传递了 10 和 20,但实际调用 Func 时,RFunc 会先传递之前绑定的值 100 作为参数1传递,而 10 会作为参数2传递,至于 20 会被丢弃。
在这里插入图片描述
这里的不能这样写 auto f = bind(func, 100, placeholders::_2);因为参数的个数不满足。

看到上面没都要传递类对象,而bind的引入将一个参数类绑死就不需要传入。

class MyClass {
public:
    void memberFunc(int a, int b) {
        cout << "a = " << a << ", b = " << b << endl;
    }
};

int main() {
    MyClass obj;

    // 绑定类成员函数,固定第一个参数为100
    auto bound_func = bind(&MyClass::memberFunc, &obj, 100, _1);

    // 调用绑定后的函数:只需传递第二个参数(原函数的第二个参数)
    bound_func(200); // 输出:a = 100, b = 200

    return 0;
}

输出结果:
在这里插入图片描述
就不需要手动每次都传递对象。下面看需要传递的麻烦处。

class MyClass {
public:
    void memberFunc(int a, int b) {
        cout << "a = " << a << ", b = " << b
            << ", this = " << this << endl;
    }
};

int main() {
    MyClass obj1, obj2;

    // 绑定成员函数,但保留对象实例的位置为占位符
    auto bound_func = bind(&MyClass::memberFunc, _1, 100, _2);

    // 调用时需显式传递对象实例和其他参数
    bound_func(obj1, 200); // 输出:a = 100, b = 200, this = 0x7ffd...(obj1的地址)
    bound_func(obj2, 300); // 输出:a = 100, b = 300, this = 0x7ffd...(obj2的地址)

    return 0;
}

三. 最后

本文深入解析了C++中lambda表达式与函数包装器的核心机制及实践应用。lambda作为匿名函数对象,通过值/引用/隐式/混合捕捉实现灵活作用域访问,本质是编译器生成的仿函数,支持代码内联封装与函数式编程范式。std::function作为统一调用包装器,可封装普通函数、仿函数、lambda及成员函数,解决函数指针类型限制问题。std::bind通过参数重排与对象绑定实现接口适配,尤其适用于回调场景。三者协同可显著提升代码简洁性,在算法排序、事件处理等场景中简化模板代码,平衡开发效率与可维护性,是现代C++函数式编程的重要工具链。关于线程的部分后面再补充。,也重要

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值