C++的函数指针


函数指针就像是电话簿中的电话号码。你有了电话号码(函数指针),就可以随时拨打电话(调用函数),而不需要直接知道这个电话(函数)在哪里。它是一种非常强大的编程工具,可以使代码更加灵活和模块化。

chatgpt讲解

什么是函数指针?

在C/C++中,函数本质上是代码块的地址,而函数指针就是保存这个地址的变量。换句话说,函数指针是一个指向函数的指针。通过这个指针,你可以调用它所指向的函数。

比喻

假设你有一家餐馆,菜单上有不同的菜品(函数)。你有个服务员(函数指针),他的任务是根据顾客的需求去准备菜品。顾客(程序的某个部分)告诉服务员需要什么菜(函数),服务员就会去厨房(代码的存储区域)取出相应的菜品(调用相应的函数)并端上来。

函数指针的声明和使用

  1. 声明函数指针:

    声明一个函数指针时,你需要指定它所指向的函数的返回类型和参数列表。例如,如果有一个返回int类型、接受两个int参数的函数,那么函数指针的声明如下:

    int (*funcPtr)(int, int);
    

    这里的funcPtr就是函数指针的名字,它可以指向任何返回int并接受两个int参数的函数。

  2. 定义函数并赋值给函数指针:

    假设我们有一个简单的函数add,它将两个整数相加并返回结果:

    int add(int a, int b) {
        return a + b;
    }
    

    现在,我们可以把这个函数的地址赋值给函数指针:

    funcPtr = &add;
    

    当然,C++中你可以省略&符号,因为函数名本身就是一个指针:

    funcPtr = add;
    
  3. 通过函数指针调用函数:

    使用函数指针调用函数与直接调用函数是类似的。你只需要在指针后加上参数列表即可:

    int result = funcPtr(5, 3); // 调用add(5, 3)
    

    这个调用实际上执行了add(5, 3),并返回了结果8

应用场景

  1. 回调函数: 当你编写一个通用的函数库时,可能需要用户传递一个自定义的函数来处理特定的操作,比如排序算法中传递比较函数。

  2. 动态调用: 在某些情况下,你可能需要在运行时决定调用哪个函数。这种情况下,函数指针就显得非常有用。

  3. 表驱动编程: 通过函数指针数组,可以创建一个操作表,根据不同的输入来执行不同的操作,而不是使用一大堆if-elseswitch-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-elseswitch-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-elseswitch-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 关键字为函数指针创建了一个新的类型别名。让我们逐步解析这个定义:

解析

  1. 基本函数指针类型:

    • void (*)(void) 是一个函数指针类型,它指向一个返回 void 且不接受任何参数的函数。
    • 具体来说,* 表示这是一个指针,() 表示它是指向函数的指针,void 是该函数的返回类型,括号中的内容(如果有)是函数的参数类型列表。在这里,括号内为空,表示该函数不接受任何参数。
  2. 函数指针类型的命名:

    • void (*HelloWorldFunction)(); 这一部分定义了一个类型,它是指向返回 void 且不接受参数的函数的指针类型,同时给这个类型起了一个名字 HelloWorldFunction
  3. 使用 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

这里的[] 叫做捕获方式,也就是如何传入传出参数
在这里插入图片描述

如果这篇文章对你有用,请帮忙点个关注、点赞、收藏,谢谢~

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值