1 关于类和对象
Alan Kay总结的SmallTalk(第一个成功的OO语言,C++发展的基础之一)的五个特性:
1.一切事物都是对象。可以把对象想象成一个变量,它存储数据,又可以向它提出请求,使它自己执行操作。理论上,可以把任何需要解决的问题的概念成分看成一个对象。
2.一个程序是一组通过发送消息告知彼此去做什么的对象组成。
3.每个对象都有自己的内存,可以由其它的对象组成。
4.每个对象都有一个类型,对象是一个类型的实例。
5.一个类的所有对象可以接收相同的消息。
对一个对象的请求(操作)被定义为
接口。一个
类是用来描述拥有相同属性和行为的集合。类也是一个数据类型,就像float数(有相同的属性和行为),不同的是,编程者用类来匹配问题,而用已存在的数据类型来描述在机器上的存储单元。
![](https://img-my.csdn.net/uploads/201304/03/1364968814_9289.png)
例子:
Light lt;
lt.on();
Light是类,lt是对象,可以进行的请求是开灯,关灯,变亮,变暗。
2 为什么要隐藏实现
为什么类应该只暴露给用户必要的接口,而隐藏那些不必要的?
1,类的创建者改变隐藏的部分时不会影响到客户;
2,不隐藏意味着客户也可以随意使用,增大导致BUG的风险;
3,防止类库过于庞大(对外提供的接口过多)。
因此有了
访问控制,public, protected, private。
3 复用实现的方式:聚合和组合
对类而言,最简单的复用就是直接用类的对象,也可以在另一个类里用这个类的对象(组合或聚合,composition(aggregation),通常说的
has-a关系)。
![](https://img-my.csdn.net/uploads/201304/03/1364970505_7175.png)
如果不用菱形,意味着不用关注是组合还是聚合。
聚合:表示两个对象之间是整体和部分的弱关系,部分的生命周期可以超越整体。空心菱形。
组合:表示两个对象之间是整体和部分的强关系,部分的生命周期不能超越整体。实心菱形。
组合:表示两个对象之间是整体和部分的强关系,部分的生命周期不能超越整体。实心菱形。
组合简单灵活,而继承因为在编译时的需要对继承的类进行编译时限制,所以没那么灵活。因此类之间的关系应该优先使用组合。防止过度复杂的设计。
4 复用接口的方式:继承
![](https://img-my.csdn.net/uploads/201304/03/1364971866_6650.png)
一个类不仅描述着一组对象的特性,也和其它类有关联。两个类可以有相同的属性和行为,但是其中一个可能有更多的发生或者能处理更多的消息(或者处理方式不同)。继承可以用来描述这种关系:基类和派生类。
如上所述,与基类区别派生类的两种方法:添加新的函数或者overriding基类的函数(在子类中重写基类中已经存在的函数)。
一个派生类可以用来替换基类(
is-a关系),但一个基类不能用来替换派生类(访问不了派生类添加的新函数),这个可以称为
like-a关系。
5 可互换的多态对象
处理继承的类对象时,经常希望不用考虑它的类型,直接作为基类来处理,这样写代码时不用判别类型,在派生新的类时也不用修改用基类来处理的函数,大大的改进设计和减小维护成本。问题的关键在于将派生类当作基类处理时,编程者不希望在编译时确定函数调用哪段代码,而是在运行时,根据派生类的类型,调用派生类本身的函数。
非OOP编译器在函数调用时是
早绑定(early binding):编译器对指定的函数名进行调用,链接器确定调用将执行的代码的绝对地址。
OOP采用
晚绑定(late binding):在给对象发送消息时,程序运行之前不去确定要执行的代码。编译器保证这个函数存在,并完成参数及返回值类型检查,但是不知道准确的执行代码,编译器在真正调用的地方插入一段特殊的二进制代码,这段代码通过对象自身中的信息在运行时计算被调用函数的地址。这样,指针的内容不同,对象产生不同的行为。
关键字virtual表明编程者需要这个函数拥有迟绑定特性。
把派生类指针当作基类指针的过程称作
向上转型(upcasting)
![](https://img-my.csdn.net/uploads/201304/03/1364975156_9540.png)
6 创建和销毁对象
C++认为效率是最重要的因素,所以它给编程者提供了选择,通过把对象放在
栈或
静态存储区上可以来决定最大化运行时速度、存储和生命周期。
栈是程序执行期间微处理器可以直接使用的内存,栈上的变量通常被称作automatic或scoped变量。静态存储区就是程序开始运行前分配的一小块固定内存区域。就创建和销毁对象的速度优先来说,选用栈或静态存储区,但是牺牲了灵活性,因为你必须在写程序时知道确切的数量,生存期(编译器决定),对象类型。如果这用来解决一般性问题,会是很大的限制。这种情况下可以用另一种方式,在内存池上动态创建,即
堆。这种方法直到运行时才需要知道有多少个对象,生存期,具体类型。用new创建,用delete销毁。这种创建方式会消耗长得多的时间(在栈上只需要一个指令向下移动一个栈指针,一个指令往回移动)。
7 异常处理:处理错误
一个
异常是从错误发生出抛出的一个对象,这个对象能够被设计用来处理这种异常的异常处理器捕获。异常处理就好像是错误发生时程序进入另一条路径执行,也因此不会干扰正常的执行代码,这样就不用不断的做错误检查,从而使编码更简单。与函数返回的错误代码不同,异常不能被忽略,必须保证在某个地方被处理,最后,异常要使程序恢复正常。