函数指针
我们通过一系列简单的例子来走进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指针。
注意:
- 父作用域指的是包含Lambda函数的语句块。
- 语法上捕捉列表由多个捕捉项组成,并以逗号分隔。如:[=, &a, &b]:以引用传递方式捕获a和b,其他变量值传递捕获;[&, a, this]:值传递方式捕获变量a和this,引用方式捕获其他变量。
- 捕获列表不允许变量重复传递,否则就会导致编译错误。如:[=, a]:=已经值传递方式捕获了所有变量,捕获a重复。
- 在块作用域以外的Lambda函数捕获列表必须为空。
- 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表达式可以不考虑命名问题,不需要去其他地方查函数的实现。