嵌入式中级编程思想,浅说5000字

摘要:没有思想的裸程序就如一副人体骨架,有个人形,但没有人样,骨骼之间的关节都是靠胶水或拉线连接起来的,生硬而呆板。假如给骨架包上皮肉,加上灵魂,我们就会惊叹:啊!这是帅哥,这是美女!由于骨架活了。

///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程,想要的同学找我拿就行了免費的,私信我就可以哦~点我头像黑色字体加我地球呺也能领取哦。最近比较闲,带做毕设,带学生参加省级或以上比赛///

正文开始:

一、裸编程是什么?

先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系统,不要想歪咯。

在裸机上编程,就犹如在一片荒地上开垦,任何一锄头下去,都会碰到硬生生的石头,要说做这有什么味?拓荒者追求的是来年的绿洲。而我们这些开垦裸机的所谓的工程师们追求的是什么?我们当然追求的是完成一个任务。

我们一般都自称是高级知识分子,那么我们在拓荒的过程中应该想些什么?当然不是想着如何把任务完成,而应该首先想着我们在想些什么。绕了是不?绕了就对了,这一绕就绕出了思想。思想是一个简略的人在一个复杂的环境里做任何事情的统帅,它影响着一个拓荒者人生的每一个细节,当然也包括裸编程自身。

当一个人拿着锄头,一锄又一锄,汗滴脚下土的时候,我们能知道他们在想什么吗?当然这不好说,假如自己去锄就知道了。但是大抵也差不多,随便举几个吧:这太阳他娘的怎么这么毒?这石头他娘的咋这么多?这地种什么最好?这还有多少天能搞完?这样干太慢了,要是有台机械搞多好。当然这只是一局部,任何人能够想出很多想法来。

那么当我们在裸机上拓荒的时候,我们该想些什么?兴许我们一般的想法是:先把一个简略的功能做了,先把一个重要的功能做了,今天终于把这个功能调试好了明天能够做下一个功能了,这个为什么不是我想象的那样的结果?真是莫名其妙!也等等一下吧。

假如拿来一个任务,搭好测试平台就初始做程序,想着一个功能一个功能的凑完,然后就自我陶醉着成功的喜悦,那这样做程序,根本就叫做没思想。有思想的做程序,是不能一下去就堆积源码的,由于那样只会让一堆生硬的数字怯生生的挤在一起,不管他们有没有多余,有没有矛盾。所以写源码之前,是要想想如何写的。兴许很多人在写之前都想过类似的问题,假如把任务模块化后再组织程序。但是这样的想法只是任务上的事情,而并不是裸编程时的思想,裸编程的思想,应该是在组织任务模块过程中及编写裸程序时影响源码组织的指教思想,它直接决定着源码的质量。

一个数据构造,一个模块构成,一个单片机的指令,一个硬指令的运行机制,一个口线的驱动方式,一个中断的顺序,一个跳变的延迟,一个代码的位置,一个逻辑的组织,一个模块与模块之间的生(运行时的状态)死(不运行时的状态)关系等等,都是裸程序思想的组成局部。

这似乎很琐碎,但是裸程序原本就如此,它不同于上位机程序,有一个强大完善的操作系统支持。单片机里不可能植入操作系统,那样做就变味了,可不要有人跳出来说,某某某单片机就有操作系统了。裸程序就应该是建设在赤裸裸的硬件根底上的程序,独有有用的功能才有代码,裸程序的质量兴许经常在应用中感觉不出来,兴许你做和他做都能实现功能,但是好的裸程序有良好的可扩充性、可维护性,系统具有高稳定性和高性能。

而追求这种高品位的技术境界,就必需要有好的思想来指教。是不是看着有些迷糊?别说看得迷糊,我说都说迷糊了,总的来说,就是把一个优秀的灵魂,植入你的源码中,让你的源码具有一个良好的思想。

二、裸编程详细做法

前文说到裸编程要有思想,兴许还不够详细,接下来就是要详细说裸编程的思想的详细做法。

没有思想的裸程序就如一副人体骨架,有个人形,但没有人样,骨骼之间的关节都是靠胶水或拉线连接起来的,生硬而呆板。假如给骨架包上皮肉,加上灵魂,我们就会惊叹:啊!这是帅哥,这是美女!由于骨架活了。

裸程序也一样,假如按传统的思维方式说这样就足够了,那么裸程序就形如骨架,通常只是一些功能的粗糙堆砌,也只会叫后人看了说这程序垃圾,而后人再做也未必能跳出这个圈子,那么后后人看了又叫这程序垃圾,如此下去,代代相传,传了什么?传了一个总被叫垃圾的东西:没思想的裸程序。

我做了程序好多年,也思考了编程好多年,不断的经历积攒告诉我:写好的程序不是如何去完成代码,而是如何去组织代码。上位机中面向对象的编程思想,就是一个非常可取的思想。

面向对象的编程思想在上位机中是有一个非常丰盛的开发包和功能强大的操作系统支持的,裸编程如何引入这样的思想呢?兴许很多人会觉得不可能。

其实,没有什么是不可能的。再复杂的思想,最终都会归结到汇编,归结到裸程序,我们的单片机程序,正是一种裸程序。只是在单片机编程时和微机编程时我们站在开发平台上的高度不一样,罢了!

对这个高度的了解,兴许很多人很困惑,由于我们平时很少注意它们,那么这里我就举个其他的例子来说明,只管和裸编程好象不很相关,但是这个例子里的高度概念十分清晰。

我们知道网络传输规范层次有七层:应用层、表示层、会话层、传输层、网络层、链路层、物理层,这么多层做什么用?兴许了解这样分层的概念也十分辛苦,但是了解这样分层的思想,就容易多了,而且这也是我们硬件工程师们最应该借鉴的思想,让我们的硬件设计更具有规范性和前瞻性。

这个七层的思想从根本上讲就是将一个网络传输产品细化,让不同的制造商选择一个合适自己的层次开发自己的产品,层次不一样,他们所选择的开发根底和开发内容就不一样,高一层开发者继承低层开发者的成果,从而节省社会资源,提高社会出产力。对这个指教思想我就不赘述了,各位自己去了解,这里要说的是,微机上的面向对象编程思想就是如同在应用层上实现的思想,而裸程序的面向对象思想则如同在链路层上实现的思想,他下面没有软件开发包,独有物理构架。但是在应用层上实现的思想,最终都要翻译到物理构架上。

看懂了上面的例子,就一定明白,裸程序的面向对象思想,是能够实现的,只是难度要大得多,了解要难得多。但是这不要紧,这正是软件水平的表现,你喜爱技术,又何惧之?其实也不会难到哪里去,只是把做事情的方式稍微变更一下罢了。

传统上我们都喜爱用功能来划分模块,细分任务,面向对象思想不这样。面向对象思想则是先从一个任务中找出对象,在对象中搀杂些模块等来实现功能的。这就是两种格调截然不同的地方。假如我们要让我们的单片机把显示信息输出到显示器,那么传统的分析方法是信息格式化、格式化数据送显示器显示,似乎这样也就足够了,不同的显示器用不同的送显示程序或者程序段,配置不同的变量,能共的共起来,不能共的分开。

但是面向对象的思想不是这样做的,而是首先把显示器当作一个对象,该对象具有一些功能和一些变量属性,不同的显示器在对象中使用相同的代码标识,如函数指针(C语言中),这样对于任何一个不同的显示器,在调用时都使用同样的代码。兴许有人说,传统的做法这样也能够做呀,为什么要弄得罗里吧唆的呢?其实不然,使用了正确的思想的益处在前头已经说了好多了,假如还含糊就上去再看一次。

说了那么多理论,此时就说些详细的做法吧。以KeilC为编译环境来说说一个对象详细组织的一些做法。首先是找出对象,如显示器,这就是一个典型的对象。其次是分析一个活对象所应具有的根本特征,即属性与动作。显示器的属性如:类型代号、亮度、比照度、显存等,动作如:初始化、内容刷新和显示、开启和关闭、内容闪烁等花样显示等。这样分也比较容易了解,下面是对于代码的组织上,要注意对象的独立性与完整性,首先把显示器对象单独放在一个文档上,属于对象特有的变量与对象的定义放在一起,要区分公有变量与私有变量的定义方式,对于私有变量要考虑临时变量与永久变量的安排,这些安排都是对变量生命期的严格确定,这样能够节省内存,避免混乱。如某一个函数要使用一个变量,函数在调用完了就退出了,而有一个变量独有它使用,却要保存每一次调用函数所产生的结果,这样的变量怎么定义呢?很多人会直接定义一个全局变量,但是一个好的做法是把这个变量定义成该函数的局部变量,但是定义成静态的,那么这样这个变量对其他代码就是透明的,完全不可能会被误修改,而且代码分类性好,便于将来的维护。用函数指针来统一不同类型的显示器不同的处理方式,也是一个很好的处理办法,那样能够让详细处理方式千差万别的显示器都能用一个统一的对象,但是函数指针要谨慎使用。

好了,说长了我就头晕,不说了,思想的精髓,不必有一样的形态,不同的人会有不同的了解,我只希望能给大家的程序生涯抛砖引玉,我就觉得很有成就感了。

三、准备工作

本文在此引用一个例子。在引入例子之前,我们要做一些准备工作,然后一步一步地走向例子里去。就以前面帖子提到过的显示器控制为例。

显示器就是一个对象。没论它是功能多么复杂的显示器,或者功能多么简略的显示器,对于须要显示信息的调用者来说,都并不重要,也就是说对于须要使用显示器的主体来讲,他只管显示信息,不管显示器的千差万别,只有显示器提供了某功能,它能够调用就行,调用前当然要遵守显示器的数据传递规则,但是不必考虑不同的显示器所产生的传递规则的差异。也就是说,对于调用者来说,永远不会希望有多条规则来让自己的调用代码变复杂。

因此,我们首先须要构造一个相对独立的代码段,也就是显示器对象,以下都以KeilC作为裸程序的编译环境。正如很多人说的,KeilC并不是OOP语言,那怎么做?正是由于我们认为KeilC不能做,所以我才把这种思想拿出来与大家探讨,让我们的程序变得更精彩,更有技术含量。

构成一个独立代码段,最好的办法就是在主工程目录下建设一个子目录,如DISPLAY,然后再在DISPLAY目录下建设一个文档,如DISPLAY.H,然后把DISPLAYDISPLAY.H文档#include到一个恰当的位置上,这样,一个独立的面向对象的代码段就初步构成了,以后维护代码的时候,你永远不要考虑调用者是什么感受,只有维护这个文档,就能够保证你对显示器的不断更新了。

很多人兴许会说,这算什么OOP?大家先鄙兖着急,这才是个初始,下面才是组织代码的详细过程。

对于一个显示器,我们必需要有显示要求,我们才会去定制它,假如连使用要求都提不出来,就不要去让人为你做显示器。所以我们首先要明确我们要的显示器必需要做什么。由于是单片机控制的显示器,我们不能想象成微机显示器那样,一个大的显存,能够显示多少页,显示多少色,满屏满屏的传递数据,假如这样想了,就是犯了盲目例比的错误,说明对问题没钻研透。对于单片机控制的显示器,我们考虑能显示单个字符、单行显示,就根本足够了。所以我们能够定义下列两个对象功能:

dispShowAChar();//显示一个字符
dispShowALine();//显示一行字符

由于是单片机的裸系统,所以我们作为一个软件设计者,我们一定要清楚,我们所面对的显示器,经常是没有CPU的,所以我们一定要明白,我们这两个函数,实质上都做些什么。很显然,这两个函数是不能长期占有CPU的,否则我们的程序将什么都不能做,专去显示了,成了显示器的一个处理芯片,所以这两个函数运行完后是肯定要退出来的,而显示不能中断呀,所以必需要有一个代码段一直存在于活动代码中而且不能影响其他的功能。做过上位机程序的人应该能看出来,这段代码就是线程。裸编程中我们也用这个概念。

我们的显示器对象正须要一个一直活动的线程,来完成单片机系统对显示功能的解释和执行,因此dispShowAChar()和dispShowALine()实质上是不能直接去做显示工作的,它俩最适宜的工作,就是去按指定的格式去设置显示内容,这样我们在使用的时候就不必在这两个函数里设置复杂的代码和嵌套调用关系,由于那样一定会浪费很多的代码,调用多了也会让单片机运行效率降低,硬件资源耗费增加,严重的可能会造成堆栈溢出最后还不晓得为什么。让我们也为这个活动线程也先命个名吧:

dispMainThread();//按指定的要求执行显示功能
//指定的要求包括颜色信息、闪烁、游动等等

程序分析下去,引出的概念也就会越来越多,这里所说的多线程概念以后有时机再说,单片机里的多线程也是一个复杂罗嗦的处理问题,此时介绍还为时过早。只是我感觉一不小心又说长了,详细下文继续展开。

四、展开思想

对于对象才能的定义,我们一般能够从重要的入手,然后慢慢地展开,把所须要的其他才能渐渐归纳为函数,从而把面向对象的思想开展下去。上文我们提到了三个函数是怎么来的,还没有波及到函数的任何实质,那么本帖就探讨一下这三个函数的实质性布局与设计。

有了功能要求,我们就要实现它,在裸程序中,实现它的一个首要任务,就是要进行数据传递方式的设计。很显然我们必需要有一个显示区域,来寄存我们所要显示的内容,以及显示内容的显示属性,我们还要布局这个显示区域到底要显示多少多少字符或者是点阵。但是由于我们事先并不知道我们的显示设备一次会提供多少显示量,所以我们能够把显示区域的内存,也就是显存,定义得大一点,以至任何一款合乎设计要求的显示器都能得到满足,这样的做法在裸编程中其实还是比较实用的,由于裸编程中我们很少去申请动态的空间,程序设计完,所有的变量位置皆已确定,行就行,不行编译就过不去,所以我们能够通常选择一些内存资源比较丰盛的新款单片机。

但是这样的做法也有一个弊端,假如当我们预先大约不足而导致数据空间不够的时候,我们就得从头来改这个显存的大小,从而导致整个显示程序都要相应的产生一些变动,这还不是最糟糕的,最糟糕的是当一款新的显示器由于新的功能需求而导致数据构造须要发生变化的时候,我们就崩溃了,前期的工作可能改动就非常大,甚至于都要重新过一遍,也就是重写重调,这么痛苦的事情,我是最讨厌的了。

所以我们要尽量避免这类事情发生,这里对面向对象的思想,就颇为需求了。这个时候,我们就要引入一个新的概念,那就是对象的儿子,子对象。前面探讨的,其实都只是一个抽象的对象,没有任何详细的样子,而只是笼统的布局了所有的显示器必需具有什么才能,而对于每一个详细的显示器来说,还没有任何详细的设计,在这里,每一个详细的显示器,就是显示器对象的子对象,他们形态各异,但是都必需能完成规定的功能。以传统的OOP语言理论来说,这里就产生了一个继承的关系,但是在裸程序思想里,我并不赞成引入这个概念,由于传统的OOP语言里的继承,纯粹是一个语法上的逻辑关系,继承关系明确,而裸程序中的这个思想,并没有任何语法支持,继承关系就非常微弱了,还不如说是归类与概括。但没论是什么关系,我还是不想就这种不言而喻的关系弄个新名词来,让看的人费解。

既然引入了子对象,我们能看出这种做法有什么实际意义吗?兴许有经历的资深程序员能看出来。我们在做父对象数据设计的时候,我们并不规定详细的数据格式和显存大小,而是一股脑儿地全推给子对象自己去搞,父对象什么都不管。哈哈!这样做事情真是很简略吧?不是我的事情我不管,不要说我偷懒,由于站在父对象的角度讲,这是最明智的做法,由于管不了,所以不管。

到这里兴许就会产生更多的疑问了,一个对象什么都不管,那作为调用者怎么使用这个对象呢?你想用它,它什么都不管,这怎么行呀?别着急,父对象不管的,是那些详细的事情,抽象的事情,还是管的,要不然它就没有理由存在了。你埋怨了,说明你在思考,既然思考了,就把思考的问题提出来,提出来的,就是我们设计父对象的依据。提问题,我想这比搞程序要简略得多,假如:显示器能显示多少乘多少的字符?颜色是多色还是单色?显示模式是否支持预定的方式(如挪动、闪烁等)?工作模式是图像还是字符?等等,这里附加说明一下,对于显示模式,我们这里都以字符显示为例,既然是面向对象的思想,相信扩充出图像显示模式,还是很容易的事情。

有问题出来了,我们就继续为它添加代码好了。

dispGetMaxCol();//取一行最多有多少列
dispGetMaxRow();//取显示器一共有多少行
dispGetMaxColors();//取显示器最多有多少色
dispSetShowMode();//设置显示的方式,对于不支持的显示方式就自动转为正常显示
dispSetWorkMode();//设置工作模式,假如没有的模式就返回0,支持的就返回1?

对于这些函数的定义,各人能够依据自己的习惯来设置,我只是临时弄了这个例子,未必就是最好的,我的目标是重在说明思想。我也怕把程序弄得庞大了,出本书都嫌厚。

似乎加了这些函数之后,我们根本就没看到显示数据的详细形式,和前面的函数一起,都并没有什么明确的说法。这种感觉很正确,我们的确没有对显存做任何定义,但是似乎功能却都已经定义了,其实也的确是定义了,而且将来我们就这样用,而且也不用怕,程序一定会写完的。

五、数据传递与程序逻辑同等重要

继续上面探讨的问题。前面我们提到,为了使用dispShowAChar()、dispShowALine()、dispMainThread()这三个函数,我们又引出五个新的函数来,这些新的函数最主要的宗旨,就是要实现调用者与被调用者之间数据的传递。

对于程序设计来讲,数据传递与程序逻辑有着同样重要的地位,前者经常在最后会构成一种协议,后者则经常表现为各种算法。

在裸程序中,我们的思想应该主要是表现为一种灵魂,而不能如C++那样,去追求语法的完美,所以对于参数的传递,我们不能去追求语法上的完美,而是不拘一格用传递。除了函数能够传递数据外,直接调用值也是一种很快速的方式,但是调用不能随便说调就调,而是也要进修C++上语法的习惯,尽量不能让一些专用的变量名称,出此时与专用变量没关的程序体中。例如,我们的设计中规定,我们这套裸系统对显示器最多支持65536色,那么我们就会用一个16位的没符号整数来保存这个指标。为了简化以后的说明,我们先定义两个数据类型:

typedef?unsigned?int?UINT;
typedef?unsigned?char?UCH;

假如我们用函数来传递这项数据,我们能够用如下的方式:

#define?Monitor01_MaxColors?0xFFFF

对于颜色调用函数则定义如下:

UINTdispGetMaxColors()
{
return?Monitor01_MaxColors;
}

很显然,假如另一个显示器是个单色显示器,则颜色调用函数只须要改为下列形式就能够了:

#define?Monitor02_MaxColors?0x0001
UINTdispGetMaxColors()
{
return?Monitor02_MaxColors;
}

之前有人提到过,用数组,这能够攻克很多问题。说得一点没错!上面的例子我们忽略了一个问题,那就是同一个函数名要去做很多不同函数所做的事情,而我们却没有在函数体内使用switch(),这显然是不对的。要真正实现不同显示器的共有属性MaxColors的传递,我们必需要添加switch()以区分不同的显示器类型。那么这里我们就须要引入一个新的父对象属性以指代它的第几号儿子:

UCHMonitorType?=?0;//显示器类型,最多支持256种显示器

并在初始化的时候,为该属性初始化为0,以作为缺少省类型显示器的代号。以下命名我们就说一个约定,以让代码更具有规范的模样:父对象的接口函数用小写的disp打头,变量用Monitor打头,宏数据用Monitor开头并且内部至少有一个下划线,宏函数则用全大写字母组成。那么不用数组的情况下,上面的代码将会变成如下形式:

#define?Monitor_00?0
#define?Monitor_01?1
#define?Monitor_02?2
UINTdispGetMaxColors()
{
//以下用多出口,但这并不会破坏什么,为节约代码,完全能够使用
switch(MonitorType)
{
?caseMonitor_01:returnMonitor01_MaxColors;
?caseMonitor_02:returnMonitor02_MaxColors;
}
?return?Monitor00_MaxColors;//缺少省则返回默认显示器
}

这样的形式很显然是太冗长了,只管非常构造化,但是一般在优化程序的时候我们还是可能会废弃它的,所以这里就提到了数组的使用。既然是数组,那么它自然不能属于某一个子对象,而是应该在父对象中定义的,只管这样做我们每次在添加新显示器的时候我们假如在父对象中添加难以了解的新的数据,但是为了节省代码,我们还是能忍受这样的痛苦。假如改用数组,则上面的代码将变更为如下形式:

#define?Max_Monitor_Types?3***
#define?Monitor00_MaxColors?1
UINTcodeMonitorMaxColorsArray[Max_Monitor_Types]=
{
?Monitor00_MaxColors,//缺少省为单色
?Monitor01_MaxColors,
?Monitor02_MaxColors,
};***

打***的语句将是前景扩充时不断须要修改的句子。那么上面的函数就简略了

UINTdispGetMaxColors()
{
?return?MonitorMaxColorsArray[MonitorType];
}

甚至有人还能够用宏函数来节省运行时长,只有修改一下调用规则就能够了:

#define?DISPGETMAXCOLORS?(c)?c?=?MonitorMaxColorsArray[MonitorType];

兴许当我们写成如上代码的时候,我们的每一次改进,都会让我们欣喜,我们的代码又优化了。但是可惜的是,这种没有思想的优化会在不远的将来,给我们带来麻烦。我觉得我的记忆力很不好,兴许一分钟前的事情我都会想不起来,这种在将来扩充中的上窜下跳地修改会让我觉得晕眩!

所以,在工程化的工作中,我们须要把父对象与子对象尽量隔离开来,减少关联性的修改量,这也是面向对象思想的重要意义之所在,对于这一改动,我将在下帖中阐述。

六、父子对象接口函数功能剥离

上文我们说到 dispGetMaxColors()的一些设计思维,我们有很多很好的办法来实现它,但是我们有没有更好的管理办法来实现它,这才是我们要站在更高层次看问题的焦点,是更重要的。这也就是一个从传统思维到面向对象思维的一个重要的转折。

要想把这个函数转变为面向对象的逻辑构造,我们也要先做些准备工作。

第一说参数传递的思想。尽量减少参数传递,这是尊重C51系列8位单片机硬件现状的一项重要措施,记着,不要埋怨C51档次低,资源少,而是要在尊重和热爱C51的前提下,我们才有热情来开展我们的裸程序面向对象思想的,也就是说,没论我们面临的系统有多简陋,我们都有策略,来实现复杂的功能,而且从开展的眼光来看,产品的升级,并不是盲目标升级我的CPU,由于那样只会让产品设计者智商下降,所以我觉得C51的特色就是应该在简洁,越来越简洁,而不是越来越复杂。所以我希望我们把思想升级作为升级产品的一个开展方向。传递参数要减少指针、浮点等类型的数据传递,尽量以UCH与UINT为主,而且参数的数量不要太多,最理想的上限是2个,假如实在多了,就使用共享缓冲区,或者全局变量。最好是不要传递参数。

本函数就利用了MonitorType省略了一个参数传递。

第二是我们要让父对象的接口函数与详细的子对象的多种可能性的功能实现剥离,这里我们就须要使用函数指针。函数指针兴许我们一般用得少,但是其实并不是很复杂。先看我们函数的形式:

UINTdispGetMaxColors(void);

为该函数定义一个指针类型,只需做如下定义,就能够了:

typedefUINT(?*dGMC)(void);

那么对于父对象中的dispGetMaxColors()函数,我们就只须要转换定义一个函数指针,在创建父对象的时候为它提供一个子对象对应功能调用的入口地址,就足够了。所以对于这个函数的实体将只在子对象中出现,而在父对象中只会出现一个变量的定义:

dGMCdispGetMaxColors;

为了给它赋初值,我们也能够定义一个空指针,作为一个未使用的判断标志:

#?define?NIL?0

那么初始化dispGetMaxColors的时候只须要写条如下语句就能够了:

dispGetMaxColors=NIL;

而且功能调用也很简略,与实质的函数是一样的:

if(dispGetMaxColors!=NIL) vMaxColors=dispGetMaxColors();

假如再加上约定,连前面的判断语句完全能够省略。由于我们的裸程序的程序空间实际上也是运行空间,不存在代码调入内存和移出内存的事情发生,所以我们不须要考虑程序内存的优化问题,只有我们规定对象一定是先创建后使用,判断语句就会变得没有意义,而且我们创建后即便不再使用,函数体我们也不会释放,由于它被放在程序空间内是固定死的,你想移出去,还不能实现呢。

第三,尽量让程序所使用的语法简略化,以减少编译器可能带来的差别而产生的了解上的误区。有人说C51是C的子集,这说法我认为不科学,只能说二者继承了C的根本精神,而在实质上它们针对的是不同的对象,所以也出现了一些不同的结果,所以我看到有些高手或者一些面试题弄出一些题目来让我望而生畏,兴许我做一辈子的裸程序也做不出他们的题目,但是我并不觉得我不如他们。他们只不过是在编译器上花了很多时长钻研他们的一些约定,而我并不想花时长去钻研那些将来可能发生变化的东西,我希望我能用一个更简略的途径把我的事情做优秀,我只关怀我的项目标速度、代码的大小、运行的效率、维护的复杂度,所以我也建议和我交流的人能用一种通俗的方法来实现我们的功能,也不必过多的考虑我的程序在8位单片机上能够用16位单片机上也能够用,我们的系统要经常升级,但不要轻易去增加硬件老本。当然假如你有精力去钻研那些约定,也没什么,只有你乐意。

好了,上面三条,独有第二条是我要说的关键,其他两条,个人能够依据自己的详细情况来寻找一些快速实用的方法。其实说来我们把父对象用函数指针来代替实体函数,这完全是一种思想,我们并没有使用复杂的语法,而是从管理上把对象别离开来,让不同的人做不同的事情,比较容易。但是同时我们又不能让这种中间环节影响我们程序的效率,所以我们完全能够自己寻求一些方法,而不必去循规蹈矩。我这样说兴许会遭到一些非议,但是我能够告诉大家,计算机语言这门学科,自身就是一门人造科学,没有最好独有更好,我们没必要完全依照别人的思维去开展设计思想,所以也不要拿着人家的一些约定来引以为荣,那些东西,独有先学后学的问题,没有水平的差异;我们应该更注意的是,人家创造了工具,我们就用工具再来创造世界!假如你停留在观赏工具的层次上,那就是没所鸟事了!

本帖实质上只说了一个转换,两条建议,这都不是详细的程序,而是思想。我想强调的,也不是格式,而是思想。下帖将再回到对象上,和大家探讨一下对象自身的组织问题,假如对象的层次关系、对象的创建、对象的书写等等,我也希望有人能有一些更好的方法回到帖子里,我们互相进修,共有提高。

七、面向对象思想的层次关系

前面的思想衍变过程已经说了很久了,面向对象的思想也就到了瓜熟蒂落呼之欲出的境界了。下面我就先图示一下裸程序设计中面向对象思想的层次关系:

相信这张图已经足够说清楚我们在KeilC中如何用语言来组织我们的显示器对象disp了。disp是一个抽象的对象,它只是一种联系,完成对所有子对象d000、d001、d002到最多d255的归纳概括并提供一组被调用者所使用的功能接口。这些功能接口正是上贴所提到的函数指针。而具体的功能实现及不同显示对象对数据结构的要求,我们都可以交给子对象设计工程师自己去决定。

很显然,大家在这套方案具体的程序设计过程中,最主要的精力还是要放在自己做自己的问题上,多思考如何把事情做得更漂亮,而不必在代码编写时黏糊不清。父对象设计者必须要完成总体方案的设计,抽象思维多而具体工作量少,子对象的设计者则恰恰相反,只需要多考虑考虑具体的设计,而不必去担心别人是怎么用自己的东西。

很显然,作为总体设计者,必须要严格考虑这中间的数据交换关系,因为我们没有操作系统,所以对于可用的内存资源的使用法则,直接关系到我们整个系统的成败,混乱使用常常会导致系统崩溃,相对独立的代码则在编译过程中由KeilC直接安排好了,我们不需要去考虑它们对程序的影响。

例子中的显存大小及显存的位置都是我们方案成败的关键。我们都知道KeilC对单片机内存划分了四种,即data、idata、pdata、xdata四种,各种存储器的大小与特点都决定着我们代码的运行效果,我们既要考虑信息所需要的空间,又要考虑单片机处理时足够达到我们的视觉要求。在这个例子中,我觉得我们可以选择xdata来作为显存。为什么呢?

因为我觉得只要我们处理得当,我们的单片机完全可以克服处理速度上的缺陷,所以我们可以优先满足信息量的要求,提供足够多的空间来实现我们想要的功能。

提速的方式有很多,比如:选择一些性能优越的新型单片机,传统的是12T的,现在有6T的,也有1T的,这让很多指令都会有更高的效率;适当的提高晶振频率;选择更科学的算法;等等。

到目前为止,基本上可以去构造我们的对象了,如果你有兴趣,你可以使用#define进行一些伪码定义,把我们的对象定义得更美观一点,更接近C++一些,不过我要说的是:我们这里没有严格的类定义,所以定义时类与对象经常是没有界限的。

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信