继承( inheritance) 机制是面向对象程序设计使代码可以
复用的最重要的手段,它允许程序员在保持原有类特性的基础
上进行扩展,增加功能。 这样产生新的类,称派生类。派生类
不是简单地继承基础类的特性,它可以调整部分成员的特性,
也可以增加一些新成员。继承呈现了面向对象程序设计的层次
结构。 体现了由简单到复杂的认识过程 。
第八章 继承性
8.1 继承与派生的概念
8.4 虚基类
8.3 多重继承与派生类成员标识
8.8 MFC基础类及其层次结构
8.5 类层次中成员名的作用域
8.2 派生类的构造函数与析构函数
8.7 多重继承的应用例子
8.6 类层次中类转换规则
8.1 继承与派生的概念
层次概念 是计算机的重要概念。通过 继承 ( inheritance)
的机制可对类( class)分层,提供类型 /子类型的关系。 C++
通过 类派生 ( class derivation)的机制来支持继承。继承是类
之间定义的一种重要关系。定义类 B时,自动得到类 A的操作和
数据属性,使得程序员只需定义类 A中所没有的新成分就可完成
在类 B的定义,这样称类 B继承了类 A,类 A派生了类 B,A是基类
(父类),B是派生类(子类)。这种机制称为继承。
称已存在的用来派生新类的类为基类 ( base class), 又
称为父类 。 由已存在的类派生出的新类称为派生类 ( derived
class), 又称为子类 。 派生类可以具有基类的特性, 共享基类
的成员函数, 使用基类的数据成员, 还可以定义自己的新特性,
定义自己的数据成员和成员函数 。 基类和派生类的集合称作类继
承层次结构 ( hierarchy)
在 C++语言中,一个派生类可以从一个基类派生,也可以
从多个基类派生。从一个基类派生的继承称为单继承;从多个基
类派生的继承称为多继承。下图反映了类之间继承和派生关系。
8.1 继承与派生的概念
交通工具 文艺节目
汽车 火车 轮船 飞机 歌曲 舞蹈 相声 小品
轿车 货车 旅游车 合唱 独唱 二重唱
通俗 美声
A
B C
X Y
Z
单继承 多继承
由基类派生出派生类的定义的一般形式为
class 派生类名:继承方式 1 基类名 1《,继承方式 2
基类名 2,……,继承方式 n 基类名 n,{
private,
成员表 1; //派生类新增加或替代的私有成员
public:
成员表 2; //派生类新增加或替代的公有成员
protected:
成员表 3; //派生类新增加或替代的保护成员
}; //分号不可少
其中基类 1,基类 2,…… 是已声明的类。 在派生类定义的
类体中给出的成员称为 派生类成员,它们是新增加的数据和函
数成员。这些新增加的成员是派生类对基类的发展,它们给派
生类添加了不同于基类的新的属性和功能。派生类成员包括新
添加的,也包括通过屏蔽作用,取代基类成员的更新成员。
8.1.1 类的派生与继承
基类 1 基类 2 …… 基类 n
派生类 1 派生类 2
基类
派生类 1 派生类 2
( a)多重继承 ( b)单继承
图 8.1 多重继承与单继承
一个基类
可以直接
派生出多
个派生类
派生出来
的新类同
样可以作
为基类再
继续派生
出更新的
类,依此
类推形成
一个 层次
结构 。
8.1.1 类的派生与继承
如果一个派生类可以同时有多个基类,称为多重继承
( multiple-inheritance),这时的派生类同时得到了多个
已有类的特征。一个派生类只有一个直接基类的情况称为单
一继承( single-inheritance)。
编制
派生
类时
可分
四步
吸收基类的成员
改造基类成员
发展新成员
重写构造函数与析构函数
8.1.1 类的派生与继承
不论是数据成员,还是函数成员,
除构造函数与析构函数外全盘接收
声明一个和某基类成员同名的新成员派
生类中的新成员就屏蔽了基类同名成员
称为同名覆盖( override)
派生类新成员必须与基类成员不同名,它
的加入保证派生类在功能上有所发展。
算法和编程的步骤必须规范化地编程:
第二步:新成员如是成员函数,参数表也必须一样,否则是
重载。
第三步:独有的新成员才是继承与派生的核心特征。
第四步:是重写构造函数与析构函数,派生类不继承这两种
函数。
8.1.2 公有派生与私有派生
继承方式,亦称为 访问控制,是对基类成员的引用作进一步的限制。
继承方式 也有三种,公有( public)方式,保护( protected)方式和
私有( private)方式,亦称公有继承、保护继承和私有继承。
在派生类的定义中,基类前所加的 继承方式 有两方面含义,派生类成员
(新增)函数对基类(继承来的)成员的访问权限,和 从派生类对象之外对
派生类对象中的基类成员的访问权限 。一般来说,公有派生是绝对主流 。
1,公有继承( public) 。
公有继承的特点是基类的公有成员和保护成员作为派生类的
成员时,它们都保持原有的状态,而基类的私有成员仍然是私有
的。
2,私有继承( private) 。
私有继承的特点是基类的公有成员和保护成员作为派生类的
私有成员,并且不能被这个派生类的子类访问。
3,保护继承( protected) 。
保护继承的特点是基类的所有公有成员和保护成员都成为派
生类的保护成员,并且只能被它的派生类成员函数或友元访问,
基类的私有成员仍然是私有的。
8.1.2 公有派生与私有派生
不可直接访问不可直接访问private
不可直接访问private protected
不可直接访问private public 私有派生
不可直接访问不可直接访问private
不可直接访问protected protected
可直接访问public public 公有派生
在派生类对象外
访问派生类对象
的基类成员
在派生类中对
基类成员的访
问限定
基类中的
访问限定
派生方式
( 1) 在公有继承时,派生类的对象可以访问基类中的公有成
员;派生类的成员函数可以访问基类中的公有成员和保护成员。
这里,一定要区分清楚派生类的对象和派生类中的成员函数对
基类的访问是不同的。
( 2) 在私有继承时,基类的成员只能由直接派生类访问,而
无法再往下继承。
8.1.2 公有派生与私有派生
( 3) 对于保护继承方式,这种继承方式与私有继承方式的
情况相同。两者的区别仅在于对派生类的成员而言,对基
类成员有不同的可访问性。
( 4) 如果派生类定义了与基类同名的成员,称派生类的成
员覆盖了基类的同名成员,若要在派生类中使用基类同名
成员,可以显式地使用类名限定符:
类名,,成员
( 5)派生类不能访问基类私有成员
基类 派生类 基类 派生类 基类 派生类
public public public public
Protected protected protected protected proteced
private
private private private
不可见 不可见 不可见
private 派生 protected 派生 public 派生
8.1.2 公有派生与私有派生
派生继承的例子:
class Base {
private:
int b3;
protected:
int b2;
public:
int b1;
};
class D1:public Base {
public:
void test()
{ b1 = 10; // 可以,b1为 public
b2 = 20; // 可以,b2为 proteced
b3 = 30; // 不可以,b3为 private
}
}
8.1.2 公有派生与私有派生
class D11:public D1 {
public:
void test()
{ b1 = 5; // 可以,b1为 public
b2 = 6; // 可以,b2为 proteced
b3 = 7; // 不可以,b3为 private
}
}
class D2:private Base {
public:
void test()
{ b1 = 8; // 可以,b1为 public
b2 = 9; // 可以,b2为 proteced
b3 = 10; // 不可以,b3为 private
}
}
8.1.2 公有派生与私有派生
class D22:public D2 {
public:
void test()
{ b1 = 11; // 不可以,b1,b2经过上次派生变为
b2 = 12; // 不可以 private
b3 = 13; // 不可以
}
}
class D3:protected Base {
public:
void test()
{ b1 = 15; // 可以,b1为 proteced
b2 = 16; // 可以,b2为 proteced
b3 = 17; // 不可以
}
}
8.1.2 公有派生与私有派生
class D33:public D3 {
public:
void test()
{ b1 = 18; // 不可以,b1为 proteced
b2 = 19; // 不可以,b2为 proteced
b3 = 20; // 不可以
}
}
main()
{ D11 d1;
d1.b1 = 1; // 合法
d1.b2 = 2;
d1.b3 = 3;
D22 d2;
d2.b1 = 4;
d2.b2 = 5;
d2.b3 = 6;
8.1.2 公有派生与私有派生
D33 d3;
d3.b1 = 7;
d3.b2 = 8;
d3.b3 = 9;
}
基类成员对派生类对象的可访问性归结如下:
1、对公有继承方式,只有基类的公有成员可被派生对象所访问,
其他成员都不能被访问
2、对私有或保护继承方式,基类中所有成员都不能被派生类的
对象所访问。
派生类 的构造函数的定义形式为:
派生类名,:派生类名(参数总表),基类名 1(参数表 1),,基
类名 2(参数表 2),……,基类名 n(参数表 n),,, 成员对
象名 1(成员对象参数表 1),……,成员对象名 m(成员对象
参数表 m),
{
…… // 派生类新增成员的初始化;
} // 所列出的成员对象名全部为新增成员对象的名字
在构造函数的声明中,冒号及冒号以后部分必须略去。
所谓不能继承并不是不能利用,而是把基类的构造函数作为新
的构造函数的一部分,或者讲调用基类的构造函数。
这个构造函数的定义,与包含成员对象类的构造函数相类似。
冒号后的基类名,成员对象名的次序可以随意,这里的次序与
调用次序无关。
8.2 派生类的构造函数与析构函数
派生类构造函数各部分的执行次序为:
1.调用基类构造函数,按它们在派生类定义的先后
顺序,顺序调用。
2.调用成员对象的构造函数,按它们在类定义中声
明的先后顺序,顺序调用。
3.派生类的构造函数体中的操作。
在派生类构造函数中,只要基类不是使用缺省构造函数都
要显式给出基类名和参数表。如果基类没有定义构造函数,
则派生类也可以不定义,全部采用系统给定的缺省构造函
数。如果基类定义了带有形参表的构造函数时,派生类就
应当定义构造函数。
8.2 派生类的构造函数与析构函数
8.2 派生类的构造函数与析构函数
析构函数的功能是作善后工作,析构函数无返回类型
也没有参数,情况比较简单。派生类析构函数定义格式与非
派生类无任何差异,只要在函数体内把派生类新增一般成员
处理好就可以了,而对新增的成员对象和基类的善后工作,
系统会自己调用成员对象和基类的析构函数来完成。
析构函数各部分执行次序与构造函数相反,首先对派
生类新增一般成员析构,然后对新增对象成员析构,最后对
基类成员析构 。
例子:由 Hard(机器名)类和 Soft(软件,由 OS和
Language组成)类派生出 Sytem类。
我们希望派生类总是和基类保持一致,原有的成员和访
问方式被保留,这只能采用公有派生来实现。在私有派生时
要 保持接口不变,则要在派生类中重编接口,去调用基类接
口成员函数。所以绝大多数场合总是用公有派生。
首先来看两个基类:
class Hard {
protected:
char bodyname[20]; // 机器名
public:
Hard(char *bn)
{ cout <<,Con H\n”;
strcpy(bodyname,bn);
}
Hard(Hard &abody)
{ cout <<,Copy H\n”;
strcpy(bodyname,abody.bodyname);
}
void print()
{ cout <<,Body_Name:” << bodyname << endl;
}
}
class Soft {
protected:
char os[10]; // 操作系统名
char lang[15]; // 程序语言
public:
Soft(char *o,char *lg)
{ cout <<,Con F\n”;
strcpy(os,o);
strcpy(lang,lg);
}
Soft(Soft &aSoft)
{ cout <<,Copy F\n”;
strcpy(os,aSoft.os);
strcpy(lang,aSoft.lang);
}
void print()
{ cout <<,OS:” << os
<<,Language:” << lang << endl;
}
}
class System:public Hard,public Soft {
protected:
char owner[10]; // 系统名
public:
System(char *ow,char *bn,char *o,char *lg),
Hard(bn),Soft(o,lg)
{ cout <<,Con S\n”;
strcpy(owner,ow);
}
System(Hard abody,Soft asoft,char *ow),
Hard(abody),Soft(asoft)
{ cout <<,Copy S\n”;
strcpy(owner,ow);
}
void print()
{ cout <<,Owner:” << owner <<,; \n”;
cout <<,Hard:” << bodyname <<,; \n”;
cout <<,Soft:” << os <<,,“
<< lang << endl;
}
}
void main()
{ System bsystem (“Wang”,“IBM-PC”,
“PC-DOS”,“Basic”);
bsystem.print();
cout <<,OK !\n”;
Hard abody(“Intel 586”);
Soft asoft(“MS Windows”,“C++”);
System asystem(abody,asoft,“Zhang”);
asystem.print();
}
在册人员
学生 (单继承 )教职工 (单继承 ) 兼职教师 (单继承 )
教师 (单继承 ) 行政人员 (单继承 )工人 (单继承 ) 研究生 (单继承 )
行政人员兼教师
(多重继承 )
在职研究生
(多重继承 )研究生助教(多重继承 )
大学在册人员继承关系
8.3 多重继承与派生类成员标识
由多个基类共同派生出新的派生类,这样的继承结构
被称为多重继承或多继承( multiple-inheritance)
椅子 床
沙发 (单继承 ) 躺椅 (多重继承 )
两用沙发 (多重继承 )
图 8.3 椅子, 床到两用沙发
8.3 多重继承与派生类成员标识
歧义性问题,参见上图,比如行政人员兼教师,在其基类教师中
有一个“教职工编号”,另一基类行政人员中也有一个“教职工编
号”,如果只讲教职工编号那么是哪一个基类中的呢?这两者可能
是一回事,但 计算机系统并不这么认为 。两个基类中可能也各有一
个“职务”,这两者可能根本不同,一个是教师的,一个是行政管
理的。但它们的标识符是一样的,这就会出现 二义性 。“职务”可
以用不同标识符来区分。但“教职工编号”不行,因为这是由两个
基类“教师”和“行政人员”共同的基类“教职工”类继承来的,
只有同一个标志符。
唯一标识问题 。通常采用作
用域分辨符,::”:
基类名,:成员名 ;//数据成员
基类名,:成员名(参数表) ;
//函数成员
例子:由圆和高多重继承派生出圆锥。本例中类 Circle为圆;
类 Line为高;类 Cone为圆锥,由 Circle和 Line公有派生而来。
在 Cone类中,Circle和 Line类的接口完全不变,可以直接调用,
这就是公有派生的优点。在 Cone的成员函数中可直接访问
Circle和 Line中的公有成员,但不能直接访问私有成员。
class Circle{
float x,y,r; //(x,y)为圆心,r为半径
public:
Circle(float a=0,float b=0,float R=0){x=a;y=b;r=R;}
void Setcoordinate(float a,float b)
{x=a;y=b;} //设置圆心坐标
void Getcoordinate(float &a,float &b)
{a=x;b=y;} //取圆心坐标
void SetR(float R) {r=R;} //设置半径
float GetR() {return r;} //取圆半径
float GetAreaCircle(){return float(r*r*3.14159);}//取圆面积
float GetCircumference()
{return float(2*r*3.14159);}//取圆周长
};
8.3 多重继承与派生类成员标识
class Line{
float High;
public:
Line(float a=0){High=a;}
void SetHigh(float a){High=a;}
float GetHigh(){return High;}
};
class Cone:public Circle,public Line{
public:
Cone(float a,float b,float R,float d):Circle(a,b,R),Line(d){}
float GetCV(){return float(GetAreaCircle()*GetHigh()/3);}
//取得圆锥体积
float GetCA(){ //取得圆锥表面积
float tr,th;
tr=GetR(); //共有派生类中不能直接访问直接基类的私有成员
th=GetHigh();
return float(GetAreaCircle()+tr*3.14159*sqrt(tr*tr+th*th));}
};
8.3 多重继承与派生类成员标识
分析:class A {
int a;
public:
A() { a = 0; }
A(int i) { a = i; }
void print() { cout << a << ?,?; }
};
class B:public A {
int b1,b2;
public:
B() { b1 = b2 = 0;}
B(int i) { b1 = 0; b2 = i;}
B(int i,int j,int k):A(i),b1(j),b2(k) { }
void print() {
A::print();
cout << b1 << ?,? << b2 << endl;
}
};
void main()
{ B b1;
B b2(15);
B b3(10,20,30);
b1.print();
b2.print();
b3.print();
}
提问:
class B {
int b1,b2;
public:
B(int i,int j) { b1 = i; b2 = j;}
void print() { cout << b1 << ?,? << b2 << endl; }
};
class D:public B {
int d;
B bb;
public:
D(int i,int j,int k,int l,int m);
void print() {
B::print(); bb.print();
cout << d << endl;
}
};
D::D(int i,int j,int k,int l,int m):B(i,j),bb(k,l),d(m) { }
void main()
{ D d1(11,12,13,14,15);
d1.print();
}
8.4 虚基类
在 C++语言中, 引入 虚基类 主要是解决类的重复继承
过程中数据的共享问题 。
当某类的部分或全部直接基类是从另一个共同基类派生
而来时, 这些直接基类中从上一级基类继承来的成员就拥有
相同的名称, 也就是说, 这些同名成员在内存中存在多个副
本 。 而多数情况下, 由于它们的上一级基类是完全一样的,
在编程时, 只需使用多个副本的任一个 。
A
B C
D
C++语言允许程序中只建立公共基类的一个副本,将直
接基类的共同基类设置为虚基类,这时从不同路径继承过来
的该类成员在内存中只拥有一个副本,这样有关公共基类成
员访问的二义性问题就不存在了。
8.4 虚基类
例如:
Class A {
public:
int a;
};
Class B,public A {
public:
int b;
};
Class C,public A {
public:
int c;
};
Class D,public B,public C {
public:
int d;
}
这里,类 A是不是虚基类,建
立派生类 D的对象 d,下面的语
句就会出现二义性。
D d;
int x = d.a;
是 d.B::a还是 d.C::a
b
c
d
a
D
B
C
A a
b
a
c
d
D
B
C
A
A
A是虚基类内存存放布局
A不是虚基类内存存放布局
8.4 虚基类
如果一个派生类从多个基类派生,而这些基类又有一个
共同的基类,则在对该基类中声明的名字进行访问时,可能
产生二义性。引进虚基类的真正目的是为了解决二义性问题。
当基类被继承时,在基类的访问控制保留字的前面加上保留
字 virtual来定义。
如果基类被声明为虚基类,则重复继承的基类在派生类
对象实例中只好存储一个副本,否则,将出现多个基类成员
的副本。
虚基类说明格式如下:
class 派生类名,virtual <继承方式 > <基类名 > {
……
};
其中,virtual是虚基类的关键字。虚基类的说明是用在
定义派生类时,写在派生类名的后面。
8.4 虚基类
例如:
Class A {
public:
int a;
};
Class B:virtual public A {
public:
int b;
};
Class C:virtual public A {
public:
int c;
};
Class D,public B,public C {
public:
int d;
}
这里,类 A是虚基类,建立派
生类 D的对象 d,下面的语句不
会出现二义性。
D d;
int x = d.a;
d.B::a和 d.C::a 是等同的
b
c
d
a
D
B
C
A a
b
a
c
d
D
B
C
A
A
A是虚基类内存存放布局
A不是虚基类内存存放布局
8.4 虚基类
虚基类的构造函数:
为了初始化基类的子对象,派生类的构造函数要调用基类
的构造函数。对于虚基类来讲,由于派生类的对象中只有一个
虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚
基类构造函数必须只被调用一次。由于继承结构的层次可能很
深,规定将在建立对象时所指定的类称为最直接派生类。虚基
类子对象是由最直接派生类的构造函数通过调用虚基类的构造
函数进行初始化的。如果一个派生类有一个直接或间接的虚基
类,那么派生类的构造函数的成员初始列表中必须列出对虚基
类构造函数的调用,如果未被列出,则表示使用该虚基类的默
认构造函数来初始化派生类对象中的虚基类子对象。
派生类构造函数(参数表),<若干个基类的构造函数 >,
<子对象类构造函数 >,
<虚基类构造函数 >
{
派生类构造函数的函数体
}
8.4 虚基类
A B S1:初始化 C的 A
S2:初始化 C的 B
C D S3:初始化 C
S4,D的 A和 B都已初始化, 初始化 D
E S5:初始化 E
C++规定, 在一个成员初始化列表中出现对虚基类和非
虚基类构造函数的调用, 则虚基类的构造函数先于非虚基类的
构造函数执行 。
从虚基类直接或间接继承的派生类中的构造函数的成员初
始化列表中都要列出这个虚基类构造函数的调用。但是,只有
用于建立对象的那个派生类的构造函数调用虚基类的构造函数,
而该派生类的基类中所列出的对这个虚基类的构造函数调用在
执行中被忽略,这样便保证了对虚基类的子对象只初始化一次。
8.4 虚基类
注意:
如果一个派生类从多个基类派生, 而这些基类又有一
个共同的基类, 则在对该基类中声明的名字进行访问时,
可能产生二义性 。 如果在多条继承路径上有一个公共的基
类, 那么在继承路径的某处汇合点, 这个公共基类就会在
派生类的对象中产生多个基类子对象 。
要使这个公共基类在派生类中只产生一个子对象,必须
将这个基类声明为虚基类。虚基类声明使用关键字 virtual。
例子:class A { // 8-4.cpp
int a;
public:
A(int i) {a = i;cout<<“Constructor ia called in A\n”;}
void print() { cout << a << ?,?; }
~A() {cout <<,Destructor is called in A\n”; }
};
class B1:virtual public A {
int b1;
public:
B1(int i,int j):A(i) {
b1 = j; cout<<“Constructor ia called in B1\n”;
}
void print() {
A::print(); cout << b1 << ?,?;
}
~B1() {cout <<,Destructor is called in B1\n”; }
};
例子:
class B2:virtual public A {
int b2;
public:
B2(int i,int j):A(i) {
b2 = j; cout<<“Constructor is called in B2\n”;
}
void print() { A::print(); cout << b2 << ?,?; }
}
~B2() {cout <<,Destructor is called in B2\n”; }
};
class C:public B1,public B2 {
int c;
public:
C(int i,int j,int k,int l,int m):B1(i,j),B2(k,l),A(i) {
c = m; cout<<“Constructor is called in C\n”;
}
void print() {B1::print(); B2::print(); cout<<c<<endl;}
~C() {cout <<,Destructor is called in C\n”; }
};
例子:
main()
{ C c(16,19,23,25,38);
c.print();
}
输出结果,Constructor is call in A
Constructor is call in B1
Constructor is call in B2
Constructor is call in C
16,19,16,25,38
Destructor is called in C
Destructor is called in B2
Destructor is called in B1
Destructor is called in A
8.5 类层次中成员名的作用域
我们在第五章简单地介绍过嵌套类成员的作用域,下面
我们进一步介绍在类继承结构层次中各成员的作用域。我们
从下面几个角度来说明:
1,private成员的作用域只在本类对象内部,不可被其他类,
不管是外部的,还是派生类对象所访问,而 public成员和
protected成员,可以有限度地在类层次中被访问。
2、基类成员作用域和派生类新增成员作用域形成相包含关
系,派生类新增成员作用域为内层,而基类成员作用域在外
层。
3、在多基类派生层次结构中,各个类的成员可以同名,在
内层派生类中,可以使外层类的成员不可见,如果在内层中
想使用外层类的同名成员时,应加上作用域分辨符,如在虚
基类例子中的 d.B::a 和 d.C::a
在任何需要基类对象的地方都可以用公有派生类的对象来
代替,这条规则称 赋值兼容规则 。它 包括以下情况:
1,派生类的对象可以赋值给基类的对象,这时是把派生类对
象中从对应基类中继承来的成员赋值给基类对象。反过来
不行,因为派生类的新成员无值可赋。
2,可以将一个派生类的对象的地址赋给其基类的指针变量,
但只能通过这个指针访问派生类中由基类继承来的成员,
不能访问派生类中的新成员。同样也不能反过来做。
3,派生类对象可以初始化基类的引用。引用是别名,但这个
别名只能包含派生类对象中的由基类继承来的成员。
8.6 类层次中类转换规则
8.7 多重继承的应用例子
例子:某个公司有如下四类人员,他们的月收入计算如下:
总经理,月工资固定 8500;
技术员,每小时 150元计算;
销售员,按当月销售额的 7%提取;
销售经理,每月固定 4000,另按其部门每月的
销售额的 0.5%提取
编程计算各类人员的收入。
分析,首先我们建立各个对象的类层次结构,如下:
Employee
Technician Manager Salesman
Salesmanager
8.7 多重继承的应用例子
#include <iostream.h>
class Employee {
public:
Employee() {
cout <<,职工编号:”; cin >> no;
cout <<,职工姓名:”; cin >> name;
salary = 0;
}
protected:
int no;
char name[10];
double salary;
};
class Technican:public Employee {
public:
Technican() { houryrate = 150; }
void pay() {
8.7 多重继承的应用例子
cout <<,\n技术员:” << name <<,,月工作小时:” ;
cin >> workhours;
salary = hourlyrate * workhours;
}
void display() {
cout <<,技术员:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl;
}
private:
double hourlyrate;
int workhours;
};
class Salesman,virtual public Employee {
public:
Salesman() { commrate = 0.07; }
void pay() {
8.7 多重继承的应用例子
cout <<,\n销售员:” << name <<,,月销售额:” ;
cin >> sales;
salary = sales * commrate;
}
void display() {
cout <<,销售员:” << name <<,,编号:” <<
no
<<,,月工资:” << salary << endl;
}
proteced:
double commrate,sales;
};
class Manager,virtual public Employee {
public:
Manager() { monthpay = 8500; }
void pay() { salary = 8500; }
8.7 多重继承的应用例子
void display() {
cout <<,经理:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl;
}
proteced:
double monthpay;
};
class Salesmanager, public Manager,public Salesman {
public:
Salesmanager() { monthpay = 4000; commrate = 0.005 }
void pay() {
cout <<,\n销售经理:” << name <<,,部门销售额:” ;
cin >> sales;
salary = monthpay + sales * commrate;
}
void display() {
cout <<,销售经理:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl; }
};
8.7 多重继承的应用例子
void main()
{ Manager m;
Technican t;
Salesman s;
Salesmanager sm;
m.pay();
m.display();
t.pay();
t.display();
s.pay();
s.display();
sm.pay();
sm.display();
}
在 VC程序运行结果:
8.8 MFC基础类及其层次结构
1989年微软成立了 Application Framework(应用程序
框架)开发小组( AFX),AFX小组的 MFC开发设计的原则,
对任何软件开发者都是非常好的借鉴:
1,使用 C++进行 Windows下的应用程序设计的过程应简单和直
观。
2,用类封装 Windows API和 Windows对象,并按照 API的方式
进行工作。
3,使用标准的 Windows命令的约定和编码风格。
4,类库应有足够的可扩展性,以便跟随 Windows的更新和发展。
5,能更方便地使用 API函数,采用 Windows API的概念,函数
和编程风格。
从这里可以看出 MFC和 Windows很好地连接起来,使
Windows下的 C语言程序设计,成为面向对象的 MFC。这里
没有追求纯粹,而是 包容和实用 !
MFC是采用单一继承,从根类 Cobject层层派生出
绝大多数 MFC中的类,其层次结构是最典型。参见下图
根类 CObject分类派生图
8.8 MFC基础类及其层次结构
Cobject根类
CCmdTarget 命令相关类
CDC设备环境类 CClientDC,CWindowDC、CPaintDC,……
CGdiObject 绘画工具类 CPen,CBrush,CFont、
CBitmap,CPalette,……
CMenu菜单
CArray,CList,CMap,…… 群(集合)类
CDatabase,CRecordset,… ODBC数据库支持
CDatabase,CDataRecordset,…… DAO 数据库支持
CFile 文件类 CMemFile,COleStreamFile、
CSocketFile,……
CException 异常类
CSyncObject 同步对象类
CInternetSession 因特网会话类
CInternetConnection 因特网连接类
CFtpConnection、
CGopherConnectio
n、
CHelpConnection
Cob
jec
t
派
生
类
层
次
示
意
图
CObject根类
CCmdTarget 命令处理类
CWinThread 线程类
CDocument 文档类
CDocTemplate 文档模板类
CWnd 窗口类
CWinApp Windows应用程序类
CSingleDocTemplate 单文档
模板类
CMultiDocTemplate 多文
档模板类
图、应用程序结构
Cobject根类派生的最重要的类组成一个应用程序结构
(Application Architecture)的集合,它是由 CCmdTarget为
基类派生出来的,其中主要部分参见下图。
CFrameWnd 框架窗口类
CControlBar 控制条类
CSplitterWnd 窗口分割类
CPropertSheet 属性表类
CMDIFrameWnd,CMDIChildWnd、
CMiniFrameWnd
CDialogBar,CToolBar、
CStatusBar
CWnd 窗口类
CDialog 对话框类
CCommonDialog 公用对话框类
CProperty 属性页表
CFileDialog,CColorDialog,…
图 8.8 应用程序结构
CView 视图类
控制类
CButton,CEdit,CListBox,CScrollBar、
CStatic,CComboBox
CCtrlView
CFormView
CEditView,CListView、
CTreeView,CRichEditView
CRecordView 图 8.8 应用程序结构
基类 Cobject提供的最基本功能有:
1,支持序列化 ( serialization)。序列化指如何让一个对象保持持
久不变,即把对象成员数据内容存入一个文件或从一个文件中读
取内容重构对象的过程。
2,运行时( Run-time)类的信息获取 。
3,提供特定的 new,delete和 =操作符,完成对象的建立与删除。
复用的最重要的手段,它允许程序员在保持原有类特性的基础
上进行扩展,增加功能。 这样产生新的类,称派生类。派生类
不是简单地继承基础类的特性,它可以调整部分成员的特性,
也可以增加一些新成员。继承呈现了面向对象程序设计的层次
结构。 体现了由简单到复杂的认识过程 。
第八章 继承性
8.1 继承与派生的概念
8.4 虚基类
8.3 多重继承与派生类成员标识
8.8 MFC基础类及其层次结构
8.5 类层次中成员名的作用域
8.2 派生类的构造函数与析构函数
8.7 多重继承的应用例子
8.6 类层次中类转换规则
8.1 继承与派生的概念
层次概念 是计算机的重要概念。通过 继承 ( inheritance)
的机制可对类( class)分层,提供类型 /子类型的关系。 C++
通过 类派生 ( class derivation)的机制来支持继承。继承是类
之间定义的一种重要关系。定义类 B时,自动得到类 A的操作和
数据属性,使得程序员只需定义类 A中所没有的新成分就可完成
在类 B的定义,这样称类 B继承了类 A,类 A派生了类 B,A是基类
(父类),B是派生类(子类)。这种机制称为继承。
称已存在的用来派生新类的类为基类 ( base class), 又
称为父类 。 由已存在的类派生出的新类称为派生类 ( derived
class), 又称为子类 。 派生类可以具有基类的特性, 共享基类
的成员函数, 使用基类的数据成员, 还可以定义自己的新特性,
定义自己的数据成员和成员函数 。 基类和派生类的集合称作类继
承层次结构 ( hierarchy)
在 C++语言中,一个派生类可以从一个基类派生,也可以
从多个基类派生。从一个基类派生的继承称为单继承;从多个基
类派生的继承称为多继承。下图反映了类之间继承和派生关系。
8.1 继承与派生的概念
交通工具 文艺节目
汽车 火车 轮船 飞机 歌曲 舞蹈 相声 小品
轿车 货车 旅游车 合唱 独唱 二重唱
通俗 美声
A
B C
X Y
Z
单继承 多继承
由基类派生出派生类的定义的一般形式为
class 派生类名:继承方式 1 基类名 1《,继承方式 2
基类名 2,……,继承方式 n 基类名 n,{
private,
成员表 1; //派生类新增加或替代的私有成员
public:
成员表 2; //派生类新增加或替代的公有成员
protected:
成员表 3; //派生类新增加或替代的保护成员
}; //分号不可少
其中基类 1,基类 2,…… 是已声明的类。 在派生类定义的
类体中给出的成员称为 派生类成员,它们是新增加的数据和函
数成员。这些新增加的成员是派生类对基类的发展,它们给派
生类添加了不同于基类的新的属性和功能。派生类成员包括新
添加的,也包括通过屏蔽作用,取代基类成员的更新成员。
8.1.1 类的派生与继承
基类 1 基类 2 …… 基类 n
派生类 1 派生类 2
基类
派生类 1 派生类 2
( a)多重继承 ( b)单继承
图 8.1 多重继承与单继承
一个基类
可以直接
派生出多
个派生类
派生出来
的新类同
样可以作
为基类再
继续派生
出更新的
类,依此
类推形成
一个 层次
结构 。
8.1.1 类的派生与继承
如果一个派生类可以同时有多个基类,称为多重继承
( multiple-inheritance),这时的派生类同时得到了多个
已有类的特征。一个派生类只有一个直接基类的情况称为单
一继承( single-inheritance)。
编制
派生
类时
可分
四步
吸收基类的成员
改造基类成员
发展新成员
重写构造函数与析构函数
8.1.1 类的派生与继承
不论是数据成员,还是函数成员,
除构造函数与析构函数外全盘接收
声明一个和某基类成员同名的新成员派
生类中的新成员就屏蔽了基类同名成员
称为同名覆盖( override)
派生类新成员必须与基类成员不同名,它
的加入保证派生类在功能上有所发展。
算法和编程的步骤必须规范化地编程:
第二步:新成员如是成员函数,参数表也必须一样,否则是
重载。
第三步:独有的新成员才是继承与派生的核心特征。
第四步:是重写构造函数与析构函数,派生类不继承这两种
函数。
8.1.2 公有派生与私有派生
继承方式,亦称为 访问控制,是对基类成员的引用作进一步的限制。
继承方式 也有三种,公有( public)方式,保护( protected)方式和
私有( private)方式,亦称公有继承、保护继承和私有继承。
在派生类的定义中,基类前所加的 继承方式 有两方面含义,派生类成员
(新增)函数对基类(继承来的)成员的访问权限,和 从派生类对象之外对
派生类对象中的基类成员的访问权限 。一般来说,公有派生是绝对主流 。
1,公有继承( public) 。
公有继承的特点是基类的公有成员和保护成员作为派生类的
成员时,它们都保持原有的状态,而基类的私有成员仍然是私有
的。
2,私有继承( private) 。
私有继承的特点是基类的公有成员和保护成员作为派生类的
私有成员,并且不能被这个派生类的子类访问。
3,保护继承( protected) 。
保护继承的特点是基类的所有公有成员和保护成员都成为派
生类的保护成员,并且只能被它的派生类成员函数或友元访问,
基类的私有成员仍然是私有的。
8.1.2 公有派生与私有派生
不可直接访问不可直接访问private
不可直接访问private protected
不可直接访问private public 私有派生
不可直接访问不可直接访问private
不可直接访问protected protected
可直接访问public public 公有派生
在派生类对象外
访问派生类对象
的基类成员
在派生类中对
基类成员的访
问限定
基类中的
访问限定
派生方式
( 1) 在公有继承时,派生类的对象可以访问基类中的公有成
员;派生类的成员函数可以访问基类中的公有成员和保护成员。
这里,一定要区分清楚派生类的对象和派生类中的成员函数对
基类的访问是不同的。
( 2) 在私有继承时,基类的成员只能由直接派生类访问,而
无法再往下继承。
8.1.2 公有派生与私有派生
( 3) 对于保护继承方式,这种继承方式与私有继承方式的
情况相同。两者的区别仅在于对派生类的成员而言,对基
类成员有不同的可访问性。
( 4) 如果派生类定义了与基类同名的成员,称派生类的成
员覆盖了基类的同名成员,若要在派生类中使用基类同名
成员,可以显式地使用类名限定符:
类名,,成员
( 5)派生类不能访问基类私有成员
基类 派生类 基类 派生类 基类 派生类
public public public public
Protected protected protected protected proteced
private
private private private
不可见 不可见 不可见
private 派生 protected 派生 public 派生
8.1.2 公有派生与私有派生
派生继承的例子:
class Base {
private:
int b3;
protected:
int b2;
public:
int b1;
};
class D1:public Base {
public:
void test()
{ b1 = 10; // 可以,b1为 public
b2 = 20; // 可以,b2为 proteced
b3 = 30; // 不可以,b3为 private
}
}
8.1.2 公有派生与私有派生
class D11:public D1 {
public:
void test()
{ b1 = 5; // 可以,b1为 public
b2 = 6; // 可以,b2为 proteced
b3 = 7; // 不可以,b3为 private
}
}
class D2:private Base {
public:
void test()
{ b1 = 8; // 可以,b1为 public
b2 = 9; // 可以,b2为 proteced
b3 = 10; // 不可以,b3为 private
}
}
8.1.2 公有派生与私有派生
class D22:public D2 {
public:
void test()
{ b1 = 11; // 不可以,b1,b2经过上次派生变为
b2 = 12; // 不可以 private
b3 = 13; // 不可以
}
}
class D3:protected Base {
public:
void test()
{ b1 = 15; // 可以,b1为 proteced
b2 = 16; // 可以,b2为 proteced
b3 = 17; // 不可以
}
}
8.1.2 公有派生与私有派生
class D33:public D3 {
public:
void test()
{ b1 = 18; // 不可以,b1为 proteced
b2 = 19; // 不可以,b2为 proteced
b3 = 20; // 不可以
}
}
main()
{ D11 d1;
d1.b1 = 1; // 合法
d1.b2 = 2;
d1.b3 = 3;
D22 d2;
d2.b1 = 4;
d2.b2 = 5;
d2.b3 = 6;
8.1.2 公有派生与私有派生
D33 d3;
d3.b1 = 7;
d3.b2 = 8;
d3.b3 = 9;
}
基类成员对派生类对象的可访问性归结如下:
1、对公有继承方式,只有基类的公有成员可被派生对象所访问,
其他成员都不能被访问
2、对私有或保护继承方式,基类中所有成员都不能被派生类的
对象所访问。
派生类 的构造函数的定义形式为:
派生类名,:派生类名(参数总表),基类名 1(参数表 1),,基
类名 2(参数表 2),……,基类名 n(参数表 n),,, 成员对
象名 1(成员对象参数表 1),……,成员对象名 m(成员对象
参数表 m),
{
…… // 派生类新增成员的初始化;
} // 所列出的成员对象名全部为新增成员对象的名字
在构造函数的声明中,冒号及冒号以后部分必须略去。
所谓不能继承并不是不能利用,而是把基类的构造函数作为新
的构造函数的一部分,或者讲调用基类的构造函数。
这个构造函数的定义,与包含成员对象类的构造函数相类似。
冒号后的基类名,成员对象名的次序可以随意,这里的次序与
调用次序无关。
8.2 派生类的构造函数与析构函数
派生类构造函数各部分的执行次序为:
1.调用基类构造函数,按它们在派生类定义的先后
顺序,顺序调用。
2.调用成员对象的构造函数,按它们在类定义中声
明的先后顺序,顺序调用。
3.派生类的构造函数体中的操作。
在派生类构造函数中,只要基类不是使用缺省构造函数都
要显式给出基类名和参数表。如果基类没有定义构造函数,
则派生类也可以不定义,全部采用系统给定的缺省构造函
数。如果基类定义了带有形参表的构造函数时,派生类就
应当定义构造函数。
8.2 派生类的构造函数与析构函数
8.2 派生类的构造函数与析构函数
析构函数的功能是作善后工作,析构函数无返回类型
也没有参数,情况比较简单。派生类析构函数定义格式与非
派生类无任何差异,只要在函数体内把派生类新增一般成员
处理好就可以了,而对新增的成员对象和基类的善后工作,
系统会自己调用成员对象和基类的析构函数来完成。
析构函数各部分执行次序与构造函数相反,首先对派
生类新增一般成员析构,然后对新增对象成员析构,最后对
基类成员析构 。
例子:由 Hard(机器名)类和 Soft(软件,由 OS和
Language组成)类派生出 Sytem类。
我们希望派生类总是和基类保持一致,原有的成员和访
问方式被保留,这只能采用公有派生来实现。在私有派生时
要 保持接口不变,则要在派生类中重编接口,去调用基类接
口成员函数。所以绝大多数场合总是用公有派生。
首先来看两个基类:
class Hard {
protected:
char bodyname[20]; // 机器名
public:
Hard(char *bn)
{ cout <<,Con H\n”;
strcpy(bodyname,bn);
}
Hard(Hard &abody)
{ cout <<,Copy H\n”;
strcpy(bodyname,abody.bodyname);
}
void print()
{ cout <<,Body_Name:” << bodyname << endl;
}
}
class Soft {
protected:
char os[10]; // 操作系统名
char lang[15]; // 程序语言
public:
Soft(char *o,char *lg)
{ cout <<,Con F\n”;
strcpy(os,o);
strcpy(lang,lg);
}
Soft(Soft &aSoft)
{ cout <<,Copy F\n”;
strcpy(os,aSoft.os);
strcpy(lang,aSoft.lang);
}
void print()
{ cout <<,OS:” << os
<<,Language:” << lang << endl;
}
}
class System:public Hard,public Soft {
protected:
char owner[10]; // 系统名
public:
System(char *ow,char *bn,char *o,char *lg),
Hard(bn),Soft(o,lg)
{ cout <<,Con S\n”;
strcpy(owner,ow);
}
System(Hard abody,Soft asoft,char *ow),
Hard(abody),Soft(asoft)
{ cout <<,Copy S\n”;
strcpy(owner,ow);
}
void print()
{ cout <<,Owner:” << owner <<,; \n”;
cout <<,Hard:” << bodyname <<,; \n”;
cout <<,Soft:” << os <<,,“
<< lang << endl;
}
}
void main()
{ System bsystem (“Wang”,“IBM-PC”,
“PC-DOS”,“Basic”);
bsystem.print();
cout <<,OK !\n”;
Hard abody(“Intel 586”);
Soft asoft(“MS Windows”,“C++”);
System asystem(abody,asoft,“Zhang”);
asystem.print();
}
在册人员
学生 (单继承 )教职工 (单继承 ) 兼职教师 (单继承 )
教师 (单继承 ) 行政人员 (单继承 )工人 (单继承 ) 研究生 (单继承 )
行政人员兼教师
(多重继承 )
在职研究生
(多重继承 )研究生助教(多重继承 )
大学在册人员继承关系
8.3 多重继承与派生类成员标识
由多个基类共同派生出新的派生类,这样的继承结构
被称为多重继承或多继承( multiple-inheritance)
椅子 床
沙发 (单继承 ) 躺椅 (多重继承 )
两用沙发 (多重继承 )
图 8.3 椅子, 床到两用沙发
8.3 多重继承与派生类成员标识
歧义性问题,参见上图,比如行政人员兼教师,在其基类教师中
有一个“教职工编号”,另一基类行政人员中也有一个“教职工编
号”,如果只讲教职工编号那么是哪一个基类中的呢?这两者可能
是一回事,但 计算机系统并不这么认为 。两个基类中可能也各有一
个“职务”,这两者可能根本不同,一个是教师的,一个是行政管
理的。但它们的标识符是一样的,这就会出现 二义性 。“职务”可
以用不同标识符来区分。但“教职工编号”不行,因为这是由两个
基类“教师”和“行政人员”共同的基类“教职工”类继承来的,
只有同一个标志符。
唯一标识问题 。通常采用作
用域分辨符,::”:
基类名,:成员名 ;//数据成员
基类名,:成员名(参数表) ;
//函数成员
例子:由圆和高多重继承派生出圆锥。本例中类 Circle为圆;
类 Line为高;类 Cone为圆锥,由 Circle和 Line公有派生而来。
在 Cone类中,Circle和 Line类的接口完全不变,可以直接调用,
这就是公有派生的优点。在 Cone的成员函数中可直接访问
Circle和 Line中的公有成员,但不能直接访问私有成员。
class Circle{
float x,y,r; //(x,y)为圆心,r为半径
public:
Circle(float a=0,float b=0,float R=0){x=a;y=b;r=R;}
void Setcoordinate(float a,float b)
{x=a;y=b;} //设置圆心坐标
void Getcoordinate(float &a,float &b)
{a=x;b=y;} //取圆心坐标
void SetR(float R) {r=R;} //设置半径
float GetR() {return r;} //取圆半径
float GetAreaCircle(){return float(r*r*3.14159);}//取圆面积
float GetCircumference()
{return float(2*r*3.14159);}//取圆周长
};
8.3 多重继承与派生类成员标识
class Line{
float High;
public:
Line(float a=0){High=a;}
void SetHigh(float a){High=a;}
float GetHigh(){return High;}
};
class Cone:public Circle,public Line{
public:
Cone(float a,float b,float R,float d):Circle(a,b,R),Line(d){}
float GetCV(){return float(GetAreaCircle()*GetHigh()/3);}
//取得圆锥体积
float GetCA(){ //取得圆锥表面积
float tr,th;
tr=GetR(); //共有派生类中不能直接访问直接基类的私有成员
th=GetHigh();
return float(GetAreaCircle()+tr*3.14159*sqrt(tr*tr+th*th));}
};
8.3 多重继承与派生类成员标识
分析:class A {
int a;
public:
A() { a = 0; }
A(int i) { a = i; }
void print() { cout << a << ?,?; }
};
class B:public A {
int b1,b2;
public:
B() { b1 = b2 = 0;}
B(int i) { b1 = 0; b2 = i;}
B(int i,int j,int k):A(i),b1(j),b2(k) { }
void print() {
A::print();
cout << b1 << ?,? << b2 << endl;
}
};
void main()
{ B b1;
B b2(15);
B b3(10,20,30);
b1.print();
b2.print();
b3.print();
}
提问:
class B {
int b1,b2;
public:
B(int i,int j) { b1 = i; b2 = j;}
void print() { cout << b1 << ?,? << b2 << endl; }
};
class D:public B {
int d;
B bb;
public:
D(int i,int j,int k,int l,int m);
void print() {
B::print(); bb.print();
cout << d << endl;
}
};
D::D(int i,int j,int k,int l,int m):B(i,j),bb(k,l),d(m) { }
void main()
{ D d1(11,12,13,14,15);
d1.print();
}
8.4 虚基类
在 C++语言中, 引入 虚基类 主要是解决类的重复继承
过程中数据的共享问题 。
当某类的部分或全部直接基类是从另一个共同基类派生
而来时, 这些直接基类中从上一级基类继承来的成员就拥有
相同的名称, 也就是说, 这些同名成员在内存中存在多个副
本 。 而多数情况下, 由于它们的上一级基类是完全一样的,
在编程时, 只需使用多个副本的任一个 。
A
B C
D
C++语言允许程序中只建立公共基类的一个副本,将直
接基类的共同基类设置为虚基类,这时从不同路径继承过来
的该类成员在内存中只拥有一个副本,这样有关公共基类成
员访问的二义性问题就不存在了。
8.4 虚基类
例如:
Class A {
public:
int a;
};
Class B,public A {
public:
int b;
};
Class C,public A {
public:
int c;
};
Class D,public B,public C {
public:
int d;
}
这里,类 A是不是虚基类,建
立派生类 D的对象 d,下面的语
句就会出现二义性。
D d;
int x = d.a;
是 d.B::a还是 d.C::a
b
c
d
a
D
B
C
A a
b
a
c
d
D
B
C
A
A
A是虚基类内存存放布局
A不是虚基类内存存放布局
8.4 虚基类
如果一个派生类从多个基类派生,而这些基类又有一个
共同的基类,则在对该基类中声明的名字进行访问时,可能
产生二义性。引进虚基类的真正目的是为了解决二义性问题。
当基类被继承时,在基类的访问控制保留字的前面加上保留
字 virtual来定义。
如果基类被声明为虚基类,则重复继承的基类在派生类
对象实例中只好存储一个副本,否则,将出现多个基类成员
的副本。
虚基类说明格式如下:
class 派生类名,virtual <继承方式 > <基类名 > {
……
};
其中,virtual是虚基类的关键字。虚基类的说明是用在
定义派生类时,写在派生类名的后面。
8.4 虚基类
例如:
Class A {
public:
int a;
};
Class B:virtual public A {
public:
int b;
};
Class C:virtual public A {
public:
int c;
};
Class D,public B,public C {
public:
int d;
}
这里,类 A是虚基类,建立派
生类 D的对象 d,下面的语句不
会出现二义性。
D d;
int x = d.a;
d.B::a和 d.C::a 是等同的
b
c
d
a
D
B
C
A a
b
a
c
d
D
B
C
A
A
A是虚基类内存存放布局
A不是虚基类内存存放布局
8.4 虚基类
虚基类的构造函数:
为了初始化基类的子对象,派生类的构造函数要调用基类
的构造函数。对于虚基类来讲,由于派生类的对象中只有一个
虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚
基类构造函数必须只被调用一次。由于继承结构的层次可能很
深,规定将在建立对象时所指定的类称为最直接派生类。虚基
类子对象是由最直接派生类的构造函数通过调用虚基类的构造
函数进行初始化的。如果一个派生类有一个直接或间接的虚基
类,那么派生类的构造函数的成员初始列表中必须列出对虚基
类构造函数的调用,如果未被列出,则表示使用该虚基类的默
认构造函数来初始化派生类对象中的虚基类子对象。
派生类构造函数(参数表),<若干个基类的构造函数 >,
<子对象类构造函数 >,
<虚基类构造函数 >
{
派生类构造函数的函数体
}
8.4 虚基类
A B S1:初始化 C的 A
S2:初始化 C的 B
C D S3:初始化 C
S4,D的 A和 B都已初始化, 初始化 D
E S5:初始化 E
C++规定, 在一个成员初始化列表中出现对虚基类和非
虚基类构造函数的调用, 则虚基类的构造函数先于非虚基类的
构造函数执行 。
从虚基类直接或间接继承的派生类中的构造函数的成员初
始化列表中都要列出这个虚基类构造函数的调用。但是,只有
用于建立对象的那个派生类的构造函数调用虚基类的构造函数,
而该派生类的基类中所列出的对这个虚基类的构造函数调用在
执行中被忽略,这样便保证了对虚基类的子对象只初始化一次。
8.4 虚基类
注意:
如果一个派生类从多个基类派生, 而这些基类又有一
个共同的基类, 则在对该基类中声明的名字进行访问时,
可能产生二义性 。 如果在多条继承路径上有一个公共的基
类, 那么在继承路径的某处汇合点, 这个公共基类就会在
派生类的对象中产生多个基类子对象 。
要使这个公共基类在派生类中只产生一个子对象,必须
将这个基类声明为虚基类。虚基类声明使用关键字 virtual。
例子:class A { // 8-4.cpp
int a;
public:
A(int i) {a = i;cout<<“Constructor ia called in A\n”;}
void print() { cout << a << ?,?; }
~A() {cout <<,Destructor is called in A\n”; }
};
class B1:virtual public A {
int b1;
public:
B1(int i,int j):A(i) {
b1 = j; cout<<“Constructor ia called in B1\n”;
}
void print() {
A::print(); cout << b1 << ?,?;
}
~B1() {cout <<,Destructor is called in B1\n”; }
};
例子:
class B2:virtual public A {
int b2;
public:
B2(int i,int j):A(i) {
b2 = j; cout<<“Constructor is called in B2\n”;
}
void print() { A::print(); cout << b2 << ?,?; }
}
~B2() {cout <<,Destructor is called in B2\n”; }
};
class C:public B1,public B2 {
int c;
public:
C(int i,int j,int k,int l,int m):B1(i,j),B2(k,l),A(i) {
c = m; cout<<“Constructor is called in C\n”;
}
void print() {B1::print(); B2::print(); cout<<c<<endl;}
~C() {cout <<,Destructor is called in C\n”; }
};
例子:
main()
{ C c(16,19,23,25,38);
c.print();
}
输出结果,Constructor is call in A
Constructor is call in B1
Constructor is call in B2
Constructor is call in C
16,19,16,25,38
Destructor is called in C
Destructor is called in B2
Destructor is called in B1
Destructor is called in A
8.5 类层次中成员名的作用域
我们在第五章简单地介绍过嵌套类成员的作用域,下面
我们进一步介绍在类继承结构层次中各成员的作用域。我们
从下面几个角度来说明:
1,private成员的作用域只在本类对象内部,不可被其他类,
不管是外部的,还是派生类对象所访问,而 public成员和
protected成员,可以有限度地在类层次中被访问。
2、基类成员作用域和派生类新增成员作用域形成相包含关
系,派生类新增成员作用域为内层,而基类成员作用域在外
层。
3、在多基类派生层次结构中,各个类的成员可以同名,在
内层派生类中,可以使外层类的成员不可见,如果在内层中
想使用外层类的同名成员时,应加上作用域分辨符,如在虚
基类例子中的 d.B::a 和 d.C::a
在任何需要基类对象的地方都可以用公有派生类的对象来
代替,这条规则称 赋值兼容规则 。它 包括以下情况:
1,派生类的对象可以赋值给基类的对象,这时是把派生类对
象中从对应基类中继承来的成员赋值给基类对象。反过来
不行,因为派生类的新成员无值可赋。
2,可以将一个派生类的对象的地址赋给其基类的指针变量,
但只能通过这个指针访问派生类中由基类继承来的成员,
不能访问派生类中的新成员。同样也不能反过来做。
3,派生类对象可以初始化基类的引用。引用是别名,但这个
别名只能包含派生类对象中的由基类继承来的成员。
8.6 类层次中类转换规则
8.7 多重继承的应用例子
例子:某个公司有如下四类人员,他们的月收入计算如下:
总经理,月工资固定 8500;
技术员,每小时 150元计算;
销售员,按当月销售额的 7%提取;
销售经理,每月固定 4000,另按其部门每月的
销售额的 0.5%提取
编程计算各类人员的收入。
分析,首先我们建立各个对象的类层次结构,如下:
Employee
Technician Manager Salesman
Salesmanager
8.7 多重继承的应用例子
#include <iostream.h>
class Employee {
public:
Employee() {
cout <<,职工编号:”; cin >> no;
cout <<,职工姓名:”; cin >> name;
salary = 0;
}
protected:
int no;
char name[10];
double salary;
};
class Technican:public Employee {
public:
Technican() { houryrate = 150; }
void pay() {
8.7 多重继承的应用例子
cout <<,\n技术员:” << name <<,,月工作小时:” ;
cin >> workhours;
salary = hourlyrate * workhours;
}
void display() {
cout <<,技术员:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl;
}
private:
double hourlyrate;
int workhours;
};
class Salesman,virtual public Employee {
public:
Salesman() { commrate = 0.07; }
void pay() {
8.7 多重继承的应用例子
cout <<,\n销售员:” << name <<,,月销售额:” ;
cin >> sales;
salary = sales * commrate;
}
void display() {
cout <<,销售员:” << name <<,,编号:” <<
no
<<,,月工资:” << salary << endl;
}
proteced:
double commrate,sales;
};
class Manager,virtual public Employee {
public:
Manager() { monthpay = 8500; }
void pay() { salary = 8500; }
8.7 多重继承的应用例子
void display() {
cout <<,经理:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl;
}
proteced:
double monthpay;
};
class Salesmanager, public Manager,public Salesman {
public:
Salesmanager() { monthpay = 4000; commrate = 0.005 }
void pay() {
cout <<,\n销售经理:” << name <<,,部门销售额:” ;
cin >> sales;
salary = monthpay + sales * commrate;
}
void display() {
cout <<,销售经理:” << name <<,,编号:” << no
<<,,月工资:” << salary << endl; }
};
8.7 多重继承的应用例子
void main()
{ Manager m;
Technican t;
Salesman s;
Salesmanager sm;
m.pay();
m.display();
t.pay();
t.display();
s.pay();
s.display();
sm.pay();
sm.display();
}
在 VC程序运行结果:
8.8 MFC基础类及其层次结构
1989年微软成立了 Application Framework(应用程序
框架)开发小组( AFX),AFX小组的 MFC开发设计的原则,
对任何软件开发者都是非常好的借鉴:
1,使用 C++进行 Windows下的应用程序设计的过程应简单和直
观。
2,用类封装 Windows API和 Windows对象,并按照 API的方式
进行工作。
3,使用标准的 Windows命令的约定和编码风格。
4,类库应有足够的可扩展性,以便跟随 Windows的更新和发展。
5,能更方便地使用 API函数,采用 Windows API的概念,函数
和编程风格。
从这里可以看出 MFC和 Windows很好地连接起来,使
Windows下的 C语言程序设计,成为面向对象的 MFC。这里
没有追求纯粹,而是 包容和实用 !
MFC是采用单一继承,从根类 Cobject层层派生出
绝大多数 MFC中的类,其层次结构是最典型。参见下图
根类 CObject分类派生图
8.8 MFC基础类及其层次结构
Cobject根类
CCmdTarget 命令相关类
CDC设备环境类 CClientDC,CWindowDC、CPaintDC,……
CGdiObject 绘画工具类 CPen,CBrush,CFont、
CBitmap,CPalette,……
CMenu菜单
CArray,CList,CMap,…… 群(集合)类
CDatabase,CRecordset,… ODBC数据库支持
CDatabase,CDataRecordset,…… DAO 数据库支持
CFile 文件类 CMemFile,COleStreamFile、
CSocketFile,……
CException 异常类
CSyncObject 同步对象类
CInternetSession 因特网会话类
CInternetConnection 因特网连接类
CFtpConnection、
CGopherConnectio
n、
CHelpConnection
Cob
jec
t
派
生
类
层
次
示
意
图
CObject根类
CCmdTarget 命令处理类
CWinThread 线程类
CDocument 文档类
CDocTemplate 文档模板类
CWnd 窗口类
CWinApp Windows应用程序类
CSingleDocTemplate 单文档
模板类
CMultiDocTemplate 多文
档模板类
图、应用程序结构
Cobject根类派生的最重要的类组成一个应用程序结构
(Application Architecture)的集合,它是由 CCmdTarget为
基类派生出来的,其中主要部分参见下图。
CFrameWnd 框架窗口类
CControlBar 控制条类
CSplitterWnd 窗口分割类
CPropertSheet 属性表类
CMDIFrameWnd,CMDIChildWnd、
CMiniFrameWnd
CDialogBar,CToolBar、
CStatusBar
CWnd 窗口类
CDialog 对话框类
CCommonDialog 公用对话框类
CProperty 属性页表
CFileDialog,CColorDialog,…
图 8.8 应用程序结构
CView 视图类
控制类
CButton,CEdit,CListBox,CScrollBar、
CStatic,CComboBox
CCtrlView
CFormView
CEditView,CListView、
CTreeView,CRichEditView
CRecordView 图 8.8 应用程序结构
基类 Cobject提供的最基本功能有:
1,支持序列化 ( serialization)。序列化指如何让一个对象保持持
久不变,即把对象成员数据内容存入一个文件或从一个文件中读
取内容重构对象的过程。
2,运行时( Run-time)类的信息获取 。
3,提供特定的 new,delete和 =操作符,完成对象的建立与删除。