C语言中的函数指针(Function Pointers):中英双语

server/2025/1/30 8:16:12/

C语言中的函数指针

在C语言中,函数指针是一个非常强大的特性。它允许我们存储和传递指向函数的指针,从而实现更灵活的代码设计,例如动态调用不同的函数、实现回调机制等。


前言

今天在阅读 https://www.buildyourownlisp.com/这本书的时候,阅读到第十一章的时候,遇到了函数指针这个知识点,感觉眼熟。其实,在CSAPP那本书中遇到过,但是还是要写一篇博客记录一下。

在这里插入图片描述

来源:https://www.buildyourownlisp.com/chapter11_variables

Function pointers are a great feature of C that lets you store and pass around pointers to functions. It doesn’t make sense to edit the data pointed to by these pointers. Instead we use them to call the function they point to, as if it were a normal function.
Like normal pointers, function pointers have some type associated with them. This type specifies the type of the function pointed to, not the type of the data pointed to. This lets the compiler work out if it has been called correctly.
In the previous chapter our builtin functions took a lval* as input and returned a lval* as output. In this chapter our builtin functions will take an extra pointer to the environment lenv* as input. We can declare a new function pointer type called lbuiltin, for this type of function, like this.
typedef lval*(lbuiltin)(lenv, lval*);
Why is that syntax so odd?
In some places the syntax of C can look particularly weird. It can help if we understand exactly why the syntax is like this. Let us de-construct the syntax in the example above part by part.
First the typedef. This can be put before any standard variable declaration. It results in the name of the variable, being declared a new type, matching what would be the inferred type of that variable. This is why in the above declaration what looks like the function name becomes the new type name.
Next all those . Pointer types in C are actually meant to be written with the star * on the left hand side of the variable name, not the right hand side of the type int x;. This is because C type syntax works by a kind of inference. Instead of reading “Create a new int pointer x”. It is meant to read “Create a new variable x where to dereference x results in an int.” Therefore x is inferred to be a pointer to an int.
This idea is extended to function pointers. We can read the above declaration as follows. "To get an lval
we dereference lbuiltin and call it with a lenv
and a lval*." Therefore lbuiltin must be a function pointer that takes an lenv* and a lval* and returns a lval*.

一、什么是函数指针?

函数指针是一种指针类型,专门用于指向函数的地址。通过函数指针,我们可以像调用普通函数一样调用它所指向的函数。

基本概念
  1. 存储函数地址:函数指针存储的是函数的入口地址。
  2. 调用函数:可以通过解引用函数指针,调用指针指向的函数。
  3. 类型匹配:函数指针的类型必须与所指向的函数类型一致,包括返回值类型和参数列表。

二、函数指针的语法

1. 定义函数指针

函数指针的定义看起来有点复杂,但我们可以逐步理解:

返回值类型 (*指针名称)(参数列表);

例如:

int (*func_ptr)(int, int);
  • int:表示函数返回值的类型是 int
  • (*func_ptr):表示这是一个指针,指向一个函数。
  • (int, int):表示该函数的参数列表包含两个 int 类型的参数。
2. 给函数指针赋值

我们可以将函数的地址赋给函数指针:

int add(int a, int b) {return a + b;
}int (*func_ptr)(int, int); // 定义函数指针
func_ptr = add;            // 将函数地址赋值给指针
3. 调用函数指针

使用函数指针调用函数的语法与普通函数调用类似:

int result = func_ptr(2, 3); // 等价于 add(2, 3)

三、函数指针的应用场景

1. 回调函数

函数指针常用于实现回调机制,例如事件处理和排序。

示例:实现一个可定制的排序函数

#include <stdio.h>int compare_asc(int a, int b) {return a - b;
}int compare_desc(int a, int b) {return b - a;
}void sort(int* array, int size, int (*cmp)(int, int)) {for (int i = 0; i < size - 1; i++) {for (int j = 0; j < size - i - 1; j++) {if (cmp(array[j], array[j + 1]) > 0) {int temp = array[j];array[j] = array[j + 1];array[j + 1] = temp;}}}
}int main() {int array[] = {5, 2, 9, 1, 5, 6};int size = sizeof(array) / sizeof(array[0]);sort(array, size, compare_asc); // 使用升序排序for (int i = 0; i < size; i++) {printf("%d ", array[i]);}printf("\n");sort(array, size, compare_desc); // 使用降序排序for (int i = 0; i < size; i++) {printf("%d ", array[i]);}return 0;
}
2. 动态函数调用

在一些场景下,我们可能需要根据运行时条件动态选择函数,函数指针可以实现这一功能。

示例:动态选择函数

#include <stdio.h>int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}int main() {int (*operation)(int, int);int choice = 1; // 1 表示加法,2 表示减法if (choice == 1) {operation = add;} else {operation = subtract;}printf("Result: %d\n", operation(10, 5)); // 根据选择调用函数return 0;
}
3. 实现多态

在设计类似面向对象的系统时,函数指针可以用来实现多态行为。


四、函数指针的复杂用法

1. 指向返回指针的函数的函数指针

如果函数的返回值是指针,函数指针的定义会更加复杂。例如:

int* (*func_ptr)(int);
  • 表示 func_ptr 是一个指针,指向返回 int* 的函数。
2. 用 typedef 简化函数指针

为了让代码更简洁,我们可以使用 typedef 定义函数指针类型:

typedef int (*operation)(int, int);int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}int main() {operation op = add; // 使用 typedef 定义的类型printf("Add: %d\n", op(5, 3));op = subtract;printf("Subtract: %d\n", op(5, 3));return 0;
}

五、函数指针的注意事项

  1. 类型匹配

    • 函数指针的类型必须与目标函数的签名完全一致(包括返回值和参数列表)。
  2. 容易出错

    • 函数指针的语法复杂,容易因为括号位置错误或类型不匹配而导致编译错误。
  3. 调试难度

    • 由于函数指针的动态性,调试时需要特别注意指向的函数地址是否正确。

六、总结

  1. 函数指针的核心作用

    • 存储函数地址,动态调用函数,实现灵活的代码设计。
    • 在回调函数、动态选择逻辑和实现多态等场景中非常有用。
  2. 关键点

    • 函数指针的定义:明确返回值类型、参数列表。
    • 函数指针的使用:赋值和调用。
    • 使用 typedef 可以简化复杂的函数指针声明。
  3. 优雅的设计工具

    • 函数指针不仅是 C 语言中基础工具,更是构建高效灵活程序的关键手段。掌握函数指针能帮助开发者写出更具通用性和扩展性的代码。

Function Pointers in C

Function pointers are one of the most powerful features of C, allowing you to store and pass pointers to functions. This feature enables flexible and dynamic program designs, such as callbacks, dynamic function dispatch, and polymorphism-like behaviors.


1. What is a Function Pointer?

A function pointer is a pointer type specifically designed to point to a function’s address. With function pointers, you can call the pointed function as if it were a normal function.

Key Characteristics:
  1. Stores Function Address: A function pointer holds the memory address of a function.
  2. Calls Functions Dynamically: You can invoke the function pointed to by the function pointer.
  3. Type Matching: The function pointer’s type must match the type of the function it points to (return type and parameter types).

2. Syntax of Function Pointers

1. Declaring a Function Pointer

The syntax for declaring a function pointer might seem tricky at first:

return_type (*pointer_name)(parameter_list);

For example:

int (*func_ptr)(int, int);
  • int: The return type of the function.
  • (*func_ptr): Declares a pointer to a function.
  • (int, int): Specifies the function’s parameter types.
2. Assigning a Function Pointer

You can assign the address of a function to the pointer:

int add(int a, int b) {return a + b;
}int (*func_ptr)(int, int) = add; // Assign function address to the pointer
3. Calling a Function Pointer

To call the function, use the function pointer like a regular function:

int result = func_ptr(2, 3); // Calls add(2, 3)

3. Use Cases for Function Pointers

1. Callback Functions

Function pointers are widely used to implement callbacks, where one function is passed as an argument to another.

Example: Customizable Sorting

#include <stdio.h>int compare_asc(int a, int b) {return a - b;
}int compare_desc(int a, int b) {return b - a;
}void sort(int* array, int size, int (*cmp)(int, int)) {for (int i = 0; i < size - 1; i++) {for (int j = 0; j < size - i - 1; j++) {if (cmp(array[j], array[j + 1]) > 0) {int temp = array[j];array[j] = array[j + 1];array[j + 1] = temp;}}}
}int main() {int array[] = {5, 2, 9, 1, 5, 6};int size = sizeof(array) / sizeof(array[0]);sort(array, size, compare_asc); // Sort in ascending orderfor (int i = 0; i < size; i++) printf("%d ", array[i]);printf("\n");sort(array, size, compare_desc); // Sort in descending orderfor (int i = 0; i < size; i++) printf("%d ", array[i]);return 0;
}
2. Dynamic Function Calls

Function pointers allow you to dynamically choose and call functions at runtime.

Example: Choosing Operations Dynamically

#include <stdio.h>int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}int main() {int (*operation)(int, int);int choice = 1; // 1 for add, 2 for subtractif (choice == 1) {operation = add;} else {operation = subtract;}printf("Result: %d\n", operation(10, 5)); // Calls add(10, 5)return 0;
}
3. Simulating Polymorphism

By storing pointers to different functions in an array, you can simulate polymorphism.

Example: Array of Function Pointers

#include <stdio.h>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 main() {int (*operations[])(int, int) = {add, subtract, multiply};printf("Add: %d\n", operations[0](4, 2));       // Calls add(4, 2)printf("Subtract: %d\n", operations[1](4, 2)); // Calls subtract(4, 2)printf("Multiply: %d\n", operations[2](4, 2)); // Calls multiply(4, 2)return 0;
}

4. Advanced Use Cases

1. Function Pointer to a Function Returning a Pointer

If a function returns a pointer, the syntax becomes more complex:

int* (*func_ptr)(int);
  • This means func_ptr is a pointer to a function that takes an int and returns an int*.
2. Simplifying with typedef

Complex function pointer declarations can be simplified using typedef:

typedef int (*operation)(int, int);int add(int a, int b) {return a + b;
}int main() {operation op = add; // Simplified syntaxprintf("Add: %d\n", op(5, 3));return 0;
}

5. Key Considerations and Pitfalls

  1. Type Matching:

    • A function pointer must have the same signature (return type and parameters) as the function it points to.
  2. Debugging:

    • Since function pointers rely on runtime behavior, debugging dynamic calls can be challenging.
  3. Complex Syntax:

    • The syntax for declaring and using function pointers can be confusing, especially with nested or complex types.

6. Summary

  1. Definition: A function pointer stores the address of a function, allowing dynamic function calls.
  2. Syntax: While initially complex, syntax can be understood by breaking it down into parts.
  3. Applications:
    • Callbacks
    • Dynamic function dispatch
    • Polymorphism-like behaviors
  4. Simplification: Use typedef to make complex function pointers easier to manage.

Function pointers are an essential feature of C, enabling powerful programming techniques and giving developers control over runtime behavior. Mastering them can significantly improve your understanding of C’s flexibility and power.

Simulating Polymorphism with Function Pointers

Polymorphism is a concept from object-oriented programming (OOP) where the same function name or interface can behave differently depending on the context (such as the type of object it operates on). C, being a procedural language, does not have built-in support for polymorphism like C++ or Java. However, we can simulate polymorphism using function pointers by dynamically selecting and invoking functions at runtime.

The provided example uses an array of function pointers to achieve polymorphic behavior. Let’s break it down step by step:


Code Explanation

1. Function Definitions
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; }

These are three basic arithmetic functions:

  • add adds two integers.
  • subtract subtracts the second integer from the first.
  • multiply multiplies two integers.

Each function has the same signature:

int function_name(int, int);

This consistency is important because all function pointers in the array must have the same type.


2. Array of Function Pointers
int (*operations[])(int, int) = {add, subtract, multiply};

Here’s what this line does:

  1. Declare an array of function pointers:

    • int (*operations[])(int, int):
      • operations is an array.
      • Each element in the array is a pointer to a function that takes two int arguments and returns an int.
  2. Assign functions to the array:

    • {add, subtract, multiply} assigns the addresses of the three functions (add, subtract, and multiply) to the elements of the array.

Visualization:

operations[0] --> Address of add function
operations[1] --> Address of subtract function
operations[2] --> Address of multiply function

3. Using the Function Pointer Array
printf("Add: %d\n", operations[0](4, 2));       // Calls add(4, 2)
printf("Subtract: %d\n", operations[1](4, 2)); // Calls subtract(4, 2)
printf("Multiply: %d\n", operations[2](4, 2)); // Calls multiply(4, 2)

Each element in the operations array is a function pointer, so operations[i] can be used to call the function it points to.

Step-by-Step Execution:

  1. operations[0](4, 2):

    • Calls the function at index 0 in the array, which is add.
    • add(4, 2) returns 6.
  2. operations[1](4, 2):

    • Calls the function at index 1 in the array, which is subtract.
    • subtract(4, 2) returns 2.
  3. operations[2](4, 2):

    • Calls the function at index 2 in the array, which is multiply.
    • multiply(4, 2) returns 8.

Output:

Add: 6
Subtract: 2
Multiply: 8

How Does This Simulate Polymorphism?

In OOP, polymorphism allows objects of different types to share a common interface but behave differently. In this example:

  1. Shared Interface:

    • The array of function pointers acts as a common interface for the three operations (add, subtract, multiply).
    • All functions share the same signature, so they can be accessed uniformly.
  2. Dynamic Behavior:

    • At runtime, you can decide which function to call by selecting the appropriate index in the operations array.
    • The actual behavior depends on the function being pointed to (e.g., addition, subtraction, or multiplication).

Example: Dynamic Selection

To make it more dynamic, you can let the user select the operation at runtime:

#include <stdio.h>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 main() {int (*operations[])(int, int) = {add, subtract, multiply};int choice, a, b;printf("Select operation (0 for add, 1 for subtract, 2 for multiply): ");scanf("%d", &choice);printf("Enter two numbers: ");scanf("%d %d", &a, &b);if (choice >= 0 && choice < 3) {printf("Result: %d\n", operations[choice](a, b)); // Dynamically invoke function} else {printf("Invalid choice!\n");}return 0;
}

Sample Run:

Select operation (0 for add, 1 for subtract, 2 for multiply): 1
Enter two numbers: 10 3
Result: 7

Benefits of This Approach

  1. Runtime Flexibility:

    • Functions can be dynamically chosen at runtime.
  2. Unified Interface:

    • All functions in the array share the same signature, making it easier to add or swap functions.
  3. Simplified Logic:

    • Instead of writing multiple if or switch statements to handle different functions, you can use the array index to directly select and call the desired function.

Summary

By storing functions in an array of function pointers, you can simulate polymorphism in C:

  • Common Interface: All functions share the same signature, making them interchangeable.
  • Dynamic Behavior: You can select and invoke functions at runtime based on conditions or input.
  • Simplified Logic: Function selection and invocation become straightforward and maintainable.

This approach is especially useful in scenarios where different functions share similar behavior but need to be dynamically chosen, such as event handling, callbacks, or customizable algorithms.

后记

2025年1月27日于山东日照,在GPT4o大模型辅助下完成。


http://www.ppmy.cn/server/163020.html

相关文章

从0到1:C++ 开启游戏开发奇幻之旅(一)

目录 为什么选择 C 进行游戏开发 性能卓越 内存管理精细 跨平台兼容性强 搭建 C 游戏开发环境 集成开发环境&#xff08;IDE&#xff09; Visual Studio CLion 图形库 SDL&#xff08;Simple DirectMedia Layer&#xff09; SFML&#xff08;Simple and Fast Multim…

2025年01月22日Github流行趋势

项目名称&#xff1a;llm-course 项目地址url&#xff1a;https://github.com/mlabonne/llm-course项目语言&#xff1a;Jupyter Notebook历史star数&#xff1a;43588今日star数&#xff1a;304项目维护者&#xff1a;mlabonne, pitmonticone项目简介&#xff1a;一个课程&…

mysql DDL可重入讨论

mysql的bug&#xff1a;当执行 MySQL online DDL 时&#xff0c;期间如有其他并发的 DML 对相同的表进行增量修改&#xff0c;比如 update、insert、insert into … on duplicate key、replace into 等&#xff0c;且增量修改的数据违背唯一约束&#xff0c;那么 DDL 最后都会执…

【STL】list 双向循环链表的使用介绍

STL中list容器的详细使用说明 一.list的文档介绍二. list的构造函数三.list中的访问与遍历操作四.list中的修改操作4.1 list中的各种修改操作4.2 list的迭代器失效问题 五.list中的其他一些操作 一.list的文档介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器…

PC端实现PDF预览(支持后端返回文件流 || 返回文件URL)

一、使用插件 插件名称&#xff1a;vue-office/pdf 版本&#xff1a;2.0.2 安装插件&#xff1a;npm i vue-office/pdf^2.0.2 1、“vue-office/pdf”: “^2.0.2”, 2、 npm i vue-office/pdf^2.0.2 二、代码实现 // 引入组件 &#xff08;在需要使用的页面中直接引入&#x…

Web3 与数据隐私:如何让用户掌控个人信息

随着数字化时代的快速发展&#xff0c;互联网已经渗透到我们生活的方方面面&#xff0c;个人数据的收集与使用也变得越来越普遍。与此同时&#xff0c;数据隐私问题逐渐成为全球关注的焦点。传统的互联网平台通常将用户的数据存储在中心化的服务器上&#xff0c;这意味着平台拥…

Android 自定义View时四个构造函数使用详解

该文章我们以自定义View继承TextView为例来讲解 创建自定义View命名MyTextView&#xff0c;并使其继承TextView 1、自定义View时第一个构造函数 // 第一个构造函数主要是在Java代码中声明一个MyTextView时所用 // 类似这种(MyTextView myTextViewnew MyTextView(this);) // 不…

MyBatis-Plus之常用注解

一、TableName 经过一系列的测试&#xff0c;在使用MyBatis-Plus实现基本的CRUD时&#xff0c;我们并没有指定要操作的表&#xff0c;只是在 Mapper接口继承BaseMapper时&#xff0c;设置了泛型User&#xff0c;而操作的表为user表 由此得出结论&#xff0c; MyBatis-Plus在确…