杨韬
(广州致远电子股份有限公司,广东 广州 510660)
摘要:多年以来,C语言在嵌入式软件开发中被广泛使用,但由于开发人员和应用场景等原因,面向对象、设计模式等优秀的软件开发方法始终没有很好地运用起来。时至今日,物联网等应用的兴起,给嵌入式软件开发带来新的挑战,而传统的面向过程开发已经难以支撑这些复杂的应用。因此,有必要在嵌入式软件开发中引入面向对象、设计模式等优秀的软件开发方法。面向对象是现代软件方法的根基,面向对象体现在类上,而封装为类的第一大特性。文章以类的封装特性为切入点,结合C语言的特性,讨论了C语言实现类封装的方法,并给出了实例。
关键词:C语言;面向对象;类;封装
0引言
物联网等应用的兴起,给嵌入式软件开发带来新的挑战,而传统的面向过程开发已经难以支撑这些复杂的应用。因此,有必要在嵌入式软件开发中引入面向对象、设计模式等优秀的软件开发方法。本文首先介绍了面向对象必要的基本概念,然后引入了UML类图,通过使用C语言来实现一个Human类,讨论了如何使用C语言来实现类的封装特性,并给出了对应的分析。
1基本概念
1.1对象[1]
对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。
对象具有状态,一般用数据值来描述它的状态。
对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。
对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中。
1.2面向对象
简而言之,面向对象就是把客观存在或主观抽象的事物(即对象)抽象成类。
所谓抽象就是去异求同,从众多的事物(即对象)中抽取出共同的、本质性的特征,舍弃其非本质的特征。比如香蕉、苹果、哈密瓜等,它们共同的特性就是水果。得出水果概念的过程就是一个抽象的过程。在抽象时,同与不同,取决于从什么角度上来抽象。抽象的角度取决于分析问题的目的。
具有相同特性(数据元素)和行为(功能)的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象。
1.3类
面向对象有三大特性:封装、继承、多态,这些特性主要通过类来体现。类就是一个封装了属性以及相关操作的代码的逻辑实体。
类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。
类具有方法,它是对象的行为的抽象,用方法名和实现该操作的方法来描述。
除了封装属性和操作外,类还具有访问控制的功能,比如,某些属性和方法可以是私有的,不能被外界访问,通过访问控制,能够对内部数据提供不同级别的保护,以防止外界意外地改变或使用了私有部分。不同的编程语言提供的访问控制等级不尽相同,但都有公有、私有两个等级。
类是抽象的数据类型,在内存中并不存在(Python等动态语言除外),只有类的实例存在于内存中。
2UML类图
在面向对象设计开发中,通常使用UML工具来进行分析设计。比如,可以使用UML类图来描述类。
UML类图很简单,用一个矩形框代表一个类,矩形框内部被隔为三部分:上面部分为类的名字,中间部分为类的属性,下面部分为类的方法。对于属性和方法,还可以使用“+”、“-”修饰符来表示访问权限,“+”为公有属性、“-”为私有属性。
如图1所示,该类图描述了一个名为“Human”的类。“Human”类抽象并封装了“人”;属性“name”是对人姓名的抽象,因为人的姓名是公开的,所以被设置为公有属性;属性“money”是对人所拥有的财富的抽象,因为每个人的财富都不是公开的,所以被设置为私有属性;方法“buy”是对购物这一行为的抽象,方法“talk”是对讲话这一行为的抽象,这两个方法都是社会活动,所以被设置为公有属性。
设计类的过程就是抽象的过程,抽象的结果取决于抽象时所站的角度,比如,如果是警察来抽象“Human”,他可能会添加一个“isBadGuy”属性。
UML类图主要用于辅助分析和设计阶段,在设计类时应聚焦在与当前问题有关的重要属性和行为,无关的属性和方法统统去掉,确保UML类图是简洁有效的。除非私有属性或方法会影响到问题的理解或者类的实现,否则UML类图中不要出现私有属性和方法,私有属性通常在实现阶段才会去考虑。
UML类图以及其他UML元素都是辅助软件开发的工具,使用UML进行设计时,只要相关人员能够通过UML图看懂你的设计、不妨碍沟通就可以了,即使用草稿纸来作图也是可以的,所以,不用太过纠结那些细节,且一定要避免过度设计。
3C语言的类封装实现
很多现代编程语言都有原生的面向对象支持,比如C++、JAVA、Python等,这些编程语言提供了class数据类型,在这些编程语言中类实际上就是一种数据类型,因此能够更好地支持面向对象编程。
实际上,面向对象是与编程语言无关的,更像是一种思想,且不局限于软件开发活动,任何需要分析解决问题的场合都可以使用面向对象。
C语言并没有类的概念,但是可以从类的特性出发,利用C语言的某些特性来实现类的用法。关于类,首先要解决的就是封装问题,类的封装特性需要能够封装属性和方法,还要有访问控制。可以使用.h、.c文件和结构体来完成封装。
下面以图1中Human类的C语言实现为例来叙述C语言的类封装问题,本文使用human.h、human.c、struct human三个元素来完成封装,human.c为human.h中函数声明的实现,本文不讨论这些细节,所以只给出human.h的关键代码片段,如下所示:
typedef struct human {
const char *name;
int _money;
} human_t;
human_t *human_init(human_t *p_this, const char *name, intmoney);
voidhuman_talk(human_t*p_this, const char*p_words);
voidhuman_buy(human_t*p_this, const char *p_something, unsignedprice, unsignedcount);
voidhuman_deinit(human_t *p_this);
(1)类名
Human类的名称体现在human.c、human.h以及human.h中所有全局符号的命名上,这些命名全部使用关键字human作为前缀。
(2)属性
Human类的属性体现在自定义类型human_t中,human_t实际上为结构体struct human,它有两个成员:name和_money,分别对应类图中的属性+name和-money,特别留意_money成员前的“_”,这是为了警示类的使用者“此成员为私有属性,不可使用”。
(3)方法
Human类的方法体现在human_talk()、human_buy()这两个函数上,分别对应类图中的方法+buy()和+talk()。此外,还可以注意到有human_init()、human_deinit()这两个函数,分别为Human类的构造、析构方法。构造、析构方法分别用于类对象的初始化和解初始化。
构造函数human_init()需要用户提供Human对象的内存,通过第一个参数p_this传递,对象的内存等价于一个human_t变量。
C语言中可以使用C文件中的static函数实现私有方法,假如Human类有私有方法money_pay(),则其C语言实现如下:
// human.c
static int __human_money_pay (human_t *p_this, unsignedcost)
{ … }
UML类图中一般不会显式地出现构造、析构和私有这三种方法,除非需要在类的构造、析构和实现上有特殊说明。
另外需要注意的是,这几个方法函数的第一个参数都是human_t *类型,且名称为p_this,这是C语言面向对象编程与面向过程的最大不同:p_this为指向类实例(即对象)的指针,所有的方法操作都需要“针对”一个对象,p_this指针由类的构造函数返回,比如,human_init()构造一个Human实例,然后返回指向此实例的p_this指针,然后就可以调用human_talk(p_this, …)等方法对实例进行操作。
(4)访问控制
在Human类的C语言实现中,属性被定义为human_t中的两个成员,而 human_t被定义在用户可见的human.h中,所以human_t是暴露给用户的,因此,从语法上讲,Human类的两个属性是暴露给用户的,即都是公有属性。虽然语法上不能支持私有,但可以在编程规范上设定“私有属性以短下划线“_”开头”,比如“_money”,如此从某种意义上实现了属性的访问控制。
在Human类的C语言实现中,方法被定义为human.c中的函数。公共方法对应的函数都没有“static”关键字,且在human.h中有对应的函数声明。而私有方法对应的函数都有“static”关键字,这些私有方法只能在human.c文件内部调用,对用户不可见。由此可知,C语言本身就能支持方法的访问控制。
4结论
本文通过使用C语言实现一个Human类,讨论了如何使用C语言来实现类的封装特性。在C++等面向对象语言中,使用class对类做了原生的支持,使用起来非常简单。尽管C语言并不是原生支持类,但通过语言、概念、规范上的处理,也能实现类的封装特性。用C语言实现类的封装相当于解决了C面向对象的关键第一步,在其基础之上可以引入更多现代软件方法。
参考文献
[1] 百度. 百度百科/面向对象[EB/OL].[2016-08-08].http://baike.baidu.com/link?url=6XlXEOSlrKn87S7SJv4UW SX7EjstoDVm wJ13OAod XUrUrnZkVg3ntPFir Ey5c6mqObZZ OevQI6K3Ungq1Mq.