引子
今天看到B站UP硬件家园分享自己在单片机上写代码的成长历程。当我看到UP现在的编程风格居然是OOP风格的时候我大为震撼(怪我才疏学浅,哈哈),而这正是最近我头疼的问题:因为我最近用的单片机性能只能支持运行C语言,但是又想用面向对象的风格进行开发,这该如何实现?
在看完这个视频以及几篇帖子后,我找到了这个问题的答案:可以利用结构体+函数指针实现。
视频链接:【十年老司机的单片机代码由垃圾到卓越的进化历程 - 硬件家园】 https://www.bilibili.com/video/BV16G4y1n7sf/?share_source=copy_web&vd_source=ca2c50eda2fd446eac10afe311b9873e
函数指针基本用法
结构体的资料很多,但是函数指针对于新手来说就会陌生很多,所以我在这里就先讲一讲函数指针的基本用法。会函数指针和结构体的知识可以直接看下一节内容,函数指针的内容先看本文以后再深入了解,如果连结构体都不会,建议看菜鸟教程先把最基础的用法学会,以后再深入了解。
结构体:https://www.runoob.com/cprogramming/c-structures.html
概念
这里要提到函数指针和指针函数的区别(类似与数组指针和指针数组),看以下代码:
//指针函数
int * fun1(int A, int B);
//函数指针
int (*pfun)(int x1, int x2);
int sum; //全局变量
int * fun1(int A, int B)
{
sum = A + B;
return ∑
}
指针函数: 返回值是一个指针的函数。
第2行代码的 “ int* ”要看作一个整体,表示返回值的类型,第10行返回值的时候也就加一个取地址符号,返回一个int类型的地址。
函数指针: 指向一个函数的指针。
第3行代码就是一个函数指针的声明。这里希望各位能理解的是:函数的最大的特征是圆括号(),看到(int x1, int x2)就一定要意识到这是一个函数!前面的圆括号则是为了防止返回值的类型int 和 * 先结合了,那就和第2行代码一样了。所以引入圆括号,将指针标志符*和函数名强制结合在一起,表示这个函数名它不是普通的函数名,它是一个 函数的指针 的 名字。
基本使用
函数指针的使用可以类比普通的指针:
//普通指针的用法
int data = 10; //先创建被指对象,注意指针要和被指对象类型保持一致,比如这里都是int类型
int *pdata; //指针操作1:指针的声明
pdata = &data; //指针操作2:指针的赋值
print("data = %d", *pdata);//指针操作3:调用指针
//函数指针的用法
int add(int A, int B){ return A + B; } //先创建被指对象,注意类型保持一致,而函数的类型呢就是返回值的类型和传入参数个数以及对应的类型
int (*padd)(int x1, int x2);//指针操作1:指针的声明,技巧:把被指函数的声明复制过来,把函数名改成有*和()的形式就行
padd = &add; //指针操作2:指针的赋值,最基本的语法,与普通指针的写法相呼应
padd = add; //指针操作2:指针的赋值,这个可以类比数组指针,函数名就是函数的指针
int sum = (*padd)(1, 2); //指针操作3:调用指针,最基本的语法,与普通指针的写法相呼应
int Sum = padd(2, 3); //指针操作3:调用指针,这个也是类比数组,或者说与padd = add的写法相呼应
总结一下,就是我们可以利用一个指针来存储一个函数的地址,并可以通过指针来调用它。
实现基本的OOP风格
基本原理
1、利用函数指针我们可以通过一个指针变量(重点!变量!)来间接访问函数。
2、C语言结构体可以存储变量,但不可以存储函数。
3、函数指针因此可以作为结构体的成员,而这个指针又能调用函数。
所以写代码的时候就可以写出形式上通过结构体成员调用函数的代码,用起来就好像C++代码里的类class。
具体实现过程:
步骤1、写一个结构体,结构体成员包含了一个函数指针。
步骤2、写一个函数的具体实现,要注意和函数指针类型匹配即可。
步骤3、让结构体内的函数指针指向刚刚写的函数。
然后就可以愉快的使用啦!
实例代码
#include <stdio.h>
//步骤1、写一个结构体,结构体成员包含了一个函数指针。
struct Test_struct
{
void (*report)(char * var); //声明一个函数指针
};
//步骤2、写一个函数的具体实现,要注意和函数指针类型匹配即可。
void test_report(char * var)
{
printf("已收到数据: %s\n",var);
}
int main(int argc, char** argv) {
struct Test_struct test;
//步骤3、让结构体内的函数指针指向刚刚写的函数。
test.report = test_report; //对函数指针赋值
//调用: 通过结构体调用函数指针,实则是调用函数
test.report("succeed!");
return 0;
}
改进
在C++中,经常利用 new 和 delete 关键字来管理内存,创建对象一般都是new一个。在C语言中也有malloc关键字可以用来申请内存,于是在mian()内可以这样写代码:
#include <malloc.h>
int main(int argc, char** argv) {
struct Test_struct *test = (struct Test_struct *)malloc(sizeof(struct Test_struct));
test->report = test_report;
test->report("succeed!");
free(test);
return 0;
}
参考资料
3、【指针函数与函数指针】 https://www.bilibili.com/video/BV1RJ411b7cq/?share_source=copy_web&vd_source=ca2c50eda2fd446eac10afe311b9873e
4、【函数指针和回调函数】 https://www.bilibili.com/video/BV1X44y1N7J4/?share_source=copy_web&vd_source=ca2c50eda2fd446eac10afe311b9873e