『C++』Lambda表达式

本文通过冒泡排序等例子,介绍了C++中函数指针、仿函数和Lambda表达式。函数指针类型处理较麻烦;仿函数借助运算符重载,比函数指针好用;Lambda表达式是C++11引入的匿名函数,底层用仿函数实现,使用方便,可现做现用,还能直接捕获变量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数指针

我们通过一系列简单的例子来走进Lambda表达式,首先,我们来看一个C语言中的冒泡排序代码

#include <stdio.h>

// 冒泡排序
void bubbleSort(int arr[], int len) {
	for (int bound = 0; bound < len; ++bound) {
		for (int cur = len - 1; cur > bound; --cur) {
			if (arr[cur] < arr[cur - 1]) {
				int temp = arr[cur];
				arr[cur] = arr[cur - 1];
				arr[cur - 1] = temp;
			}
		}
	}
}

int main(){
	int arr[] = {
		1, 3, 5, 7, 9,
		2, 4, 6, 8, 0
	};

	printf("The origin array is: \n");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
		printf("%d ", arr[i]);
	}
	printf("\n");

	bubbleSort(arr, sizeof(arr) / sizeof(arr[0]));

	printf("\nThe sorted array is: \n");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
		printf("%d ", arr[i]);
	}
	printf("\n");

	return 0;
}

运行结果如下
在这里插入图片描述


我们上面写的冒泡排序排的是升序,如果我们想要使得冒泡排序既能排升序也能排降序,我们可以传一个回调函数进去,使用回调函数来控制排升序还是降序

#include <stdio.h>

typedef int(*Cmp)(int, int);

void arrDisplay(int arr[], int len) {
	for (int i = 0; i < len; ++i) {
		printf("%d ", arr[i]);
	}
	printf("\n");
}

// 升序
int asc(int num1, int num2) {
	return num1 < num2;
}

// 降序
int desc(int num1, int num2) {
	return num1 > num2;
}

// 可排升序和降序的冒泡排序
void bubbleSort(int arr[], int len, Cmp cmp) {
	for (int bound = 0; bound < len; ++bound) {
		for (int cur = len - 1; cur > bound; --cur) {
			if (cmp(arr[cur], arr[cur - 1])) {
				int temp = arr[cur];
				arr[cur] = arr[cur - 1];
				arr[cur - 1] = temp;
			}
		}
	}
}

int main(){
	int arr[] = {
		1, 3, 5, 7, 9,
		2, 4, 6, 8, 0
	};

	printf("The origin array is: \n");
	arrDisplay(arr, sizeof(arr) / sizeof(arr[0]));
	printf("\n");

	// 升序
	bubbleSort(arr, sizeof(arr) / sizeof(arr[0]), asc);
	printf("The asc sorted array is: \n");
	arrDisplay(arr, sizeof(arr) / sizeof(arr[0]));
	printf("\n");

	// 降序
	bubbleSort(arr, sizeof(arr) / sizeof(arr[0]), desc);
	printf("The desc sorted array is: \n");
	arrDisplay(arr, sizeof(arr) / sizeof(arr[0]));

	return 0;
}

程序运行结果如下
在这里插入图片描述


仿函数

在C++中,如果我们想要对一个数组进行排序,就不需要这么麻烦,因为C++的algorithm库中为我们提供了sort函数,方便我们使用,同时可以通过传入一个仿函数的方式来控制升序降序
在这里插入图片描述


下面我们来具体使用一下

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void arrDisplay(const std::vector<int>& arr) {
	for (const auto& e : arr){
		std::cout << e << " ";
	}
	std::cout << std::endl;
}

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

	std::cout << "The origin array is: ";
	arrDisplay(arr);

	// 升序
	std::sort(arr.begin(), arr.end(), std::less<int>());

	std::cout << "The asc sorted array is: ";
	arrDisplay(arr);

	// 降序
	std::sort(arr.begin(), arr.end(), std::greater<int>());

	std::cout << "The desc sorted array is: ";
	arrDisplay(arr);
	
	return 0;
}

运行结果如下
在这里插入图片描述


注意,C++中控制升序降序传入的是一个仿函数,仿函数就是一个类,这个类中对函数调用操作符()进行了重载,使得这个类可以像函数一样使用。比回调函数好用,因为回调函数本质传的是一个函数指针,函数指针类型是比较令人头疼的一件事


上面是对基本类型的一个排序,所以可以直接使用库中实现好的仿函数,很方便,如果是对自定义类型的比较,就需要手动去实现仿函数,下面来看一个例子。

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>

// 商品
class Goods{
	friend std::ostream& operator<<(std::ostream& _cout, const Goods& gds);
public:
	std::string _name;
	double _price;
};

class goodsComp {
public:
	bool operator()(const Goods& gds1, const Goods& gds2) {
		return gds1._price <= gds2._price;
	}
};

std::ostream& operator<<(std::ostream& _cout, const Goods& gds) {
	_cout << gds._name << ": " << gds._price << std::endl;
	return _cout;
}

void goodsDisplay(const std::vector<Goods>& gds) {
	for (const auto& e : gds) {
		std::cout << e;
	}
}

int main(){
	std::vector<Goods> gds = {
		{ "apple", 9.4 }, { "banana", 5.2 },
		{ "pear", 8.9 }, { "berry", 7.7 }
	};

	std::cout << "The origin goods: \n";
	goodsDisplay(gds);

	std::sort(gds.begin(), gds.end(), goodsComp());

	std::cout << "\nThe sorted goods: \n";
	goodsDisplay(gds);

	return 0;
}

运行结果如下
在这里插入图片描述

Lambda表达式

上面对于自定义类型的排序,每次排序还需要自己写一个仿函数,还是比较麻烦的,下面我们来使用Lambda表达式来完成对自定义类型的排序

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>

// 商品
class Goods{
	friend std::ostream& operator<<(std::ostream& _cout, const Goods& gds);
public:
	std::string _name;
	double _price;
};

std::ostream& operator<<(std::ostream& _cout, const Goods& gds) {
	_cout << gds._name << ": " << gds._price << std::endl;
	return _cout;
}

void goodsDisplay(const std::vector<Goods>& gds) {
	for (const auto& e : gds) {
		std::cout << e;
	}
}

int main(){
	std::vector<Goods> gds = {
		{ "apple", 9.4 }, { "banana", 5.2 },
		{ "pear", 8.9 }, { "berry", 7.7 }
	};

	std::cout << "The origin goods: \n";
	goodsDisplay(gds);

	std::sort(gds.begin(), gds.end(), [](const Goods& gds1, const Goods& gds2) {
		return gds1._price <= gds2._price;
	});

	std::cout << "\nThe sorted goods: \n";
	goodsDisplay(gds);

	return 0;
}

在这里插入图片描述
可以看到,使用Lambda表达式还是比较方便的,Lambda表达式实际上就是一个匿名函数,底层是用仿函数来实现的

Lambda表达式语法

Lambda表达式书写格式

[capture-list](parameters)mutable->return-type{
	statement
}
  • [capture-list]:捕捉列表,该列表总是出现在Lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为Lambda函数,捕捉列表能够捕捉上下文中的变量共Lambda函数使用
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,Lambda函数总是一个const函数,mutable可以取消其常属性。使用该修饰符时,参数列表不可省略(即使参数为空)
  • ->return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可以省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。函数体内除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:在Lambda表达式定义中,参数列表和返回值类型都是可选部分,而捕获列表和函数体可以为空。因此C++11中最简单的Lambda函数为:[]{};该Lambda函数不能做任何事情


捕获列表说明
捕获列表描述了上下文中哪些数据可以被Lambda使用,以及使用的方式是传值还是传引用

  • [var]:表示值传递方式捕获变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕获变量var
  • [&]:表示引用传递捕获所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  1. 父作用域指的是包含Lambda函数的语句块
  2. 语法上捕捉列表由多个捕捉项组成,并以逗号分隔。如:[=, &a, &b]:以引用传递方式捕获a和b,其他变量值传递捕获;[&, a, this]:值传递方式捕获变量a和this,引用方式捕获其他变量。
  3. 捕获列表不允许变量重复传递,否则就会导致编译错误。如:[=, a]:=已经值传递方式捕获了所有变量,捕获a重复。
  4. 块作用域以外的Lambda函数捕获列表必须为空
  5. Lambda表达式之间不能相互赋值,即使看起来类型相同。

仿函数和Lambda表达式

我们直接来看一个例子

#include <iostream>

class Rate {
public:
	Rate(double rate)
		: _rate(rate)
	{}

	double operator()(double money, int year) {
		return money * _rate * year;
	}

private:
	double _rate;
};

int main(){
	double rate = 0.4;

	// 仿函数
	Rate r1(rate);
	r1(10000, 2);

	// Lambda表达式
	// Lambda表达式实际上可以理解为匿名函数,该函数无法直接调用,如果想要直接调
	// 用,可借助auto将其赋值给一个变量。
	auto r2 = [=](double money, int year)->double{
		return money * rate * year;
	};
	r2(10000, 2);
	
	return 0;
}

我们调试来看一下反汇编代码
在这里插入图片描述
在这里插入图片描述
通过上述反汇编可以看出,实际在底层编译器对于Lambda表达式的处理方式,完全就是按照仿函数的方式处理的,即:如果定义了一个Lambda表达式,编译器会自动生成一个类,在该类中重载了函数调用操作符()


函数指针、仿函数、Lambda表达式

  • 函数指针:类型比较恶心
  • 仿函数(C++98):借助运算符重载,比函数指针好用
  • Lambda表达式(C++11):因为仿函数需要先定义再使用,而Lambda表达式可以现做现用,可以更好的和局部域结合起来,可以直接捕获,而且Lambda表达式可以不考虑命名问题,不需要去其他地方查函数的实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值