Arduino从面向过程到面向对象

如果你以前没有过面向对象程序设计的经历。上一节介绍的1602的一些示例代码,可能会让你感到困惑。回顾之前出现过的器件,譬如LED、变阻器等。它们都是直接输入输出一些比较简单的数字量或者模拟量。但是1602液晶屏却不一样,它的屏幕上最多能同时显示32个字符,传统的方法显然不适合操纵它。那怎么办呢?
为了从处理繁琐的细节问题中解脱出来,程序设计言语发展史上提出大规模的程序设计应该从面向过程转而面向对象的。而要说明面向对象,就绕不开对象和类的概念。

对象(object)
对象可以看成是计算机世界对现实世界的一种模拟。譬如仙剑奇侠传的游戏中,男主角李逍遥就是一个对象。为了便于和真实世界的武侠类比,他可以有类似属性和方法。譬如,体力和真气,可以用一个int类型的整数来描述。李逍遥这个对象还可以有些动作,譬如说,行走和快速行走,但这会消耗体力;它还可以喝酒,就会补充真气;他还可以施展已经习得的法术,譬如万剑诀,这会较少相应敌方对象的体力。如果体力低于零或者等于零,就会触发死亡,游戏结束的事件。
对象是作用是封装和抽象。封装后的对象李逍遥只剩下几个简单的接口,从外部看来,我们只需要对这些属性或者方法进行操作即可。

类(class)
类是一个模板或者说一个蓝图,譬如说林月如和李逍遥一样,都有体力值和真气值,都能行走、快速行走和饮酒,但是不会万剑诀,她会的是一阳指。还有赵灵儿也基本类似,这几个具体的对象都有一些类似的属性和方法,于是,我们把这些共同的属性抽象成一个模板我们命名为武侠人物,武侠人物就是一个类。此外,还需要介绍共有(public)的私有(private)这两个概念。其实共有就是对象的这个属性或者方法在对象之外都可以直接操作,但私有的属性或者方法只能由这个对象内部的其它方法来操作,在武侠人物这个类中,体力值和真气值就是这样的类型,它不应该允许外部直接操作,必须经过一些内部机制,譬如饮酒增加真气值来做到。
我们可以用伪代码描述如下:
class 武侠人物
{
private:
int 体力值;
int 真气值;
public:
void 行走()
{
体力值–;
}
void 快速行走()
{
体力值 = 体力值 – 2;
}
void 饮酒()
{
真气值++;
}
}
有没有发现公共的方法很像是函数,没错,在面向过程的程序设计中,我们称之为函数或者过程,在面向对象的程序设计中,我们称之为方法。此后,除特别声明外,方法、函数和过程这三者可在一定上下文环境内互换,不做特别区分。
构造函数(constructor)

用武侠人物的模板,创造出一个具体的武侠人物李逍遥。这个过程叫做类的实例化(instantiation)。在这个实例化的过程中,我们往往需要做一些初始化的工作,譬如设定李逍遥的体力值和真气值。这就需要构造函数,就像人这个类,不管张三还是李四,都需要有出生这个过程,而构造函数就是实例出生的函数。

构造函数的函数名和类名一样,并且如果你不在类的定义中声明,它也是默认存在的(系统会在装载程序到Arduino前在后台默认加上),只是没有参数。譬如前面的class 武侠人物,它默认的构造函数就是
武侠人物()
{
}
请注意,构造函数前面没有返回类型,即使是写成void空类型也不行,即
void 武侠人物()
是完全错误的写法。
还有一个问题没有解决,那就是,创建类的实例时,往往需要传入一些参数,譬如初始的体力值和真气值。那么这个时候,就不能使用默认没有参数传入的构造函数了。我们将上诉武侠人物这个类的定义修改如下,加入重新定义的构造函数
class 武侠人物
{
private:
//此处略去类的属性成员
public:
武侠人物(int 初始体力值, int 初始真气值)
{
体力值 = 初始体力值;
真气值 = 初始真气值;
}
//此处略去类的其他方法成员
}
下面我们用 武侠人物 这个类 来创建一个实例 李逍遥 ,并且让它的初始体力值为80,初始真气值为60
武侠人物 李逍遥(80,60);
然后我们让李逍遥这个对象快速前进,则其体力值会由80减少到78
李逍遥.快速行走();
通过上述的比喻说明,读者对面向对象的程序设计是否有了一点眉目?接下来回归正题,运用面向对象的思想,把1602液晶屏视为一个对象,来逐步分析上一小节的程序背后的一些东西。
#include <LiquidCrystal.h>
这句话表示在装载Arduino程序前,先将LiquidCrystal.h这个头文件(head file)引用进来,那么在这个头文件中所包含的类定义就会生效。这个文件在哪里呢?我们先找到Arduino目录下的libraries文件夹,我们发现有个LiquidCrystal命名的文件夹(目录)。打开它,看看里面都有些什么?

这时候我们终于看到了这个文件的庐山真面目!此外我们还看到两个文件keywords.txt和LiquidCrystal.cpp。我们用UltraEdit这样的专业文本编辑工具分别打开这三个文件,因为UltraEdit这一类专业工具能够根据语法自动识别并用颜色标记不同功能的字符。
看着这些直接操作硬件底层的晦涩代码,很容易让人似懂非懂,甚至是云里雾里。不要这不要紧,我们的目的不是要读懂它,至少现在不需要。但是,针对这三个文件的功能,还是需要知道一些简单的介绍。

图 头文件LiquidCrystal.h

这个文件一般称为头文件(head file),后缀名.h,除后缀门外,其文件名与类名相同,这是约定的。其中的一些内容主要是一些常量的定义和类的定义(见图中箭头所示),一般采用#define预处理命令。此外,它里面往往完整的定义了整个类的成员,包括属性和方法。但是具体方法的相关执行代码,则没有给出。

图 C++源文件LiquidCrystal.cpp

这个文件一般称为源文件(source file),后缀名.cpp(C plus plus),除后缀门外,其文件名与类名相同,这也是约定的。与头文件相对,它往往作为补充同名头文件中相关方法成员的具体执行代码。如图中箭头所指为begin()方法的具体执行代码。

图 说明文件keyword.txt和Arduino官方参考

keyword.txt这个文件和简明扼要的记录了LiquidCrystal类中可供外部调用的方法,它的名字是固定的。其实对于LiquidCrystal这样Arduino内置的类,它的方法在菜单中选择help->Reference,然后在打开的网页中选择Libraries->LiquidCrystal,则弹出如图所示的详细列表,详细参考中对该类方法的叙述会更加详细和权威,请读者自己参阅。
此外,图的目录内容中还有一个examples的文件夹,打开一看,其中的内容正好和菜单里的示例对应,所以每个子文件夹下的.ino文件其实就是菜单中看到的范例。

这一节讲述的思想非常重要,Arduino之所以简单高效,就是因为稍微复杂一点的器件都已经有了简单易用的类。结合本节内容,请重新仔细阅读上一节的代码以及其中的注释,相信你一定会受益匪浅。