感受面向对象编程的魅力(uCosII C++类封装篇)
作者:exdata
OO思想已经出现了20多年,各种成功的实践告诉我们它是软件开发的必然趋势。面向对象编程(OOP)的一个关键原则之一就是封装(encapsulation),把暴露的数据封装起来,尽可能的让对象管理它们自己的状态,因为过多的依存性会造就紧耦合(highly coupled)系统,使得任何一点小小改动都可能造成许多无法预料的结果。而数据隐藏/封装机制是一个控制对象数据和状态强而有力的方法,它对外部世界隐藏其内部细节,这就意味着每一个对象都应该尽可能少的了解系统的其他部分或者被其他部分所了解,这样一来一旦发生了变化,需要了解这一个变化的对象会比较少,因此变化也就相对容易地进行。
C++之父Bjarne Stroustrup也曾说过:"你使用一个语言特征是因为你需要它,而不是因为它存在"。设计ucClass(uCosII封装类)的初衷是由于过去很多的实现都是在C++中完成,深切感受到面向对象分析、设计乃至编程的独特魅力。回想微软的MFC1为win32程序设计所带来的便利、DriverWork2在PC硬件的驱动开发的高效,促使完成ucClass的设计(这里头没有什么高深的学问),其实更重要的一点是它能改进现有的设计模式,能让今后的uCosII相关开发及维护变得更加轻松和高效!
对于OO我也就几年的开发经验,没有做过什么"巨项",对于ARM也是接触时间不长,不过有一个概念一直影响着我,"ARM不就是复杂一点的单片机吗",我想也不应该难到那里去。于是有空就玩玩,对于很多做这行做开发的对C++这样的面向对象编程语言大多不甚了解,要想用OO思想来武装自己又谈何容易啊,这就是一种挑战习惯、挑战思维方式的行为,等于放弃过去曾经为你有效解决难题完成工作的开发模式。在此我也没有什么灵丹妙药可改变这些处境,唯一的建议就是坚持及不断实践、只有这样的思想基础才能提高成功的机会。
下面简单设计一个ucClass的demo程序(当然会可能隐藏了一些bug or error),目标板使用的是菲利普LPC2100芯片(周立功的EasyARM 2100开发板),有些头文件这里就不贴了,旨在看看主程序的结构及设计过程,就让一些软件的大虾们见笑了。
若有机会的我还准备写几个基于OO之上的在嵌入式开发的应用demo……
设计需求:
使用ucClass设计一个demo程序完成下列任务:
任务编号 任务指责和功能描述 物理资源占用
1 每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量通知任务2,随后自身进入挂起等待状态。 蜂鸣器及IO端口
2 无限等待由任务1触发的信号量,当有信号时引发1秒蜂鸣器长鸣,其后恢复任务1,自身再次进入信号量的无限等待状态中。 蜂鸣器及IO端口
3 孤立任务,简单的一秒间隔led1闪烁。 Led1及IO端口
4 孤立任务,通过检查key1按键状态控制led4的二值状态(亮/灭)。 key1,led4及IO端口
// main.cpp文件,ucClass测试主程序
// exdata 2004-8-18
#include "config.h" // 一些硬件相关的声明
#include "GPIO.H" // GPIO类头文件
#include "ucClass.H" // uCosII的收集类库头文件
/*
// 在浏览demo程序时要注意以下几点:
// 1,带参数的对象构造
// 2,静态函数调用
// 3,带参数的派生类构造
// 4,虚拟函数重载
// 5,特别注意一些头文件(不在此文档中)包含写法及原有C函数声明方法,关注extern "C"
// 6,不拘泥于传统的设计思想(模式)
// 7,比较不使用class时候又是如何处理的?分析每个对象背后隐藏了什么?
*/
/************************** 全局变量 *****************************/
// 定义几个task对象,这是实例化对象,隐藏并实现了任务栈的构造
CTask t1(128); // 参数是任务堆栈分配大小(在ARM中以32bit为单位)
CTask t2(64);
CTask t3(64);
CSem t2WaitEvent; // 定义一个信号量对象用于任务t1、t2通信
CGPIO buzz("P0.7",1); // 定义一个GPIO对象,输出模式,用于控制蜂鸣器
/*
// 前置声明几个任务运行函数,这个与C模式下没有什么区别。
// 还保留这种处理方法仅仅为求兼顾一些使用uCosII习惯,建议参考CMyTask的
// 设计及t4的定义,使用子类设计实现具体任务的处理方法要比C模式的的全局
// 孤立启动函数更具有设计上及维护上的优点,当做一个中大型项目时候效果尤
// 为明显。
*/
void t1Run(void *p);
void t2Run(void *p);
void t3Run(void *p);
/*******************************************************************/
/*
// t1任务运行函数。
// 功能:每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量t2WaitEvent通
// 知任务t2,随后自身进入挂起等待状态。
*/
void t1Run(void *p)
{
TargetInit(); // 初始化硬件目标板
INT32U i = 0;
while(1)
{
CuCos::TimeDlyHMSM(0,0,1,0);
if(++i == 10)
{
i = 0;
t2WaitEvent.Post();
t1.Suspend();
}
// 蜂鸣器短促发声两次
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Set();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Set();
}
p = p;
}
/*
// t2任务运行函数。
// 功能:无限等待信号量t2WaitEvent,当有信号时引发1秒蜂鸣器长鸣,其后恢
// 复t1任务,任务t2再次自身进入信号量t2WaitEvent的无限等待状态。
*/
void t2Run(void *p)
{
INT8U err = 0;
while(1)
{
t2WaitEvent.Pend(0,&err);
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,1,0);
buzz.Set();
CuCos::TimeDlyHMSM(0,0,0,50);
t1.Resume();
}
p = p;
}
/*
// t3任务运行函数。
// 功能:简单的一秒间隔led1闪烁。
*/
void t3Run(void *p)
{
static CGPIO led1("P0.22",1);
while(1)
{
led1.Set();
CuCos::TimeDlyHMSM(0,0,1,0);
led1.Clear();
CuCos::TimeDlyHMSM(0,0,1,0);
}
p = p;
}
/*
// 为求原汁原味体现一个task对象的封装,下面设计一个子类实现一个特定任务
// 的处理,在此我们可以认为一个任务就是一个子系统,通过一个子类表现一个
// 特有任务的具体属性和行为,注意这种设计方法与传统的面向对象编程模式是
// 有区别的。子系统是一个有自主能力主体,例如在MyTask子系统中我们有两
// 个IO资源m_key1和m_led4分别对应着电路板上的按键Key1和发光二极管Led4,
// 通过检查key1按键状态控制led4的二值状态(亮/灭)。重载基类了Run函数为
// 求体现类的封装特性及表现一个任务的内聚能力,它是一个强制的运行接口,
// 当任务启动时便能自动地从重载的Run函数开始运行(具体调用的中转过程在
// 基类中已经实现)。
*/
class CMyTask : public CTask // 注意这里的派生关系!!!
{
public:
CMyTask(INT32U StkSize) : CTask(StkSize),m_key1("P0.16",0),m_led4("P0.25",1,0)
{
// 注意带参数的构造处理方法!!!
}
protected:
virtual void Run(void *p); // 需要重载Run()函数实现任务执行
private:
// 这是任务用到的一些资源定义,当然添加子系统的其他的属性和方法也是允许的
CGPIO m_key1;
CGPIO m_led4;
};
/*
// t4任务运行函数。
// 通过函数重载实现重写Run函数,该函数也就是任务运行函数。
// 功能:通过检查key1按键状态控制led4的二值状态(亮/灭)。
*/
void CMyTask::Run(void *p) // Run函数将会被uCosII系统自动运行!
{
while(1)
{
if(m_key1.GetStatus() == 0) // 判断按键是否按下
{
if(m_led4.GetStatus() == 0)
{
m_led4.Set();
}
else
{
m_led4.Clear();
}
while(m_key1.GetStatus() == 0)
{
CuCos::TimeDly(20); // 等待按键释放
}
}
else
{
CuCos::TimeDly(10);
}
}
p = p;
}
/*
// 注意一般应该把上面的CMyTask定义及实现另行写在其他的文件中!把它写在
// 这里仅仅为求说明方便。
*/
/*-------------------------------------------------------------------------------------------------*/
CMyTask t4(128); //注意任务对象t4是由CMyTask实例化的对象
// 看看这个简洁的main函数,这时候您想到了什么?
int main()
{
CuCos::Init(); // uCosII的系统函数,用于初始化uCosII,等效于OSInit()
t2WaitEvent.Create(); // 创建t2WaitEvent的信号量
// 创建任务(参数指定任务函数启动地址、任务函数参数和优先级)
t1.Create(t1Run,NULL,1);
t2.Create(t2Run,NULL,2);
t3.Create(t3Run,NULL,3);
t4.Create(NULL,4); // 创建任务,注意Create函数的重载
CuCos::Start(); // 等效于OSStart()
return 0;
}
关于我……:
我是一个不折不扣硬件的狂热者、焊机派,自会使用圆珠笔的年龄开始就爱上了烙铁,开始玩弄万用表,在高考后足足整理了两箱电路板运回老家,因为要到外地上学我这些家当父母都帮我好好的"封存"。我可以自信及自豪地对那些比我大10岁的硬件工程师说:我焊板子的时候你还不知道什么是电阻电容呢!虽然对硬件有丰富感性认识及解不开的情结,但我现在清楚的明白到一点,就算是天天在长江黄河里泡大的孩子也不是个个都能登上奥运游泳项目的领奖台,近些年硬件的发展也很快,现在自己在硬件上的工作也相对较少,没有什么锻炼的机会,三人行,必有我师,现在身边牛人不少,也是一个学习的机会。对于软件开发我也不是行家(更谈不上什么程序员),比起有十几年开发经验的老前辈还是太嫩!用老江的话说就是too simple、sometimes naive!不过有爱好就可以发烧(在低于0度的室温下对着黑底白字的debug窗口熬上几个夜晚通宵不是一件容易的事情……这样一不小心就回发烧~~~)。以前做51也是从ASM开始,马老和Franklin带给我第一次[硬件+C]的喜悦。在大二的时候就和毕业生混在一起搞毕业设计,现在想起来这分明就是害了人家,不分明是一个抢手么?虽然不是求什么利益,只在乎有发挥和动手的机会,对这些锻炼我乐此不疲,接下来的大学生活都是在项目中度过(错失了很多陪mm花前月下的机会~~~),那时候软硬件都做(专门有中间人联系或者朋友介绍),就大三一年下来的开发报酬剩下来的就大于10K(对于很多工作后的人来说这也算不了什么,明显我也不是一个挥霍的人)。那时候在学校带领着一个学生的科技团体,为学校、组织争得不少得荣誉,这当然也包括为自己。回想过去,更值得怀念得是那些志同道合的朋友们,有师兄、有同学还有师弟和体谅我的学院和老师……,无论是技术还是生活上都不能缺少,有着开心快乐的日子……!现在在一家公司做开发,软件硬件也不怎么分了,自从做了一个硬件项目后就好像转向了软件开发(怀疑在掉价,他们都说做硬件的吃香、干得久),反正现开的工作性质还算开心愉快、又不怎么需要养家糊口的,所以生活还算轻松(就是回家后要自己做饭,呜呜~~~)。
说了这么多,我只想说明的一点是我并不是写了几个hello world就用C++在这里大呼小叫,一些经验一些想法都是实践的体会,拿上来献丑不要介意p,很多时候只想分享一下开发中的愉悦,为求抛砖引玉。
技术从来都只是一种手段,一个好的方法可让您少掉好几根头发,养妻活儿我可不敢包,但是做一个sohu的开发人员总是可以的吧,若是自娱自乐的则有更高的境界和追求。
多掌握一门技能就就多一条出路,生活多了一把小刀,工作也就少一点磨难。知识和学问最终以经验的形式沉淀在你的大脑中,是你终生受用!
说多了,就此罢了。注意:如有雷同,实属巧合,切勿对号入座。
愿大家工作愉快!
BTW,今天开学了,非常怀念我远方的同学和朋友,愿他们身体健康,在生活及技术道路上一马平川!
作者:exdata
OO思想已经出现了20多年,各种成功的实践告诉我们它是软件开发的必然趋势。面向对象编程(OOP)的一个关键原则之一就是封装(encapsulation),把暴露的数据封装起来,尽可能的让对象管理它们自己的状态,因为过多的依存性会造就紧耦合(highly coupled)系统,使得任何一点小小改动都可能造成许多无法预料的结果。而数据隐藏/封装机制是一个控制对象数据和状态强而有力的方法,它对外部世界隐藏其内部细节,这就意味着每一个对象都应该尽可能少的了解系统的其他部分或者被其他部分所了解,这样一来一旦发生了变化,需要了解这一个变化的对象会比较少,因此变化也就相对容易地进行。
C++之父Bjarne Stroustrup也曾说过:"你使用一个语言特征是因为你需要它,而不是因为它存在"。设计ucClass(uCosII封装类)的初衷是由于过去很多的实现都是在C++中完成,深切感受到面向对象分析、设计乃至编程的独特魅力。回想微软的MFC1为win32程序设计所带来的便利、DriverWork2在PC硬件的驱动开发的高效,促使完成ucClass的设计(这里头没有什么高深的学问),其实更重要的一点是它能改进现有的设计模式,能让今后的uCosII相关开发及维护变得更加轻松和高效!
对于OO我也就几年的开发经验,没有做过什么"巨项",对于ARM也是接触时间不长,不过有一个概念一直影响着我,"ARM不就是复杂一点的单片机吗",我想也不应该难到那里去。于是有空就玩玩,对于很多做这行做开发的对C++这样的面向对象编程语言大多不甚了解,要想用OO思想来武装自己又谈何容易啊,这就是一种挑战习惯、挑战思维方式的行为,等于放弃过去曾经为你有效解决难题完成工作的开发模式。在此我也没有什么灵丹妙药可改变这些处境,唯一的建议就是坚持及不断实践、只有这样的思想基础才能提高成功的机会。
下面简单设计一个ucClass的demo程序(当然会可能隐藏了一些bug or error),目标板使用的是菲利普LPC2100芯片(周立功的EasyARM 2100开发板),有些头文件这里就不贴了,旨在看看主程序的结构及设计过程,就让一些软件的大虾们见笑了。
若有机会的我还准备写几个基于OO之上的在嵌入式开发的应用demo……
设计需求:
使用ucClass设计一个demo程序完成下列任务:
任务编号 任务指责和功能描述 物理资源占用
1 每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量通知任务2,随后自身进入挂起等待状态。 蜂鸣器及IO端口
2 无限等待由任务1触发的信号量,当有信号时引发1秒蜂鸣器长鸣,其后恢复任务1,自身再次进入信号量的无限等待状态中。 蜂鸣器及IO端口
3 孤立任务,简单的一秒间隔led1闪烁。 Led1及IO端口
4 孤立任务,通过检查key1按键状态控制led4的二值状态(亮/灭)。 key1,led4及IO端口
// main.cpp文件,ucClass测试主程序
// exdata 2004-8-18
#include "config.h" // 一些硬件相关的声明
#include "GPIO.H" // GPIO类头文件
#include "ucClass.H" // uCosII的收集类库头文件
/*
// 在浏览demo程序时要注意以下几点:
// 1,带参数的对象构造
// 2,静态函数调用
// 3,带参数的派生类构造
// 4,虚拟函数重载
// 5,特别注意一些头文件(不在此文档中)包含写法及原有C函数声明方法,关注extern "C"
// 6,不拘泥于传统的设计思想(模式)
// 7,比较不使用class时候又是如何处理的?分析每个对象背后隐藏了什么?
*/
/************************** 全局变量 *****************************/
// 定义几个task对象,这是实例化对象,隐藏并实现了任务栈的构造
CTask t1(128); // 参数是任务堆栈分配大小(在ARM中以32bit为单位)
CTask t2(64);
CTask t3(64);
CSem t2WaitEvent; // 定义一个信号量对象用于任务t1、t2通信
CGPIO buzz("P0.7",1); // 定义一个GPIO对象,输出模式,用于控制蜂鸣器
/*
// 前置声明几个任务运行函数,这个与C模式下没有什么区别。
// 还保留这种处理方法仅仅为求兼顾一些使用uCosII习惯,建议参考CMyTask的
// 设计及t4的定义,使用子类设计实现具体任务的处理方法要比C模式的的全局
// 孤立启动函数更具有设计上及维护上的优点,当做一个中大型项目时候效果尤
// 为明显。
*/
void t1Run(void *p);
void t2Run(void *p);
void t3Run(void *p);
/*******************************************************************/
/*
// t1任务运行函数。
// 功能:每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量t2WaitEvent通
// 知任务t2,随后自身进入挂起等待状态。
*/
void t1Run(void *p)
{
TargetInit(); // 初始化硬件目标板
INT32U i = 0;
while(1)
{
CuCos::TimeDlyHMSM(0,0,1,0);
if(++i == 10)
{
i = 0;
t2WaitEvent.Post();
t1.Suspend();
}
// 蜂鸣器短促发声两次
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Set();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,0,50);
buzz.Set();
}
p = p;
}
/*
// t2任务运行函数。
// 功能:无限等待信号量t2WaitEvent,当有信号时引发1秒蜂鸣器长鸣,其后恢
// 复t1任务,任务t2再次自身进入信号量t2WaitEvent的无限等待状态。
*/
void t2Run(void *p)
{
INT8U err = 0;
while(1)
{
t2WaitEvent.Pend(0,&err);
buzz.Clear();
CuCos::TimeDlyHMSM(0,0,1,0);
buzz.Set();
CuCos::TimeDlyHMSM(0,0,0,50);
t1.Resume();
}
p = p;
}
/*
// t3任务运行函数。
// 功能:简单的一秒间隔led1闪烁。
*/
void t3Run(void *p)
{
static CGPIO led1("P0.22",1);
while(1)
{
led1.Set();
CuCos::TimeDlyHMSM(0,0,1,0);
led1.Clear();
CuCos::TimeDlyHMSM(0,0,1,0);
}
p = p;
}
/*
// 为求原汁原味体现一个task对象的封装,下面设计一个子类实现一个特定任务
// 的处理,在此我们可以认为一个任务就是一个子系统,通过一个子类表现一个
// 特有任务的具体属性和行为,注意这种设计方法与传统的面向对象编程模式是
// 有区别的。子系统是一个有自主能力主体,例如在MyTask子系统中我们有两
// 个IO资源m_key1和m_led4分别对应着电路板上的按键Key1和发光二极管Led4,
// 通过检查key1按键状态控制led4的二值状态(亮/灭)。重载基类了Run函数为
// 求体现类的封装特性及表现一个任务的内聚能力,它是一个强制的运行接口,
// 当任务启动时便能自动地从重载的Run函数开始运行(具体调用的中转过程在
// 基类中已经实现)。
*/
class CMyTask : public CTask // 注意这里的派生关系!!!
{
public:
CMyTask(INT32U StkSize) : CTask(StkSize),m_key1("P0.16",0),m_led4("P0.25",1,0)
{
// 注意带参数的构造处理方法!!!
}
protected:
virtual void Run(void *p); // 需要重载Run()函数实现任务执行
private:
// 这是任务用到的一些资源定义,当然添加子系统的其他的属性和方法也是允许的
CGPIO m_key1;
CGPIO m_led4;
};
/*
// t4任务运行函数。
// 通过函数重载实现重写Run函数,该函数也就是任务运行函数。
// 功能:通过检查key1按键状态控制led4的二值状态(亮/灭)。
*/
void CMyTask::Run(void *p) // Run函数将会被uCosII系统自动运行!
{
while(1)
{
if(m_key1.GetStatus() == 0) // 判断按键是否按下
{
if(m_led4.GetStatus() == 0)
{
m_led4.Set();
}
else
{
m_led4.Clear();
}
while(m_key1.GetStatus() == 0)
{
CuCos::TimeDly(20); // 等待按键释放
}
}
else
{
CuCos::TimeDly(10);
}
}
p = p;
}
/*
// 注意一般应该把上面的CMyTask定义及实现另行写在其他的文件中!把它写在
// 这里仅仅为求说明方便。
*/
/*-------------------------------------------------------------------------------------------------*/
CMyTask t4(128); //注意任务对象t4是由CMyTask实例化的对象
// 看看这个简洁的main函数,这时候您想到了什么?
int main()
{
CuCos::Init(); // uCosII的系统函数,用于初始化uCosII,等效于OSInit()
t2WaitEvent.Create(); // 创建t2WaitEvent的信号量
// 创建任务(参数指定任务函数启动地址、任务函数参数和优先级)
t1.Create(t1Run,NULL,1);
t2.Create(t2Run,NULL,2);
t3.Create(t3Run,NULL,3);
t4.Create(NULL,4); // 创建任务,注意Create函数的重载
CuCos::Start(); // 等效于OSStart()
return 0;
}
关于我……:
我是一个不折不扣硬件的狂热者、焊机派,自会使用圆珠笔的年龄开始就爱上了烙铁,开始玩弄万用表,在高考后足足整理了两箱电路板运回老家,因为要到外地上学我这些家当父母都帮我好好的"封存"。我可以自信及自豪地对那些比我大10岁的硬件工程师说:我焊板子的时候你还不知道什么是电阻电容呢!虽然对硬件有丰富感性认识及解不开的情结,但我现在清楚的明白到一点,就算是天天在长江黄河里泡大的孩子也不是个个都能登上奥运游泳项目的领奖台,近些年硬件的发展也很快,现在自己在硬件上的工作也相对较少,没有什么锻炼的机会,三人行,必有我师,现在身边牛人不少,也是一个学习的机会。对于软件开发我也不是行家(更谈不上什么程序员),比起有十几年开发经验的老前辈还是太嫩!用老江的话说就是too simple、sometimes naive!不过有爱好就可以发烧(在低于0度的室温下对着黑底白字的debug窗口熬上几个夜晚通宵不是一件容易的事情……这样一不小心就回发烧~~~)。以前做51也是从ASM开始,马老和Franklin带给我第一次[硬件+C]的喜悦。在大二的时候就和毕业生混在一起搞毕业设计,现在想起来这分明就是害了人家,不分明是一个抢手么?虽然不是求什么利益,只在乎有发挥和动手的机会,对这些锻炼我乐此不疲,接下来的大学生活都是在项目中度过(错失了很多陪mm花前月下的机会~~~),那时候软硬件都做(专门有中间人联系或者朋友介绍),就大三一年下来的开发报酬剩下来的就大于10K(对于很多工作后的人来说这也算不了什么,明显我也不是一个挥霍的人)。那时候在学校带领着一个学生的科技团体,为学校、组织争得不少得荣誉,这当然也包括为自己。回想过去,更值得怀念得是那些志同道合的朋友们,有师兄、有同学还有师弟和体谅我的学院和老师……,无论是技术还是生活上都不能缺少,有着开心快乐的日子……!现在在一家公司做开发,软件硬件也不怎么分了,自从做了一个硬件项目后就好像转向了软件开发(怀疑在掉价,他们都说做硬件的吃香、干得久),反正现开的工作性质还算开心愉快、又不怎么需要养家糊口的,所以生活还算轻松(就是回家后要自己做饭,呜呜~~~)。
说了这么多,我只想说明的一点是我并不是写了几个hello world就用C++在这里大呼小叫,一些经验一些想法都是实践的体会,拿上来献丑不要介意p,很多时候只想分享一下开发中的愉悦,为求抛砖引玉。
技术从来都只是一种手段,一个好的方法可让您少掉好几根头发,养妻活儿我可不敢包,但是做一个sohu的开发人员总是可以的吧,若是自娱自乐的则有更高的境界和追求。
多掌握一门技能就就多一条出路,生活多了一把小刀,工作也就少一点磨难。知识和学问最终以经验的形式沉淀在你的大脑中,是你终生受用!
说多了,就此罢了。注意:如有雷同,实属巧合,切勿对号入座。
愿大家工作愉快!
BTW,今天开学了,非常怀念我远方的同学和朋友,愿他们身体健康,在生活及技术道路上一马平川!
评论
发表评论