极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 14670|回复: 0

探讨一下方法的封装

[复制链接]
发表于 2016-11-20 15:14:53 | 显示全部楼层 |阅读模式
本帖最后由 wwwymq 于 2016-11-20 15:33 编辑

函数,宏,类都能对代码进行抽象,实验代码的复用。
用一个简单的应用来探讨下。
首先大家认为单片机怎么才算是入门了?
我认为是从开始思考处理多任务,开始思考如何消除万恶delay()函数开始的。
所以说我决定用定时调用来举例:
首先是最基本的用法,刚开始肯定会写出这样的代码:
  1. unsigned long startTime, delayTime;
  2. void setup() {
  3.   // put your setup code here, to run once:
  4.   startTime = millis();
  5.   delayTime = 1000;
  6. }

  7. void loop() {
  8.   // put your main code here, to run repeatedly:
  9.   if (millis() - startTime > delayTime)
  10.   {
  11.     //做一些事情
  12.     startTime = millis();
  13.   }
  14. }
复制代码

如果同时有两个任务要运行怎么办?粘贴复制。
  1. unsigned long startTime, delayTime;
  2. unsigned long startTime1, delayTime1;
  3. void setup() {
  4.   // put your setup code here, to run once:
  5.   startTime = millis();
  6.   delayTime = 1000;
  7.   startTime1 = millis();
  8.   delayTime1 = 2000;
  9. }

  10. void loop() {
  11.   // put your main code here, to run repeatedly:
  12.   if (millis() - startTime > delayTime)
  13.   {
  14.     //做一些事情
  15.     startTime = millis();
  16.   }
  17.   if (millis() - startTime1 > delayTime1)
  18.   {
  19.     //做一些事情
  20.     startTime1 = millis();
  21.   }
  22. }
复制代码

如果有N个任务又怎么办呢,如果N超级大,我们还能粘贴复制吗?显然是不行的,这时候必须考虑吧这个功能进行封装。
在封装一个函数的时候,一定要考虑函数的可重入问题。
那么什么样的函数是可重入的呢?就是在函数在一个地方调用的时候不会影响到另一个地方的调用,这就必须要求我们的数据和功能是分离的。
  1. unsigned long startTime, delayTime;
  2. unsigned long startTime1, delayTime1;
  3. typedef void (*action)();//回调函数指针
  4. void timer(unsigned long *, action , unsigned long) ;
  5. void setup() {
  6.   // put your setup code here, to run once:
  7.   startTime = millis();
  8.   delayTime = 1000;
  9.   startTime1 = millis();
  10.   delayTime1 = 2000;
  11.   Serial.begin(9600);
  12. }

  13. void loop() {
  14.   // put your main code here, to run repeatedly:
  15.   timer(&startTime, things, delayTime);
  16.   timer(&startTime1, things, delayTime1);
  17. }
  18. void timer(unsigned long *startTime, action doSomething, unsigned long delayTime) {
  19.   if (millis() - *startTime > delayTime)
  20.   {
  21.     //做一些事情
  22.     doSomething();
  23.     *startTime = millis();
  24.   }
  25. }
  26. void things() {
  27.   Serial.println("hello");
  28. }
复制代码

如果要更整洁一些可以这样写,把细节封装到结构体中。
  1. typedef void (*action)();//回调函数指针
  2. typedef struct timStruct//定时结构体
  3. {
  4.   unsigned long startTime;
  5.   action func;
  6. };
  7. void timer(timStruct *, unsigned long) ;
  8. timStruct tim1, tim2;
  9. void setup() {
  10.   // put your setup code here, to run once:
  11.   tim1.startTime = millis;
  12.   tim1.func = things;
  13.   tim2.startTime = millis;
  14.   tim2.func = things;

  15.   Serial.begin(9600);
  16. }

  17. void loop() {
  18.   // put your main code here, to run repeatedly:
  19.   timer(&tim1, 1000);
  20.   timer(&tim2, 2000);
  21. }
  22. void timer(timStruct *tim, unsigned long delayTime) {
  23.   if (millis() - tim->startTime > delayTime)
  24.   {
  25.     //做一些事情
  26.     tim->func();
  27.     tim->startTime = millis();
  28.   }
  29. }
  30. void things() {
  31.   Serial.println("hello");
  32. }
复制代码

其实仔细思考一下,startTime这个参数并不是我想知道的,用户只关心定时器在什么时候做什么事情,而不是关心实现的细节,只有doSomething和delayTime是需要关心的,因此应该把start这个参数藏起来,而这个工作,就是cpp中的类,面向对象编程所完成的工作。
  1. typedef void (*action)();//回调函数指针

  2. class timer
  3. {
  4.   private:
  5.     unsigned long startTime;
  6.     static void kong() {
  7.       Serial.println("nothing");
  8.     }
  9.   public:
  10.     action func;
  11.     timer(action);
  12.     timer();
  13.     Run(unsigned long delayTime);
  14. };

  15. timer tim1, tim2;
  16. void setup() {
  17.   // put your setup code here, to run once:
  18.   tim1 = timer(things);
  19.   tim2 = timer();
  20.   Serial.begin(9600);
  21. }

  22. void loop() {
  23.   // put your main code here, to run repeatedly:
  24.   tim1.Run(1000);
  25.   tim2.Run(2000);
  26. }
  27. void things() {
  28.   Serial.println("hello");
  29. }
  30. timer::timer(action doSomething)
  31. {
  32.   func = doSomething;
  33. }
  34. timer::timer()
  35. {
  36.   func = kong;
  37. }
  38. timer::Run(unsigned long delayTime)
  39. {
  40.   if (millis() - startTime > delayTime)
  41.   {
  42.     //做一些事情
  43.     func();
  44.     startTime = millis();
  45.   }
  46. }
复制代码

只要把类的声明放到.h文件中,实现放到.cpp文件中,就完成了一个方法的封装。
最后看看用宏如何封装
  1. #define timer(func,delayTime)({\
  2.     static unsigned long startTime = millis();\
  3.     if (millis() - startTime > delayTime){\
  4.       func; \
  5.       startTime = millis(); \
  6.     }\
  7.   })
  8. void setup() {
  9.   Serial.begin(9600);
  10. }
  11. void loop() {
  12.   // put your main code here, to run repeatedly:
  13.   timer(things(), 1000);
  14.   timer(Serial.println("timer2"), 2000);
  15. }
  16. void things() {
  17.   Serial.println("hello");
  18. }
复制代码

可以看出来,用宏封装异常的简洁,在宏中用static定义的静态变量实现了数据上的隔离,每一个都是独立的副本,同时还能在完成初始化的工作。在方法的调用上也是最灵活的,可以写入任意的表达式,而不一定要局限于函数指针。
但是宏也只限于封装比较简单的方法,过于复杂的方法会造成代码量过大,同时也不容易改错,思路不清晰时会很不安全。
但就个人而言认为用c语言加传递结构体指针的方法更加通用,毕竟不是所有的单片机都有c++编译器的。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-27 11:54 , Processed in 0.037676 second(s), 17 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表