文章目录
函数指针就像是电话簿中的电话号码。你有了电话号码(函数指针),就可以随时拨打电话(调用函数),而不需要直接知道这个电话(函数)在哪里。它是一种非常强大的编程工具,可以使代码更加灵活和模块化。
chatgpt讲解
什么是函数指针?
在C/C++中,函数本质上是代码块的地址,而函数指针就是保存这个地址的变量。换句话说,函数指针是一个指向函数的指针。通过这个指针,你可以调用它所指向的函数。
比喻
假设你有一家餐馆,菜单上有不同的菜品(函数)。你有个服务员(函数指针),他的任务是根据顾客的需求去准备菜品。顾客(程序的某个部分)告诉服务员需要什么菜(函数),服务员就会去厨房(代码的存储区域)取出相应的菜品(调用相应的函数)并端上来。
函数指针的声明和使用
-
声明函数指针:
声明一个函数指针时,你需要指定它所指向的函数的返回类型和参数列表。例如,如果有一个返回
int
类型、接受两个int
参数的函数,那么函数指针的声明如下:int (*funcPtr)(int, int);
这里的
funcPtr
就是函数指针的名字,它可以指向任何返回int
并接受两个int
参数的函数。 -
定义函数并赋值给函数指针:
假设我们有一个简单的函数
add
,它将两个整数相加并返回结果:int add(int a, int b) { return a + b; }
现在,我们可以把这个函数的地址赋值给函数指针:
funcPtr = &add;
当然,C++中你可以省略
&
符号,因为函数名本身就是一个指针:funcPtr = add;
-
通过函数指针调用函数:
使用函数指针调用函数与直接调用函数是类似的。你只需要在指针后加上参数列表即可:
int result = funcPtr(5, 3); // 调用add(5, 3)
这个调用实际上执行了
add(5, 3)
,并返回了结果8
。
应用场景
-
回调函数: 当你编写一个通用的函数库时,可能需要用户传递一个自定义的函数来处理特定的操作,比如排序算法中传递比较函数。
-
动态调用: 在某些情况下,你可能需要在运行时决定调用哪个函数。这种情况下,函数指针就显得非常有用。
-
表驱动编程: 通过函数指针数组,可以创建一个操作表,根据不同的输入来执行不同的操作,而不是使用一大堆
if-else
或switch-case
。
形象化的理解
可以想象你在做一个万能遥控器(函数指针)。你有不同的家电(函数),比如电视、空调和灯。每个按钮(函数指针)都可以配置成控制不同的家电。你可以随时改变按钮对应的家电,也可以用同一个按钮控制不同的家电功能(即动态改变函数指针的指向)。这使得你的遥控器非常灵活和强大。
总之,函数指针在C/C++编程中是一个非常有用的工具,它使得代码更加灵活,可以处理很多复杂的情况。
回调函数
场景:你正在编写一个通用的排序函数,可以对不同类型的数据进行排序。由于不同类型的数据有不同的排序规则,你希望用户能够自定义比较逻辑。
示例:
假设你有一个简单的整数数组和一个字符串数组,你希望通过一个通用的排序函数来排序它们。这个通用排序函数需要一个回调函数来比较数组中的元素。
首先,定义比较函数:
int compareInt(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
int compareString(const void* a, const void* b) {
return strcmp(*(const char**)a, *(const char**)b);
}
然后,编写一个通用的排序函数,接受数组、元素个数、每个元素的大小以及比较函数指针:
void sort(void* arr, size_t n, size_t size, int (*comp)(const void*, const void*)) {
char* base = (char*)arr;
for (size_t i = 0; i < n - 1; i++) {
for (size_t j = 0; j < n - i - 1; j++) {
if (comp(base + j * size, base + (j + 1) * size) > 0) {
char temp[size];
memcpy(temp, base + j * size, size);
memcpy(base + j * size, base + (j + 1) * size, size);
memcpy(base + (j + 1) * size, temp, size);
}
}
}
}
最后,调用排序函数并传递相应的比较函数:
int arr[] = {5, 3, 8, 6, 2};
size_t n = sizeof(arr)/sizeof(arr[0]);
sort(arr, n, sizeof(int), compareInt);
const char* strs[] = {"apple", "orange", "banana", "pear"};
n = sizeof(strs)/sizeof(strs[0]);
sort(strs, n, sizeof(char*), compareString);
解释:在这个例子中,sort
函数是一个通用的排序函数,它接受一个比较函数作为回调。这使得同一个排序函数可以用于排序不同类型的数据。
动态调用
场景:假设你在编写一个命令行工具,这个工具可以根据用户输入执行不同的功能。在运行时,你需要根据用户的选择来调用不同的函数。
示例:
首先,定义几个命令函数:
void sayHello() {
std::cout << "Hello, World!" << std::endl;
}
void sayGoodbye() {
std::cout << "Goodbye, World!" << std::endl;
}
void askName() {
std::cout << "What is your name?" << std::endl;
}
然后,在主程序中根据用户输入决定调用哪个函数:
int main() {
std::cout << "Choose a command (1: Hello, 2: Goodbye, 3: Ask Name): ";
int choice;
std::cin >> choice;
void (*command)();
if (choice == 1) {
command = sayHello;
} else if (choice == 2) {
command = sayGoodbye;
} else if (choice == 3) {
command = askName;
} else {
std::cout << "Invalid choice!" << std::endl;
return 1;
}
// 动态调用选择的函数
command();
return 0;
}
解释:这里,根据用户的输入,在运行时动态地决定要调用哪个函数。这种灵活性使得程序可以根据不同的输入执行不同的操作。
表驱动编程
场景:你在编写一个简单的虚拟机(或命令解释器),它可以执行一组命令。为了避免使用大量的if-else
或switch-case
语句,你可以使用函数指针数组来实现命令的执行。
示例:
首先,定义一组命令函数:
void command1() {
std::cout << "Executing Command 1" << std::endl;
}
void command2() {
std::cout << "Executing Command 2" << std::endl;
}
void command3() {
std::cout << "Executing Command 3" << std::endl;
}
然后,创建一个函数指针数组来映射命令编号与命令函数:
void (*commandTable[3])() = {command1, command2, command3};
在主程序中,根据用户输入或命令编号来执行相应的命令:
int main() {
int commandIndex;
std::cout << "Enter command number (0-2): ";
std::cin >> commandIndex;
if (commandIndex >= 0 && commandIndex < 3) {
commandTable[commandIndex](); // 根据索引调用对应的函数
} else {
std::cout << "Invalid command!" << std::endl;
}
return 0;
}
解释:在这个例子中,通过函数指针数组commandTable
,可以直接根据命令编号来调用对应的命令函数。这种方法避免了冗长的if-else
或switch-case
,使代码更简洁易读。
视频解说学习
函数指针: 就是将一个函数赋值给变量的方法,依照这么说,还可以将函数作为参数传递给其他函数
不能用函数做任何逻辑运算
函数指针中的auto
这里报错是因为HelloWorld函数没有返回任何值,auto不知道是什么类型的变量。
你的代码似乎有一些小问题,包括缺少必要的标点符号和大小写错误。我先修正一下代码,然后解释为什么最后一行没有报错,以及auto
推导出的类型是什么。
修正后的代码
#include <iostream>
void HelloWorld() {
std::cout << "Hello World!" << std::endl;
}
int main() {
auto function = HelloWorld;
std::cin.get();
return 0;
}
auto为什么没有报错?
在这段代码中,auto function = HelloWorld;
没有报错,因为auto
关键字会自动推导出function
的类型。在这个上下文中,HelloWorld
是一个函数名,而函数名在C++中可以隐式转换为指向该函数的指针。因此,auto
推导出的类型是void (*)()
,即一个指向返回类型为void
、无参数的函数的指针类型。
auto
的类型
auto
关键字是C++11引入的功能,用于让编译器根据初始化表达式的类型自动推导变量的类型。在auto function = HelloWorld;
这一行中,HelloWorld
函数名被推导为一个函数指针类型,因此auto
的类型实际上是void (*)()
。
所以,function
变量的类型是void (*)()
,你可以像调用普通函数一样调用它:
function(); // 等价于调用 HelloWorld()
总结来说,auto
在这个例子中被推导为函数指针类型,允许你使用变量function
来调用HelloWorld
函数。
实际类型是 void (*)()
,但是我们需要给一个名字,所以给的是function名字
还可以这么写,更改变量的名字
typedef 创建一个类型别名
解析
typedef void(*HelloWorldFunction)();
是一个类型定义语句,它使用 typedef
关键字为函数指针创建了一个新的类型别名。让我们逐步解析这个定义:
解析
-
基本函数指针类型:
void (*)(void)
是一个函数指针类型,它指向一个返回void
且不接受任何参数的函数。- 具体来说,
*
表示这是一个指针,()
表示它是指向函数的指针,void
是该函数的返回类型,括号中的内容(如果有)是函数的参数类型列表。在这里,括号内为空,表示该函数不接受任何参数。
-
函数指针类型的命名:
void (*HelloWorldFunction)();
这一部分定义了一个类型,它是指向返回void
且不接受参数的函数的指针类型,同时给这个类型起了一个名字HelloWorldFunction
。
-
使用
typedef
:typedef
关键字将该函数指针类型起了一个别名,称为HelloWorldFunction
。- 这样你以后在代码中就可以使用
HelloWorldFunction
来代替void (*)(void)
,使代码更简洁,更易读。
例子
假设我们有一个函数:
void HelloWorld() {
std::cout << "Hello, World!" << std::endl;
}
现在我们可以使用 HelloWorldFunction
类型来声明一个指向这个函数的指针:
HelloWorldFunction funcPtr = HelloWorld;
然后可以像调用函数一样使用这个指针:
funcPtr(); // 输出: Hello, World!
总结
typedef void(*HelloWorldFunction)();
定义了一个名为 HelloWorldFunction
的类型,它代表一个指向返回 void
且不接受任何参数的函数的指针。这个类型可以用来声明这样的函数指针,使代码更简洁和可读。
带有参数的函数
将函数参数化
经典案例
将函数以变量的方式传递给另一个函数。
ForEach()其中第一个是传的参数,第二个是传的处理这个参数的函数指针
使用lambda
这里的[] 叫做捕获方式,也就是如何传入传出参数