C语言结构体封装函数指针

C语言结构体(Struct)从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由 int、char、float 等基本类型组成的。例如,在校学生有姓名、年龄、身高、成绩等属性,学了结构体后,我们就不需要再定义多个变量了,将它们都放到结构体中即可,如图所示:

 

API封装方法

那么我们怎么用C语言的结构体来封装函数API呢?
首先看看C++和C的区别及方法:

C++类

C++语言类中可以封装函数,体现了模块操做的整体性,下面代码便是C++语言对某个函数的封装,这样操作便于调用。

lass MarlinSerial //: public Stream
{
    public:
        MarlinSerial();
        void begin(long);
        void end();
        int peek(void);
        int read(void);
        void flush(void);
}

调用方法:

MarlinSerial MSerial; //实例化一个对象
MSerial.begin(9600);//设置串口的波特率为9600

这样便实现了串口的初始化。对串口操作函数进行了模块化封装,代码结构清晰。

C语言结构体

那么C语言是否可以实现这种方式呢?C语言结构体不能直接封装函数,但可以通过封装函数指针的方式来实现,可以很方便的移植到任何编译器上测试。具体方法如下:

/* serial.h文件 */
//定义封装函数的结构体,并声明外部引用
//对串口操作函数封装。
typedef struct 
{
	void begin(long);
	void end();
	int (*peek)();
	uint8_t (*read)(void);
	void (*flash)();
	int (*availiable)();
	void (*checkRx)();
}MarlinSerial;
 
extern MarlinSerial MSerial;
 
/* serial.c文件 */
 
MarlinSerial MSerial; //定义MarlinSerial类型的结构体MSerial 
 
Serial_Init()
{
	MSerial.read = &Serial_Read;
}
 
uint8_t Serial_Read(void)
{ 
	printf("hello word!"); //在这里仅作测试所用,未列出真正的串口读取函数
} 
 
/* main.c文件 */
int main(void)
{
	Serial_Init();
	MSerial.read(); //调用串口读取函数,目前功能为打印字符串
	while(1)
	{
	   //
	}
}

 

首先看看结构体的基本使用及定义方法,也就是上面所说的定义不同的数据类型,如下所示:

typedef struct
{
    uint8_t     uart1;
    uint32_t    uart2;
    uint8_t     uart3;
    uint8_t     uart4;
    
    uint16_t     channel;
}serial_t;

函数封装方法如下,首先比如我们有如下几个函数

void hs_register_usart_callback(uint8_t usart_x,void (*ptr)(uint8_t*,uint16_t),uint16_t BuffSize,uint8_t reMode);
void usart_config(uint8_t usart_x,uint32_t baud_rate);
void hs_usart_printf(uint8_t usart_x, const char *Data,...);
void hs_usart_sendHex(uint8_t usart_x,uint8_t *data,uint16_t len);

那么要想封装这结果函数,结构体封装就如下所示(其中函数指针名称可自定义):

ypedef struct
{
    uint8_t uart1;
    uint8_t uart2;
    uint8_t uart3;
    uint8_t uart4;
    uint8_t channel;//自定义数据
    
    void (*config)(uint8_t usart_x,uint32_t baud_rate);
    void (*register_callback)(uint8_t usart_x,void (*ptr)        
    (uint8_t*,uint16_t),uint16_t BuffSize,uint8_t reMode);
    void (*print)(uint8_t usart_x, const char *Data,...);
    void (*sendHex)(uint8_t usart_x,uint8_t *data,uint16_t len);
}serial_t;/*API 操作结构体*/

然后申明一下就可以了,如下:

serial_t serial;

然后在名称后面加一点就可以调用对应的函数了。如下图所示:

 

 注意:经过这两步,虽然能直接调用不报错,但是是否考虑过它们之间存在着怎样的联系?很明显是没有的,所以还需要进行最重要的一步,也就是实例化:

/*API 函数初始化*/
serial_t serial =
{
    .uart1 = 0,
    .uart2 = 1,
    .uart3 = 2,
    .uart4 = 3,
    .config = usart_config,
    .register_callback = hs_register_usart_callback,
    .print = hs_usart_printf,
    .sendHex = hs_usart_sendHex,
};

这里也就是对结构体取个别名,然后在里面进行对应的赋值和定义,这里“.”后面的名字要和结构体里面“*”后面的一一对应。而”=”后面就是对应底层的函数名称,而uart1这几个直接赋的值,因为他们是结构体内的变量,后面直接调用serial.uart1,就等于是直接赋值0.
而函数的封装,这里就相当于起一个链接的作用,将结构体内和函数链接起来。

 

 

 

posted @ 2022-06-23 21:58  quliuliu2013  阅读(432)  评论(1编辑  收藏  举报