第 9章 继 承 性
9.1 基类和派生类
9.2 单 继 承
9.3 多 继 承
9.4 虚 基 类继承性是面向对象程序设计的一种重要功能,是实现代码复用的一种形式 。 继承可以使程序设计人员在一个已存在类的基础上很快建立一个新的类,而不必从零开始设计新类 。 新设计类能够具有原有类的属性和方法,并且为了使新类具有自己独特的功能,新类还要添加新的属性和方法 。
当一个类被其他的类继承时,被继承的类称为基类,又称为父类,超类 。 继承其他类属性和方法的类称为派生类,又称为子类,继承类 。
9.1 基类和派生类
9.1.1 派生类的定义派生能用从派生类到基类的箭头图形表示,
箭头指向基类表示派生类引用基类中的函数和数据,而基类则不能访问派生类,如图 9-1所示。任何一个类均可作为基类 。仅从一个基类派生的继承称为单继承。

9-
1
派生子类基类单继承声明语句的一般形式为:
class <派生类名 >,<继承方式 > <基类名 >
{
数据成员和成员函数声明
}
基类可分为两类:直接基类和间接基类 。
如果某个基类在基类列表中提及,则称它是直接基类 。 例如:
class A
{ };
class B:public A //类 A为直接基类 。
{ };
间接基类可写为:
class A
{ };
class B:public A
{ };
class C:public B //类 A是间接基类,
可扩展到任意级数
{ };
9.1.2 继承方式继承方式有 3种:公有继承方式 ( public),
私有继承方式 ( private) 和保护继承方式
( protected) 。
9.1.2.1 公有继承在公有派生类中:
( 1) 基类的公有成员在派生类中仍是公有成员 。
( 2) 基类的保护成员在派生类中仍是保护成员 。
( 3) 基类的私有成员在派生类中是不可访问的 。
9.1.2.2 私有继承在私有派生类中:
( 1) 基类的公有成员在派生类中是私有成员 。
( 2) 基类的保护成员在派生类中是私有成员 。
( 3) 基类的私有成员在派生类中仍是不可访问的 。
9.1.2.3 保护继承在保护派生类中:
( 1) 基类的公有成员在派生类中是保护成员 。
( 2) 基类的保护成员在派生类中是保护成员 。
( 3) 基类的私有成员在派生类中仍是不可访问的 。
9.2 单 继 承
9.2.1 单继承中的成员访问权限
( 1) 公有成员:一个类的公有成员允许本类的成员函数,本类的对象,公有派生类的成员函数,公有派生类的对象访问 。
( 2) 私有成员:一个类的私有成员只允许本类的成员函数访问 。
( 3) 保护成员:具有私有成员和公有成员的特征 。 一个类的保护成员允许本类的成员函数,公有派生类的成员函数访问 。 本类的对象,公有派生类的对象不能访问 。
【 例 9.1】 成员访问权限举例 。
class A //基类
{
private:
int privA;
protected:
int protA;
public:
int pubA;
};
class B,public A //派生类
{
public:
void fn()
{
int a;
a = privA; //错误:不可访问
a = protA; //有效
a = pubA; //有效
}
};
void main()
{
A a; //基类对象
a.privA = 1; //错误:不可访问
a.protA = 1; //错误:不可访问
a.pubA = 1; //有效
B b; //派生类对象
b.privA = 1; //错误:不可访问
b.protA = 1; //错误:不可访问
b.pubA = 1; //有效
}
【 例 9.2】 分析下面的程序 。
#include "iostream.h"
class A
{
private:
int x;
public:
void f1(int a);
int f2();
};
class B:public A
{
private:
int y;
public:
void g1(int a);
int g2();
};
void A::f1(int a)
{
x=a;
}
int A::f2()
{
return x;
}
void B::g1(int a)
{
y=a;
}
int B::g2()
{
return y+f2();
}
void main()
{
B b;
b.f1(10);
b.g1(10);
cout<<b.g2()<<endl;
}
运行程序,输出结果为,20
9.2.2 构造函数和析构函数
9.2.2.1 构造函数派生类的数据是由基类中的数据和在派生类中新定义的数据组成 。 由于构造函数不能够继承 。 因此,在定义派生类的构造函数时,除了对自己新定义的数据成员进行初始化外,还必须调用基类的构造函数使基类的数据成员得以初始化 。
【 例 9.3】 分析下面的程序 。
class Base
{
protected:
int a;
public:
Base(){ a = 0;} //默认构造函数
Base(int c) { a = c;}
//单参数构造函数
};
class Derived,public Base
{
public:
Derived():Base(){}; //默认构造函数
Derived(int c):Base(c){}; //单参数构造函数
};
【 例 9.4】 分析下面的程序 。
class engine
{
private:
int num;
public:
engine(int s) { num = s; }
};
class jet
{
private:
int jt;
engine eobj; //这里声明一个对象
public:
jet(int x,int y),eobj(y)
{
jt = x;
}
};
总结派生类的构造函数的调用顺序如下:
( 1) 基类的构造函数;
( 2) 子对象类的构造函数 ( 如果子对象存在 ) ;
( 3) 派生类的构造函数 。
9.2.2.2 析构函数析构函数的调用顺序与构造函数相反,析构函数首先为派生类调用,然后为子对象类的析构函数调用,最后调用基类的析构函数 。 仅当派生类的构造函数通过动态内存管理分配内存时,才定义派生类的析构函数 。 如果派生类的构造函数不起任何作用或派生类中未添加任何附加数据成员,
则派生类的析构函数可以是一个空函数 。
【 例 9.5】 分析下面的程序 。
#include "iostream.h"
class A
{
private:
int x;
public:
A(){x=0;}
A(int xx){x=xx;}
~A(){cout<<"A Destructor called."<<endl;}
void display(){cout<<x<<" ";}
};
class B:public A
{
private:
int b1;
A b2; //这里声明一个对象
public:
B(){b1=0;}
B(int i,int j):b2(j){b1=i;}
B(int i,int j,int k),A(i),b2(j),b1(k){}
~B(){cout<<"B Destructor called."<<endl;}
void print()
{
display();
cout<<b1<<" ";
b2.display();
cout<<endl;
}
};
void main()
{
B obj1;
B obj2(5,6);
B obj3(7,8,9);
obj1.print();
obj2.print();
obj3.print();
}
程序运行结果为:
0 0 0
0 5 6
7 9 8
B Destructor called.
A Destructor called.
A Destructor called.
B Destructor called.
A Destructor called.
A Destructor called.
B Destructor called.
A Destructor called.
A Destructor called.
9.2.2.3 调用成员函数派生类中的成员函数与基类中的成员函数可以有相同的名称。当使用基类的对象调用函数时,基类的函数被调用。当使用派生类对象的名称时,派生类的函数被调用。
如果派生类的成员函数要调用相同名称的基类函数,它必须使用作用域运算符,:。
基类中的函数既可使用基类的对象,也可使用派生类的对象调用。如果函数存在于派生类而不是基类中,那么它只能被派生类的对象调用。
【 例 9.6】 分析下面的程序 。
class Base
{
protected:
int ss;
public:
int func() {return ss;}
void print(){cout<<ss;}
};
class Derived,public Base
{
public:
int func() { return Base::func(); }
};
void main()
{
Base b1; //基类对象
b1.func(); //调用基类函数 func
Derived a1; //派生类对象
a1.func(); //调用派生类对象 func
}
9.3 多 继 承
9.3.1 多继承的概念从多个基类派生的继承称为多继承,或称多重继承,即一个派生类可以有多个直接基类 。
多继承声明语句的一般形式为:
class <派生类名 >,< 继承方式 > <基类名
1>,<继承方式 > <基类名 2>,··
{
数据成员和成员函数声明
};
例如:
class A
{
···
};
class B
{
···
};
class C:public A,public B
{
···
};
【 例 9.7】 分析下面的程序 。
#include "iostream.h"
class A
{
public:
void printA(){cout<<"Hello ";}
};
class B
{
public:
void printB(){cout<<"C++ ";}
};
class C,public A,public B
{
public:
void printC(){cout<<"World!\n";}
};
void main()
{
C obj;;
obj.printA();
obj.printB();
obj.printC();
}
程序执行结果为:
Hello C++ World!
9.3.2 多继承的构造函数和析构函数多基派生类的构造函数的一般形式为:
<派生类名 >:,<派生类名 >( 〈 参数表
1〉,〈 参数表 2〉,···),<基类名 1>
( 〈 参数表 1〉 ),<基类名 2>( 〈 参数表
2〉 ),··
{
<派生类成员 >
}
多重继承的构造函数按照下面的原则被调用,
( 1) 先基类,后自己 。
( 2) 如果在同一层上有多个基类,按照派生时定义的先后顺序执行 。
多重继承的析构函数的执行顺序与多重继承的构造函数的执行顺序相反 。
【 例 9.8】 分析下列程序的输出结果 。
#include "iostream.h"
class Base1
{
private:
int a1;
public:
Base1(int i){a1=i;cout<<"Constructor
Base1 called "<<a1<<endl;}
~Base1(){cout<<"Desstructor Base1
called"<<endl;}
};
class Base2
{
private:
int a2;
public:
Base2(int j){a2=j;cout<<"Constructor
Base2 called "<<a2<<endl;}
~Base2(){cout<<"Desstructor Base2
called"<<endl;}
};
class Base3
{
private:
int a3;
public:
Base3(int k=0){a3=k;cout<<"Constructor
Base3 called "<<a3<<endl;}
~Base3(){cout<<"Desstructor Base3
called"<<endl;}
};
class Derived:public Base3,public
Base1,public Base2
{
private:
int a4;
Base1 obj1;
Base2 obj2;
Base3 obj3;
public:
Derived(int i,int j,int k,int m,int
n):obj2(m),obj3(j),obj1(k),Base2(i),Base1(j)
{
a4=n;
cout<<"Constructor Derived called
"<<a4<<endl;
}
~Derived(){cout<<"Desstructor Derived
called"<<endl;}
};
void main()
{
Derived obj(1,2,3,4,5);
}
程序输出结果:
Constructor Base3 called 0
Constructor Base1 called 2
Constructor Base2 called 1
Constructor Base1 called 3
Constructor Base2 called 4
Constructor Base3 called 2
Constructor Derived called 5
Desstructor Derived called
Desstructor Base3 called
Desstructor Base2 called
Desstructor Base1 called
Desstructor Base2 called
Desstructor Base1 called
Desstructor Base3 called
注意,执行基类构造函数的顺序取决于定义派生类时基类的顺序 。 在派生类构造函数的成员初始化列表中各项顺序可以任意地排列 。
9.3.3 二义性问题在多继承情况下,当两个基类有相同的函数或数据成员名称时,编译器将不能理解使用哪个函数,出现对基类成员访问不唯一的情况,称为对基类成员访问的二义性问题 。
例如:
class Alpha
{
public:
void display();
};
class Beta
{
public:
void display();
};
class Gamma,public Alpha,public Beta
{
};
void main()
{
Gamma obj;
obj.display();
//含义模糊:不能编译
}
若要访问正确的函数或数据成员,需要使用作用域运算符,:。
例如:
obj.Alpha::display();
obj.Beta::display();
9.4 虚 基 类
9.4.1 虚基类的引入和说明引入虚基类就是为了解决二义性问题。
虚基类说明格式如下:
virtual <继承方式 ><基类名 >
例如:
class window
{
protected:
int basedata;
};
class border,virtual public window
{ };
class menu,virtual public window
{ };
class border_and_menu,public border,
public menu
{
public:
int show()
{
return basedata;
}
};
虚基类用在多重继承层次结构中,避免同一数据成员的不必要重复 。
9.4.2 虚基类的构造函数虚基类的出现改变了构造函数的调用顺序。
在初始化任何非虚基类之前,将先初始化虚基类。如果存在多个虚基类,初始化顺序由它们在继承图中的位置决定,其顺序是从上到下、从左到右。调用析构函数遵守相同的规则,但是顺序相反。
【 例 9.9】 分析下列程序的输出结果。
#include "iostream.h"
class A
{
public:
A(const char *s1){cout<<s1<<endl;}
~A(){cout<<"Destructor A called"<<endl;}
};
class B:virtual public A
{
public:
B(const char *s1,const char
*s2):A(s1){cout<<s2<<endl;}
~B(){cout<<"Destructor B called"<<endl;}
};
class C:virtual public A
{
public:
C(const char *s1,const char
*s2):A(s1){cout<<s2<<endl;}
~C(){cout<<"Destructor C called"<<endl;}
};
class D:public B,public C
{
public:
D(const char *s1,const char *s2,const char
*s3,const char *s4):B(s1,s2),C(s1,s3),A(s1)
{
cout<<s4<<endl;
}
~D(){cout<<"Destructor D called"<<endl;}
};
void main()
{
D obj("class A","class B","class C","class
D");
}
程序运行结果为:
class A
class B
class C
class D
Destructor D called
Destructor C called
Destructor B called
Destructor A called
9.4.3 虚基类的应用
【 例 9.10】 设计一个表示在职学生的类,
分析下列程序的输出结果 。
首先设计基类 people,表示一般人员的信息,在设计一个表示工作人员的类 job,接下来设计一个表示学生的类 student,在职学生类以这些类为基类 。
#include "iostream.h"
#include "string.h"
class people
{
private:
char name[20];
char ID[20];
char sex;
int age;
public:
people(char *n="",char *i="",char
s='m',int a=19);
void pdisplay();
};
people::people(char *n,char *i,char s,int a)
{
strcpy(name,n);
strcpy(ID,i);
sex=s;
age=a;
}
void people::pdisplay()
{
cout<<"人员,\n身份证号 ---"<<ID<<endl;
cout<<"姓名 ---"<<name<<endl;
if(sex=='m'||sex=='M')cout<<"性别 ---"<<"
男 "<<endl;
else if(sex=='f'||sex=='F')cout<<"性别 ---
"<<"女 "<<endl;
cout<<"年龄 ---"<<age<<endl;
}
class job:virtual public people
{
private:
int number; //工作证号
char department[20]; //工作部门
public:
job(char *n,char *i,char s,int a,int
num=0,char *dep="");
void jdisplay();
};
job::job(char *n,char *i,char s,int a,int
num,char *dep):people(n,i,s,a)
{
number=num;
strcpy(department,dep);
}
void job::jdisplay()
{
cout<<"工作人员,"<<endl;
cout<<"编号 ---"<<number<<endl;
cout<<"工作单位 ---"<<department<<endl;
}
class student,virtual public people
{
private:
int snum;
int classnum;
public:
student(char *n,char *i,char s,int a,int
sn=0,int cn=0):people(n,i,s,a)
{
snum=sn;
classnum=cn;
}
void sdisplay()
{
cout<<"在校学生 "<<endl;
cout<<"学号 ="<<snum<<endl;
cout<<"班级 ="<<classnum<<endl;
}
};
class job_student:public job,public student
{
public:
job_student(char *n,char *i,char s='m',int
a=19,int mn=0,char *md="",int no=0,int
sta=1):job(n,i,s,a,mn,md),student(n,i,s,a,no,
sta),people(n,i,s,a)
{}
void tdisplay();
};
void job_student::tdisplay()
{
cout<<"在职学生 "<<endl;
}
void main()
{
job_student w("张国媛
","122334571908655",'f',22,102,"民族学院
",5282,2004);
w.tdisplay();
w.pdisplay();
w.jdisplay();
w.sdisplay();
}
程序运行结果为:
在职学生人员,
身份证号 ---122334571908655
姓名 ---张国媛性别 ---女年龄 ---22
工作人员,
编号 ---102
工作单位 ---民族学院在校学生学号 =5282
班级 =2004