第一章 嵌入式常用裸机编程框架
第二章 面向对象编程基础
学习韦东山老师的七天物联网实战及直播课相关内容,以其课程笔记为骨,记录一下学习的过程,可能会加入一些自己的感想。
最后欢迎点赞、收藏与评论交流!
面向对象的总体思想来源《代码大全》的第5章,他把程序设计分为这以下几个层次:
为了方便阅读,本文从底层开始介绍代码,由底向上进行介绍,这样更方便阅读。本文主要介绍第4层与第3层的内容。
以按键为例进行说明:
以下代码实现以下功能,key值代表是否按下按键PF6。如果按下按键,就点亮LED灯;若没有按下按键,则关闭LED。
void main(void) { GPIO_PinState key; while (1) { key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6); if (key == GPIO_PIN_RESET) HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET); else HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET); } }
但这样写代码有明显的缺陷:
为了使自己的代码尽量远离硬件知识,我们在程序中加入大量的子函数。这样预留的API可以大大提高程序的可阅读性。方便以后自己代码的扩展,也方便与上层的算法人员进行代码的交流。
// main.c void main(void) { int key; while (1) { key = read_key(); if (key == UP) led_on(); else led_off(); } } // key.c int read_key(void) { GPIO_PinState key; key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6); if (key == GPIO_PIN_RESET) return 0; else return 1; } // led.c void led_on(void) { HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET); } void led_off(void) { HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET); }
但此时我们仅完成了
第4层:分解成子程序:实现那些结构体(结构体中有函数指针)。
当程序中包含特别多的子函数时,有串口的,LED灯的,按键的,CAN口、SPI等等,这时候大量的子函数放到一起就会很头痛,因此我们想要将一些功能相似的函数耦合起来,这样更上一层楼到进到第三层:
面向对象编程的基础是结构体与函数指针,因此在本节中我们先对函数指针和结构体进行介绍。
指针是C语言的灵魂,是必须掌握的内容,在此我先进行简单的介绍,如果后期看的人多并且点赞多的话,我会写专门的文章进行说明。
指针的基本概念是指向地址的变量,而指针也有相应的类型:例如int *P表示的是指针,并且大小为4个字节的内存的内容。其他数据类型有类似的效果。
而函数指针代表的是指向函数入口的指针变量,通过调用指针就可以找到函数入口,从而实现函数的调用。
假如有两个版本的按键读取函数,通过函数指针来确定哪一个使用哪一个版本的代码,代码如下(示例):
int (*read_key)(void); // 返回值: 0表示被按下, 1表示被松开 int read_key_ver1(void) { GPIO_PinState key; key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_5); if (key == GPIO_PIN_RESET) return 0; else return 1; } // 返回值: 0表示被按下, 1表示被松开 int read_key_ver2(void) { GPIO_PinState key; key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6); if (key == GPIO_PIN_RESET) return 0; else return 1; } void key_init() { int ver = read_hardware_ver();// 读取硬件版本,版本1读PF5,版本2读PF6 if (ver == 1) read_key = read_key_ver1; else read_key = read_key_ver2; } // main.c void main(void) { int key; key_init(); while (1) { key = read_key(); if (key == UP) led_on(); else led_off(); } }
面向对象的初衷是为了解决一类问题,因此要解决的是一类问题,即:
- 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。
在本文中就是要表达所有的按键处理函数。
本例中代表一个系统共有两个按键,通过结构体的表述代码如下所述(示例):
// key.c // 返回值: 0表示被按下, 1表示被松开 int read_key(int which) { GPIO_PinState key; switch (which) { case 0: key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6); if (key == GPIO_PIN_RESET) return 0; else return 1; break; case 1: key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7); if (key == GPIO_PIN_RESET) return 1; else return 0; break; } }
该方法在小的系统代码中使用较多,但是不太容易进行扩展,如果想要加K3的话仍然需要再写一个case分支。
改进方法是通过结构体进行函数的编写,在结构体中包含指针函数的成员,这样关于按键的操作基本都被一个结构体所包含了。具体过程如下所述:
定义一个结构体变量代码示例如下:
typedef struct key { char *name; void (*init)(struct key *k); int (*read)(struct key *k); }key, *p_key;
每个按键都实现一个key结构体:
// key1.c key k1 = {"k1", NULL, read_key1}; // key2.c key k2 = {"k2", NULL, read_key2}; // key_net.c key k_net = {"net", NULL, read_key_net};
本文主要讲述了子函数、指针函数和结构体体的内容,主要完成的是代码第3和第4层的内容;
为接下来的代码分层打下基础,在下一篇文章中我们将主要介绍第2和1层的内容。如有不清楚的,欢迎提问或指出错误。