Bootstrap

c语言函数指针、指针数组、二级指针、结构体、联合体、枚举实例解析(一文快速掌握)

一、概述

1.1 简介

本文主要讲解函数指针及用法,同时实例讲解结构体和枚举。
函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。

1.2 简单示例

int Func(int x);//这一句是声明一个函数,是我们要被函数指针调用的函数,而且函数名表示函数的首地址。
int (*p) (int x);//这里定义了一个函数指针
int (*p) (int x); 
//里的int指的是函数的返回值,指要被调用的函数的返回值是整形。
//里的*p指的是指向函数首地址的指针变量。
//(int x)表示被调用的函数参数是一个且是整形。

p = Func;//这一句是把被调函数的首地址存在定义的指针变量里,用来指向被调函数。

二、实例操作

2.1 定义一个函数指针

typedef int (*stFunc_t)(stateCtx_t * st_ctx);  //typedef可以使用stFunc_t去申明一个函数指针

2.2 使用数组将多个函数指针存放

const stFunc_t _CleanShutDownStateFuncs[SHUTDOWN_STEP_NUM] =
{
    ShutDownPrepare,
    ShutDownStopUsrService,
    ShutDownStopLowTask,
    ShutDownAdjustSysTask
};

具体函数定义如下:

int ShutDownPrepare(stateCtx_t * st_ctx){
#TODO
}

2.3 使用枚举确定数组成员值

typedef enum _MediaShutDownState_t
{
    SHUTDOWN_PREPARE = 0,
    SHUTDOWN_STOP_USER_SERVICE,
    SHUTDOWN_STOP_LOW_TASK,
    SHUTDOWN_ADJUST_SYS_TASK,
    SHUTDOWN_STEP_NUM
}MediaShutDownState_t;

2.4 定义一个结构体

typedef struct _stateMachine_t
{
    uint32_t sig;               // state machine signature, debugging purpose
    const stFunc_t *funcs;      //成员申明一个函数指针 
    uint32_t nrSteps;          // # states
} stateMachine_t;

对结构体进行赋值

const stateMachine_t _CleanShutDownStm =
{
    INT_CHR('M', 'C', 'S', 'D'),
    _CleanShutDownStateFuncs,//将最开始的指针数组传入(每个成员指向设置的函数 )
    ARRAY_SIZE(_CleanShutDownStateFuncs)
};

2.5 定义一个联合体

联合是一种特殊的自定义类型,该种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间,所以联合体也被称为共用体。
如下该联合体占用32 bit

///  @brief 0x244000C4 : CPU interrupt mode
typedef union
{
    uint32_t all;///< All bits.
    struct
    {
        uint32_t CIF_INT_MODE        : 1;        ///<BIT [0]: RW (0x0). 1'b0: single interrupt; 1'b1: multiple interrupt for every port.
        uint32_t R244000C4_31_1_RSVD :31;        ///<BIT [31:1]: Reserved.
    } b;
} SataCifIntMode_t;


SataCifIntMode_t  cifIntMode.all = (SataCifIntMode_t)
    { .b = { .CIF_INT_MODE = 0x0}
    }.all;

其他赋值方式

typedef union _nr_iv_info_tab_1
{
    u32 w;
    struct
    {
        rw32 cd                : 1;
        rw32 msi_en            : 1;
        rw32 msix_en           : 1;
        rw32 msix_fm           : 1;
        rw32 ivmask            : 1;
        rw32 rsv5              : 27;
    };
} nr_iv_info_tab_1;
nr_iv_info_tab_1 info1 = (nr_iv_info_tab_1){ .cd = 1, .msi_en = 0, .msix_en = 0, .msix_fm = 0, .ivmask = 0 };

2.6 指针数组

实际为一个数组,存放的是一个指针,指针指向char类型的字符串

char * p[] = {
	"start",
	"end",
	NULL,
};
printf("the p[0]:%s,p[1]:%s\n",p[0],p[1]);
//注意:该指针数组中每个元素(即指针)存放的地址为&p[0],&p[1],二级指针用到
//输出如下
the p[0]:start,p[1]:end
//p[1]表示该数组中的第一个元素的指针指向的字符串
2.6.1 二级指针的使用

该指针指向的类型是一个指针,最后指向一个char类型的字符串

char ** p;
p = &p[0];  //将p赋值一个指针的存放地址,p[0]为一个指针,指向"start"字符串
printf("the p:%s\n",*p);
**p = "hello";  //给p[0]赋值为hello

三、完整程序讲解

3.1 指针综合示例一

#include <stdio.h>
int main() {
        int a = 10;
        int *p = &a;
        int **pp = &p;
        printf("a=%d\n",a); // 打印a的值
        printf("&a=%p\n",&a); // 打印a的地址
        printf("p=%p\n",p); // 打印p的值,即a的地址
        printf("*p=%d\n",*p); // 打印p所指向的变量的值,即a的值
        printf("&p=%p\n",&p); // 打印p的地址
        printf("*pp=%p\n",*pp); // 打印pp所指向的指针变量p的值,即a的地址
        printf("**pp=%d\n",**pp); // 打印pp所指向的指针变量p所指向的变量的值,即a的值
        printf("&pp=%p\n",&pp); // 打印pp的地址
        printf("*&p=%p\n",*&p); // 打印p的值,即a的地址
        printf("&*p=%p\n", &p); // 打印p的地址
        return 0;
}

结果:

a=10
&a=0x7fff8ac8b2b4
p=0x7fff8ac8b2b4
*p=10
&p=0x7fff8ac8b2b8
*pp=0x7fff8ac8b2b4
**pp=10
&pp=0x7fff8ac8b2c0
*&p=0x7fff8ac8b2b4
&*p=0x7fff8ac8b2b8

进阶版:

#include <stdio.h>
int main() {
        //int a = 10;
        char *a = "acb";
        char *p = a;
        char **pp = &p;
        printf("a=%s\n",a); // 打即指针a所指向的值,acb
        printf("a=%p\n",a); // 打印acb的地址
        printf("&a=%p\n",&a); // 打印a的地址
        printf("p=%p\n",p); // 打印p的值,即acb的地址
        printf("*p=%s\n",p); // 打p所指向的变量的值,即acb
        printf("&p=%p\n",&p); // 打印指针p的地址
        printf("pp=%p\n",pp); // 打印pp所指向的指针变量p的地址
        printf("*pp=%p\n",*pp); // 打印pp所指向的指针变量p的值,即acb的地址
        printf("**pp=%s\n",*pp); // 打pp所指向的指针变量p所指向的变量的值,即acb的值
        printf("&pp=%p\n",&pp); // 打pp的地址
        printf("*&p=%p\n",*&p); // 打印p的值,即acb的地址
        printf("&*p=%p\n", &*p); // 打印acb的地址
        return 0;
}

结果:
注意打印类型%p为以16进制打印传入地址存放的值,%s为打印传入的首地址的字符串

a=acb
a=0x55a4d76eb004
&a=0x7ffed2c2db70
p=0x55a4d76eb004
*p=acb
&p=0x7ffed2c2db78
pp=0x7ffed2c2db78
*pp=0x55a4d76eb004
**pp=acb
&pp=0x7ffed2c2db80
*&p=0x55a4d76eb004
&*p=0x55a4d76eb004

3.2 关于函数地址的示例二

void fun()
{
}

int main(void)
{
	printf("%p %p %p\n", &fun, fun, *fun);
	return 0;
}

这三个值的结果是一样的. 其实对于最后的那个*fun, 即使前面加上很多个*号, 其结果也不变, 即**fun, ***fun的结果都是一样的.
对于这个问题, 因为之前讲过函数是一种function-to-pointer方式, 其会自动转换成指针的类型, &fun是该函数的地址, 为指针类型, fun是一个函数, 会转换成其指针类型, 而对于*fun, 由于fun已经变成了指针类型, 指向这个函数, 所以*fun就是取这个地址的函数, 而又根据function-to-pointer, 该函数也转变成了一个指针, 所以以此类推, 这三个值的结果是相同的。

3.3 函数指针的嵌套运用

#include <stdio.h>
void test(int);
typedef void (*fp1)(int); //申明一个函数指针

int main(void){
  const fp1 fp=test;  //指针指向test函数 
  printf( "the fp is %x\n", fp);
  (*fp)(9);//调用test函数,传入参数为9
  fp(9);
  const fp1 *fp2;//申明另外的函数指针
  const fp1 *fp3;//申明另外的函数指针
  fp3=test;//指针指向test函数 
  printf( "the fp2 is %x\n,the fp2 %x\n", fp2,fp3);//打印fp2,fp3指向的函数的地址值
  return 0;
}

struct test_fp{
  const fp1 fp;
};

void test(int a)
{
  printf( "%d\n", a );
  struct test_fp fp4;
  printf( "the fp4 is %x\n", fp4);
}

打印结果:

the fp * is e3a2d1c2
9
the fp4 is 0
9
the fp4 is 0
the fp2 is 0
,the *fp2 e3a2d1c2

解析:
1、fp1 、*fp和fp1 fp都是定义一个函数指针,赋值的话只能fp=test;
2、const fp1 fp;定义的函数指针地址都为0;
3、程序中的 fp( 9 ); 是直接使用函数指针 fp来调用。既然 fp已经是函数指针了,所以在类型上就不需要任何转换了。

4、(*fp)( 9 ),也是合法的函数调用。在这里,fp 是函数指针,所以*fp 是对于函数的引用,是函数类型。根据标准规定的 function-to-pointer 转换又把 *fp 由函数类型转换为了函数指针类型,所以实际上 (*fp)( 9 ),相当于 fp( 9 ),这种直接的函数指针调用方式。
5、test 函数也可这样调用:(*test)( 9 );。可以这样来理解:根据 function-to-pointer 转换规定,首先 test 由函数类型转换为函数指针,那么 *test 表示的又是函数类型,
最后又根据 function-to-pointer 转换为函数指针来调用函数。这其实和 (*fp)( 9 ); 是等价的。甚至 test 函数还可以这样调用:(**test)( 9 );、(***test)( 9 );、(****test)( 9 ); 等等,
或者函数指针形式:(**fp)( 9 );、(***fp)( 9 );、(****fp)( 9 ); 等等。对此的理解可参看上段中的分析。
从上面的分析可以看出,函数调用的时候可以使用函数指针的方式,也可以使用函数指示符的方式。不过,后者会由编译器自动转换为前者的形式,即函数指针的形式。和指向对象的指针相比,这是函数指针一个比较特殊的地方。

四、其他相关链接

1、C语言中指针、数组作为作为函数参数使用总结

2、C语言常见数据类型字节数和打印格式总结

3、C语言常用函数详细总结附示例

;