26
1 C/C++ 程程程程 程 程 一、 程程程程程 程程程程程程程程程程程

一、组 合 二、继承和派生 三、派生类的声明和对象定义

  • Upload
    prue

  • View
    208

  • Download
    0

Embed Size (px)

DESCRIPTION

第 18 章 组合和继承. 一、组 合 二、继承和派生 三、派生类的声明和对象定义. 一、组 合 1. 组合的概念 C++ 中组合是指结构和类的声明嵌入结构变量或对象作 为其成员的现象。嵌入的对象成员亦称为成员对象,包容这 个成员对象的类可称为组合类或包容类。 当构造一个组合类的对象时,系统首先为每一个数据成 员分配内存空间,仅当其中的成员对象获得完备的内存时才 导致组合类实例的诞生。 编译器先调用嵌入对象的构造函数,然后调用组合类的 构造函数。. 嵌入对象所隶属的类或嵌入类对应既定的事物,组合类 对应某种新生的事物。 - PowerPoint PPT Presentation

Citation preview

Page 1: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

1

C/C++ 程序设计

一、组 合

二、继承和派生

三、派生类的声明和对象定义

Page 2: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

2

C/C++ 程序设计

一、组 合1. 组合的概念 C++ 中组合是指结构和类的声明嵌入结构变量或对象作为其成员的现象。嵌入的对象成员亦称为成员对象,包容这个成员对象的类可称为组合类或包容类。 当构造一个组合类的对象时,系统首先为每一个数据成员分配内存空间,仅当其中的成员对象获得完备的内存时才导致组合类实例的诞生。 编译器先调用嵌入对象的构造函数,然后调用组合类的构造函数。

Page 3: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

3

C/C++ 程序设计

嵌入对象所隶属的类或嵌入类对应既定的事物,组合类对应某种新生的事物。 C++ 中一个重要的思想是先构造既定的对象,然后构造新生的对象。

这也适应继承的情况,先构造基类再构造派生类。但析构的次序与构造相反,先诞生的对象后撤离内存空间。 对象的构造和析构遵循堆栈空间先进后出的原理。

Page 4: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

4

C/C++ 程序设计

2. 引用型成员和 const 成员 类中允许存在引用型数据成员和 const 数据成员。引用应依附于另一个独立的变量,等待及时的初始化。 Const 数据成员是仅仅初始化一次其值便不再改变的数据成员。

对于存在 const 型的不变成员或引用型成员,系统提供的缺省的赋值运算符函数不起作用。引用型成员和不变成员只能借助冒号语法初始化。

Page 5: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

5

C/C++ 程序设计

当基于内存数据共享而设置引用型成员的时候,应注意引用型成员应关联到一个具有独立的生存期长或等长的同类型变量。 具有 const 成员的对象预埋一个不变的成员,这样的类是一个奇怪的类,因此编编程时应仔细加以处理。 下面的例子为简单见嵌入一个内置 int 类型的引用型成员和不变成员,通过该例说明构造函数初始化列表的语法格式。

Page 6: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

6

C/C++ 程序设计

[ 例 ] 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); }

Page 7: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

7

C/C++ 程序设计

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");

}

Page 8: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

8

C/C++ 程序设计

///// 程序运行输出结果:

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 是一个生存

期由对象控制的引用型成员。 将引用型成员关联到生存期短的局部变量是危险的。

Page 9: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

9

C/C++ 程序设计

3. 嵌入对象的初始化 嵌入对象所属类最好存在显式或隐含的可访问的缺省构造函数,从而可被编译器自动隐含调用。下面的规则描述嵌入对象显式的初始化: a. 组合类的构造函数显式调用嵌入对象自身的带参构造函数,这一调用是通过冒号初始化语法进行的。 b. 嵌入对象按照其在组合类中的声明次序调用自身的构造函数,而不是冒号语法列表中出现的次序。 c. 嵌入对象的构造函数对于组合类是可访问的,可访问的含义是或者嵌入对象的构造函数是公共的,或者组合类声明为嵌入类的友员类等。

Page 10: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

10

C/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); }

Page 11: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

11

C/C++ 程序设计

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);

Page 12: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

12

C/C++ 程序设计

4. 冒号初始化语法 冒号初始化是对象定义时调用构造函数进行初始化的一种方式 。构造函数冒号初始化语法的一般格式为: 类名 :: 类名 ( 形参列表 ): 引用型成员 ( 左值 ),

嵌入对象 ( 实参列表 ),const 型成员 ( 右值 ),......

{ 其它成员初始化 ; }

例如: CEmbed (int x, int y): m_r (n) {...}

CContain (int x,int y):a (x,x),b (y,y) {...}

Page 13: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

13

C/C++ 程序设计

该语法的格式是在类的构造函数表题头后紧跟冒号“:” ,冒号之后是成员对象的构造函数初始化调用列表,各列表分项之间以逗号分隔,但不构成先后求值的语义。

每一个嵌入成员对象采用函数对象名语法显式调用自身的构造函数,对于简单变量也采用构造函数的形式,不采用等号形式的初始化。 冒号之后的列表全部省略时对应编译器隐含调用成员对象可访问的缺省构造函数,这是编译器的默认设置。

构造函数中形参作用域始于冒号处止于最后一个右花括号。 因此可以采用形参或其它的全局名称初始化嵌入对象、引用型成员或 const 型数据成员。

Page 14: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

14

C/C++ 程序设计

冒号初始化语法原本是为成员对象,引用型成员或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 的别名。

Page 15: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

15

C/C++ 程序设计

二、继承和派生1. 继承的概念 继承是将已有的数据结构和算法加以接纳融为自身的一部分,派生是将新的成果连结于原来的知识体系中。继承和派生是承上启下的一个联系过程,原来的数据结构和算法对应基类,连结于基类的新的数据结构和算法构成派生类,由此形成继承树层次体系。

继承是面向对象理论程序设计中一种重要的机制,继承面向事物动态的发展,也是人认识自然的一个写照。人首先直面的是简单的自然现象,把这些简单现象以文字记录下来就形成基本规律的总结。

Page 16: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

16

C/C++ 程序设计

对于程序员而言是建立一个基本的类。随着对自然现象的深入研究和广泛的积累,各种学科流派相应的应运而生。对于跟踪模拟这些流派动态发展的程序员而言,相应建立各种各样的派生类,而无须另起炉灶。 知识的每一积累,对应类的一个扩展。古老的经验沉积在基类的数据结构和算法中,时髦的思潮注册在最近派生的类中。编译器优先利用最近成果的思想,优先使用派生类中的资源信息。这是面向对象理论为程序员提供的一种总体策划和具体实施纲领。

这其中的优点是程序的开发和课题的突破可以同步协调的前进,而不必从零出发。

Page 17: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

17

C/C++ 程序设计

基于历史的原因和书写的习惯,继承树层次体系将基类画在上面,派生类则画在基类的下面。 基类也有称为父类的,派生类亦有称为子类的。 父类和子类的称谓容易构成误解。因此本书不用或废弃模糊的父类和子类的称谓。 C++ 语言允许继承和派生出现多对一、一对多的关系。 一个基类可以拥有多个派生类,这样的基类在程序设计中的地位举足轻重,一个派生类可以同时拥有多个基类。如果一个派生类上逆时只有一个基类,称为单继承层次体系。 如果一个派生类上逆时存在两个以上的基类,称为多继承层次体系。 MFC 类库是单继承体系。 iostream 类是多继承体系。

Page 18: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

18

C/C++ 程序设计

CObject

CCmdTarget

CWinAppCUserDoc

CDocument CWinThread

CWnd

CUserApp

CView CFrameWnd CDialog

CUserView CUserDialogCMainFrame

图 MFC 关键类的层次体系

Page 19: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

19

C/C++ 程序设计

2. 间接基类和间接派生类 直接基类就是直接出现于类声明的基类表中的类。一个类都可以候选为基类,也可以作为派生类连结到另外一个既定的类上,一个类既是另外一个类的基类本身又是上层类的派生类,由此形成复杂的类层次体系。 如 CWnd上有直接基类 CCmdTarget ,间接基类CObject ,下有直接派生类 CView ,更等待用户间接派生的类 CUserView 。

C++ 语言不允许上面的 ( 间接 ) 基类成为 ( 间接 ) 派生类的

派生类,也就是禁止数据结构中环的出现。这样继承和派生就是一个有向无环图。

Page 20: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

20

C/C++ 程序设计

三、派生类的声明和对象定义 对于一个特定的演化断面,设若干基类已经适当的构建,则新类追加于这些基类的声明格式分为两种形式: 一种是单继承,一种是多继承 这里给出单继承方式的声明格式: class CDerived :继承方式 CBase

{ CDerived 类的成员声明语句 ; };

class 派生类名:继承方式 直接基类名 { 派生类的成员声明语句 ; };

Page 21: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

21

C/C++ 程序设计

其中派生类名 CDerived 是用户新引入的类名, CBase

是已经建立的类名,作为派生类的直接基类名,冒号表现一种承前启后的方式。 通过这种方式用户定义的类可插入到已经存在的类族体系。派生类的成员包括继承的成员和新增加的成员,成员的访问属性只有三种,优先由类声明内的 private,public,protected

明显确定,继承的成员可由继承方式隐含确定。 继承方式由关键字 private,public,protected给出。

Page 22: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

22

C/C++ 程序设计

单继承略微具体的格式为: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: 保护成员声明语句 ;

Page 23: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

23

C/C++ 程序设计

CBase:: m_t1;

基类名 :: 基类非私有的成员名 1;

private: 私有成员声明语句 ;

CBase:: f3;

基类名 :: 基类非私有的成员名 3;

}; // 不将基类中 private: 冒号界定的私有成员在派生类

中 //界定为其它的属性

Page 24: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

24

C/C++ 程序设计

派生类声明之后就可以用其类名定义对象,格式和基类对象定义一致。 基类的对象和派生类的对象是不同的对象,拥有独立的内存空间。 如果结构派生于基类则 public 是其默认的继承

方式;而类从基类派生出来, private 是其缺省的继承方式。 具体含义为: class CDerived: CBase{...};

等价于 class CDerived: private CBase {... };

struct CDerived: CBase {...};

等价于 struct CDerived: public CBase {... };

Page 25: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

25

C/C++ 程序设计

派生类的数据集合包含基类声明中的一个子集合。 这个子集合 subobject 许多书籍中称为子对象,嵌入对象也称为子对象。本课件将基类部分构成的子集合称为派生类的基部对象, ( 派生 ) 类的实例为完整对象。 基部对象是不独立存在的,可以将其单独提取出来赋给基类的完整对象或基类的对象引用,基类的对象引用就成为派生类中的 subobject 的替身。

Page 26: 一、组 合 二、继承和派生 三、派生类的声明和对象定义

26

C/C++ 程序设计