C++面向对象程序设计计算机与信息学院罗宪第四章 派生类、基类和继承性
(教材① P201-240)
本章主要内容:
继承的概念
单继承的派生类
派生类的构造函数与析构函数
基类和派生类的赋值规则
多继承
综合举例第四章 派生类、基类和继承性
(教材① P201-240)
继承是面向对象程序设计最重要的概念之一;
继承主要作用:提高软件的可重用性和可维护性,从而大大减少软件的代码量;
继承的思想是学习面向对象程序设计应建立的最主要的思想 —— 在程序设计时,面对多个实体,首先应分析的是能否采用继承机制处理。
在 C++面向对象程序设计中,实现继承的机制是通过派生得到的,即在已有类的基础上创建新类,从而在新类中继承原类的成员(数据及函数)。
原类(被继承的类)称为基类,在基类的基础上创建的新类称为派生类。
第一节 继承的概念
(教材① P201-203)
一、什么是继承
继承是客观世界中实体间的一种关系。如:
汽车运输汽车消防车货车客车 洒水车专用汽车第一节 继承的概念
(教材① P201-203)
Point(x,y)
Text
Editor
Window
Rectangle Circle
Graphics
Window
Text
Window
Icon
Editor
第一节 继承的概念
(教材① P201-203)
由此可知,继承具有以下特点:
继承具有层次结构
越在上面的类越具有普通性和共性,下层比上层更具体,越在下层越细化、专门化。
继承具有传递性,即派生类能自动继承上层基类的全部数据结构及操作方法(数据成员及成员函数)。
三、继承的分类及有关术语
1、继承的分类:
单继承:只有一个基类的继承 —— 构成树形类层次结构( P203)
多继承:有二个或以上基类的继承 —— 构成有向图的层次结构( P203)
第一节 继承的概念
(教材① P201-203)
2、继承的有关术语
基类( Base)
派生类 (Derived)
直接基类
间接基类
直接派生
间接派生第二节 单继承的派生类
(教材① 203-219)
一、派生类的定义
1、定义格式:
class 派生类名,<继承方式 > 基类名
{ <派生类新定义的成员 > }
例 【 4.1】 在基类 base中定义一个平面点,通过派生
derived定义空间点。
第二节 单继承的派生类
(教材① 203-219)
#include<iostream>
using namespace std;
class Base //基类 Base的定义
{ private:
float x,y;
public:
Base(float a=0,float b=0)
{x=a;y=b;}
void print(void)
{ cout<<"x="<<x<<"\ny="<<y<<endl;}
};
第二节 单继承的派生类
(教材① 203-219)
class Derived:public Base
//派生类 Derived的定义 —— 注意定义方法
{ private:
float z;
public:
Derived(float a=0,float b=0,float c=0):Base(a,b)
{ z=c;}//派生类的构造函数定义
void print(void) //派生类的成员函数
{ Base::print();//调用基类 Base的成员函数 print();
cout<<"z="<<z<<endl;
}
};
第二节 单继承的派生类
(教材① 203-219)
void main(void)
{ Base a(10.0,20.0);
Derived b(10.0,20.0,30.0);
a.print();
b.print();
}
第二节 单继承的派生类
(教材① 203-219)
2、说明:
在定义派生类时用,:”将派生类与基类分开,构成类的层次结构;
派生类与普通类的定义一样,仍然有数据成员与成员函数;
派生类的构造函数中必须提供为基类数据成员进行初始化的参数表。格式:
派生类名 (参数表 ),基类名 (参数名 1,参数名 2,…)
{ <函数体 > }
其中,参数表中必须包含基类的数据成员和派生类中新增数据成员的初始化参数。
第二节 单继承的派生类
(教材① 203-219)
二、公有继承和私有继承
从上例可以看到,在定义派生类时,需要在基类名前加上派生方式,或称为继承方式。
㈠ 继承方式
1、继承方式种类
继承方式包括:公有继承( public)、私有继承
( private)、保护继承( protected)等三种继承方式;
其派生类分别称为公有派生类、私有派生类、保护派生类。
2、不同继承方式下基类成员的访问特性 —— 教材①
P206表 4.1。
由于保护继承很少使用,主要讨论公有继承和私有继承。
第二节 单继承的派生类
(教材① 203-219)
由表中可知:
⑴ 基类的私有成员在派生类的作用域内均不可见。
无论那种继承方式,派生类的成员函数均不能使用基类的私有成员;
派生类中可以通过调用基类中的公有成员函数访问基类中的私有数据成员。
⑵ 保护成员
私有继承:保护成员在派生类中变为私有成员 —— 可以在派生类中直接访问;
公有继承:保护成员在派生类中的访问权限不改变 —
— 可以在派生类中直接访问;
第二节 单继承的派生类
(教材① 203-219)
例 【 4.2 】 保护成员的使用( e402.cpp)
⑶ 公有成员
私有继承:公有成员在派生类中变为私有成员;
公有继承:公有成员在派生类中的访问权限不改变。
3、结论:
公有继承与私有继承的相同点:两种派生方式,其基类中的私有成员,在派生类中均无权使用,只能通过基类的公有成员访问;
公有继承与私有继承的不同点:公有派生时,基类中的公有成员、保护成员在派生类中的访问权限不变;
私有继承时,基类中的公有成员、保护成员均变为私有成员。
第二节 单继承的派生类
(教材① 203-219)
三、基类对象和派生类对象
对象对数据成员的访问是通过调用公有成员函数实现的。
在不同继承方式下,派生类对象访问基类成员的权限是不相同的。
㈠ 类及对象的访问权限
在第三章中已介绍类及对象的访问权限(无论是基类或者是派生类)。概括如下(教材① P208图 4.6所示)
类中的成员函数可以直接访问类中的数据成员
(包括私有成员、公有成员、保护成员);
类中的成员函数可以相互访问;
类的对象只能访问类的公有成员,不能访问私有成员及保护成员第二节 单继承的派生类
(教材① 203-219)
㈡ 派生类及对象的访问权限
1、公有继承方式
由于在公有继承方式下,基类的公有成员在派生类中仍然是公有的,保护成员仍然是保护的,私有成员不可见,因此,访问权限为(教材① P208图 4.6所示),
派生类的成员函数可以访问基类的公有成员、保护成员,不能访问基类的私有成员(不可见);
派生类的对象能访问基类的公有成员,不能访问保护成员(保护成员不能被自身对象访问)、私有成员(不可见)
派生类的对象可以通过调用基类的公有成员访问基类的私有成员 —— 打通了派生类访问基类私有成员的消息通道。
教材① P208-209例 4.3
第二节 单继承的派生类
(教材① 203-219)
公有继承方式下,派生类对象访问基类成员(私有成员或保护成员)的消息通路:
⑴ 通过调用基类的公有成员函数访问格式,派生类对象,基类公有成员函数名();
或,派生类对象名,基类名,:基类公有成员函数名() ;
—— 直接消息通道
⑵ 通过调用派生类公有成员函数,在公有成员函数中调用基类的公有成员函数访问,即:
派生类对象 ->派生类公有成员函数 ->基类公有成员函数 ->基类私有成员 —— 间接消息通道。
第二节 单继承的派生类
(教材① 203-219)
2、私有继承
由于在私有继承方式下,基类的公有成员、保护成员在派生类中都变为私有的,私有成员不可见,因此,
访问权限为(教材① P210图 4.7所示),
派生类的成员函数可以访问基类的公有成员、保护成员,不能访问基类的私有成员(不可见);
派生类的对象不能访问基类的任何成员(公有成员在派生类中已变为私有,不能被对象访问) —
— 隔断了派生类对象访问基类私有数据成员的消息通道;
在私有继承下可以在派生类中设置专门的成员函数调用基类的公有成员函数,通过其访问基类的私有成员。即:
派生类对象 ->派生类公有成员函数 ->基类公有成员函数 ->基类私有成员 —— 间接消息通道。
第二节 单继承的派生类
(教材① 203-219)
私有继承方式下,派生类对象访问基类成员(私有成员或保护成员)的消息通路:
派生类对象 ->派生类公有成员函数 ->基类公有成员函数 ->基类私有成员
—— 间接消息通道。
访问保护成员:
派生类对象 ->派生类公有成员函数 ->基类保护成员
—— 间接消息通道。
(教材① P211例 4.4)
第二节 单继承的派生类
(教材① 203-219)
在私有继承方式下,C++提供了一种声明调整机制,
即在派生类中重新将基类的公有成员函数声明公有的 —— 这样派生类的对象就可以调用基类的公有成员函数访问基类的私有成员。格式:
class 派生类名,private 基类名
{ …
public:
基类名,:基类公有成员函数名;

}
声明调整不是重载,不能有参数表;
只能对直接基类进行调整
不能改变原有权限第二节 单继承的派生类
(教材① 203-219)
五,C++结构的继承(自学)
六、继承的传递性
派生类的继承具有传递性 —— 派生类既可以继承直接基类的数据结构及操作方法,也可继承间接基类的数据结构及操作方法。
由于公有继承方式与私有继承方式在派生类中访问基类成员的权限不一样,其传递方法也是不同的 —— 公有继承、私有继承方式下访问权限如教材① P214表 4.2。
1、公有继承的传递性
公有继承时,类中的公有成员、保护成员在派生类中的访问权限不改变;
第二节 单继承的派生类
(教材① 203-219)
派生类(直接派生、间接派生)的成员函数可直接调用基类(直接基类、间接基类)的公有成员及保护成员;
派生类(直接派生、间接派生)的对象可以调用基类
(直接基类、间接基类)的公有成员,并通过基类的公有成员函数访问基类的私有成员。
公有继承方式下的传递性见教材① P215例 4.6—— 由此可知:
基类对象可通过基类的公有成员函数访问其私有数据成员 —— 第一条消息通道;
直接派生类对象可以调用基类的公有成员函数访问基类的私有成员 —— 第二条消息通道;
间接派生类对象可直接调用间接基类的公有成员函数访问间接基类的私有数据成员 —— 第三条消息通道。
第二节 单继承的派生类
(教材① 203-219)
2、私有继承的传递性
私有继承方式下,基类的公有成员、保护成员均变为了私有的,派生类对象不能直接访问基类的公有成员、
保护成员 —— 不能通过基类的公有成员函数访问基类的私有成员 —— 必须通过其它方法打开其消息通道。
私有继承方式下的传递性见教材① P216例 4.7—— 由此可知:
基类对象可通过基类的公有成员函数访问其私有数据成员 —— 第一条消息通道;
在直接派生类中重新编写公有成员函数,通过调用基类的公有成员函数访问基类的私有成员;格式:
基类名,:成员函数名(参数表)
—— 第二条消息通道;
第二节 单继承的派生类
(教材① 203-219)
在间接派生类中重新编写公有成员函数,通过调用直接基类的公有成员函数访问间接基类的公有成员函数访问间接基类的私有成员 —— 接力式的访问 —— 第三条通道
可通过访问声明调整机制在私有派生类中重新将基类的公有成员函数声明为公有的。
第三节 派生类的构造函数和析构函数(教材① 219-223)
一、派生类的构造函数的定义
派生类继承了基类的所有成员,在定义派生类对象时,
既要对派生类成员提供初值,也必须对所继承的基类的数据成员提供初值 —— 通过派生类的构造函数实现。
格式:
派生类名 (参数表 ),基类名 (参数名 1,参数名 2,…)
{ <函数体 > }
例 【 4.3】 派生类的构造函数、析构函数举例第三节 派生类的构造函数和析构函数(教材① 219-223)
#include<iostream>
using namespace std;
class Base //基类 Base的定义
{ private:
float x,y;
public:
Base(float a=0,float b=0)
{x=a;y=b;
cout<<"基类构造函数被调用 !\n";
}
~Base()
{cout<<"基类析构函数被调用 !\n";}
void setBase(float a=0,float b=0)
{x=a;y=b;}
void print(void)
{ cout<<"x="<<x<<"\ny="<<y<<endl;}
};
第三节 派生类的构造函数和析构函数(教材① 219-223)
class Derived:public Base //派生类 Derived的定义
{ private:
float z;
public:
Derived(float a=0,float b=0,float c=0):Base(a,b)
{ z=c; //派生类的构造函数定义
cout<<"派生类构造函数被调用 !\n";
}
~Derived()
{cout<<"派生类析构函数被调用 !\n";}
void setDerived(float a=0,float b=0,float c=0)
{ Base::setBase(a,b);z=c;}
void print(void) //派生类的成员函数
{ Base::print();//调用基类 Base的成员函数 print();
cout<<"z="<<z<<endl;
}
};
第三节 派生类的构造函数和析构函数(教材① 219-223)
void main(void)
{ Base a(10.0,20.0);
Derived b(10.0,20.0,30.0);
a.print();
b.print();
b.setDerived(15.5,25.5,35.5);
b.print();
}
第三节 派生类的构造函数和析构函数(教材① 219-223)
二,派生类对象构造函数、析构函数的执行顺序
由例 【 4.3】 程序的输出结果可知:
定义派生类对象时,构造函数的执行顺序:先执行基类的构造函数,再执行派生类的构造函数;
释放对象时,先调用派生类的析构函数,再调用基类的析构函数(与构造函数的调用顺序相反)
三、为派生类提供值的普通成员函数的定义
由例 【 4.3】 可知:派生类中通常也应定义为其对象提供值的普通成员函数(如 void setDerived()) ——
同构造函数一样,既要为派生类的数据成员提供值,
也应为所继承的基类的数据成员提供值(通过调用基类成员函数实现)
第三节 派生类的构造函数和析构函数(教材① 219-223)
四、在派生类中含有对象成员时的构造函数的定义
(派生类为容器类)
格式 (P219):
派生类名 (总参数表 ):基类名 (参数表 ),成员对象名 (参数表 )
{ <函数体 > }
构造函数的执行顺序:基类构造函数 → 成员对象构造函数 → 派生类构造函数
析构函数的执行顺序:派生类的析构函数 → 成员对象的析构函数 → 基类的析构函数(与构造函数的执行顺序相反)
第四节 基类和派生类的赋值规则
(教材① 223-231)
一、赋值兼容性规则
在公有继承方式下,基类与派生类具有以下赋值规则:
⑴ 可以将派生类对象赋值给基类对象。即:
基类对象 =派生类对象;
如:
Base ob1;
Derived ob2(2.0,3.0,4.0);
ob1=ob2;
(相当于,ob1.x=ob2.x ; ob1.y=ob2.y)
注意:不能将基类对象赋值给派生类对象
(思考:为什么?)
第四节 基类和派生类的赋值规则
(教材① 223-231)
⑵ 基类的对象指针可以指向基类对象,也可以指向派生类对象。即:
基类对象指针 =派生类对象指针;
或:
基类对象指针 =&派生类对象 ;
如,Base ob1,*pb;
Derived ob2(2.0,3.0,4.0),*pd;
ob1=ob2;
pb=&ob1;pd=&ob2;
pb=pd;
pb=&ob2;
这样就可以用基类指针变量访问派生类对象从基类继承的数据成员。
第四节 基类和派生类的赋值规则
(教材① 223-231)
⑶ 可以将派生类的对象引用赋值给基类的对象引用。
即:
基类的对象引用 =派生类的对象引用或:
基类名 &基类的引用名 =派生类对象名如,Base ob1,&cb=ob1;
Derived ob2(2.0,3.0,4.0),&cd=ob2;
cb=cd;
Base &c=ob2;
例 【 4.4】 分析程序的输出结果,熟悉基类与派生类的兼容性规则( e404.cpp)。
注意:私有派生时无以上规则第四节 基类和派生类的赋值规则
(教材① 223-231)
二、基类和派生类的对象指针
在公有继承方式下,基类指针既可以指向基类的对象,
也可以指向派生类的对象 —— C++实现运行时多态性的关键途径;
基类指针指向派生类对象后,可直接访问该对象从基类继承的公有成员,如果要访问派生类新增的公有成员,需要用强制类型转换成派生类指针。格式:
((派生类名 *)基类指针名 )->新增成员;
例 【 4.5】 分析程序的输出结果,熟悉用基类指针访问派生类对象的方法( e405.cpp) 。
第四节 基类和派生类的赋值规则
(教材① 223-231)
三、基类和派生对象作为函数的参数
根据基类和派生类的赋值规则,在公有继承方式下,
可以用基类的对象、指针、引用作为形参,派生类的对象、对象地址(指针)作为实参。
格式 1:
函数原型,<返回类型 >函数名 (基类名 对象名,…);
调用语句:函数名(派生类对象名,… ) ;
其中,形参中的基类通常是类层次结构中的最顶层,
派生类对象可以是类层次结构中的任一层。
此种方式为传值调用。
第四节 基类和派生类的赋值规则
(教材① 223-231)
格式 2:
函数原型,<返回类型 >函数名 (基类名 *指针名,…);
调用语句:函数名( &派生类对象名,… ) ;
其中,形参中的基类通常是类层次结构中的最顶层,
派生类对象可以是类层次结构中的任一层。
此种方式为传址调用。
格式 3:
函数原型,<返回类型 >函数名 (基类名 &引用名,…);
调用语句:函数名(派生类对象名,… ) ;
同上,此种方式为传址调用。
第四节 基类和派生类的赋值规则
(教材① 223-231)
四、子类型和类型适应
子类型:如果类 B是类 A的公有派生类,则称 B类为 A
类的子类型。
类型适应:类 A的操作可以用作类 B的操作(基类的操作可用作派生类的操作) —— 或者说,凡是基类 A
的对象出现的地方,都可以用公有派生类的对象代替。
分析教材① P229中的例 4.10。
五、不能继承的部分
构造函数、析构函数不能继承;
友元不能继承。
第五节 多继承(教材① 232-240)
如果一个派生类由二个或二个以上的基类派生得到,
称为多重继承(多继承)
一、多继承派生类的定义
格式:
class 派生类名,<继承方式 1>,基类名 1,<继承方式 2>,基类名 2,…
{ 类体 }
如,
class z,public x,private y;
{…….}
—— 派生类 z公有继承了 x,私有继承了 y
例 【 4.6】 分析程序,熟悉多继承的使用( e406.cpp)
第五节 多继承(教材① 232-240)
#include<iostream.h>
class X
{ int x;
public,
X(int a=0){x=a;}//基类 X的构造函数
void set_x(int a){x=a;}
void show_x(void)
{cout<<"x="<<x<<endl;}
};
class Y
{ int y;
public,
Y(int b=0){y=b;}//基类 Y的构造函数
void set_y(int b){y=b;}
void show_y(void)
{cout<<"y="<<y<<endl;}
};
第五节 多继承(教材① 232-240)
class Z:public X,public Y
{ int z;
public,
Z(int a=0,int b=0,int c=0),X(a),Y(b)
{z=c;} //派生类 Z的构造函数
void set_xyz(int a,int b,int c)
{ set_x(a); set_y(b);z=c;}
void show_z(void)
{cout<<"z="<<z<<endl;}
void show(void)
{ show_x();show_y();cout<<"z="<<z<<endl;}
};
void main()
{ Z obj1(3,4,5),obj2;
obj2.set_xyz(10,20,30);
obj1.show_x();obj1.show_y();obj1.show_z();
obj2.show();
}
第五节 多继承(教材① 232-240)
二、多继承派生类的构造函数
格式:
构造函数名 (总参数表 ):基类名 1(参数表 1),基类名 2(参数表 2),…
{ 构造函数的函数体 }
说明:
多继承的构造函数的总参数表必须提供所有基类构造函数的值及新增数据成员的值;
多继承方式下构造函数的执行顺序:先执行所有基类的构造函数(按排列顺序)后执行派生类的构造函数,其析构函数的执行顺序相反;
第五节 多继承(教材① 232-240)
三、多继承派生类中为数据成员提供值的函数
多继承方式下,为数据成员提供值的函数必须同时为所有基类的数据成员提供值。如前例中:
void set_xyz(int a,int b,int c)
{ set_x(a); set_y(b);z=c;}
四、多重继承的二义性问题
在多重继承方式下,如果在基类及派生类中有同名成员函数,可能出现二义性问题;
解决二义性问题的办法:在调用函数时使用类名及作用域运算符。
例 【 4.7】 调试程序,熟悉二义性问题的处理方法
( e407.cpp)
第五节 多继承(教材① 232-240)
五、虚基类
在多重继承下,如果一个派生类由二个或以上基类派生得到,而这些基类又是由一个基类派生得到的,也会产生二义性。如:
class base
{ … };
class base1,public base
{ … };
class base2,public base
{ … };
class derived,public base1,public base2
{ … };
base base
base1 base2
derived
即:间接基类 base被继承了二次 —— 二义性第五节 多继承(教材① 232-240)
解决办法 —— 使用虚基类。如上例改为:
class base
{ … };
class base1,virtual public base
{ … };
class base2,virtual public base
{ … };
class derived,public base1,public base2
{ … };
base
base1 base1
derived
虚基类的定义格式:
class 派生类名,virtual <继承方式 > 基类名
{ 构造函数的函数体 }
例 【 4.8】 虚基类应用举例( e408.cpp)
第六节 综合举例
例 【 4.9】 通过继承方式处理学生成绩。
1、分析:
2、类的定义:
⑴ 人 person类( person.h中定义):包括姓名、性别、
出生日期(日期 date本身是一个类,包括年、月、日三个成员 —— 定义在 date.h中)三个数据成员 —— 可用 person类公有派生出学生 student类(也可以派生教师 teacher类等);
⑵ 学生 student类( student.h中定义 ),公有继承 person
类,新增学号、系、进校时间( date对象)三个数据成员;
⑶ 成绩 score类( score.h中定义):新增课程科数、成绩数组数据成员;
第六节 综合举例
3、测试 —— main函数(有 ex401.cpp):成绩处理。