第 7章 继承与派生
6.1 类的继承与派生
6.2 类的继承方式
6.3 派生类的构造过程和析构过程
6.4 多继承
6.1 类的继承与派生
6.1.1 继承与派生的基本概念在原有类的基础上派生出新的类,新类继承原有类的属性和方法,称原有的类为 基类,新类称为 派生类 。
第 7章 继承与派生人类,姓名、性别、年龄、身高、体重教师类,姓名、性别、年龄、身高、体重,专业、职称管理人员类,姓名、性别、年龄、身高、体重,职务教师管理人员类,姓名、性别、年龄、身高、体重,专业、职称,职务学生类,姓名、性别、年龄、身高、体重,学号、班级、专业人类教师类 学生类管理人员类教师管理人员类 多继承:派生类 有多个基类单继承:派生类只有一个基类直接基类间接基类
6.1 类的继承与派生
6.1.2 派生类的声明单继承派生类的声明语法为:
class 派生类名,继承方式 基类名
{
派生类新增成员的声明 ;
}
继承方式 有三种( private,protected,public),在下一节中详细介绍。
第 7章 继承与派生例 6.1 定义一个位置坐标类,属性有 x坐标和 y坐标,成员函数包括构造函数、获取 x坐标的函数、获取 y坐标的函数和移动位置到新的坐标点函数。然后定义派生类点类,除了继承基类
(位置类)的成员外,又增加了新的数据成员颜色、获取颜色值的成员函数和显示数据成员值的函数。
#include <iostream.h>
#include <string.h>
class CLocation //位置坐标类
{
private:
int x;
int y;
public:
CLocation(int x=0,int y=0);
void MoveTo(int x,int y);
int Getx();
int Gety();
};
第 7章 继承与派生例 6.1 (续一)
CLocation::CLocation(int x,int y)
{
CLocation::x = x; // 可以写成 this->x = x;
CLocation::y = y; // 可以写成 this->y = y;
}
void CLocation::MoveTo(int x,int y)
{
CLocation::x = x; // 可以写成 this->x = x;
CLocation::y = y; // 可以写成 this->y = y;
}
int CLocation::Getx()
{
return x;
}
int CLocation::Gety()
{
return y;
}
可通过域运算符(,:)访问类中的成员 x和 y,与使用 this指针有同样作用第 7章 继承与派生例 6.1 (续二)
class CPoint,public CLocation //从 CLocation中公有继承
{
private:
char Color[10];
public:
CPoint(char *c);
void SetColor(char *c);
void Show();
};
CPoint::CPoint(char *c)
{
strcpy(Color,c);
}
void CPoint::SetColor(char *c)
{
strcpy(Color,c);
}
void CPoint::Show()
{
cout << Getx() << "," << Gety() << " " << Color << endl ;
}
第 7章 继承与派生调用从 CLocation类中继承来的成员函数 GetX()和 GetY()。
例 6.1 (续三)
void main(void)
{
CPoint p("Red");
p.Show();
p.MoveTo(7,8);
p.Show();
p.SetColor("Green");
p.Show();
}
位置坐标通过 CLocation类构造函数的默认参数值设置为( 0,0)
程序运行结果为:
0,0 Red
7,8 Red
7,8 Green
第 7章 继承与派生返 回
6.2 类的继承方式类成员的访问权限:
私有成员 (private):可以被类自身的成员和友元访问,但不能被包括派生类在内的其他任何类和任何普通函数访问公有成员 (public):可以被任何普通函数和任何类的成员函数访问保护成员 (protected):可以被类自身的成员和友元访问外,还可以被派生类的成员函数访问,但不能被任何非友元的普通函数访问类的三种继承方式:
公有继承 (public):
保护继承 (protected):
私有继承 (private):
第 7章 继承与派生
6.2 类的继承方式
6.2.1 公有继承公有继承的特点:
(1) 基类的 私有成员 不能被派生类的函数成员访问
(2) 基类的 公有成员 和 保护成员 在派生类中的访问权限不变分析例 6.1
基类 CLocation中的公有成员 Getx(),Gety() 和
MoveTo(),在派生类 CPoint中的访问权限仍然是公有的。
而 x,y是基类 CLocation的私有成员,在派生类 CPoint的成员函数中不能访问。如果将 CPoint类的成员函数 Show() 改写成下面的形式:
void CPoint::Show()
{
cout << x << "," << y << " " << Color << endl ;
}
第 7章 继承与派生错误信息:,不能访问 CLocation类中的私有成员 x,y”
6.2 类的继承方式
6.2.1 公有继承(续一)
若将基类 CLocation中的 x,y改成保护成员,即:
class CLocation
{
protected:
int x;
int y;
public:
int Getx();
int Gety();
void MoveTo(int x,int y);
CLocation(int x=0,int y=0);
};
第 7章 继承与派生将 x,y改为保护成员后,上面的 show()
就可以直接访问成员 x和 y了
6.2 类的继承方式
6.2.1 公有继承(续二)
分析例 6.1CPoint类的部分成员属性:
CPoint类的成员函数 不能访问的成员 有:
int x; //继承基类的私有成员,类的成员函数不可访问
int y; //继承基类的私有成员,类的成员函数不可访问
CPoint类的 私有成员 有:
char Color[10]; //自己定义的私有成员
CPoint类的 公有成员 有:
int Getx(); //继承基类的公有成员,仍然是公有成员
int Gety(); //继承基类的公有成员,仍然是公有成员
void MoveTo(int x,int y); //继承基类的公有成员,仍然是公有成员
void SetColor(char *c); //自己定义的公有成员
void Show(); //自己定义的公有成员第 7章 继承与派生赋值兼容规则
赋值兼容规则:是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。例,6.3
Isa和 has- a的区别类与类的关系:一个是继承和派生的问题;一个是一个类使用另外一个类的问题
6.2 类的继承方式
6.2.2 保护继承保护继承的特点:
(1) 基类的 私有成员 不能被派生类的函数成员访问
(2) 基类的 公有成员 和 保护成员 在派生类中的访问权限变为保护类型例 6.2 将例 6.1的继承方式改为保护继承,其他代码保持不变
class CPoint,protected CLocation
{
private:
char Color[10];
public:
CPoint(char *c);
void SetColor(char *c);
void Show();
};
第 7章 继承与派生
6.2 类的继承方式
6.2.2 保护继承(续一)
改为保护继承后,主函数不变编译时,是否正确?
void main(void)
{
CPoint p("Red");
p.Show();
p.MoveTo(7,8);
p.Show();
p.SetColor("Green");
p.Show();
}
第 7章 继承与派生错误:不能在主函数中访问 CLocation
类的 函数 MoveTo(),只能在派生类的成员函数中访问
6.2 类的继承方式
6.2.2 保护继承(续二)
为实现点的移动,可在 CPoint类中再添加一个公有成员函数
MoveTo(),如下:
class CPoint:protected CLocation
{
private:
char Color[10];
public:
CPoint(char *c);
void SetColor(char *c);
void Show();
void MoveTo(int x,int y);
};
void CPoint::MoveTo(int x,int y)
{
CLocation::MoveTo(x,y);
}
第 7章 继承与派生用基类名和域运算符(,:)可以访问基类的成员函数 MoveTo(),
若不加限定,由于基类和派生类都有成员函数 MoveTo(),将优先调用派生类本身的同名成员函数
MoveTo()。
6.2 类的继承方式
6.2.2 保护继承(续三)
标识符的作用范围,成员函数作用域 (如函数内部定义的局部变量及函数参数),类或者派生类作用域 (如类中定义的数据成员或成员函数),基类作用域 (如基类中定义的数据成员或成员函数),全局作用域 (如定义的全局变量,及全局函数,即普通函数)。
标识符的访问原则:如果在某一程序段中有一个以上的同名标识符都有效,则标识符的 作用范围越小,被访问到的优先级越高 。
如果希望访问作用范围更大的标识符,则可以用类名和作用域运算符 进行限定。如,CLocation::MoveTo(x,y)。
第 7章 继承与派生
6.2 类的继承方式
6.2.2 保护继承(续四)
分析本例 CPoint类的部分成员属性:
CPoint类的成员函数 不能访问的成员 有:
int x; //继承基类的私有成员,类的成员函数不可访问
int y; //继承基类的私有成员,类的成员函数不可访问
CPoint类的 私有成员 有:
char Color[10]; //自己定义的私有成员
CPoint类的 保护成员 有:
int Getx(); //继承基类的公有成员,成为保护成员
int Gety(); //继承基类的公有成员,成为保护成员
void MoveTo(int x,int y); //继承基类的公有成员,成为保护成员
CPoint类的 公有成员 有:
void MoveTo(int x,int y); //自己定义的公有成员
void SetColor(char *c); //自己定义的公有成员
void Show(); //自己定义的公有成员第 7章 继承与派生
6.2 类的继承方式
6.2.3 私有继承私有继承的特点:
(1) 基类的 私有成员 不能被派生类的函数成员访问
(2) 基类的 公有成员 和 保护成员 在派生类中的访问权限变为私有类型将例 6.1的继承方式改为私有继承,其他代码保持不变
class CPoint,private CLocation
{
private:
char Color[10];
public:
CPoint(char *c);
void SetColor(char *c);
void Show();
};
第 7章 继承与派生
6.2 类的继承方式
6.2.3 私有继承(续)
分析本例 CPoint类的部分成员属性:
CPoint类的成员函数 不能访问的成员 有:
int x; //继承基类的私有成员,类的成员函数不可访问
int y; //继承基类的私有成员,类的成员函数不可访问
CPoint类的 私有成员 有:
char Color[10]; //自己定义的私有成员
int Getx(); //继承基类的公有成员,成为私有成员
int Gety(); //继承基类的公有成员,成为私有成员
void MoveTo(int x,int y); //继承基类的公有成员,成为私有成员
CPoint类的 公有成员 有:
void SetColor(char *c); //自己定义的公有成员
void Show(); //自己定义的公有成员第 7章 继承与派生
6.2 类的继承方式派生类继承基类成员访问权限的变化表:
第 7章 继承与派生派生控制基类成员 private protected public
private 不能访问 不能访问 不能访问
protected private protected protected
public private protected public
例 6.3 定义一个钟表类,数据成员有时、分、秒,成员函数包括设置时间和显示时间。再从钟表类派生出闹钟类,新增数据成员有响铃时间,成员函数包括响铃、显示响铃时间和设置响铃时间。
#include <iostream.h>
class Clock
{
public,
Clock(int h=0,int m=0,int s=0);
Clock(Clock &c);
void SetTime(int h,int m,int s);
void ShowTime();
private,
int Hour;
int Minute;
int Second;
};
第 7章 继承与派生例 6.3 (续一)
Clock::Clock(int h,int m,int s) //构造函数的参数用于初始化时、分、秒
{
Hour = h;
Minute = m;
Second = s;
}
Clock::Clock(Clock &c) //拷贝构造函数将引用作为参数初始化新建对象
{
Hour = c.Hour;
Minute = c.Minute;
Second = c.Second;
}
void Clock::SetTime(int h,int m,int s) //设置时间
{
Hour = h;
Minute = m;
Second = s;
}
void Clock::ShowTime() //显示时间
{
cout << Hour << ":" << Minute << ":" << Second << endl;
}
第 7章 继承与派生例 6.3 (续二)
class AlermClock,public Clock
{
private:
int AlermHour;
int AlermMinute;
int AlermSecond;
public:
AlermClock(int h=12,int m=0,int s=0);
void Alerm();
void SetAlermTime(int h,int m,int s);
void ShowAlermTime();
};
AlermClock::AlermClock(int h,int m,int s)
{
AlermHour = h;
AlermMinute = m;
AlermSecond = s;
}
第 7章 继承与派生例 6.3 (续三)
void AlermClock::Alerm() // 转义字符‘ \a’完成响铃
{
cout << "\a\a\a\a\a\a\a";
}
void AlermClock::SetAlermTime(int h,int m,int s)
{
AlermHour = h;
AlermMinute = m;
AlermSecond = s;
}
void AlermClock::ShowAlermTime()
{
cout << AlermHour << ":" << AlermMinute << ":" << AlermSecond << endl;
}
第 7章 继承与派生例 6.3 (续四)
void main()
{
AlermClock c;
c.ShowTime();
c.ShowAlermTime();
c.SetTime(10,30,40);
c.SetAlermTime(6,30,0);
c.ShowTime();
c.ShowAlermTime();
c.Alerm();
}
第 7章 继承与派生构造函数采用缺省参数值,基类继承的数据成员初始化为( 0,0,0),将
AlermClock类自己定义的数据成员响铃时间置为( 12,0,0)
程序运行结果为:
0,0,0
12,0,0
10,30,40
6,30,0
返 回
6.3 派生类的构造过程和析构过程基类的构造函数和析构函数都不被继承,需要在派生类中重新定义。由于派生类继承了基类的 成员,在初始化时,也要同时初始化基类成员。可通过调用基类的构造函数对完成初始化。
6.3.1 派生类的构造过程派生类构造函数定义的一般格式为:
派生类名,:构造函数名(参数表):基类名(参数表),内嵌对象
1(参数表 1),内嵌对象 2(参数表 2),…,常量 1(初值 1),
常量 2(初值 2),…,引用 1(变量 1),引用 2(变量 2) …
{
派生类构造函数体;
}
第 7章 继承与派生
6.3 派生类的构造过程和析构过程
6.3.1 派生类的构造过程(续)
派生类构造函数的执行顺序:
( 1)先调用基类的构造函数
( 2)然后按照数据成员(包括内嵌对象、常量、引用等必须初始化的成员)的声明顺序,依次调用数据成员的构造函数或初始化数据成员
( 3)最后执行派生类构造函数的函数体注意,构造函数的执行顺序只与成员声明的顺序有关,而与 初始化表 中各项的排列顺序无关。
常量成员、引用成员、内嵌对象,只能通过 初始化表 的方法初始化。
第 7章 继承与派生例 6.4 派生类的构造过程
#include <iostream.h>
class A
{
int a;
public:
A(int x):a(x)
{
cout << "construct A " << a << endl;
}
};
class B,public A
{
private:
int b,c;
const int d;
A x,y;
public:
B(int v),b(v),y(b+2),x(b+1),d(b),A(v)
{
c = v;
cout << "construct B " << b <<" " << c << " " << d << endl;
}
};
第 7章 继承与派生用初始化表的方式为 a赋值,与函数体中使用 a=x 语句作用一样用初始化表的方式为 b,d赋值,
为 x和 y初始化,调用基类 A的构造函数为基类成员初始化例 6.4 (续)
void main(void)
{
B b1(10);
}
第 7章 继承与派生执行顺序:
先调用基类的构造函数,即执行初始化表中的
A(v);
然后按着成员声明的先后先后顺序,应首先初始化 b,
即执行初始化表中的 b(v),接下来是成员 d,即执行初始化表中的 d(b);
接下来构造内嵌对象 x,即执行初始化表中的 x(b+1)。
再构造内嵌对象 y,即执行初始化表中的 y(b+2);
最后执行类 B自己的构造函数体。
程序运行结果为:
construct A 10
construct A 11
construct A 12
construct B 10 10 10
6.3 派生类的构造过程和析构过程
6.3.2 派生类的析构过程派生类析构函数执行时将自动调用基类及内嵌对象的析构函数,因此不必显式调用。
派生类构造函数的执行顺序:
( 1)先执行派生类的析构函数。
( 2)然后按着内嵌对象声明的相反顺序,依次调用内嵌对象的析构函数。
( 3)最后调用基类的析构函数。
第 7章 继承与派生例 6.5 派生类的析构顺序
#include <iostream.h>
class A
{
int a;
public:
A(int x):a(x)
{
cout << "construct A " << a << endl;
}
~A()
{
cout << "destruct A " << a << endl;
}
};
第 7章 继承与派生例 6.5 派生类的析构顺序
class B,public A
{
private:
int b,c;
const int d;
A x,y;
public:
B(int v),b(v),y(b+2),x(b+1),d(b),A(v)
{
c = v;
cout << "construct B" << b <<" " << c << " " << d << endl;
}
~B()
{
cout << "desstruct B" << b <<" " << c << " " << d << endl;
}
};
void main(void)
{
B b1(10);
}
第 7章 继承与派生程序运行结果为:
construct A 10
construct A 11
construct A 12
construct B 10 10 10
destruct B 10 10 10
destruct A 12
destruct A 11
destruct A 10
例 6.6 定义一个点类 CPoint,数据成员有点的坐标,再定义一个几何形状类 CShape,数据成员只有颜色,以 CShape类为基类派生出线段类 CLine和圆类 CCircle,其中线段类 CLine的数据成员包括起点和终点(为 CPoint类的内嵌对象),圆类
CCircle的数据成员包括圆心(为 CPoint类的内嵌对象)和半径。
#include <iostream.h>
#include <string.h>
class CPoint
{
private:
int X;
int Y;
public:
CPoint(int x=0,int y=0)
{
X=x;
Y=y;
}
第 7章 继承与派生
CPoint(Point &p)
{
X=p.X;
Y=p.Y;
}
int GetX()
{
return X;
}
int GetY()
{
return Y;
}
};
例 6.6 (续一)
class CShape
{
private:
char Color[10];
public:
CShape(char *c)
{
strcpy(Color,c);
}
void Draw()
{
cout << "Draw a shape,The color is " << Color << endl;
}
void PrintColor()
{
cout << Color << endl;
}
};
第 7章 继承与派生例 6.6 (续二)
class CLine:public CShape
{
private:
CPoint Start; //线段的起点
CPoint End; //线段的终点
public:
CLine(CPoint s,CPoint e,char *c):CShape(c),Start(s),End(e)
{}
void Draw()
{
cout << "Draw a Line from (" << Start.GetX() << "," << Start.GetY();
cout << ") to ("<< End.GetX() << "," << End.GetY() << "),with color ";
PrintColor();
}
};
第 7章 继承与派生输出颜色 Color。因为基类的颜色 Color是私有成员,在这里不能直接访问,只能通过它的公有函数 PrintColor()访问例 6.6 (续三)
class CCircle:public CShape
{
private:
CPoint Center;
int Radius;
public:
CCircle(CPoint ctr,int r,char *c):CShape(c),Center(ctr)
{
Radius = r;
}
void Draw()
{
cout << "Draw a circle at center (" << Center.GetX() << "," ;
cout << Center.GetY()<< ") with radius " << Radius << " and color ";
PrintColor();
}
};
第 7章 继承与派生例 6.6 (续四)
void main()
{
CShape s("Red");
CPoint p1(10,10),p2(100,100),p3(50,50);
CLine l(p1,p2,"Green");
CCircle c(p3,20,"Black");
s.Draw();
l.Draw();
c.Draw();
}
第 7章 继承与派生程序运行结果为:
Draw a Shape,The color is Red
Draw a Line from (10,10) to (100,100),with color Green
Draw a Circle at center (50,50) with radius 20 and color Black
例 6.6 (续五)
父类与子类,如果派生类的派生控制为 public,则这样的派生类称为基类的 子类,而相应的基类则称为派生类的 父类。
C++允许 父类指针直接指向子类对象,也允许 父类引用直接引用子类对象。
void main()
{
CShape *ps[3];
CShape s("Red");
CPoint p1(10,10),p2(100,100),p3(50,50);
CLine l(p1,p2,"Green");
CCircle c(p3,20,"Black");
ps[0] = &s;
ps[1] = &l;
ps[2] = &c;
for(int i=0; i<3; i++)
ps[i]->Draw();
}
第 7章 继承与派生程序运行结果为:
Draw a Shape,The color is Red
Draw a Shape,The color is Green
Draw a Shape,The color is Red Black
虽然父类的指针可以指向子类的对象,但调用的函数 Draw()都是父类 CShape的成员函数返 回
6.4 多重继承
6.4.1 多继承的构造与析构一个派生类可以有多于一个的基类,称之为 多继承。
派生类构造函数的 执行顺序,
( 1)先按着声明的顺序(从左至右)依次调用各基类的构造函数。
( 2)然后按照数据成员(包括内嵌对象、常量、引用等必须初始化的成员)的声明顺序,依次调用数据成员的构造函数或初始化数据成员。
( 3)最后执行派生类构造函数的函数体。
派生类 析构顺序,
( 1)先执行派生类的析构函数。
( 2)然后按着内嵌对象声明的相反顺序,依次调用内嵌对象的析构函数。
( 3)最后按基类声明的相反顺序调用各基类的析构函数。
第 7章 继承与派生例 6.7 多继承派生类的构造过程与析构过程。
#include <iostream.h>
class CBase1
{
protected:
int b;
public:
CBase1(int x=0)
{
b=x;
cout << "Construct CBase1! " << b <<endl;
}
~CBase1()
{
cout << "Destruct CBase1! " << b <<endl;
}
};
第 7章 继承与派生例 6.7 (续一)
class CBase2
{
protected:
int b;
public:
CBase2(int x=0)
{
b=x;
cout << "Construct CBase2! " << b <<endl;
}
~CBase2()
{
cout << "Destruct CBase2! " << b <<endl;
}
};
第 7章 继承与派生例 6.7 (续二)
class CDerived,public CBase1,private CBase2
{
protected:
CBase1 b1;
CBase2 b2;
int d;
public:
CDerived(int x,int y,int z):,b1(y),CBase2(y),b2(z),CBase1(x)
{
d=z;
cout << "Construct CDerived! " << d <<endl;
}
~CDerived()
{
cout << "Destruct CDerived! " << d <<endl;
}
};
第 7章 继承与派生例 6.7 (续三)
void main()
{
CDerived d1(1,2,3);
}
第 7章 继承与派生程序运行结果为:
Construct CBase1 1
Construct CBase2 2
Construct CBase1 2
Construct CBase2 3
Construct CDerived! 3
Denstruct CDerived! 3
Denstruct CBase2! 3
Denstruct CBase1! 2
Denstruct CBase2! 2
Denstruct CBase1! 1
构造函数执行顺序:
先调用基类 CBase1构造函数,再调用基类 CBase2构造函数,然后调用内嵌对象
b1构造函数,再调用内嵌对象 b2的构造函数,最后执行 CDerived类本身的构造函数体。
析构过程与构造过程恰好相反。
6.4 多继承
6.4.2 多继承的二意性
1,基类有同名成员引起的二意性多继承时,不同基类可能有同名成员,这样派生类中就可能有从不同基类继承的同名成员,在引用时产生二意性。
2,从多个路径继承同一个基类引起的二意性多重继承时,低层的派生类有可能从 不同的路径继承同一个基类的成员多次,引用这样的成员时也会产生二意性。
第 7章 继承与派生
CBase0
CBase1 CBase2
CDerived 类中继承了两次 CBase0的成员例 6.8 多继承派生产生的二意性。
#include <iostream.h>
class CBase1
{
protected:
int b;
public:
CBase1(int x=0)
{
b=x;
}
int GetB()
{
return b;
}
};
第 7章 继承与派生
class CBase2
{
protected:
int b;
public:
CBase2(int x=0)
{
b=x;
}
int GetB()
{
return b;
}
};
例 6.8 (续)
class CDerived,public CBase1,private CBase2
{
protected:
int d;
public:
CDerived(int x,int y,int z):CBase1(x),CBase2(y)
{
d=z;
}
void Output()
{
cout << d << b << endl; // Error,CDerived::b' is ambiguous
}
};
void main()
{
CDerived d1(1,2,3);
int x = d1.GetB(); // Error,CDerived::GetB' is ambiguous
d1.Output();
}
第 7章 继承与派生
1
2
3
CBase1::b
CBase2::b
d
对象 d1的数据成员产生二意性,因为不知道访问哪一个基类继承来的 b,可使用域运算符和基类名来限定,改为:
CBase1::b或 CBase2::b
产生二意性,改为:
d1.CBase1::GetB()
例 6.9 从多个路径继承同一个基类,引起的二意性。
#include <iostream.h>
class CBase0
{
protected:
int b0;
public:
CBase0(int x=0)
{
b0=x;
}
int GetB0()
{
return b0;
}
};
第 7章 继承与派生
class CBase1,public CBase0
{
public:
CBase1(int x=0),CBase0(x)
{
}
};
class CBase2,public CBase0
{
public:
CBase2(int x=0),CBase0(x)
{
}
};
例 6.9 (续)
class CDerived,public CBase1,public CBase2
{
public:
CDerived(int x,int y):CBase1(x),CBase2(y)
{
}
};
void main()
{
CDerived d1(1,2);
cout << d1.GetB0() <<endl;
}
第 7章 继承与派生产生二意性,可使用域运算符和基类名来限定,
改为:
d1.CBase1::GetB0()或 d1.CBase2::GetB0()
b0
GetB0()CBase0
CBase1
b0
GetB0() CBase2
b0
GetB0()
CDerived
CBase1::b0
CBase2::b0
CBase1::GetB0()
CBase2::GetB0()
虽然可以通过域运算符与基类名解决二意性问题,但一般情况下并不希望派生类中有两份基类成员。
6.4 多继承
6.4.3 虚基类为了避免在派生类中从不同路径继承间接基类多次,可以通过将间接基类声明为虚基类,虚基类的成员在它的间接派生类中只被继承一次。
1,虚基类的声明
class 派生类名,virtual 继承方式 基类名第 7章 继承与派生例 6.10 虚基类的应用。
#include <iostream.h>
class CBase0
{
protected:
int b0;
public:
CBase0(int x=0)
{
b0=x;
}
int GetB0()
{
return b0;
}
};
第 7章 继承与派生
class CBase1,virtual public CBase0
{
public:
CBase1(int x=0),CBase0(x)
{
}
};
class CBase2,virtual public CBase0
{
public:
CBase2(int x=0),CBase0(x)
{
}
};
例 6.10 (续)
class CDerived,public CBase1,public CBase2
{
public:
CDerived(int x,int y,int z):CBase0(x),CBase1(y),CBase2(z)
{
}
};
void main()
{
CDerived d1(10,15,20);
cout << d1.GetB0() <<endl;
cout << d1.CBase1::GetB0() <<endl;
cout << d1.CBase2::GetB0() <<endl;
}
第 7章 继承与派生只从基类 CBase0中继承了一次 GetB0(),因此直接调用不会产生二意性,也可以使用
d1.CBase1::GetB0()或 d1.CBase2::GetB0()
调用同一个 GetB0()函数如果虚基类没有不带参数的构造函数,且有参数的构造函数又没有默认参数值,则其所有的派生类(包括间接派生类)的构造函数都必须为它的构造函数提供参数。基类的数据成员 b0得到的值是由最底层的派生类 CDerived的构造函数提供的 。
程序运行结果为:
10
10
10
6.4 多继承
6.4.3 虚基类(续)
2,含有虚基类派生类的构造过程派生类对象的 构造顺序,
( 1)按定义顺序自左至右地构造所有虚基类;
( 2)按定义顺序构造派生类的所有直接基类;
( 3)按定义顺序构造派生类的所有数据成员,包括对象成员、
const成员和引用成员;
( 4)执行派生类自身的构造函数体。
派生类对象的 析构顺序 与构造顺序相反。
第 7章 继承与派生例 6.11 含有虚基类的派生类的构造过程。
#include <iostream.h>
class CBase0
{
protected:
int b0;
public:
CBase0(int x)
{
b0 = x;
cout << "construct CBase0 " << b0 << endl;
}
};
class CBase1,virtual public CBase0
{
public:
CBase1(int x=0),CBase0(x)
{
cout << "construct CBase1 " << x << endl;
}
};
第 7章 继承与派生例 6.11 (续一)
class CBase2,virtual public CBase0
{
public:
CBase2(int x=0),CBase0(x)
{
cout << "construct CBase2 " << x << endl;
}
};
class CDerived,public CBase1,public CBase2
{
private:
CBase0 B0;
CBase1 B1;
CBase2 B2;
public:
CDerived(int x,int y,int z,int a,int b,int c)
:CBase0(x),CBase1(y),CBase2(z),B0(a),B1(b),B2(c)
{
cout << "construct CDerived " << x <<"," << y << "," << z << endl;
}
};
第 7章 继承与派生例 6.11 (续二)
void main()
{
CDerived d1(10,15,20,25,30,35);
}
第 7章 继承与派生在构造派生类的对象时,虚基类的成员只被初始化一次,且是由最底层派生类的构造函数通过调用虚基类的构造函数进行的,其他基类对虚基类构造函数的调用都被忽略 。
程序运行结果为:
construct CBase0 10
construct CBase1 15
construct CBase2 20
construct CBase0 25
construct CBase0 30
construct CBase1 30
construct CBase0 35
construct CBase2 35
construct CDerived 10,15,20
例 6.12 含有虚基类的派生类的构造过程。以学校职工为基类,
派生出教师类和管理人员类,又从教师类和管理人员类共同派生出教师管理人员类。
#include <iostream.h>
#include <string.h>
class CStaff
{
protected:
int number;
char name[10];
int age;
public:
CStaff(int num,char *na,int a)
{
number = num;
age = a;
strcpy(name,na);
}
void Display()
{
cout << name << " is a Staff " << age << " yeas old," << endl;
}
};
第 7章 继承与派生例 6.12 (续一)
class CTeacher,virtual public CStaff
{
protected:
char zch[10];
public:
CTeacher(int num,char *na,int a,char *zc),CStaff(num,na,a)
{
strcpy(zch,zc);
}
void Display()
{
cout << name << " is a Teacher " << age << " yeas old," << zch << endl;
}
};
第 7章 继承与派生例 6.12 (续二)
class CManagement,virtual public CStaff
{
protected:
char zw[10];
public:
CManagement(int num,char *na,int a,char *z),CStaff(num,na,a)
{
strcpy(zw,z);
}
void Display()
{
cout << name << " is a management " << age << " yeas old," << zw << endl;
}
};
第 7章 继承与派生例 6.12 (续三)
class CTeacherManagement,public CTeacher,public CManagement
{
public:
CTeacherManagement(int num,char *na,int a,char *zc,char *z):
CStaff(num,na,a),CTeacher(num,na,a,zc),CManagement(num,na,a,z)
{
}
void Display()
{
cout << name << " is a Teacher management " << age << " yeas old,"
<< zch << "," << zw << endl;
}
};
第 7章 继承与派生不会产生二意性,因为只从虚基类 CStuff中继承了一次成员 name和 age
例 6.12 (续四)
void main()
{
CStaff s1(101,"Zhao",20);
CTeacher t1(102,"Zhang",30,"Lecture");
CManagement m1(103,"Wang",35,"dean");
CTeacherManagement tm1(104,"Li",40,"Peofessor","department head");
s1.Display();
t1.Display();
m1.Display();
tm1.Display();
}
第 7章 继承与派生程序运行结果为:
Zhao is a Staff 20 years old
Zhang is a Teacher 30 years old,Lecture
Wang is a management 35 years old,dean
Li is a Teacher management 40 years old,Peofessor,department head
不会产生二意性,将优先调用自己类定义的
Display( ) 函数返 回谢 谢!