?教学目的与要求:
了解虚基类的声明和作用 。
掌握多基派生类的声明和构造函数,虚基类
教学内容提要:
1,多继承的声明;
2,多继承的构造函数和析构函数;
3,虚基类;
教学重点:多继承的派生类的声明和构造函数 。
教学难点:多继承的派生类的声明和构造函数 。
教学进度,P136~P148
教学过程:
第 14 讲 多继承和虚基类多继承 可以看作是单继承的扩充,它是指由多个基类派生出一个新类的情形。
声明形式如下:
class 派生类名,派生类型 1 基类名 1,派生类型 2 基类名 2,…
派生类型 n 基类名 n
{
派生类新成员声明
};
每一个派生类型对应的是紧接其后给出的基类,而且必须给每个基类指定一种派生类型,如果缺省,相应的基类则取私有派生类型,而不是和前一个基类取相同的派生类型。
【 14.1 多继承的声明 】
多继承
#include<iostream.h>
class A
{
public:
void setx(int a){x=a;}
void sety(int b){y=b;}
比如,class A
{
...
};
class B
{
...
};
class C:public A,B
{
...
}
说明:类 A派生类 C时,采用的是公有派生,也即类 A中的公有和保护成员被类 C
继承过来且成员类型保持不变,但类 C中的成员函数不能访问类 A的私有成员。
例 14-1
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
class B
{
public:
void setz(int c){z=c;}
int getz() const {return z;}
protected:
int z;
};
class C:public A,private B
{
public:
void setCz(int c){setz(c);}
int getCz() const{return z;}
int getsum() const{return x+gety()+z;}
(续)
X=2 Y=3 Z=5
X+Y+Z=10
多继承可以看作是多个单继承的 组合,单继承可以看作是多继承的 特例 。
};
void main()
{
C c;
c.setx(2);
c.sety(3);
c.setCz(5);
cout<<″X=″<<c.getx()<<″\tY=″<<c.gety()<<″\tZ=″<<c.getCz()<<endl;
cout<<″X+Y+Z=″<<c.getsum()<<endl;
}
(续)
派生类名::派生类名(参数总表):基类 1(参数表 1),基类 2(参数表 2),…,基类 n(参数表 n),对象成员 1(对象成员参数表 1),对象成员 2(对象成员参数表 2),…,对象成员 n(对象成员参数表 n)
{
派生类中新声明的数据成员初始化语句
}
参数总表 应包括初始化基类数据成员、内嵌对象数据成员及其他数据成员所需的全部数据。
是需要用参数初始化的 基类名、对象成员名及各自对应的参数表,基类名和对象成员名 之间的顺序可以是任意的,且对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名。 对象成员 是指在派生类中新声明的数据成员,它是属于另外一个类的对象。对象成员必须在初始化列表中初始化。
多继承派生类的构造函数的一般形式如下:
【 14.2 多继承的构造函数和析构函数 】
#include<iostream.h>
class A
{
public:
A(){cout<<″调用类 A的构造函数 A()″<<endl;}
A(int a,int b)
{
x=a;
y=b;
cout<<″调用类 A的构造函数 A(int,int)″<<endl;
}
多继承时构造函数的调用例 14-2
( 1) 调用基类的构造函数,调用顺序与声明派生类时基类在声明语句中出现的顺序一 致。
( 2) 调用对象成员的构造函数,调用顺序是按照它们在 派生类声明中的顺序。
( 3) 调用派生类的构造函数。
派生类构造函数执行的一般顺序如下:
~ A(){cout<<″调用类 A的析构函数 ″<<endl;}
protected:
int x;
private:
int y;
};
class B
{
public:
B(){cout<<″调用类 B的构造函数 B()″<<endl;}
B(int c)
{
z=c;
cout<<″调用类 B的构造函数 B(int)″<<endl;
}
~ B(){cout<<″调用类 B的析构函数 ″<<endl;}
protected:
int z;
};
class C
(续)
{
public:
C(){cout<<″调用类 C的构造函数 ″<<endl;}
~ C(){cout<<″调用类 C的析构函数 ″<<endl;}
};
class D:public B,private A,public C
{
public:
D(){cout<<″调用类 D的构造函数 D()″<<endl;}
D(int a,int b,int c,int d,int e,int f,int g):A(a,b),a(d,e),b(f),B(c),w(g)
{
cout<<″调用类 D的构造函数 D(int,int,int,int,int,int,int)″<<endl;
}
~ D(){cout<<″调用类 D的析构函数 ″<<endl;}
private:
C c;
A a;
B b;
int w;
};
说明:在类 D的构造函数的初始化列表中没有列出基类 C和内嵌对象成员 c,因此系统在创建类 D的对象时,将两次调用类 C的默认构造函数,分别对从类 C继承的成员和内嵌对象 c进行初始化。
(续)
void main()
{
D d1;
D d2(1,2,3,10,20,30,40);
}
调用类 B的构造函数 B()
调用类 A的构造函数 A()
调用类 C的构造函数调用类 C的构造函数调用类 A的构造函数 A()
调用类 B的构造函数 B()
调用类 D的构造函数 D()
调用类 B的构造函数 B(int)
调用类 A的构造函数 A(int,int)
调用类 C的构造函数调用类 C的构造函数调用类 A的构造函数 A(int,int)
调用类 B的构造函数 B(int)
调用类 D的构造函数 D(int,int,int,int,int,int,int)
(续)
调用类 D的析构函数调用类 B的析构函数调用类 A的析构函数调用类 C的析构函数调用类 C的析构函数调用类 A的析构函数调用类 B的析构函数调用类 D的析构函数调用类 B的析构函数调用类 A的析构函数调用类 C的析构函数调用类 C的析构函数调用类 A的析构函数调用类 B的析构函数
( 1) 调用派生类析构函数。
( 2) 调用派生类中新增对象成员的析构函数,顺序与它们在派生类中声明的顺序相反。
( 3) 调用基类的析构函数,调用顺序与声明派生类时基类在声明语句中出现的顺序相反。
析构函数的执行顺序与构造函数正好相反。其顺序是:
(续)
例 14-3 虚基类的引例。
#include <iostream.h>
class base {
public:
base(){ a=5; cout<<"base a="<<a<<endl; }
protected:
int a;
};
class base1:public base{
public:
base1()
{ a=a+10; cout<<"base1 a="<<a<<endl; }
};
class base2:public base{
public:
base2(){a=a+20; cout<<"base2 a="<<a<<endl;}
};
1、为什么要引入 虚基类。
【 14.3 虚基类 】
class derived:public base1,public base2{
public:
derived()
{ cout<<"base1::a="<<base1::a<<endl;
cout<<"base2::a="<<base2::a<<endl;
}
};
main()
{ derived obj;
return 0;
}
程序运行结果如下:,
base a=5
base1 a=15
base a=5
base2 a=25
base1::a=15
base2::a=25
base base
base1 base2
derived
图 非虚基类的类层次图在声明派生类时,虚基类的声明形式如下:
class 派生类名,virtual 派生类型 基类名
C++语言允许程序中只建立公共基类的一个副本。这时,就需要在声明直接派生类时,将这个公共基类声明为 虚基类。
在多继承条件下,关键字 virtual仅是 声明其后紧跟的一个基类为虚基类 。
例 14-4 虚基类的使用 。
#include <iostream.h>
class base {
public:
base( ){ a=5; cout<<"base a="<<a<<endl;}
protected:
int a;
};
virtual是声明虚基类的关键字,它也可以放在 派生类型之后,其后紧跟的基类为 派生类的虚基类。
2,虚基类
class base1,virtual public base{
public:
base1( ){ a=a+10; cout<<"base1 a="<<a<<endl;}
};
class base2,virtual public base{
public:
base2( ){ a=a+20; cout<<"base2 a="<<a<<endl;}
};
class derived:public base1,public base2{
public:
derived( ){ cout<<"derived a="<<a<<endl;}
};
main( )
{ derived obj; return 0;
}
程序运行结果如下:,
base a=5
base1 a=15
base2 a=35
derived a=35
base
base1 base2
derived
图 虚基类的类层次图说明:类 A声明为虚基类,系统在创建类 D的一个对象时,仅建立了一个类 A副本,通过类 B和类 C访问变量 a,其实是访问同一个变量。
例 14-5#include<iostream.h>
class A
{
public:
int a;
void fun(){cout<<″类 A成员函数被正确调用 ″<<endl;}
};
class B:virtual public A
{ public:
void display()
{
cout<<″Ab=″<<a<<endl;
}
};
class C:virtual public A
{ public:
void display()
{
cout<<″Ac=″<<a<<endl;
}
};
虚基类的使用
class D:public B,public C
{
public:
void display()
{
B::display();
C::display();
}
};
void main()
{
D d;
d.B::a=1;
d.C::a=2;
d.display();
d.fun();
}
Ab=2
Ac=2
A类成员函数被正确调用
C++语言规定,对于继承过程中的虚基类,它们由最后派生出来的用于声明对象的类来初始化。而这个派生类的基类中对这个虚基类的初始化都被忽略。 虚基类的构造函数也就只被调用一次 。

(续)A
类 D的派生过程 图虚 基类继承
B C
D
3,虚基类的初始化在使用虚基类机制时应该注意以下几点,:
(1) 如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。
(2) 建立一个对象时,如果这个对象中含有从虚基类继承来的成员,
则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。
(3) 若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数 ;
(4) 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下 ;
(5) 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下 ;
(6) 若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。
#include<iostream.h>
class A
{
public:
A(int a)
{
x=a;
cout<<″调用类 A构造函数 ″<<endl;
}
~ A(){};
int getX(){return x;}
protected:
int x;
};
class B:virtual public A
{
public:
B():A(1){cout<<″调用类 B构造函数 ″<<endl;}
~ B(){};
};
虚基类构造函数的执行说明:类 A构造函数仅执行了一次,且类 A中数据成员是用声明类 D对象时指定的值初始化的。而类 B、类 C对类 A的初始化并没有执行。
例 14-6
class C,public virtual A
{
public:
C():A(2){cout<<″调用类 C构造函数 ″<<endl;}
~ C(){};
};
class D:public B,public C
{
public:
D(int d):A(d){cout<<″调用类 D构造函数 ″<<endl;}
~ D(){};
};
void main()
{
D d(3);
cout<<″X=″<<d.getX()<<endl;
}
调用类 A构造函数调用类 B构造函数调用类 C构造函数调用类 D构造函数
X=3
(续)
小结
1、多基派生类的声明格式
2、多基派生类的构造函数和析构函数
3、虚基类作业:
P159 4.11