title: 函数指针与匿名函数
date: 2024-02-02 22:00:00
categories:
- C++
- 函数
tags: 函数指针与匿名函数 #
函数指针
什么是函数指针?
函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
函数指针的声明形式如下:
return_type (*pointer_name)(parameter_types);
其中,return_type
是函数返回类型,pointer_name
是指针变量的名称,parameter_types
是函数参数的类型。下面是一个简单的例子:
#include <iostream>
void myFunction(int x) {
std::cout << "Function called with value: " << x << std::endl;
}
int main() {
// 声明一个指向函数的指针
void (*funcPtr)(int);
// 将函数指针指向特定的函数
funcPtr = &myFunction;
// 通过函数指针调用函数
(*funcPtr)(42);
return 0;
}
这个例子中,funcPtr
是一个指向接受一个整数参数并返回 void
的函数的指针。然后,它被赋值为指向 myFunction
函数的指针,并通过该指针调用函数
或者我们现在外部定义一个函数HelloWorld,然后用auto
类型的变量function
来试着推导函数名的类型,发现自动推导得到function
类型为void(*)(int a)
就和之前说的一样,这时候我们可以使用function来调用函数(这里例子起名为了func
)function(8)/func(8);
一般来说这个类型名是不够直观的,可以直接使用auto或typedef来定义其他名字就像line 14
.
void HelloWorld(int a)
{
std::cout << "Hello World! the value:" <<a <<std::endl;
}
int main()
{
auto function=HelloWorld;
void(*func)(int a);
func = HelloWorld;
typedef void(*HelloWorldfunc)(int a) ;
HelloWorldfunc function=HelloWorld;
function(8);
func(8);
}
使用
函数指针其实就像一个变量,但它存储的是函数的地址。通过函数指针,我们可以在程序运行时动态地选择调用哪个函数。
想象一下你有两个不同的函数,但你希望在程序运行时决定到底调用哪个函数。这时候就可以用函数指针。让我用一个更简单的例子来解释:
cppCopy code
#include <iostream>
// 定义两个简单的函数
void sayHello() {
std::cout << "Hello, ";
}
void sayWorld() {
std::cout << "World!" << std::endl;
}
int main() {
// 声明一个函数指针,指向不接受参数也不返回任何值的函数
void (*ptr)();
// 根据需要,让函数指针指向不同的函数
ptr = sayHello;
ptr(); // 调用 sayHello 函数
ptr = sayWorld;
ptr(); // 调用 sayWorld 函数
return 0;
}
这个例子中,ptr
是一个函数指针,它可以指向 sayHello
或 sayWorld
中的任一个函数。通过改变 ptr
的指向,你可以在程序运行时选择调用不同的函数。
函数指针通常用于实现一些高级的功能,比如回调函数、事件处理等。在大型项目中,函数指针也有助于实现灵活的代码结构。
还可以这样做,在之前的例子中再定义一个foreach函数,在函数里的for循环中通过一个函数来对传入的values操作,这个函数也可以由外部传入,是的,把函数当作一个参数传入另一个函数,(函数在最终的代码中也是存放在某个位置,所以可以传入它的指针),那么就在这个foreach的参数列表中定义一个void(*func)(int)来接受函数的指针
void foreach(const std::vector<int>&values,void(*func)(int))
{
for (int value : values)
func(value);
}
这样我们就可以在main中foreach(values,PrintValue);
意味着将values传入foreach中并对每个values进行进行PrintValue操作
对于一个简单的操作再定义一个函数是比较麻烦的,所以有匿名函数( lambda 函数)便于随用随定义,不用到其他地方去定义个函数 ↓ ↓ ↓
lambda
什么是lambda
Lambda表达式是C++11引入的一个特性,它允许你在代码中定义匿名函数,通常用于需要传递函数作为参数的地方。Lambda表达式的语法相对简洁,特别适用于一些短小的函数功能。
Lambda表达式的基本形式如下:
[capture](parameter_list) -> return_type {
// 函数体
}
其中:
capture
:用于捕获外部变量,可以省略,或者使用=
表示按值捕获,或者&
表示按引用捕获。parameter_list
:函数参数列表,与普通函数类似,可以为空。return_type
:返回类型,可以省略,编译器可以自动推断。函数体
:包含了实际的函数逻辑。
下面是一个简单的例子,演示了Lambda表达式的用法:
#include <iostream>
int main() {
// Lambda表达式示例:求两个数的和
auto add = [](int a, int b) -> int {
return a + b;
};
// 使用Lambda表达式
int result = add(3, 4);
std::cout << "Sum: " << result << std::endl;
return 0;
}
在这个例子中,add
是一个Lambda表达式,它接受两个整数参数并返回它们的和。通过auto
关键字,编译器会自动推断add
的类型。
Lambda表达式通常用于STL算法、函数对象、回调函数等场景,以提供更为简洁的语法。
除了基本形式,Lambda表达式还支持一些高级特性,比如捕获列表的详细控制、变参函数等。
接着函数指针那里的例子,我们不必要去定义PrintValue函数而是直接
foreach(values,[](int value) {std::cout << value << " "; });
或者是写成
auto lambda = [=](int value) {std::cout << value << " "; };
foreach(values, lambda);
这里传入的参数是lambda,所以不能使用原来的原始函数指针,而是使用中的function来定义参数,因为如果lambda不捕获外部变量的话是可以隐式转换为函数指针形式的,但是如果捕获外部变量的话,对应的函数指针并没有处理该变量的能力,所以不能接收。
函数指针不能接收捕获外部变量的lambda表达式,原因在于lambda表达式和函数指针的本质区别。函数指针是指向函数的指针,它只能指向没有捕获任何外部变量的普通函数或没有捕获外部变量的lambda表达式。这是因为函数指针仅仅存储了函数的地址,而不包含任何上下文信息。
当lambda表达式捕获了外部变量时,它实际上是一个包含了捕获列表的闭包对象。这个闭包对象不仅包含了函数体,还包含了捕获的变量的副本或引用。因此,它的类型不再是一个简单的函数指针,而是一个类的实例,这个类有一个重载的operator()
方法。
由于捕获的外部变量是闭包对象的一部分,这使得闭包对象的类型和函数指针的类型不兼容,因此不能将捕获外部变量的lambda表达式赋值给函数指针。如果需要以类似函数指针的方式使用lambda表达式,可以考虑使用std::function
,它是一个模板类,能够存储和调用任何可调用对象,包括函数指针、成员函数指针、以及捕获外部变量的lambda表达式。
void foreach(const std::vector<int>&values,std::function<void(int)> func){//...}
std::function
提供了一种将不同类型的可调用对象包装成一个统一接口的方式,使得它们可以以相同的方式被调用
这个lambda实际上是我们构造的一个稍后会用到的函数,如果我们想要传入一个外部变量,也就是例子中定义的这个lambda中,我们想打印的是一个int a =7;的这个外部变量a,就会报错 封闭函数(enclosing function)局部变量不能在 lambda 体中引用,除非其位于捕获列表中 。所以就要再[]这里捕获列表中加入a,=
或者 &
,也可以直接将a写入。
还有一个使用匿名函数的地方是使用find_if时,