未分类 · 2022年11月5日

C语言|函数指针作为结构体成员实现成员操作

阅读器查看->点击查看

一、起因

在嵌入式操作系统开发的时候、发现非常多的功能实现是基于结构体函数指针实现,函数指针在嵌入式中的应用非常广泛,常常把函数指针作为结构体的成员、作为函数的参数等。为了填补c语言的基础知识,所以发了这个帖子记录学习过程

在C语言程序中,数据结构和算法是两个基本的元素。C语言的基本数据类型、结构体、数组和联合体是数据结构的代表;C语言中的函数则是算法的代表。只有将数据结构和算法有机结合才能构成具有一定功能的程序。

1. C语言函数指针(指向函数的指针)

函数指针的定义形式为:

returnType (*pointerName)(param list);

returnType 为函数返回值类型,pointerName 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。

注意( )的优先级高于,第一个括号不能省略,如果写作returnType pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType *。

【实例】用指针来实现对函数的调用。

#include <stdio.h>
//返回两个数中较大的一个
int max(int a, int b){
    return a>b ? a : b;
}
int main(){
    int x, y, maxval;
    //定义函数指针
    int (*pmax)(int, int) = max;  //也可以写作int (*pmax)(int a, int b)
    printf("Input two numbers:");
    scanf("%d %d", &x, &y);
    maxval = (*pmax)(x, y);
    printf("Max value: %d\n", maxval);
    return 0;
}

2. 结构体中定义函数指针

结构体指针变量的定义

定义结构体变量的一般形式如下:

形式1:先定义结构体类型,再定义变量

struct 结构体标识符
{
  成员变量列表;…
};
struct 结构体标识符 *指针变量名;
变量初始化:struct 结构体标识符 变量名={初始化值1,初始化值2,…,初始化值n };

形式2:在定义类型的同时定义变量

struct 结构体标识符
{
  成员变量列表;…
} *指针变量名;

形式3:直接定义变量,用无名结构体直接定义变量只能一次

struct
{
  成员变量列表;…
}*指针变量名;

其中“指针变量名”为结构体指针变量的名称。形式1是先定义结构体,然后再定义此类型的结构体指针变量;形式2和形式3是在定义结构体的同时定义此类型的结构体指针变量。

函数指针的定义

int add2(int x,int y)
{
  return x+y;
}
那么在实际使用指针func时可以这样实现:

int (*func)(int,int);
func=&add2;  //指针赋值,或者func=add2; add2与&add2意义相同
printf("func(3,4)=%d\n",func(3,4));

事实上,为了代码的移植考虑,一般使用typedef定义函数指针类型.

typedef int (*FUN)(int,int); //参考下面

/ typedef int (funcptr)(); 这个的意思是:定义一个返回值为int,不带参数的函数指针,
就是说funcptr 是 int ( )( )型的指针
funcptr table[10];
定义一个数组,这个数组是funcptr类型的。就是说这个数组内的内容是一个指针,这个指针指向一个返回值为int,不带参数的函数
/
FUN func=&add2;
func();

结构体中指向函数的指针

C语言中的struct是最接近类的概念,但是在C语言的struct中只有成员,不能有函数,但是可以有指向函数的指针,这也就方便了我们使用函数了。举个例子,如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  

typedef struct student  
{  
    int id;  
    char name[50];   
    void (*initial)();  
    void (*process)(int id, char *name);  
    void (*destroy)();  
}stu;  

void initial()  
{  
    printf("initialization...\n");  
}  

void process(int id, char *name)  
{  
    printf("process...\n%d\t%s\n",id, name);  
}  

void destroy()  
{  
    printf("destroy...\n");  
}  

int main()  
{  
    stu *stu1;  
    //在VC和TC下没有malloc也可以正常运行,但是linux gcc下就会出错,为段错误,必须使用malloc  
    stu1=(stu *)malloc(sizeof(stu));  
    //使用的时候必须要先初始化  
    stu1->id=1000;  
    strcpy(stu1->name,"C++");  
    stu1->initial=initial;  
    stu1->process=process;  
    stu1->destroy=destroy;  
    printf("%d\t%s\n",stu1->id,stu1->name);  
    stu1->initial();  
    stu1->process(stu1->id, stu1->name);  
    stu1->destroy();  
    free(stu1);  
    return 0;  
}

终端显示:1000    C++
initialization...
process...
1000    C++
destroy..

c语言中,如何在结构体中实现函数的功能?把结构体做成和类相似,让他的内部有属性,也有方法,
这样的结构体一般称为协议类,提供参考:
struct {
  int funcid;
  char funcname;
  int (
funcint)(); / 函数指针 int 类型/
  void (funcvoid)(); / 函数指针 void类型*/
};
每次都需要初始化,比较麻烦

#include <stdio.h>  

typedef struct  
{  
    int a;  
    void (*pshow)(int);  
}TMP;  

void func(TMP *tmp)  
{  
    if(tmp->a >10)//如果a>10,则执行回调函数。  
    {  
        (tmp->pshow)(tmp->a);  
    }  
}  

void show(int a)  
{  
    printf("a的值是%d\n",a);  
}  

void main()  
{
    TMP test;  
    test.a = 11;  
    test.pshow = show;  
    func(&test);  
}  
终端显示:a的值是11      
 ```
一般回调函数的用法为: 
甲方进行结构体的定义(成员中包括回调函数的指针) 

乙方定义结构体变量,并向甲方注册, 
甲方收集N个乙方的注册形成结构体链表,在某个特定时刻遍历链表,进行回调。 
当函数指针做为函数的参数,传递给一个被调用函数, 
被调用函数就可以通过这个指针调用外部的函数,这就形成了回调<p>一般的程序中回调函数作用不是非常明显,可以不使用这种形式</p><p>最主要的用途就是当函数不处在同一个文件当中,比如动态库,要调用其他程序中的函数就只有采用回调的形式,通过函数指针参数将外部函数地址传入来实现调用</p><p>函数的代码作了修改,也不必改动库的代码,就可以正常实现调用便于程序的维护和升级</p>

##  3. 函数指针及其两个主要用途

0.基本的函数指针的声明与初始化
 首先我们来声明一个函数。

int Max_Int(int a, int b)
{
    return (a>b)?a:b;
}
把函数名替换成指针表达式是创建指向函数指针最简单的方式:

int (*pFun)(int a, int b);    //声明函数指针,此时函数指针没有指向任何函数

接下来我们对函数指针进行初始化:

pFun = Max_Int;         //使函数指针指向Max_Int函数

这样就完成了函数指针的初始化。接下来,我么就可以通过此函数指针引用此函数。
```c
int max;
int m = 5;
int n = 8;
max = (*pFun)(m,n);             //使用函数指针引用Max_Int函数
max = pFun(m,n);                //与上一句效果完全相同
printf(“The max value is %d\n”, max);   //print ‘8’ 

1、更高级的声明方式:

1)使用typedef声明函数指针。

我们可以使用typedef来声明函数,以使代码的可读性更强。

typedef bool(pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int, bool);
通过之前的介绍,我们可以知道,此函数指针可以指向“返回bool型,带int, unsigned int, unsigned short, unsigned int*, bool型参数” 的函数。

借此声明,我们可以像声明变量那样同时创建多个函数指针。

pCopySDMMC2Mem  pf1, pf2;

2)使用define来定义函数指针

#define CopySDMMCtoMem(z,a,b,c,e) (((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned int *)0xD0037F98)))(z,a,b,c,e))

2.函数指针的两个典型应用

1)将函数作为参数传递给函数

#include   

//Calculate用于计算积分。一共三个参数。第一个为函数指针func,指向待积分函数。二三参数为积分上下限  
double Calculate(double(*func)(double x), double a, double b)  
{  
    double dx = 0.0001;//细分的区间长度  
    double sum = 0;  
    for (double xi = a+dx; xi <= b; xi+=dx)  
    {  
       double area = func(xi)*dx;  
       sum +=area;  
    }  
    return sum;  
}  

double func_1(double x)  
{  
    return x*x;  
}  

double func_2(double x)  
{  
    return x*x*x;  
}  

void main()  
{  
    printf("%lf\n", Calculate(func_1, 0, 1));  
    printf("%lf\n", Calculate(func_2, 0, 1));  
} 

通过函数指针,我们可以在函数中使用别的函数作为参数。此程序可以完成对不同函数的积分。

2)引用不在代码段中的函数

此功能在嵌入式系统中经常使用。我们知道,我们写的用户程序的code是存放在代码段中的,在嵌入式系统中,一般情况下是存放在flash中的。什么叫不在代码段中的函数?很多微控制器在出厂前会将一些功能函数(系统函数)固化在rom中(类似于PC机中的BIOS),如Flash擦写功能,Flash Copy功能。而我们写的代码是不认识这些函数的,不能直接使用函数名调用。所以,当我们想在用户程序中调用这些系统函数时,就只能使用函数指针的方式,通过将系统函数的入口地址传给函数指针,来达到调用rom中程序的目的。这些系统函数一般都会在官方手册中给出功能,返回值类型和参数列表。

4.嵌入式系统中结构体中函数指针的应用

1、函数指针作为结构体成员

2、函数指针作为函数的参数


函数指针可以作为一个参数传递给另一个函数。这时函数指针的使用就像普通的常量和变量一样。当函数指针作为参数传递的时候,这时接收参数传递的函数通常需要根据这个指针调用这个函数。作为参数传递的函数指针通常表示回调函数(Callback Functions)。

5.使用结构体指针编写回到函数历程

#include 
#include 

/****************************************
 * 函数指针结构体
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

/****************************************
 * 加减乘除函数
 ***************************************/
float ADD(float a, float b) 
{
    return a + b;
}

float SUB(float a, float b) 
{
    return a - b;
}

float MUL(float a, float b) 
{
    return a * b;
}

float DIV(float a, float b) 
{
    return a / b;
}

/****************************************
 * 初始化函数指针
 ***************************************/
void init_op(OP *op)
{
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}

/****************************************
 * 库函数
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    return (*op_func)(a, b);
}

int main(int argc, char *argv[]) 
{
    OP *op = (OP *)malloc(sizeof(OP)); 
    init_op(op);

    /* 直接使用函数指针调用函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2), 
            (op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));

    /* 调用回调函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", 
            add_sub_mul_div(1.3, 2.2, ADD), 
            add_sub_mul_div(1.3, 2.2, SUB), 
            add_sub_mul_div(1.3, 2.2, MUL), 
            add_sub_mul_div(1.3, 2.2, DIV));

    return 0; 
}