然后,在VC中启动的时候,输出栏会显示一系列这个exe所加载的dll或其他插件,将那些dll和插件(系统的除外)与生成的exe放在一起,然后就可以发布了。
我说的只是大概流程,中间一些细节自己体会吧。
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.
在团队工作中,经常会有模块维护和代码封装的问题。把需要封装的代码打成一个lib无疑是一种很好的方式。1.创建lib
创建一个lib很容易,只需要创建一个target,然后把需要封装的代码全部加进来,然后再Options of Target中选择Create Library,然后编译,因为是lib所以不需要链接,编译过了,你的lib就创建了。当然了,为了别人可以轻松的使用,请提供头文件支持哦。
2.使用lib
使用lib就更容易了,把lib和头文件加入你的工程,直接调用就是了。lib库会和你工程中其它编译后的obj一起链接,形成最后的目标文件。
3.注意事项
首先,Startup和中断处理程序不要封入LIB,这些程序会在链接的时候产生问题。具体的原因么,有点复杂,应该是中断程序的link机制有所不同的关系吧。
其次,Lib的文件要分的细一点,没有调用关系的两个函数不要放到同一个C文件中,因为LIB51在链接的时候是按模块来链接的,一个模块就对应一个C文件,假如链接器因为要使用你一个函数fA而引入了A模块,那么A模块中的另外的函数也会被引入,而另外的函数你又没有使用的话,那么就会引发Keil经典的UNCALLED FUNC的warning。这个warning在Keil的文档中说的好清楚了,我粘过来吧:
It is common practice during the development process to write but not call
additional functions. While the compiler permits this without error, the
Linker/Locator does not treat this code casually because of the support for data
overlaying, and emits a warning message.
Interrupt functions are never called, they are invoked by the hardware. An
uncalled routine is treated as a potential interrupt routine by the linker. This
means that the function is assigned non-overlayable data space for its local
variables. This quickly exhausts all available data memory (depending upon the
memory model used).
If you unexpectedly run out of memory, be sure to check for linker warnings
relating to uncalled or unused routines. You can use the linker’s IXREF
directive to include a cross reference list in the linker map (.M51) file.
大意就是说,Keil的内存应用模式是指定地址的,也就是要根据调用关系来决定哪块地址可以被复用。对于这种没人调用的函数,Keil会认为是中断处理程序,并不能决定调用关系,所以此类uncalled函数的空间不能和其他的程序共享,也就是说,这函数用多少RAM,你就少多少RAM。那uncall多了会怎么样?----废话,当然是内存溢出了。
所以,lib的功能可以做的大而全,但是里面的模块一定要分的要多细,有多细,只有这样,你才能像在windows上用CRT一样舒服的使用LIB。
个人总结
1. 生成lib 的工程可以没有main函数,可以只有一个.c文件,一个.c文件中可以只有一个函数
2. 需要在lib工程中建立一个.h文件, 必须用extern声明各全局变量和函数.
3. 调用lib文件的工程中必须包括lib中的.h文件, 也就是lib工程和调用工程都包含同一个.h文件(好像有点废话)
4.Lib的文件要分的细一点,没有调用关系的两个函数不要放到同一个C文件中. 没有调用关系的最好是一个函数单独放在一个.c文件中.这是为了避免在keil中应用程序调用lib库里出现告警. 因为LIB51在链接的时候是按模块来链接的,一个模块就对应一个C文件,假如链接器因为要使用你一个函数fA而引入了A模块,那么A模块中的另外的函数也会被引入,而另外的函数你又没有使用的话,那么就会引发Keil经典的UNCALLED FUNC的warning。
通常为每一个函数编一个.C文件,而整个lib用一个.h文件,这样就可以使只有被调用的函数参与连接
5.调用lib库时需要在工程中将.lib文件加进来. 在Group中右键,然后Add ,注意文件类型中选择*.lib.
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)