本帖最后由 wwwymq 于 2016-11-20 15:33 编辑
函数,宏,类都能对代码进行抽象,实验代码的复用。
用一个简单的应用来探讨下。
首先大家认为单片机怎么才算是入门了?
我认为是从开始思考处理多任务,开始思考如何消除万恶delay()函数开始的。
所以说我决定用定时调用来举例:
首先是最基本的用法,刚开始肯定会写出这样的代码: - unsigned long startTime, delayTime;
- void setup() {
- // put your setup code here, to run once:
- startTime = millis();
- delayTime = 1000;
- }
- void loop() {
- // put your main code here, to run repeatedly:
- if (millis() - startTime > delayTime)
- {
- //做一些事情
- startTime = millis();
- }
- }
复制代码
如果同时有两个任务要运行怎么办?粘贴复制。 - unsigned long startTime, delayTime;
- unsigned long startTime1, delayTime1;
- void setup() {
- // put your setup code here, to run once:
- startTime = millis();
- delayTime = 1000;
- startTime1 = millis();
- delayTime1 = 2000;
- }
- void loop() {
- // put your main code here, to run repeatedly:
- if (millis() - startTime > delayTime)
- {
- //做一些事情
- startTime = millis();
- }
- if (millis() - startTime1 > delayTime1)
- {
- //做一些事情
- startTime1 = millis();
- }
- }
复制代码
如果有N个任务又怎么办呢,如果N超级大,我们还能粘贴复制吗?显然是不行的,这时候必须考虑吧这个功能进行封装。
在封装一个函数的时候,一定要考虑函数的可重入问题。
那么什么样的函数是可重入的呢?就是在函数在一个地方调用的时候不会影响到另一个地方的调用,这就必须要求我们的数据和功能是分离的。 - unsigned long startTime, delayTime;
- unsigned long startTime1, delayTime1;
- typedef void (*action)();//回调函数指针
- void timer(unsigned long *, action , unsigned long) ;
- void setup() {
- // put your setup code here, to run once:
- startTime = millis();
- delayTime = 1000;
- startTime1 = millis();
- delayTime1 = 2000;
- Serial.begin(9600);
- }
- void loop() {
- // put your main code here, to run repeatedly:
- timer(&startTime, things, delayTime);
- timer(&startTime1, things, delayTime1);
- }
- void timer(unsigned long *startTime, action doSomething, unsigned long delayTime) {
- if (millis() - *startTime > delayTime)
- {
- //做一些事情
- doSomething();
- *startTime = millis();
- }
- }
- void things() {
- Serial.println("hello");
- }
复制代码
如果要更整洁一些可以这样写,把细节封装到结构体中。 - typedef void (*action)();//回调函数指针
- typedef struct timStruct//定时结构体
- {
- unsigned long startTime;
- action func;
- };
- void timer(timStruct *, unsigned long) ;
- timStruct tim1, tim2;
- void setup() {
- // put your setup code here, to run once:
- tim1.startTime = millis;
- tim1.func = things;
- tim2.startTime = millis;
- tim2.func = things;
- Serial.begin(9600);
- }
- void loop() {
- // put your main code here, to run repeatedly:
- timer(&tim1, 1000);
- timer(&tim2, 2000);
- }
- void timer(timStruct *tim, unsigned long delayTime) {
- if (millis() - tim->startTime > delayTime)
- {
- //做一些事情
- tim->func();
- tim->startTime = millis();
- }
- }
- void things() {
- Serial.println("hello");
- }
复制代码
其实仔细思考一下,startTime这个参数并不是我想知道的,用户只关心定时器在什么时候做什么事情,而不是关心实现的细节,只有doSomething和delayTime是需要关心的,因此应该把start这个参数藏起来,而这个工作,就是cpp中的类,面向对象编程所完成的工作。 - typedef void (*action)();//回调函数指针
- class timer
- {
- private:
- unsigned long startTime;
- static void kong() {
- Serial.println("nothing");
- }
- public:
- action func;
- timer(action);
- timer();
- Run(unsigned long delayTime);
- };
- timer tim1, tim2;
- void setup() {
- // put your setup code here, to run once:
- tim1 = timer(things);
- tim2 = timer();
- Serial.begin(9600);
- }
- void loop() {
- // put your main code here, to run repeatedly:
- tim1.Run(1000);
- tim2.Run(2000);
- }
- void things() {
- Serial.println("hello");
- }
- timer::timer(action doSomething)
- {
- func = doSomething;
- }
- timer::timer()
- {
- func = kong;
- }
- timer::Run(unsigned long delayTime)
- {
- if (millis() - startTime > delayTime)
- {
- //做一些事情
- func();
- startTime = millis();
- }
- }
复制代码
只要把类的声明放到.h文件中,实现放到.cpp文件中,就完成了一个方法的封装。
最后看看用宏如何封装 - #define timer(func,delayTime)({\
- static unsigned long startTime = millis();\
- if (millis() - startTime > delayTime){\
- func; \
- startTime = millis(); \
- }\
- })
- void setup() {
- Serial.begin(9600);
- }
- void loop() {
- // put your main code here, to run repeatedly:
- timer(things(), 1000);
- timer(Serial.println("timer2"), 2000);
- }
- void things() {
- Serial.println("hello");
- }
复制代码
可以看出来,用宏封装异常的简洁,在宏中用static定义的静态变量实现了数据上的隔离,每一个都是独立的副本,同时还能在完成初始化的工作。在方法的调用上也是最灵活的,可以写入任意的表达式,而不一定要局限于函数指针。
但是宏也只限于封装比较简单的方法,过于复杂的方法会造成代码量过大,同时也不容易改错,思路不清晰时会很不安全。
但就个人而言认为用c语言加传递结构体指针的方法更加通用,毕竟不是所有的单片机都有c++编译器的。 |