MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++函数指针和指针函数的本质区别

2022-06-194.3k 阅读

一、C++ 中的函数指针

1.1 函数指针的定义

在 C++ 中,函数指针是一个指向函数的指针变量。函数在内存中也有其地址,就像变量一样。函数指针保存了函数的入口地址,通过这个指针可以调用它所指向的函数。

函数指针的一般定义语法如下:

返回类型 (*指针变量名)(参数列表);

例如,假设有一个简单的函数 add,它接受两个整数并返回它们的和:

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

那么可以定义一个指向 add 函数的函数指针:

int (*funcPtr)(int, int);
funcPtr = add;

也可以在定义函数指针的同时进行初始化:

int (*funcPtr)(int, int) = add;

这里 funcPtr 就是一个函数指针,它指向 add 函数。(*funcPtr) 两边的括号很重要,因为如果没有括号,int *funcPtr(int, int) 这种形式就变成了指针函数的定义(稍后会介绍指针函数)。

1.2 函数指针的调用

定义并初始化函数指针后,就可以通过它来调用函数。调用方式和普通函数调用类似,只是使用函数指针变量来代替函数名。

#include <iostream>

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

int main() {
    int (*funcPtr)(int, int) = add;
    int result = (*funcPtr)(3, 5);
    std::cout << "The result of addition is: " << result << std::endl;
    return 0;
}

在上述代码中,(*funcPtr)(3, 5) 就是通过函数指针 funcPtr 来调用 add 函数。实际上,在 C++ 中,也可以省略 *,直接写成 funcPtr(3, 5),这两种调用方式在功能上是等效的。编译器会理解 funcPtr 是一个函数指针,并进行正确的调用。

1.3 函数指针作为参数

函数指针的一个重要用途是作为函数的参数。这在实现回调函数机制时非常有用。回调函数是一种通过函数指针调用的函数,它允许在程序运行时动态地决定调用哪个函数。

例如,假设有一个函数 calculate,它接受两个整数和一个函数指针作为参数,通过函数指针来决定执行哪种计算操作:

#include <iostream>

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

int subtract(int a, int b) {
    return a - b;
}

int calculate(int a, int b, int (*operation)(int, int)) {
    return operation(a, b);
}

int main() {
    int num1 = 10;
    int num2 = 5;

    int sum = calculate(num1, num2, add);
    int diff = calculate(num1, num2, subtract);

    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Difference: " << diff << std::endl;

    return 0;
}

calculate 函数中,operation 是一个函数指针参数。根据传入的不同函数指针(addsubtract),calculate 函数会执行不同的计算操作。这种灵活性使得代码更加通用,可以根据具体需求在运行时选择不同的行为。

1.4 函数指针数组

可以定义一个数组,数组的元素是函数指针。这在需要根据不同条件调用不同函数的场景下非常方便。

例如,假设有多个算术运算函数,并且希望通过一个索引来选择执行哪个运算:

#include <iostream>

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

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int divide(int a, int b) {
    if (b != 0) {
        return a / b;
    }
    std::cerr << "Division by zero error!" << std::endl;
    return 0;
}

int main() {
    int (*operations[4])(int, int) = {add, subtract, multiply, divide};

    int num1 = 10;
    int num2 = 2;
    int choice = 2;

    if (choice >= 0 && choice < 4) {
        int result = operations[choice](num1, num2);
        std::cout << "Result: " << result << std::endl;
    } else {
        std::cerr << "Invalid choice!" << std::endl;
    }

    return 0;
}

在上述代码中,operations 是一个函数指针数组,每个元素指向不同的算术运算函数。通过 choice 变量来选择调用数组中的哪个函数指针,从而执行相应的运算。

二、C++ 中的指针函数

2.1 指针函数的定义

指针函数是一个返回指针的函数。其定义语法为:

返回类型* 函数名(参数列表);

例如,下面是一个简单的指针函数,它返回一个动态分配的整数的指针:

int* createInt() {
    int* num = new int(5);
    return num;
}

在这个例子中,createInt 是一个指针函数,它返回一个指向 int 类型的指针。函数内部使用 new 运算符动态分配了一个整数,并返回其指针。

2.2 指针函数的调用和内存管理

调用指针函数后,需要注意返回指针所指向的内存的管理。如果返回的是动态分配的内存,在使用完毕后需要释放该内存,以避免内存泄漏。

#include <iostream>

int* createInt() {
    int* num = new int(5);
    return num;
}

int main() {
    int* ptr = createInt();
    std::cout << "Value: " << *ptr << std::endl;
    delete ptr;
    return 0;
}

在上述代码中,调用 createInt 函数获取一个指向整数的指针 ptr,然后输出指针所指向的值。最后,使用 delete 运算符释放动态分配的内存。如果不释放内存,这块内存将一直占用,直到程序结束,这就是内存泄漏。

2.3 指针函数的应用场景

指针函数常用于需要返回动态分配的数据结构或对象的场景。例如,在实现链表、树等数据结构时,可能会使用指针函数来创建新的节点。

下面是一个简单的链表节点创建的指针函数示例:

#include <iostream>

struct Node {
    int data;
    Node* next;
};

Node* createNode(int value) {
    Node* newNode = new Node();
    newNode->data = value;
    newNode->next = nullptr;
    return newNode;
}

int main() {
    Node* head = createNode(10);
    std::cout << "Node data: " << head->data << std::endl;
    delete head;
    return 0;
}

在这个例子中,createNode 是一个指针函数,它创建一个新的链表节点并返回其指针。main 函数调用该函数创建一个节点,并输出节点中的数据。最后释放节点的内存。

三、函数指针和指针函数的本质区别

3.1 定义本质区别

从定义上看,函数指针是一个指针变量,它指向的是函数的入口地址。其定义的重点在于声明一个指针,该指针能够指向特定类型(由返回类型和参数列表决定)的函数。例如 int (*funcPtr)(int, int),这里 funcPtr 是一个指针,它指向的函数返回 int 类型,接受两个 int 类型的参数。

而指针函数是一个函数,只是它的返回值是一个指针。其定义的重点在于声明一个函数,该函数按照定义的参数列表进行运算,并返回一个指针类型的值。例如 int* createInt()createInt 是一个函数,它返回一个指向 int 类型的指针。

3.2 语法结构区别

函数指针的定义中,指针变量名被括号包围,紧接着是参数列表,表明这是一个指向函数的指针。例如 int (*funcPtr)(int, int)(*funcPtr) 明确表示 funcPtr 是一个指针,而括号后的参数列表和返回类型定义了它所指向函数的特征。

指针函数的定义中,函数名在前,紧接着是参数列表,返回类型是指针类型。例如 int* createInt(),这里 createInt 是函数名,参数列表为空,返回类型是 int*,即指向 int 的指针。

3.3 内存模型区别

函数指针本身占用一定的内存空间(通常为机器字长,如 32 位系统中为 4 字节,64 位系统中为 8 字节),用于存储它所指向函数的入口地址。函数指针指向的函数在内存中有其独立的代码段空间,函数的代码指令存储在该区域。当通过函数指针调用函数时,程序会跳转到函数代码段执行相应的指令。

指针函数在调用时,函数体中的代码会在栈上执行(如果函数内有局部变量等),函数返回的指针可能指向动态分配在堆上的内存(如通过 new 分配),也可能指向栈上的局部变量(但这种情况需要特别小心,因为局部变量在函数结束时会被销毁,导致指针悬空)。例如在 int* createInt() 函数中,动态分配的 int 在堆上,函数返回的指针指向堆上的这块内存。

3.4 应用场景区别

函数指针主要用于实现回调函数机制,使得程序能够在运行时根据不同的条件动态选择调用不同的函数。例如在一些图形库中,可能会通过函数指针来注册事件处理函数,当相应事件发生时,调用注册的函数。另外,函数指针数组也常用于根据索引值选择执行不同的函数,增加代码的灵活性和可扩展性。

指针函数主要用于需要返回动态分配的数据结构或对象的场景。比如在数据结构的实现中,创建新节点的函数常为指针函数,返回指向新创建节点的指针,方便在其他地方构建和操作数据结构。在一些资源管理的场景中,指针函数也可用于返回指向特定资源的指针,如文件指针等。

3.5 代码示例对比

下面通过一组代码示例来更直观地对比函数指针和指针函数:

// 函数指针示例
#include <iostream>

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

int subtract(int a, int b) {
    return a - b;
}

int calculate(int a, int b, int (*operation)(int, int)) {
    return operation(a, b);
}

int main() {
    int num1 = 10;
    int num2 = 5;

    int sum = calculate(num1, num2, add);
    int diff = calculate(num1, num2, subtract);

    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Difference: " << diff << std::endl;

    return 0;
}

// 指针函数示例
#include <iostream>

struct Point {
    int x;
    int y;
};

Point* createPoint(int xVal, int yVal) {
    Point* newPoint = new Point();
    newPoint->x = xVal;
    newPoint->y = yVal;
    return newPoint;
}

int main() {
    Point* myPoint = createPoint(3, 5);
    std::cout << "Point: (" << myPoint->x << ", " << myPoint->y << ")" << std::endl;
    delete myPoint;
    return 0;
}

在函数指针的示例中,calculate 函数通过函数指针 operation 来动态选择执行 addsubtract 函数。而在指针函数的示例中,createPoint 函数返回一个指向 Point 结构体的指针,用于创建新的点对象。

通过以上对函数指针和指针函数在定义、语法结构、内存模型、应用场景以及代码示例等方面的详细分析,可以清晰地理解它们之间的本质区别。在实际编程中,根据具体需求正确使用函数指针和指针函数,能够提高代码的灵活性、可维护性和效率。