【C语言】深入解开指针(四)3

简介: 【C语言】深入解开指针(四)3

【C语言】深入解开指针(四)2:https://developer.aliyun.com/article/1474726

四、🚤函数指针变量

4.1 函数指针变量的创建

类比一下:

数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

C语言中的函数指针变量是指向函数的指针变量。

函数指针变量的定义格式是:

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

例如:

int (*ptr)(int, char); // ptr是一个指向返回类型为int,参数为int和char的函数的指针

函数指针变量和普通指针变量一样,它也需要指向具体的函数地址才能调用该函数。

例如:

int func(int a, char b) 
{
  return a + b;
}

int main() 
{
  int (*ptr)(int, char); // 函数指针变量声明
  
  ptr = func; // 指向func函数
  
  ptr(10, 'a'); // 通过函数指针调用func函数
  
  return 0;
}

函数指针变量的主要特点:

  • 可以指向不同类型的函数
  • 通过它可以调用被指向的函数
  • 可以作为函数参数或返回值进行传递
  • 常用在回调函数机制中

函数指针变量是用来存放函数地址的,通过这个地址可以调用函数。函数确实有地址!

#include <stdio.h>

void print()
{
  printf("lalala\n");
}
int main()
{
  printf("test: %p\n", print);
  printf("&test: %p\n", &print);

  return 0;
}

输出:

确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:

void print()
{
  printf("lalala\n");
}
void (*pf1)() = &print;
void (*pf2)() = print;

int Add(int x, int y)
{
  return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

看到这里,你可能会发现数组指针变量和函数指针变量其实好像也是差别不大呀!

4.2 函数指针变量的使⽤

通过函数指针调⽤指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int(*pf3)(int, int) = Add;

  printf("%d\n", (*pf3)(1, 2));
  printf("%d\n", pf3(3, 4));
  return 0;
}

代码输出的结果是一样的,为什么这两种方式都可以呢?

解释:

首先, pf3是一个函数指针变量,它指向Add函数。

在C语言中,函数指针与一般指针运算方式是一致的。

也就是说,对函数指针进行解引用(*pf3)后的结果,就是被指向的函数本身。

所以:


- (*pf3)(1, 2)

- 等价于 解引用pf3,得到Add函数,然后调用Add(1, 2)


- pf3(3, 4)

- 等价于 调用函数指针pf3,它指向的函数Add(3, 4)


两者调用的函数都是Add,参数也一致,所以结果是相同的。


函数指针与一般指针在语法上表现形式不同,但本质上都是指向函数地址的指针。所以对函数指针进行解引用或直接调用效果是一致的。


因此上述代码两种打印方式结果相同,原因就是函数指针与普通指针在语法和语义上是一致的。


4.3 两段有趣的代码

注:两段代码均出⾃:《C陷阱和缺陷》这本书

代码1

1 (*(void (*)())0)();

让我们分析一下:


(void (*)())0 意思是把0这个整数值,强制类型转换成一个地址,这个函数没有参数,返回类型是void。


*操作符对它进行解引用,得到void ()类型的匿名函数。


对这个匿名函数进行调用(),也就是调用0地址处的地址。


所以整个表达式:


(void (*)())0 - 获取函数指针,指向0地址


解引用函数指针,得到匿名函数

() - 调用匿名函数

换句话说,这个代码是:


获取一个指向0地址的函数指针,然后解引用它得到一个匿名函数,并对这个匿名函数进行调用。


由于指针指向0地址,实际调用的是内核NULL地址下的代码。这通常会触发异常或者崩溃。


所以这个代码展示了一个通过函数指针调用匿名函数的语法,它实际上是在尝试访问空指针下的代码从而触发错误。

代码2

1 void (*signal(int , void(*)(int)))(int);
• 1

精简理解:

首先上述代码是函数声明

signal是一个函数

signal函数的参数有2个,第一个是int类型

第二个是函数指针类型,该指针指向的函数参数是int,返回类型是void

signal函数的返回类型是这种类型的void(*)(int)函数指针

该指针指向的函数参数是int,返回类型是void

细节分析拓展如下:

此时注意signal并没有与前面的*用括号结合 这个代码定义了一个signal函数,它的功能是设置信号处理函数。


void(*)(int) 定义了一个函数指针,该函数指针指向一个返回void,接受一个int参数的函数。


signal是函数名,它有两个参数:


int: 表示信号编号


void(*)(int): 函数指针,表示要设置的信号处理函数


signal函数的返回值是一个函数指针:void (*)(int)


这个返回值函数指针也指向一个返回void,接受一个int参数的函数。

以整个函数声明可以解释为:

signal函数用于设置信号处理函数。它接收两个参数:

  • 信号编号
  • 要设置的信号处理函数

signal函数返回原来的信号处理函数。


所以这个函数声明定义了一个典型的设置信号处理函数的接口 - signal(),它可以用来设置和获取信号的处理回调函数。

**总结来说:**这个函数声明使用了嵌套的函数指针定义了signal函数的接口格式,目的是为了设置和获取信号处理回调函数。

4.3.1 typedef关键字

当你看到了这里,你可能在想,这么长void (*signal(int , void(*)(int)))(int);的代码,写出来真麻烦,有没有办法可以简化他的长度呢,看起来可观,容易理解呢?可不能自己写着写着把自己转忽悠了哈哈哈。

当然!

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

C语言中的typedef主要用于定义类型别名。

typedef语法:

typedef 旧类型名 新类型名;
• 1

例如:

typedef int Int;

这行代码定义Intint类型的别名。

typedef的主要用途:

  1. 为复杂类型定义简短的名称
    比如定义指针、函数指针等:
typedef int (*FuncPtr)(int);

这行代码定义Intint类型的别名。

typedef的主要用途:

  1. 为复杂类型定义简短的名称
    比如定义指针、函数指针等:
typedef struct {
  int x;
  int y;
} Point;

typedef Point* PointPtr;

向下兼容

如果需要修改类型定义,可以使用typedef避免修改大量代码。


提高代码可读性

给类型起个有意义的名称,比如用Person替换struct Person。


标准库也广泛使用typedef

如size_t、ptrdiff_t等标准类型都是通过typedef定义的。


所以总体来说,typedef主要用于为类型起别名,简化和隐藏类型,提高代码可读性和兼容性。它广泛应用于C标准库和程序开发中。

本小节由于篇幅有限,我们先讲第一点:

⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边

⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边

那么要简化代码2,可以这样写:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

五、🚢函数指针数组

数组

⽐如:

int *arr[10];
//数组的每个元素是int*

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?

是 int (*)() 类型的函数指针。

这个定义相当于:

  • 定义一个函数指针数组parr1
  • 数组长度为3
  • 每个元素都是一个函数指针
  • 指向一个返回int,无参数的函数

这里给出一个C语言函数指针数组的简单实现示例:

// 定义函数原型
int func1(void);
int func2(void);

int func1(void)
{
  printf("func1 called\n");
  return 0;
}

int func2(void)
{
  printf("func2 called\n");
  return 0;
}

int main()
{

  // 定义函数指针数组,可以存储2个函数指针
  int (*funcPtrArr[2])(void);

  // 初始化函数指针数组元素
  funcPtrArr[0] = func1;
  funcPtrArr[1] = func2;

  // 通过索引调用函数指针数组元素指向的函数
  funcPtrArr[0]();
  funcPtrArr[1]();

  return 0;
}

输出结果为:

主要实现步骤定义函数原型

定义函数指针数组

初始化数组元素,使每个元素指向对应的函数

通过数组索引,调用函数指针指向的函数

这个示例演示了如何定义和使用函数指针数组来管理和调用多个函数。


实际应用中,可以通过函数指针数组实现回调函数、插件等机制。函数也可以作为参数传递给其他函数。


总之,函数指针数组提供了一种灵活高效的方式来管理和调用多个函数在C语言中。怎么高效?下一届我们做一个计算器,转移表就可以清楚理解他的巧妙之处!


⚓️总结

一、字符指针变量

字符指针变量用来存储字符串,可以通过字符指针访问字符串中的每个字符。


二、数组指针变量

2.1 数组指针变量实际指向数组第一个元素的地址。

2.2 可以通过数组名直接初始化数组指针,也可以通过地址运算符&初始化。


三、二维数组传参的本质

二维数组传参实际上是传一级指针,等同于传数组指针。


四、函数指针变量

4.1 通过函数原型声明函数指针变量类型,并使用地址运算符&初始化。

4.2 通过函数指针调用函数,等同于使用普通函数名调用。

4.3 typedef可以简化函数指针变量类型定义。


五、函数指针数组

函数指针数组可以存储和管理多个函数指针,通过数组索引调用不同函数。


相关文章
|
18天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
44 0
|
17天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
13 2
|
18天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
18天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
24天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
24天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
24天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
25天前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
25天前
|
C语言
C语言指针(2)
C语言指针(2)
13 1
|
17天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
15 0