本周是C++面向对象高级编程(上) 的第三周,也是最后一周学习了。本周的内容渐渐的进入到了C++设计的关键,也就是面向对象编程,而不是基于对象编程 —— 即,让对象与对象之间产生联系。
那么,在面向对象编程中,对象与对象之间的关联有三种关系,分别是
1. 继承 Inheritance
2. 复合 Composition
3. 委托 Delegation
1. 其中,从最简单的复合来入手,复合表示has-a,即一个类里面具有的成员变量是另一个类的对象。在内存中,复合了的类中,会包含另一个类对象的内存,如果有多层复合,那便会产生多层的包含。当然,如果有包含指针的话,就只包含指针本身的内存,指针所指的内存除外。
在复合关系下,构造函数将先调用它成员变量所属类的构造函数,然后再来构造自己。析构函数反之,像剥洋葱一样,先析构自身,再析构成员变量 —— 由外而内。
2. 对于委托来说,又称,Composition by reference。一个类中有一个成员变量,它是一根指向另外一个类对象的指针。这个指针在类构造时,可能不会马上分配内存。或者,若干个类对象中的这个指针,会指向另外一个类的同一个对象。这样会比较节省内存。当其中一个对象要更改指针所指向内存的值的时候,指针所指的对象会单独拷贝一份出来让那个对象拿去修改,而其他对象可以继续共享原来的那个值。
此外,使用委托的外部类,在其外部接口不变的情况下,内部指针指向的对象类可以有各种修改和变动。但最终外部用户所使用的外部类的成员函数和方法都不变,因此保证了用户使用的遍历和稳定。
3. 继承。对于继承来说,父类,或者叫基类,它的成员特性和函数,可以被子类所继承。继承后,子类拥有父类的所有成员变量和函数,而不需要重新定义了。从感性理解上,可以认为子类是一种父类的类型,比如香蕉是一种水果;白菜是一种蔬菜。这时,水果是香蕉的父类,香蕉是子类;蔬菜是白菜的父类,白菜是子类。子类可以通过子类对象调用父类的函数。在构造时,子类的构造函数中要在初始列前加入对父类的构造函数调用。此外,在析构时,在子类析构函数执行后,才调用父类的析构函数。
3.1 对应继承关系,有额外的一种特性,virtual函数,即虚函数。对于一般的普通函数,非虚函数,如果父类没定义,子类可以自己定义。如果父类定义了,那么子类不能再更改了。对于虚函数,父类有函数的定义,但是子类可以自己重新定义。而纯虚函数,在父类中完全没有默认定义,子类务必要在类设计中实现。
3.2 实际中的类设计,很可能产生 (继承 + 复合)的类设计,这个时候构造和析构函数的执行顺序就比较特别了。
3.3 面向对象程序设计中,还有(委托 + 继承)的类设计,它的功能最为强大。
3.3.1 它可以用来实现视图+数据的功能。可以将一份数据中attach上一系列的视图对象的指针,当数据更新后,数据对象通过指针一一通知各个视图对象更新它们的界面和表现。而这个视图类Observer是父类,将来派生出来的子类都可以放入到数据对象的容器中。
3.3.2 另一个异常强大并且巧妙的设计模式是prototype的模式。父类在设计的时候,预留下将来可能会派生的子类的调用方法,将子类的静态原型通过子类的静态函数返回并放入父类的原型列表中,这样,父类在今后需要生成子类实例的时候,通过子类原型的调用,得到相应的子类对象。这样的设计可谓精妙。
注意:委托关系里面,若干对象的成员指针共享一个成员对象时,这个对象可以根据修改的需要,单独拷贝一份出来,不影响其他对象。这种方式叫做Copy on Write。这个方式和前两周在设计拷贝赋值和拷贝构造函数时强调的深拷贝有略微不同。这个地方并不矛盾。Copy on Write模式在需要的时候可以节省内存并提供一些独特的特性。而预防浅拷贝时,被拷贝的那种类并不具备Copy on Write的特性。
网友评论