260 likes | 418 Views
第 18 章 组合和继承. 一、组 合 二、继承和派生 三、派生类的声明和对象定义. 一、组 合 1. 组合的概念 C++ 中组合是指结构和类的声明嵌入结构变量或对象作 为其成员的现象。嵌入的对象成员亦称为成员对象,包容这 个成员对象的类可称为组合类或包容类。 当构造一个组合类的对象时,系统首先为每一个数据成 员分配内存空间,仅当其中的成员对象获得完备的内存时才 导致组合类实例的诞生。 编译器先调用嵌入对象的构造函数,然后调用组合类的 构造函数。. 嵌入对象所隶属的类或嵌入类对应既定的事物,组合类 对应某种新生的事物。
E N D
第18章 组合和继承 • 一、组 合 • 二、继承和派生 • 三、派生类的声明和对象定义
一、组 合 • 1. 组合的概念 • C++中组合是指结构和类的声明嵌入结构变量或对象作 • 为其成员的现象。嵌入的对象成员亦称为成员对象,包容这 • 个成员对象的类可称为组合类或包容类。 • 当构造一个组合类的对象时,系统首先为每一个数据成 • 员分配内存空间,仅当其中的成员对象获得完备的内存时才 • 导致组合类实例的诞生。 • 编译器先调用嵌入对象的构造函数,然后调用组合类的 • 构造函数。
嵌入对象所隶属的类或嵌入类对应既定的事物,组合类嵌入对象所隶属的类或嵌入类对应既定的事物,组合类 • 对应某种新生的事物。 • C++中一个重要的思想是先构造既定的对象,然后构造 • 新生的对象。 • 这也适应继承的情况,先构造基类再构造派生类。但析 • 构的次序与构造相反,先诞生的对象后撤离内存空间。 • 对象的构造和析构遵循堆栈空间先进后出的原理。
2. 引用型成员和const成员 • 类中允许存在引用型数据成员和const数据成员。引用 • 应依附于另一个独立的变量,等待及时的初始化。Const数 • 据成员是仅仅初始化一次其值便不再改变的数据成员。 • 对于存在const型的不变成员或引用型成员,系统提供 • 的缺省的赋值运算符函数不起作用。引用型成员和不变成员 • 只能借助冒号语法初始化。
当基于内存数据共享而设置引用型成员的时候,应注意当基于内存数据共享而设置引用型成员的时候,应注意 • 引用型成员应关联到一个具有独立的生存期长或等长的同类 • 型变量。 • 具有const成员的对象预埋一个不变的成员,这样的类 • 是一个奇怪的类,因此编编程时应仔细加以处理。 • 下面的例子为简单见嵌入一个内置int类型的引用型成 • 员和不变成员,通过该例说明构造函数初始化列表的语法格 • 式。
[例] CType内含int类型的引用型成员和const成员 • #include <stdio.h> • static int numc=0; • class CType • { public: • CType (int x=1,int y=2); CType::~CType (); • private: int n; • const int c; public: int& r; • }; • CType::~CType() • { printf ("%d.[n=%d,r=%d] CType::~CType();", • numc--,n,r); }
CType::CType (int x,int y) • :r(y), • c(x) • { n=x; • printf ("%d. [n=%d,r=%d] CType (int,int);", • ++numc,n,r); } • CType g; • void main() • { printf ("Enter into main(){\n"); • { CType x (3,4); printf ("x.r=%d\n",x.r); } • printf ("Go outof main() }\n"); • }
/////程序运行输出结果: • 1. [n=1,r=2] CType(int,int);Enter into main(){ • 2. [n=3,r=4] CType(int,int);x.r=4 • 2. [n=3,r=4199100] CType::~CType();Go outof main() } • 1. [n=1,r=-858993460] CType::~CType(); • 此题输出一个不可靠的结果。原因在于: • y是一个函数入口堆栈空间的局部变量,r是一个生存期 • 由对象控制的引用型成员。 • 将引用型成员关联到生存期短的局部变量是危险的。
3. 嵌入对象的初始化 • 嵌入对象所属类最好存在显式或隐含的可访问的缺省构 • 造函数,从而可被编译器自动隐含调用。下面的规则描述嵌 • 入对象显式的初始化: • a.组合类的构造函数显式调用嵌入对象自身的带参构 • 造函数,这一调用是通过冒号初始化语法进行的。 • b. 嵌入对象按照其在组合类中的声明次序调用自身的 • 构造函数,而不是冒号语法列表中出现的次序。 • c. 嵌入对象的构造函数对于组合类是可访问的,可访 • 问的含义是或者嵌入对象的构造函数是公共的,或者组合类 • 声明为嵌入类的友员类等。
[例]嵌入类和组合类的对象生灭 • #include <stdio.h> • static int num=0; • class CEmbed • { public: int n; • CEmbed (int ,int ); • private: int& m_r; }; • CEmbed::CEmbed (int x, int y) • :m_r (n) • { n =x; • printf ("%d.n=%d, CEmbed (int,int) ;",++num, n); }
class CContain • { public: CEmbed a; CContain (int x=1,int y=2); • private: const CEmbed b; }; • CContain::CContain(int x,int y):b(y,y),a(x,x) • { • printf ("%d.a=%d, b=%d, CContain (int,int); \n",++num, a.n,b.n); • } • void main() { CContain z (3,4); } • //输出: 1. n=3, CEmbed(int,int); • 2. n=4, CEmbed(int,int); • 3.a=3,b=4, CContain(int,int);
4. 冒号初始化语法 • 冒号初始化是对象定义时调用构造函数进行初始化的一 • 种方式 。构造函数冒号初始化语法的一般格式为: • 类名::类名(形参列表):引用型成员(左值), • 嵌入对象(实参列表),const型成员(右值),...... • { 其它成员初始化; } • 例如: • CEmbed (int x, int y): m_r (n) {...} • CContain (int x,int y):a (x,x),b (y,y) {...}
该语法的格式是在类的构造函数表题头后紧跟冒号该语法的格式是在类的构造函数表题头后紧跟冒号 • “:”,冒号之后是成员对象的构造函数初始化调用列表,各 • 列表分项之间以逗号分隔,但不构成先后求值的语义。 • 每一个嵌入成员对象采用函数对象名语法显式调用自身 • 的构造函数,对于简单变量也采用构造函数的形式,不采用 • 等号形式的初始化。 • 冒号之后的列表全部省略时对应编译器隐含调用成员对 • 象可访问的缺省构造函数,这是编译器的默认设置。 • 构造函数中形参作用域始于冒号处止于最后一个右花括 • 号。 • 因此可以采用形参或其它的全局名称初始化嵌入对象、 • 引用型成员或const型数据成员。
冒号初始化语法原本是为成员对象,引用型成员或冒号初始化语法原本是为成员对象,引用型成员或 • const数据成员设置的,但对于其它的成员变量也可以从构 • 造函数体迁移至冒号后,如将: • CEmbed::CEmbed (int x, int y) : m_r(n) { n=x; } • 变为: • CEmbed::CEmbed (int x, int y) : m_r(m) , n(x) { } • 变量成员模拟构造函数n(x)形式进行初始赋值。 • m_r(n)建立引用名和变量名的关联,m_r是相对独立 • 的成员变量n的别名。
二、继承和派生 • 1. 继承的概念 • 继承是将已有的数据结构和算法加以接纳融为自身的一 • 部分,派生是将新的成果连结于原来的知识体系中。继承和 • 派生是承上启下的一个联系过程,原来的数据结构和算法对 • 应基类,连结于基类的新的数据结构和算法构成派生类,由 • 此形成继承树层次体系。 • 继承是面向对象理论程序设计中一种重要的机制,继承 • 面向事物动态的发展,也是人认识自然的一个写照。人首先 • 直面的是简单的自然现象,把这些简单现象以文字记录下来 • 就形成基本规律的总结。
对于程序员而言是建立一个基本的类。随着对自然现象对于程序员而言是建立一个基本的类。随着对自然现象 • 的深入研究和广泛的积累,各种学科流派相应的应运而生。 • 对于跟踪模拟这些流派动态发展的程序员而言,相应建立各 • 种各样的派生类,而无须另起炉灶。 • 知识的每一积累,对应类的一个扩展。古老的经验沉积 • 在基类的数据结构和算法中,时髦的思潮注册在最近派生的 • 类中。编译器优先利用最近成果的思想,优先使用派生类中 • 的资源信息。这是面向对象理论为程序员提供的一种总体策 • 划和具体实施纲领。 • 这其中的优点是程序的开发和课题的突破可以同步协调 • 的前进,而不必从零出发。
基于历史的原因和书写的习惯,继承树层次体系将基类基于历史的原因和书写的习惯,继承树层次体系将基类 • 画在上面,派生类则画在基类的下面。 • 基类也有称为父类的,派生类亦有称为子类的。 • 父类和子类的称谓容易构成误解。因此本书不用或废弃 • 模糊的父类和子类的称谓。 • C++语言允许继承和派生出现多对一、一对多的关系。 • 一个基类可以拥有多个派生类,这样的基类在程序设计 • 中的地位举足轻重,一个派生类可以同时拥有多个基类。如 • 果一个派生类上逆时只有一个基类,称为单继承层次体系。 • 如果一个派生类上逆时存在两个以上的基类,称为多继 • 承层次体系。 MFC类库是单继承体系。iostream类是多继 • 承体系。
CObject CCmdTarget CWnd CDocument CWinThread CView CDialog CFrameWnd CUserDoc CWinApp CUserApp CUserView CMainFrame CUserDialog 图 MFC关键类的层次体系
2. 间接基类和间接派生类 • 直接基类就是直接出现于类声明的基类表中的类。一个 • 类都可以候选为基类,也可以作为派生类连结到另外一个既 • 定的类上,一个类既是另外一个类的基类本身又是上层类的 • 派生类,由此形成复杂的类层次体系。 • 如CWnd上有直接基类CCmdTarget,间接基类 • CObject,下有直接派生类 CView,更等待用户间接派生的 • 类CUserView。 • C++ 语言不允许上面的(间接)基类成为(间接)派生类的 • 派生类,也就是禁止数据结构中环的出现。这样继承和派生 • 就是一个有向无环图。
三、派生类的声明和对象定义 • 对于一个特定的演化断面,设若干基类已经适当的构 • 建,则新类追加于这些基类的声明格式分为两种形式: • 一种是单继承,一种是多继承 • 这里给出单继承方式的声明格式: • class CDerived:继承方式 CBase • { CDerived 类的成员声明语句; }; • class 派生类名:继承方式 直接基类名 • { 派生类的成员声明语句; };
其中派生类名CDerived是用户新引入的类名,CBase其中派生类名CDerived是用户新引入的类名,CBase • 是已经建立的类名,作为派生类的直接基类名,冒号表现一 • 种承前启后的方式。 • 通过这种方式用户定义的类可插入到已经存在的类族体 • 系。派生类的成员包括继承的成员和新增加的成员,成员的 • 访问属性只有三种,优先由类声明内的 • private,public,protected • 明显确定,继承的成员可由继承方式隐含确定。 • 继承方式由关键字private,public,protected给出。
单继承略微具体的格式为: • class CBase { public: float m_t1; void f3(); • protected:int m_t2; }; • class CDerived: public /* protected , • private */ CBase • { //类声明内的private,public,protected屏蔽继承方式 • //对访问控制的影响 • public: 公共成员声明语句; • CBase:: m_t2; • 基类名::基类非私有的成员名2; • protected: 保护成员声明语句;
CBase:: m_t1; • 基类名::基类非私有的成员名1; • private: 私有成员声明语句; • CBase:: f3; • 基类名::基类非私有的成员名3; • }; • // 不将基类中private:冒号界定的私有成员在派生类中 • //界定为其它的属性
派生类声明之后就可以用其类名定义对象,格式和基类派生类声明之后就可以用其类名定义对象,格式和基类 • 对象定义一致。 • 基类的对象和派生类的对象是不同的对象,拥有独立的 • 内存空间。 如果结构派生于基类则public是其默认的继承方 • 式;而类从基类派生出来,private是其缺省的继承方式。 • 具体含义为: • class CDerived: CBase{...}; • 等价于 class CDerived: private CBase {... }; • struct CDerived: CBase {...}; • 等价于 struct CDerived: public CBase {... };
派生类的数据集合包含基类声明中的一个子集合。派生类的数据集合包含基类声明中的一个子集合。 • 这个子集合subobject许多书籍中称为子对象,嵌入对 • 象也称为子对象。本课件将基类部分构成的子集合称为派生 • 类的基部对象,(派生)类的实例为完整对象。 • 基部对象是不独立存在的,可以将其单独提取出来赋给 • 基类的完整对象或基类的对象引用,基类的对象引用就成为 • 派生类中的subobject的替身。