【C语言】结构体中的函数指针

本文详细介绍了函数指针的概念,包括其定义、正确形式的区别,以及在C/C++中的调用方式。还涵盖了函数指针作为结构体成员的应用实例,展示了如何在结构体中存储和调用函数。

目录

一、函数指针是什么?

二、结构体中的函数指针


交流群:648551442

一、函数指针是什么?

函数指针是指向函数指针变量。 通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。 函数指针可以像一般函数一样,用于调用函数、传递参数。

正确形式:int (*f) ( );

这个声明有两对括号,每对的含义各不相同。第2对括号是函数调用操作符,但第1对括号只起到聚组的作用。它迫使间接访问在函数调用之前进行,使f成为一个函数指针,它所指向的函数返回一个整型值。

容易混淆的地方:int (*f)(); 与 int * f ();

两者看似只相差了一个(),但是在编译器看来却是两种截然不同的情况。

首先我们知道

1、函数名代表这个函数的入口,也就是首地址。

2、符号' * '在不同的情况下有不同的含义。声明时:加上 * 表示这个变量是一个指针。调用时:加上 * 表示访问指针;

#include <stdio.h>
int a = 1;

int * p = &a; 
/* 这里我们分成4个部分来看。左边是3个部分:'int','*','p',右边是'&a。
 * 习惯上我们以 从右往左 的顺序来解读 左边 部分。
 * p :变量p
 * *p :表示p是一个指针,它的值是会被当做地址处理,而不是一个数值
 * int * p : 表示变量p所保存的地址指向的是int类型的数,帮助编译器确定类型
 * &a : 取a的地址
 */   
int main()
{
	printf("*p = %d",*p);
}

输出结果:

结合上面的例子我们来分析一下 int (*f) () 与 int *f ();

二者与int * p一个明显的不同之处就在于f的后面跟了()。这代表它可以输入参数,也就是说 f 表示某个函数的入口。

int * f () 与 int * p类似,代表函数f()的返回值是 指针。这个指针指向int类型。

int (*f) () : 表示 f 是一个指向函数的指针,该函数没有参数并返回 int 值。

int *f () :表示 f 是一个函数,它不接受任何参数,并返回指向 int 值的指针。

  • 函数指针的调用方式 
void test(int);

int main(void)
{
    void (*fp)(int);
    fp=test;    
    (*fp)(9);   //调用方式一:函数指针  

    fp(9);      //调用方式二:函数指示符
    return 0;
}

void test(int a)
{

  printf( "%d\n", a );

}

输出结果:
9
9

这两种形式是等价的。

这是因为在 C/C++ 中总是使用函数指针的形式来调用函数。即使在函数调用中使用的是函数指示符(代表函数类型), 也会被转换为函数指针使用,这就是默认的 function-to-pointer 转换。

尽管二者等价,但个人还是比较推荐用函数指针的形式来调用函数。

二、结构体中的函数指针

函数指针可以作为结构体的成员使用。

以下是使用示例

void Dance_func()

{
  printf("Xiao Ming is dancing!\n");
}

void Sing_func(int i) {
  printf("Xiao Ming Sang %d Songs.\n", i);
}

typedef struct {

  void (*dance)();   //函数指针的正确形式
  void (*sing)(int i);
  //void * sing (int i);  错误形式,无法通过编译
} student;

int main(void)
{

  student Xiao_Ming;
  Xiao_Ming.dance = Dance_func; //对结构体变量的赋值。函数名Dance_func是指针
  Xiao_Ming.dance();            //调用Dance_func()
  Xiao_Ming.sing = Sing_func;   //同上
  Xiao_Ming.sing(3);

  return 0;
}

输出结果:

Xiao Ming is dancing!
Xiao Ming Sang 3 Songs.

注意:

  Xiao_Ming.dance = Dance_func; //对结构体变量的赋值。函数名Dance_func是指针
  Xiao_Ming.dance();            //调用Dance_func()

 二者只相差一个括号,但是意义不同。

<think> 我们正在讨论C语言结构体函数指针的初始化方法。根据用户的问题,我们需要详细说明如何在结构体中初始化函数指针。参考之前的引用,特别是引用[2]和引用[4]中提到了结构体初始化和函数指针的相关内容。 在C语言中,结构体可以包含函数指针作为成员。初始化这样的结构体时,我们需要为函数指针成员赋值,使其指向一个具体的函数。初始化可以在声明结构体变量时进行,也可以在后续的代码中赋值。 以下是几种初始化结构体函数指针的方法: 1. 在声明结构体变量时使用初始化列表(类似于普通结构体变量的初始化): 在初始化列表中,我们可以为函数指针成员指定一个匹配类型的函数地址。 2. 在定义之后,通过赋值语句单独为函数指针成员赋值。 3. 如果结构体变量是通过动态内存分配(如malloc)创建的,那么我们需要在分配内存后,手动为函数指针成员赋值。 注意:函数指针的类型必须与所指向函数的签名(参数类型和返回类型)一致。 下面通过示例代码详细说明: 假设我们有一个结构体`Calculator`,它包含一个函数指针成员`operation`,该函数指针指向一个接受两个`int`参数并返回`int`的函数。 步骤1:定义结构体类型 ```c typedef struct { // 函数指针成员,指向一个函数:接受两个int,返回int int (*operation)(int, int); } Calculator; ``` 步骤2:定义几个符合函数指针类型的函数 ```c int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } ``` 步骤3:初始化结构体变量 方法1:声明时初始化(使用初始化列表) ```c int main() { // 在声明结构体变量时直接初始化函数指针 Calculator calc_add = {add}; // 将operation指向add函数 Calculator calc_sub = {subtract}; // 将operation指向subtract函数 // 使用 printf("10 + 5 = %d\n", calc_add.operation(10, 5)); // 输出:15 printf("10 - 5 = %d\n", calc_sub.operation(10, 5)); // 输出:5 return 0; } ``` 方法2:先声明,后赋值 ```c int main() { Calculator calc; // 将函数指针指向add函数 calc.operation = add; printf("10 + 5 = %d\n", calc.operation(10, 5)); // 15 // 改为指向subtract函数 calc.operation = subtract; printf("10 - 5 = %d\n", calc.operation(10, 5)); // 5 return 0; } ``` 方法3:动态分配结构体并初始化函数指针 ```c int main() { // 动态分配Calculator结构体 Calculator *calc = (Calculator *)malloc(sizeof(Calculator)); if (calc == NULL) { // 错误处理 return -1; } // 初始化函数指针 calc->operation = add; printf("10 + 5 = %d\n", calc->operation(10, 5)); // 15 // 改变指向 calc->operation = subtract; printf("10 - 5 = %d\n", calc->operation(10, 5)); // 5 free(calc); // 释放内存 return 0; } ``` 另外,引用[2]中提到,结构体初始化时可以对成员一一赋值,但要注意顺序。对于函数指针成员,同样可以在初始化列表中赋值。同时,引用[4]中展示了一个结构体包含其他结构体成员以及函数指针的打印,说明函数指针的使用。 注意事项: - 确保函数指针指向的函数签名与函数指针类型匹配。 - 如果结构体包含多个成员(包括函数指针和其他类型),在初始化列表中可以按顺序初始化,也可以使用指定初始化(C99标准): ```c // 假设Calculator结构体还有一个成员int id typedef struct { int id; int (*operation)(int, int); } Calculator; // 指定初始化 Calculator calc_add = { .id = 1, .operation = add }; ``` 总结: 结构体函数指针的初始化方法包括:声明时初始化、后续赋值、动态分配后赋值。关键是给函数指针赋予一个匹配类型的函数地址。 根据用户的问题,我们提供了上述方法。现在,我们生成相关问题: §§相关问题§§ 1. 如何定义指向结构体成员函数的函数指针? 2. C语言函数指针的类型安全如何保证? 3. 结构体函数指针与普通成员在内存布局上有何不同? </think> ### C语言结构体函数指针的初始化方法 在C语言中,结构体中的函数指针初始化可以通过以下三种方式实现,这些方法适用于任何函数(不仅是`main`函数): #### 1. **声明时直接初始化(推荐)** 在定义结构体变量时直接为函数指针赋值: ```c #include <stdio.h> // 定义函数指针类型 typedef void (*PrintFunc)(int); // 结构体定义 struct Printer { PrintFunc print; // 函数指针成员 }; // 匹配的函数 void print_number(int num) { printf("Value: %d\n", num); } int main() { // 声明时直接初始化函数指针 struct Printer my_printer = {print_number}; // 直接赋值[^2] my_printer.print(42); // 调用 return 0; } ``` **特点**: - 最简洁安全的方式 - 符合C99/C11标准[^2] - 适用于全局/局部结构体变量 #### 2. **先声明后赋值** 先声明结构体变量,再单独初始化函数指针: ```c int main() { struct Printer my_printer; // 后续单独赋值 my_printer.print = print_number; // 通过赋值语句[^4] my_printer.print(100); return 0; } ``` **适用场景**: - 需要动态切换函数指针时 - 函数指针在条件分支中赋值 #### 3. **动态内存分配初始化** 通过`malloc`动态创建结构体并初始化: ```c struct Printer* create_printer(PrintFunc func) { struct Printer* p = malloc(sizeof(struct Printer)); if (p) p->print = func; // 初始化函数指针[^1] return p; } int main() { struct Printer* dyn_printer = create_printer(print_number); dyn_printer->print(256); free(dyn_printer); return 0; } ``` **注意事项**: - 必须检查`malloc`返回值(避免空指针) - 需手动管理内存释放[^1] - 适合需要长期存在的结构体 --- ### 关键注意事项 1. **类型匹配** 函数指针签名必须与目标函数完全一致: ```c // 错误示例:签名不匹配 void wrong_func(char*); struct Printer p = {wrong_func}; // 编译错误! ``` 2. **未初始化的危险** 未初始化的函数指针会导致未定义行为: ```c struct Printer uninit; uninit.print(100); // 程序崩溃![^3] ``` 3. **C99指定初始化(增强可读性)** ```c struct Printer p = { .print = print_number // 明确指定成员[^2] }; ``` 4. **函数指针数组** 结构体可包含函数指针数组: ```c struct Menu { void (*actions[5])(); // 函数指针数组 }; struct Menu m = { {func1, func2} }; // 部分初始化[^4] ``` --- ### 三种方法对比 | 方法 | 优点 | 缺点 | 适用场景 | |---------------------|-----------------------|-----------------------|-----------------------| | 声明时直接初始化 | 安全简洁 | 需在声明时确定函数 | 固定功能的简单结构体 | | 先声明后赋值 | 灵活可动态切换 | 可能遗漏初始化 | 需要运行时变更的场景 | | 动态内存分配 | 生命周期可控 | 需手动管理内存 | 长期存在的复杂对象 | > 最佳实践:优先使用**声明时直接初始化**,需要动态行为时改用**后续赋值法**,涉及复杂生命周期时使用**动态分配**[^1][^2][^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值