30
第第第 继继继继 继继继继继继继 继继继继继继继继继继继 继继继继继继继 继继继继继 继继继继继继继继继继继继继继 继继继继 继继继继

第 七 章 继承机制

  • Upload
    leoma

  • View
    137

  • Download
    0

Embed Size (px)

DESCRIPTION

第 七 章 继承机制. 继承机制的作用 继承成员的访问控制规则 继承成员的调整 类型兼容性 类层次中的构造函数与析构函数 多重继承 重复继承. A. 基类. B. 派生类. § 7.1 继承 机制 一、继承. 继承是类与类之间的一种关系 定义: “ 类 B 继承类 A ” , 或者说 “ 类 A 派生类 B ” 图解为: 则在类 B 中除了自己定义的成员之外,还自动包括了类 A 中定义的数据成员与成员函数,这些自动继承下来的成员称为类 B 的 继承成员 。. 二、继承的语法 1. 继承的语法:. class 派生类名 : 基类类名表 { - PowerPoint PPT Presentation

Citation preview

Page 1: 第 七 章 继承机制

第七章 继承机制

继承机制的作用 继承成员的访问控制规则 继承成员的调整 类型兼容性 类层次中的构造函数与析构函数 多重继承 重复继承

Page 2: 第 七 章 继承机制

§7.1 继承机制一、继承 继承是类与类之间的一种关系 定义:“类 B 继承类 A” ,

或者说“类 A 派生类 B”

图解为:

则在类 B 中除了自己定义的成员之外,还自动包括了类 A 中定义的数据成员与成员函数,这些自动继承下来的成员称为类 B 的继承成员。

A

B

基类

派生类

Page 3: 第 七 章 继承机制

二、继承的语法1. 继承的语法:

class 派生类名 : 基类类名表 {public: 公有成员说明列表 ;protected: 受保护成员说明列表 ;private: 私有成员说明列表 ;

};其中基类类名表的格式为:

access 基类类名 1, ……, access 基类类名 naccess 为继承访问控制符,规定了派生类对基类的继承方式,可为 public , private 或者 protected ,

继承访问控制符可省略,此时认为为 private

Page 4: 第 七 章 继承机制

例:class BASE {……};class A:public BASE{…… // 单继承};class B:private BASE, public D{…… // 多重继承};class C:public A, B{……};

BASE

A B

DBASE

C

私有派生

Page 5: 第 七 章 继承机制

2. 保护访问控制属性: protected 在 protected 后定义的是保护段,其中的数据

成员或成员函数称为受保护成员:具有公有成员与私有成员的双重角色。

一个类的受保护成员,对于其子孙类(派生类)的成员函数来说是公有的,对类本身及后代类之外定义的其他函数则是私有成员。

例:class BASE {

private: int x;protected: int i, j;

};class D:public BASE{

void make();};void D::make(){ int k = i* j ; …… }

成员访问控制

类自身 派生类 其他类

public 可访问 可访问 可访问

protected 可访问 可访问 不可访问

private 可访问 不可访问 不可访问

Page 6: 第 七 章 继承机制

3. 继承成员函数的重定义 派生类可以重新定义基类的成员函数,覆盖基类的

同名函数 例:class DATE {public:

DATE(int yy = 0, int mm = 0, int dd = 0); // 构造函数void set_date(int yy, int mm, int dd); // 设置日期void get_date(int& yy, int& mm, int& dd); // 取日期void print_date(); // 以 ANSI 格式( yy.mm.dd )打印日期

protected:int year, month, day; // 年、月、日

};class EUROPE_DATE: public DATE {public:

void print_date(); // 以欧洲格式( dd-mm-yy )打印日期void print(int isANSI);

};

Page 7: 第 七 章 继承机制

4. 通过类名限定符在派生类中使用基类的同名成员

void DATE::print_date(){ // 以 ANSI 格式( yy.mm.dd )打印日期

cout<<year<<“.”<<month<< “.”<<day<<“\n”;} void EUROPE_DATE::print_date(){ // 以欧洲格式( dd-mm-yy )打印日期

cout<< day <<“-”<<month<< “-”<<year<<“\n”;}

void EUROPE_DATE::print(int isANSI){ if (isANSI) DATE::print_date();

else print_date();}void main(){ EUROPE_DATE test ;

test.print_date(); // 调用 EUROPE_DATE 中重定义的同名函数// 以欧洲格式( dd-mm-yy )打印

test.DATE::print_date(); // 以 ANSI 格式( yy.mm.dd )打印}

Page 8: 第 七 章 继承机制

二、继承访问控制规则 公有继承(公有派生)、私有继承、保

护继承

无论采用什么派生的方式,派生类中都不能访问基类的私有成员

继承访问控制 基类成员访问控制

在派生类中的访问控制

public

public public

protected protected

private 不可访问

protected

public protected

protected protected

private 不可访问

private

public private

protected private

private 不可访问

Page 9: 第 七 章 继承机制

例:class BASE{

protected: int i, j;public: void get_ij();private:int x_temp;

};

class Y1:public BASE{ 公有派生:在 Y1 类中, i 、 j 是受保护成员float yMember; get_ij() 是公有成员

};

class Y2:protected BASE{ 保护派生:在 Y2 类中, i 、 j 是受保护成员…… get_ij() 变成受保护成员};

class Y3:private BASE{ 私有派生:在 Y3 类中, i 、 j 、 get_ij()都变

…… 成私有成员};

Page 10: 第 七 章 继承机制

三、派生类对象的存储组织 派生类的对象不仅存放了在派生类中定

义的非静态数据成员,而且也存放了从基类中继承下来的非静态数据成员。

例:BASE obj1;Y1 obj2;

obj1 iJ

x_temp函数指针

obj2 iJ

x_tempyMember

函数指针

Page 11: 第 七 章 继承机制

四、类型兼容性1. 赋值运算的类型兼容性

类型的赋值兼容性规则允许将后代类的对象赋值给祖先类,但反之不成立。

例: BASE obj1; Y1 obj2; obj1 = obj2 ; 把 obj2 中基类部分的内容赋给 obj1 obj2 = obj1 ;

但此规则只适用于公有派生,只有公有派生类才能兼容基类类型

Page 12: 第 七 章 继承机制

2. 参数传递与对象初始化的类型兼容性

指向基类对象的指针也可指向公有派生类对象

BASE *p ; Y1 *p1;p = &obj1; p1 = &obj1;p = &obj2; p1 = &obj2;

p = p1 ;

与赋值运算类型兼容性相同

Page 13: 第 七 章 继承机制

§7.2 继承与构造函数、析构函数一、构造函数与析构函数的调用次序 1. 构造函数的调用次序 在创建一个派生类的对象时

先调用其基类的构造函数 再调用本类对象成员的构造函数 最后才调用本类的构造函数

2. 析构函数的调用次序 先调用本类的析构函数 再调用本类对象成员的析构函数 最后才调用其基类的析构函数

Page 14: 第 七 章 继承机制

例: #include <iostream.h> class C {public: C() // 构造函数

{ cout << "Constructing C object.\n"; }~C() // 析构函数{ cout << "Destructing C object.\n"; }

};class BASE {public: BASE() // 构造函数

{ cout << "Constructing base object.\n"; }~BASE() // 析构函数{ cout << "Destructing base object.\n"; }

}; class DERIVED: public BASE {

C mOBJ;public: DERIVED() // 构造函数

{ cout << "Constructing derived object.\n";}~DERIVED() // 析构函数{ cout << "Destructing derived object.\n"; }

};

Page 15: 第 七 章 继承机制

int main(){ DERIVED obj; // 声明一个派生类的对象 // 什么也不做,仅完成对象 obj 的构造与析构

return 0;}

运行结果:Constructing base object.Constructing C object.Constructing derived object.Destructing derived object.Destructing C object.Destructing base object.

Page 16: 第 七 章 继承机制

二、向基类构造函数传递实际参数 给基类构造函数传递实际参数是通过向派生

类构造函数传递实际参数以及初始化列表来间接实现传递的。

带初始化列表的派生类构造函数的一般形式派生类名 ( 参数表 ) : 基类名 ( 调用基类构造函数参数表 )

{ 派生类构造函数体}

Page 17: 第 七 章 继承机制

例:#include <iostream.h>class Base {

int private1, private2;public:

Base(int p1, int p2){ private1 = p1; private2 = p2;}

int inc1() { return ++private1; }int inc2() { return ++private2; }

void display() { cout<<"private1 = "<<private1<<", privte2 = "<<private2<<"\n";}

};

class Derived:private Base{int private3;Base private4;

public:Derived(int p1, int p2, int p3, int p4, int p5):Base(p1,p2),private4(p3,p4){ private3 = p5 ; }

int inc1() { return Base::inc1();}int inc3() { return ++private3 ; }

Page 18: 第 七 章 继承机制

void display() {Base::display();private4.display();cout<<"private3 = "<<private3<<"\n";

}};void main(){ Derived obj( 17, 18, 1, 2, -5);

obj.inc1();obj.display();

}

输出结果:private1 = 18, private2 = 18private1 = 1, private2 =2private3 = -5

obj

1718-15

12

private1 从 基 类继承private2private3private4.private1private4.private2

18

Page 19: 第 七 章 继承机制

§7.3 多重继承一、多重继承

多重继承:一个类由多个基类派生而来单继承: 一个类由单个基类派生而来

多重继承的语法:class 派生类名 : access 基类名 1, ……,

access 基类名 n{…

}; 基类名 2基类名 1 基类名 n

派生类名

……

Page 20: 第 七 章 继承机制

二、多重继承的名字冲突问题 名字冲突:指两个基类具有相同名字的成员时,在派生

类中这个名字会产生二义性,即编译程序无法确定派生类的对象使用该名字时应调用哪一基类中的版本。

例:class BASE1 {public: void show() { cout << i << "\n"; }protected:int i;}; class BASE2 {public: void show() { cout << j << "\n"; }protected:int j;};

Page 21: 第 七 章 继承机制

// 多重继承引起名字冲突: DERIVED 的两个基类 BASE1 和 BASE2 有相同的名字 show() 。

class DERIVED: public BASE1, public BASE2 {public: void set(int x, int y)

{ i = x; j = y; }}; // 派生类在编译时不出错: C++并不禁止名字冲突的产生 int main(){ DERIVED obj; // 声明一个派生类的对象

obj.set(5, 7); // set() 是 DERIVED 类自身定义的// obj.show();// 二义性错误,编译程序无法决定调用哪一个版本obj.BASE1::show(); // 显式地调用从 BASE1 继承下来 show()obj.BASE2::show(); // 显式地调用从 BASE2 继承下来 show()return 0;

}

Page 22: 第 七 章 继承机制

名字冲突的解决方法 使用时,用作用域运算符明确指明使用那个基类的成

员函数 obj.BASE1::show();

在派生类中重定义有名字冲突的成员class DERIVED: public BASE1, public BASE2 {public: void set(int x, int y) { i = x; j = y; }

void show(){ cout << i << "\n"; cout << j << "\n"; }

}; int main(){ DERIVED obj; // 声明一个派生类的对象

obj.set(5, 7); // set() 是 DERIVED 类自身定义的obj.show();// 无二义性问题,调用的是 DERIVED 中新定义的版本obj.BASE1::show(); // 仍然可调用从 BASE1 继承下来 show()obj.BASE2::show(); // 仍然可调用从 BASE2 继承下来 show()return 0;

}

Page 23: 第 七 章 继承机制

三、多重继承的构造函数和析构函数 多个基类构造函数的调用次序是按基类在被继

承时所声明的次序、从左到右依次调用的,与它们在派生类构造函数实现中的初始化列表中出现的次序无关。

例:class DERIVED: public BASE2, public BASE1 {

public:DERIVED(int x, int y): BASE1(x), BASE2(y){ cout << "Constructing derived object.\n";}……

};……DERIVED obj(3,4); 则 obj 在创建时,先调用 BASE2 的构造函数,

然后调用 BASE1 的构造函数,最后才执行自己 DERIVED 的构造函数

Page 24: 第 七 章 继承机制

§7.4 重复继承 一、重复继承 1. 定义 定义:指一个派生类多次继承同一个基

类 C++中关于继承的限制

不允许直接或间接让一个类继承自己 不允许一个派生类直接继承同一个基类两次

以上 不允许一个基类即是直接基类又是间接基类

B B

A C

D

Page 25: 第 七 章 继承机制

2. 重复继承的两种类型 复制继承:被多次重复继承的基类有多个实体副本

共享继承:被多次重复继承的基类只有一个实体副本

例:

B B

A C

D

B

A C

D

Page 26: 第 七 章 继承机制

二、重复继承的二义性问题 若在继承时没有作特殊声明,此时采用的是复制继承,会导致重复继承的二义性问题。

例:class BASE {public: int i;}; class BASE1: public BASE {

public: int j;}; class BASE2: public BASE {

public: int k;}; class DERIVED: public BASE1, public BASE2 {

public: int sum;}; void main(){ DERIVED obj; // 声明一个派生类对象

// obj.i = 3; // 错误,编译程序无法确定使用 i 的哪一份副本 obj.j = 5; // 正确的,使用从 BASE1 继承下来的 jobj.k = 7; // 正确的,使用从 BASE2 继承下来的 k

}

BASE BASE

BASE1

BASE2

DERIVED

obj

BASE1. BASE.i BASE1. j BASE2. BASE.i BASE2. k sum 函数指针

Page 27: 第 七 章 继承机制

解决二义性的方法 如不改变重复继承的方法(还是复制继

承),则采用作用域运算符 ::明确指明采用哪个副本

int main(){ DERIVED obj;

obj.BASE1::i = 3; ……

} 改用共享方式继承:用虚基类机制保证任何派生类中只提供一个基类的副本

Page 28: 第 七 章 继承机制

三、虚基类 定义:虚基类是当基类被继承时,在基

类的继承访问控制关键字前面加上关键字 virtual 来定义的。

普通基类与虚基类之间的唯一区别只有在派生类重复继承了某一基类时才表现出来,虚基类用于实现共享继承

Page 29: 第 七 章 继承机制

class BASE {public: int i;}; class BASE1: virtual public BASE {

public: int j;}; class BASE2: virtual public BASE {

public: int k;}; class DERIVED: public BASE1, public BASE2 {

public: int sum;}; int main(){ DERIVED obj; // 声明一个派生类对象

obj.i = 3; // 正确obj.j = 5; // 正确的,使用从 BASE1 继承下来的 jobj.k = 7; // 正确的,使用从 BASE2 继承下来的 kreturn 0;

}

BASE

BASE1

BASE2

DERIVED

obj

BASE1. BASE.i BASE1. j BASE2. k sum 函数指针

Page 30: 第 七 章 继承机制

虚基类的构造函数与析构函数 对虚基类构造函数的调用总是先于普通基类的构

造函数。 虚基类的唯一副本只被初始化一次 C++中构造函数的调用次序

(1) 最先调用虚基类的构造函数。(2) 其次调用普通基类的构造函数,多个基类则按派生

类声明时列出的次序、从左到右调用,而不是初始化列表中的次序。

(3) 再次调用对象成员的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序。

(4) 最后执行派生类的构造函数。