问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针

创作时间:
作者:
@小白创作中心

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针

引用
CSDN
1.
https://blog.csdn.net/m0_71503225/article/details/135895313

本文是一篇关于C语言函数指针的进阶教程,详细介绍了函数指针的基本概念、使用方法、函数指针数组以及指向函数指针数组的指针等知识点。通过理论讲解结合代码示例的方式,帮助读者深入理解这些较为复杂的C语言概念。

1. 指针知识回顾

  1. 指针就是个变量,用来存放地址,地址唯一标识了一片空间。
  2. 内存会划分成一个个的内存单元,每个内存单元都有一个独立的编号,编号也称为地址,而C语言也把地址叫做指针。地址(指针)需要存储在变量中,这个变量就被称为指针变量。
  3. 指针的大小是固定的4/8字节(32位平台、64位平台)
  4. 地址是由物理的电线产生的(高低频的电信号),32位机器对应的就是32根地址线,产生32个0/1序列,32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址也就是需要8个字节才能存储,所以在32位机器下,地址的大小就是4个字节。
  5. 对于64位机器来说, 对应的就是32根地址线,产生64个0/1序列,64个0/1组成的二进制序列,把这个二进制序列就作为地址,64个bit位才能存储这个地址也就是需要8个字节才能存储,所以在64位机器下,地址的大小就是8个字节。
  6. 指针是有类型的,指针的类型决定了指针的+-整数的步长,指正解引用操作时候的权限。
  7. 列如一个字符类型的数据解引用只可以访问一个字节的内存空间

2. 数组知识回顾

C语言基础 -数组

3. 函数指针

3.1 函数指针引入

指向函数的指针
整型指针-----指向整型数据的指针
字符指针----指向字符的指针
那么函数指针就是指向函数的指针,函数指针变量里面保存了函数的地址。
那就是说函数是不是应该有自己的地址,我们知道取地址数组名就得到函数的地址,那么对于函数来说,是不是也是取地址函数名就可以拿到函数的地址呢,我们来看一下。,见如下代码
说明:&函数名和函数名都是函数的地址
那么现在如果我要把函数的地址保存到一个变量里边去,那么就应该是一个指针变量,我们思考一下那么这个指针变量的类型应该是什么样的呢?
首先应该是一个指针
(*p)
接着这个指针的类型应该是函数指针类型,就应该是函数类型,函数类型应该有参数和返回值呀,所以:上述代码中的函数指针就可以写为:
int (p)(int ,int) = &add;
P先和
结合,说明其为一个指针,指向一个函数,返回类型是int ,有两个参数
再来看一个例子:

这个函数的指针该如何写呢?
我们应该关注两个点:函数的返回值,函数的参数类型
所以这个函数的函数指针就可以写为:

  1. void (*p)(char *,int [10]) = &test;//10也可以省略
    注意第二个参数为数组类型,当然也可以写为:void (*p)(char *,int *) = &test;因为数组作为形参的本质还是一个指针。
  2. 如果去掉括号可以吗?
    形如void * p(char *,int [10]) = &test;
    这样写就导致P先和()结合成为一个函数,void 和 * 结合变成一个返回值类型

3.2 函数指针的使用


这是从指针角度解引用然后进行传参使用。
然后我们对比一下下面这个写法,看看有什么结论
一样是可行的说明,这里的*号可有可无

3.3 阅读两段有趣的代码

  1. (void()() ) 0) ()
    然后末尾一个括号就是正常的函数传参
    所以这段代码要表示的含义就是:
    调用0地址处的函数
    第一步:把0强制类型转换为void(*)()类型的函数指针
    第二步:解引用调用0地址处的函数
  2. void (signal(int ,(void()(int)))(int)
    那么这段代码就这样理解
  3. signal是一个函数声明
  4. signal 函数有两个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型
  5. 该函数指针指向的函数有一个int类型的参数,返回类型是void
  6. signal函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void
    那么对代码②进行简化:

3.4 类型重定义,typedef补充

平常我们说这样一个类型比如:unsigned int这个类型,我们觉得每次书写都太长了很麻烦,那我们就可以进行类型重定义:
typeddef unsigned int uint;
那么下次我们书写的时候就可以使用uint来代替unsigned int,使得类型简化。
同样的,对于指针类型也可以重定义,比如将int 重定义
typedef int
ptr
那么原来的:int * p1
就可以改写为: ptr p1
同理,也可以对我们的数组指针或者函数指针类型进行重定义,但是稍微不同的是,重定义的类型名要和在一起。
比如:这是一个数组指针类型:int (
)[10];如何进行类型重定义?

  1. typedef int (*)[10] parr_t 这样定义是错误的
    正确的定义方法为:
    typedef int (*parr_t )[10],重定义的类型名放在1括号里才行,对于函数指针来说也是一样的。
    那么上面的代码就可以1这样来做:
    typedef void(*ptr_t)(int)
    那么:void (signal(int ,(void()(int)))(int)
    就可以简化为: ptr_t signal(int ,ptr_t);

4. 函数指针数组

4.1 函数指针数组引入

引言:我们前面知道可以把一个整型类型的指针放入一个数组,比如:int * arr[10],可以把一个字符类型的指针放入一个数组,比如:char *arr[5],那么可不可以把统一类型的函数指针也放入一个数组里面呢。
我们来看一下函数指针数组的形式:
指针:int *p
函数指针:int (*p)(int ,int)
指针数组:int *parr[10]
那么函数指针数组首先肯定是数组,数组的每一个元素应该是函数类型的指针
那么函数指针数组的形式: int (*parr[5])(int,int)
例子引入:计算器实现加减乘除(只针对整数的运算),如果没有引入这个函数指针数组我们会怎么写呢
我们发现四个函数,除了执行的运算不一样,函数的返回类型和参数个人、类型都是一样的,那么如果我们要把函数的地址保存在一个指针变量里面,这个指针变量的类型是不是应该都是一样的,都是 int (*)(int,int)类型,那么我们是不是就可以把这四个指针放到一个数组里面呢(数组的每一个元素的类型是一样的)
如果没有引用到函数指针数组我们来看一下这个计算器的实现过程:

#include<stdio.h>
//定义执行加减乘除的四个运算
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x *y;
}
int Div(int x, int y)
{
    return x / y;
}
void menu()
{
    printf("************************\n");
    printf("****1.add  2.sub********\n");
    printf("****3.mul  4.div********\n");
    printf("****0.exit  ************\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int  y = 0;
    int ret = 0;
    do
    {
        menu();
        printf("请选择:\n");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("请输入两个操作数:\n");
            scanf("%d %d", &x, &y);
            ret = Add(x, y);
            printf("%d\n", ret);
            break;
        case 2:
            printf("请输入两个操作数:\n");
            scanf("%d %d", &x, &y);
            ret = Sub(x, y);
            printf("%d\n", ret);
            break;
        case 3:
            printf("请输入两个操作数:\n");
            scanf("%d %d", &x, &y);
            ret = Mul(x, y);
            printf("%d\n", ret);
            break;
        case 4:
            printf("请输入两个操作数:\n");
            scanf("%d %d", &x, &y);
            ret = Div(x, y);
            printf("%d\n", ret);
            break;
        case 0:
            printf("退出计算器\n");
            break;
        defult:
            printf("选择错误重新选择\n");
            break;
        }
    } while (input);
    return 0;
}

但是,这个代码虽然实现了功能,很多代码确实重复了,代码冗余,,第二个这个函数只能实现四个功能,但是像按位与等运算类型本质也是一样的,如果这样写case语句就会越来越长。所以我们需要更好更简洁的代码书写方式:

4.2 函数指针数组的使用(转移表)

看代码:

int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x *y;
}
int Div(int x, int y)
{
    return x / y;
}
void menu()
{
    printf("************************\n");
    printf("****1.add  2.sub********\n");
    printf("****3.mul  4.div********\n");
    printf("****0.exit  ************\n");
    printf("************************\n");
}
int main()
{
    int input = 0;
    int x = 0;
    int  y = 0;
    int ret = 0;
    int (*pfarr[])(int, int) = { NULL,Add,Sub,Mul,Div };
    //                            0    1   2   3   4
    do
    {
        menu();
        printf("请选择:\n");
        scanf("%d", &input);
        if (input >= 1 && input <= 4)
        {
            printf("请输入两个操作数:\n");
            scanf("%d %d", &x, &y);
            ret = pfarr[input](x, y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("选择错误,重新选择\n");
        }

使用了函数指针,就直接省略了很长的Switch并且冗余的代码,并且后续添加的时候只用修改很小一部分内容。
我们看一下实现效果:

5. 指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。
形式:
int (*pf)(int ,int)//这是函数指针
int(*pfarr[4])(int ,int) //这是函数指针数组
数组可以取地址:&pfarr 这是函数指针数组的指针。
在函数指针数组的基础上得到指向函数指针数组的指针
*int ((p)[4])(int ,int) = &pfarr*
数组和指针我个人决定就有点像面多加水,水多加面的相互状态,大家可以多多理解使用。这是本期的所有内容。欢迎大家指正与讨论,创作不易,希望可以收获到大家的三连。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号