C++程序设计课件 设计制作:徐龙琴 2
教学内容:
12.1 继承 的概念
12.2 派生类
12.3 二义性问题
12.4 赋值兼容规则
C++程序设计课件 设计制作:徐龙琴 3
?能 通过继承已有的类 建立新类
?掌握 继承的三种方式,公有、保护、私有继承
?掌握 派生类 和 基类 的 概念 ;
?掌握 派生类 的 构造函数 和 析构函数 ;
?理解 多重继承 和 虚基类
教学要求:
C++程序设计课件 设计制作:徐龙琴 4
§ 12.1 继承的概念
继承 是 面向对象程序设计的基本特征之一, 它允许 在原类的
基础上 创建新类, 新类 即可从 一个或多个原类中继承 (共享)其
函数和数据,也可以 重新定义原类中没有的数据和函数。故 通过
类的, 继承,, 可将原来的程序代码重复使用,从而减少了程序
代码的冗余度,提高了编程的效率。
类的 继承 是 新类从已有类那里得到已有的特性。从已有类产
生新类的过程就 是 类的派生 。在继承过程中,原类 称为 基类或
父类,而由类派生的 新类 则 称为 派生类或子类。 派生新类的过
程 包括 吸收基类的成员、调整基类成员和添加新的成员三步。
C++程序设计课件 设计制作:徐龙琴 5
据 派生类 所 拥有 的 基类数目 不同,可以分为,
单继承,一个类只有一个直接基类时,称为单继承
多继承,一个类同时有多个直接基类时,则称为多继承
基类与派生类 之间的 关系 如下:
① 基类是对派生类的抽象,派生类是对基类的具体化
② 派生类是基类的组合。多继承可看作是多个单继承的简
单组合。
③ 新类也可作为基类再派生新类,且一个基类可同时派生出
多个派生类。基类的基类甚至更高层的基类称为间接基类
④ 派生类可对一些继承来的函数重新定义,以适应新的要求
⑤ 派生类包含了它所有基类中除构造和析构函数之外的所有成员。
C++程序设计课件 设计制作:徐龙琴 6
§ 12.2 派生类
⒈ 派生类的定义格式:
class 派生类名, 继承方式 基类名 1,… 继承方式 基类名 n
{ 派生类 新成员 声明; //与前面声明一个类时完全相同
};
例, 假设基类 Base1,Base2是已声明的类,声明一个名为 Dr1的
派生类,该类从基类 Base1,Base2派生而来:
class Dr1:public Base1,private Base2
{ 派生类 新成员 声明;
};
?继承方式, 指定了 派生类成员以及类外对象对于从基类
继承来的成员的访问权限 。有 三种继承方式,
public:表示 公有继承 ;
private:表示 私有继承,是 默认方式 ;
protected:表示 保护继承 。
C++程序设计课件 设计制作:徐龙琴 7
下表是不同继承方式的基类在派生类中的访问权限
继承方式 基类特性 继承后在派生类特性
公有继承
public public
protected protected
private 不可访问
私有继承
public private
protected private
private 不可访问
保护继承
public protected
protected protected
private 不可访问
C++程序设计课件 设计制作:徐龙琴 8
从上表可以看出:
① 公有继承时,基类成员的 访问权限 在派生类中不变 。于是 派
生类的其他成员 可以直接访问继承来的公有和
保护成员 。其他 外部使用 者只能 通过 派生类的
对象访问继承来的公有成员
② 保护继承时,基类成员的 访问权限 在派生类中 全变为保护的
访问方式 。于是派生类的其他成员可直接访
问从基类继承来的公有和保护成员,但在类外
部通过派生类的对象无法访问它们
C++程序设计课件 设计制作:徐龙琴 9
③ 私有继承时,基类成员的访问权限在派生类中 全变为私有的访
问方式
④ 基类中私有成员在派生类中 是隐藏的,只能在基类内部访问。
⑤ 派生类中的成员 不能访问 基类中的私有成员, 可以访问 基类
中的 公有成员和保护成员
⑥ 派生类中 用, 类名,,成员, 访问基类成员
⑦ 若派生类定义了与基类同名的成员, 若要 在派生类中使用基
类同名成员, 可用,类名,,成员
⑧ 派生类 对基类的静态成员的访问用, 类名,,成员
C++程序设计课件 设计制作:徐龙琴 10
⒉ 派生类的 构造函数:
派生类的数据成员 由 所有基类的数据成员 与 派生类新增
的数据成员 共同 组成, 如果 派生类新增成员中 包括其他类的
对象 (子对象),派生类的数据成员 中实际上还 间接包括 了
这些对象的数据成员 。因此,构造派生类的对象时, 必须对
基类数据成员、新增数据成员和成员对象的数据成员进行初
始化 。 派生类的构造函数 必须 要 以 合适的初值作为参数, 隐
含调用 基类和新增对象成员的构造函数,来初始化它们各自
的数据成员,然后再加入新的语句 对新增普通数据成员进行
初始化。
C++程序设计课件 设计制作:徐龙琴 11
⑴ 派生类构造函数的 一般格式 如下:
派生类构造函数 执行的一般次序如下:
1) 调用基类构造函数, 调用顺序按照它们在定义派生类中声
明的顺序;
2) 调用内嵌成员对象的构造函数, 其调用顺序同上
3) 派生类的构造函数体中的内容
派生类名,:派生类名(参数总表),基类名 1(参数表 1), …,
基类名 n(参数表 n)
子对象名 1:(子对象参数表 1), …,
子对象名 m(子对象参数表 m)
{ 派生类新增成员的初始化语句;
}
C++程序设计课件 设计制作:徐龙琴 12
① 若基类中 定义了 缺省构造函数 或根本 没有 定义任何一个
构造函数 (此时,由编译器自动生成缺省构造函数)时,
在 派生类构造函数的定义 中 可以省略 对该基类构造函数的
调用 。 子对象的情况也一样 。
② 若所有基类和子对象的构造函数 都不需要参数 且 派生类
也不需要 参数时, 派生类构造函数 可以不定义 。
③若 所有基类和子对象的构造函数 都 可省略时, 可以省略
派生类构造函数的定义
④ 若基类的构造函数定义了 一个或多个参数时,派生类 必
须定义构造函数
注意:
C++程序设计课件 设计制作:徐龙琴 13
⑤ 每个 派生类 只需负责 其直接基类的构造
⑥ 对基类成员和子对象成员的 初始化必须在 成员初始化列表
中进行,新增成员的初始化既可以在成员初始化列表中进
行,也可以在构造函数体中进行。
C++程序设计课件 设计制作:徐龙琴 14
例,派生类构造函数举例
#include<iostream.h>
class B1 ////基类 B1,构造函数有参数
{ public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;}
};
class B2 ////基类 B2,构造函数有参数
{ public:
B2(int j){ cout<<"constructing B2 "<<j<<endl;}
};
class B3 ////基类 B3,构造函数无参数
{ public:
B3( ) {cout<<"constructing B3 *"<<endl;}
};
C++程序设计课件 设计制作:徐龙琴 15
class C:public B2,public B1,public B3 //派生新类 C,注意基类名的顺序
{ public,//派生类的公有成员
C(int a,int b,int c,int d):B1(a),memberB2(d),memberB1(c),B2(b)
{ } //注意基类名的个数与顺序及成员对象名的个数与顺序
private,//派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main( )
{ C obj(1,2,3,4); }
程序运行结果是:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
C++程序设计课件 设计制作:徐龙琴 16
⑵ 派生类 析构造函数,
功能, 是在该类对象消亡之前进行一些必要的 清理工作 。
定义方法, 与没有继承关系的 类中析构函数的定义方法完全相同,
只要在函数体中负责把派生类新增的非对象成员的清
理工作做好就够了,系统会自己调用基类及成员对象
的析构函数来对基类及对象成员进行清理
执行次序, 和构造函数正好相反,首先对派生类新增普通成员
进行清理,再新增的对象成员进行清理,最后对所
有从基类继承来的成员进行清理。这些清理工作分
别是执行派生类析构函数体、调用派生类对象成员
所在类的析构函数和调用基类析构函数
C++程序设计课件 设计制作:徐龙琴 17
例,派生类析构函数举例
#include<iostream.h>
class B1 ////基类 B1,构造函数有参数
{ public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;}
~B1( ) {cout<<"destructing B1"<<endl;}
};
class B2 ////基类 B2,构造函数有参数
{ public:
B2(int j){ cout<<"constructing B2 "<<j<<endl;}
~B2() {cout<<"destructing B2"<<endl;}
};
class B3 ////基类 B3,构造函数无参数
{ public:
B3( ) {cout<<"constructing B3 *"<<endl;}
~B3( ) {cout<<"destructing B3 "<<endl;}
};
C++程序设计课件 设计制作:徐龙琴 18
class C:public B2,public B1,public B3 //派生新类 C,注意基类名的顺序
{ public,//派生类的公有成员
C(int a,int b,int c,int d):B1(a),memberB2(d),memberB1(c),B2(b)
{ } //注意基类名的个数与顺序及成员对象名的个数与顺序
private,//派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main( )
{ C obj(1,2,3,4); }
程序运行结果是:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
C++程序设计课件 设计制作:徐龙琴 19
§ 12.3 二义性问题
一般说来, 在派生类中对基类成员的访问应该是惟一的 。
但是, 由于 多继承情况下, 可能造成对基类中某个成员的访
问出现了不惟一的情况 (例;访问多基类中的同名成员 ),则
称为 对基类成员访问的二义性问题 。
1,同名成员的二义性
在多重继承中,如果不同基类中有同名的函数,则
在派生类中就有同名的成员,这种成员会造成二义性。
C++程序设计课件 设计制作:徐龙琴 20
例,class A
{ protected:
void f();
};
class B
{ public:
void f();
void g();
};
class C,public A,public B
{ public:
void g();
void h();
};
C obj;
obj.f( ); //无法确定访问 A中或是 B中的 f()obj.A::f( );//A中的 f( );
注意, 二义性检查在访问控制权限或类型检查之前进行,
访问控制权限不同或类型不同不能解决二义性问题 。
C++程序设计课件 设计制作:徐龙琴 21
使用基类名可避免这种二义:
obj.A::f();//A中的 f();
obj.B::f();//B中的 f();
例如:
obj.g(); //隐含用 C的 g()
obj.B::g();//用 B的 g()
以上这种 用基类名来控制成员访问的规则 称为 支配原则 。
注意,若派生类的多个基类拥有同名的成员,同时,派生类又
新增这样的同名成员,则派生类成员将覆盖所有基类的同名成
员。若派生类的多个基类拥有同名的成员但派生类没有声明同
名成员这时,这时就必须通过作用域分辨符,:来标识成员
C++程序设计课件 设计制作:徐龙琴 22
例,多继承同名覆盖举例
#include<iostream.h>
class B1
{ public,int nV;
void fun() {cout<<"Member of B1"<<endl;} };
class B2
{ public,int nV;
void fun() {cout<<"Member of B2"<<endl;} };
class D1:public B1,public B2 //声明派生类 D1
{ public,int nV;
void fun() {cout<<"Member of D1"<<endl;}};
void main( )
{ D1 d1; d1.nV=1; //对象名,成员名标识
d1.fun( ); //访问 D1类成员
d1.B1::nV=2; //作用域分辨符标识
d1.B1::fun( ); //访问 B1基类成员
d1.B2::nV=3; //作用域分辨符标识
d1.B2::fun( ); } //访问 B2基类成员
程序运行结果是:
Member of D1
Member of B1
Member of B2
C++程序设计课件 设计制作:徐龙琴 23
2.多重继承的二义性
B B
D1 D2
D3
从上图可以看出,派生类 D3中将继承两份类 B的成员,一份
由类 D1派生得到,另一份由 D2派生而来,
这时通过派生类 D3的对象访问类 D1和 D2
的成员不会有问题,但访问类 B的成员时
编译程序不知道到底要访问哪一份的成员,
C++为此提供了 虚基类, 以解决这种二义性。
C++程序设计课件 设计制作:徐龙琴 24
class 派生类名,virtual 继承方式 基类名
{ 声明派生类成员 };
⑴ 虚基类,是这样的一个基类,它虽然被一个派生类间接地
多次继承,但派生类却只继承一份该基类的成员,
这样,避免了在派生类中访问这些成员时产生二
义性。
将一个基类 声明为虚基类 必须在 各派生类 定义时,
在基类的名称前面加上关键字 virtual。
⑵ 虚基类的派生类的定义 格式如下:
C++程序设计课件 设计制作:徐龙琴 25
例,虚基类举例
#include<iostream.h>
class B0
{ public,int nV;
void fun( )
{cout<<"Member of B0"<<endl;} };
class B1,virtual public B0 //BO为虚基类,派生 B1类
{ public,int nV1; };
class B2,virtual public B0 //BO为虚基类派生 B2类
{ public,int nV2; };
class D1,public B1,public B2 //派生类 D1声明
{ public,int nVd;
void fund( )
{cout<<"Member of D1"<<endl;}
};
void main() //程序主函数
{ D1 d1; //定义 D1类对象 d1
d1.nV=2; //使用直接基类
d1.fun( );
d1.B0::nV=2;
d1.B0::fun( ); }
程序运行结果是:
Member of B0
Member of B0
C++程序设计课件 设计制作:徐龙琴 26
⑶ 虚基类及其派生类的构造函数,
在上例中,虚基类的使用显得非常方便、简单,这是由于
该程序中的 所有类使用的都是 编译器自动生成的默认构造函
数 。 若虚基类 定义有非默认形式的 (即带形参的 )构造函数,
并且没有定义默认形式的构造函数,这时,在整个继承结构
中,直接或间接继承 虚基类 的 所有派生类, 都必须在构造函
数的成员初始化表 中 列出对虚基类 的 初始化 。
总之,对虚基类的任何派生类, 其构造函数 不仅 负责 调用
直接基类的构造函数, 还需调用 虚基类的构造函数,
C++程序设计课件 设计制作:徐龙琴 27
在使用虚基类时要注意:
(1) 类 在类族中既 可用作 虚基类,也可被用作 非虚基类
(2) 派生类的构造函数 的 成员初始化列表 中 必须列出对虚基
类构造函数的调用 ;如果未列出,则表示使用该虚基类
的缺省构造函数。
(3)从 虚基类 直接或间接派生 的派生类中 的 构造函数的成员初
始化列表中都要列出对虚基类构造函数的调用 。 但只有 用
于建立对象的 最派生类 的 构造函数调用虚基类的构造函数,
而该派生类的所有基类中列出的对虚基类的构造函数的调
用在 执行中被忽略
(4)若 同时 出现 对虚基类 和 非虚基类构造函数 的 调用时, 虚基
类的构造函数 先 于非虚基类的构造函数 执行 。
C++程序设计课件 设计制作:徐龙琴 28
例,虚基类及其派生类构造函数举例:
#include <iostream.h>
class B0
{ public,B0(int n) {nV=n;}
int nV;
void fun( ) {cout<<"Member of B0"<<endl;} };
class B1:virtual public B0 //BO为虚基类,派生 B1类
{ public,B1(int a):B0(a){ }
int nV1; };
class B2:virtual public B0 //BO为虚基类,派生 B2类
{ public,B2(int a):B0(a){ }
int nV2; };
class D1:public B1,public B2 //派生类 D1声明
{ public:
D1(int a):B0(a),B1(a),B2(a){ }
int nVd;
void fund() {cout<<"Member of D1"<<endl;} };
void main( )
{ D1 d1(10); //声明 D1类对象 d1
d1.nV=2;
d1.fun( ); }
程序运行结果是:
Member of B0
C++程序设计课件 设计制作:徐龙琴 29
§ 12.4 赋值兼容规则
⒈ 赋值兼容规则,是指 在需要 基类对象 的任何地方 都可用公
有派生类的对象来替代 。 由于 通过公有继
承,派生类得到了基类中除构造函数、析
构函数之外的所有成员,这样,公有派生
类 实际就 具备了基类的所有功能,凡是基
类能解决的问题,公有派生类都可以解决。
① 派生类的对象 可以赋值给基类对象 。
② 派生类的对象 可以初始化基类的引用 。
③ 派生类对象的地址 可以赋给指向基类的指针 。
⒉ 赋值兼容规则中所指的 替代包括以下的情况:
C++程序设计课件 设计制作:徐龙琴 30
例,赋值兼容规则实例,
#include <iostream.h>
class B0
{ public:
void display() {cout<<"B0::display()"<<endl;}
};
class B1:public B0
{ public:
void display() {cout<<"B1::display()"<<endl;}
};
class D1:public B1
{ void display() {cout<<"D1::display()"<<endl;}
};
void fun(B0 *ptr) //参数为指向基类对象的指针
{ ptr->display( ); //"对象指针 ->成员名 "
}
C++程序设计课件 设计制作:徐龙琴 31
void main( )
{ B0 b0; //声明 B0类对象
B1 b1;
D1 d1;
B0 *p; //声明 B0类指针
p=&b0; //BO类指针指向 B0类对象
fun(p);
p=&b1; //BO类指针指向 B1类对象
fun(p);
p=&d1; //BO类指针指向 D1类对象
fun(p);
}
程序运行结果是:
B0::display
B0::display
B0::display
C++程序设计课件 设计制作:徐龙琴 32
§ 程序举例
例,编写一个程序计算出球、圆柱和圆锥的表面积和体积。
分析,计算它们都需要 用到 圆的半径,有时还可能用到 圆的
面积, 故可把圆 定义为一个类 。它 包含的 数据 成员为 半径,
由于不需要作图,所以不需要定义圆心坐标。圆的半
径应定义为保护属性,以便派生类能够继承和使用。圆类的
公用函数是 给半径赋初值的构造函数, 计算圆面积的函数 ;
也可以包含 计算圆的体积的函数,但让其返回 0,表示圆的体
积为 0。 定义好圆类后,再 把球类, 圆柱类 和 圆锥类 定义为圆
的派生类 。在这些 类中同样包含有 新定义的构造函数, 求表面
积的函数 和 求体积的函数 。另外在 圆柱和圆锥类 中 应分别新定
义 一个 表示其高度的数据成员 。此题的完整程序如下:
C++程序设计课件 设计制作:徐龙琴 33
#include<iostream.h>
#include<math.h>
const double PI=3.1415926;
class Circle //圆类
{ protected,double r; //半径
public:
Circle(double radius = 0)
{ r =radius; }
double Area( ) //计算圆的面积
{return PI*r*r ; }
double Volume( ) //计算圆的体积
{ return 0; }
};
C++程序设计课件 设计制作:徐龙琴 34
class Sphere,public Circle //球体类
{ public,
Sphere(double radius = 0),Circle(radius)
{ }
double Area( )
{ return 4*PI*r*r ; } // 可用 4*Circle::Area( )来代替
double Volume( ) //计算球的体积
{ return 4*PI*pow(r,3)/3;} } ; // pow(r,3)求出 r的立方值
class Cylinder:public Circle //圆柱体类
{ double h; //高度
public,
Cylinder(double radius=0,double height = 0 ), Circle(radius)
{ h = height; }
double Area( ) // 计算圆柱体的表面积
{ return 2*PI*r*(r+h); }
double Volume( ) //计算圆柱体的体积
{ return PI*r*r*h; } //它可以用 Circle:,Area( ) * h来代替
};
C++程序设计课件 设计制作:徐龙琴 35
class Cone:public Circle //圆锥体类
{ double h; //高度
public,
Cone(double radius = 0,double height = 0):Circle(radius)
{ h=height; }
double Area( ) // 计算圆锥体的表面积
{ double l=sqrt(h*h+r*r); //sqrt函数求出参数值的平方根
return PI*r*(r+l); }
double Volume( ) //计算圆锥体的体积
{ return PI*r*r*h/3; }
};
C++程序设计课件 设计制作:徐龙琴 36
void main ( )
{ Circle r1(2);
Sphere r2(2);
Cylinder r3(2,3);
Cone r4(2,3);
cout << "Circle:" << r1.Area( ) << " "<<r1.Volume( )<<endl;
cout << "Sphere:" << r2.Area( ) << " "<<r2.Volume( )<<endl;
cout << "Clinder:" << r3.Area( ) << " "<<r3.Volume( )<<endl;
cout << "Cone:" << r4.Area( ) << " "<<r4.Volume( )<<endl;
}
程序运行结果是,
Circle,12.5664 0
Sphere,50.2655 33.5103
Cylinder,62.8319 37.6991
Cone,35.2207 12.5664