9.3 拷贝初始化构造函数
<class_name,,>class_name(class_name&)
{
func_body;
}
拷贝初始化构造函数为利用一个已存在的对象来创建另一个对象提供了接口。例如:
Person per1("张三 ",21,'m'),per2(per1);
其中,对象 per2 就是利用对象 per1 来初始化的。因此这两个对象具有其值 完全相同 的数据成员。
注意:前边并没有为 Person 类定义拷贝初始化构造函数,创建 per2 时调用的是什么呢?是缺省的拷贝初始化构造函数。
若类中没有显式地定义拷贝初始化构造函数,则系统将自动生成一个函数体为空的缺省的拷贝初始化构造函数。
然而,对于本例而言,利用缺省的拷贝初始化构造函数却会带来一个严重和问题:当对象 per1 生存期结束时,其析构函数会释放对象中数据成员 pName 所指的动态内存并释放整个对象所占的内存。而当 per2 消亡时,析构函数在释放整个对象所占内存前首先要释放 per2.pName 所指的动态内存,而这一块内存已经在撤消 per1 时释放掉了。这将导致一个“运行时间错误( Run-Time Error)”。
对于那些涉及到动态内存分配的类,必须显式地定义拷贝初始化构造函数 。
从拷贝初始化构造函数的一般形式可以看出,该函数是不允许重载的,因此显式定义的拷贝初始化构造函数将覆盖缺省的拷贝初始化构造函数。
// 在 PERSON.H 中添加公有的成员函数原型:
Person(Person&);
// 在 PERSON.CPP 中添加函数定义:
Person,,Person(Person& per)
{
pName = new char[strlen(per.pName) + 1];
strcpy(pName,per.pName);
uAge = per.uAge;
uSex = per.uSex;
}
经过这样的定义,上述 per2 在创建时将为自身的 pName 申请一块动态内存,并将 per1 中的 pName 所指的字符串复制到该动态内存中。当任一对象消亡时,释放的均为自身的动态内存,从而防止了相应的运行时间错误发生。
9.4 构造函数与对象成员结构变量可以用作另一个结构的成员。同理,对象也可以作为另一个类的数据成员。例:
#include "person.h"
class Family { // 三口之家类
private:
Person Father;
Person Mother;
Person Child;
//…
};
为了初始化类中的对象成员,类中应当定义具有以下一般形式的构造函数:
class_name(args),c1(arg1),c2(arg2)…c n(argn)
{
func_body;
}
其中:各 ci 分别为类中的对象成员名; args 为多个形式参数;
而各 argi 则为 args 的子集。
例:
Family(Person pa,Person ma,Person kid),
Father(pa),Mother(ma),Child(kid) {}
这里,冒号后边的各项就分别调用了 Person 类中的拷贝初始化构造函数。因此,显式定义 Person 类中的拷贝初始化构造函数就显得更为重要。
第 10章 继承和派生类
10.1 继承
OOP 技术的继承是对生物学中分类概念的模拟,如下图所示。
在 C++ 语言中,称一个类继承另一个类的过程为派生一个类。
派生出的类叫做派生类或子类,被继承的类叫做基类或父类。
昆虫无翅 有翅蛾 苍蝇 蝴蝶人工 农 商 学 兵人黄种人 白种人 黑种人,..
OOP 技术支持单一继承和多重继承。单一继承是指一个类从另 一个 类中派生而来;而多重继承则指一个类拥有 多个 父类。
上述的各类均属于单一继承,下图所示则是一个多重继承的例子。
硬件 软件电脑
10.1.1 单一继承单一继承具有以下的一般形式:
class derivative,<access> base
{
<private:>
pri_members;
public:
pub_members;
<protected:
pro_members>
};
其中,derivative 和 base 分别为派生类和基类的类名;可选的 access 是访问控制字,它或者为 private(可以省略),或者为 public,分别表示子类从父类公有派生和私有派生。
10.1.1.1 公有派生当类 D 是从类 B 公有派生而来时,类 B 中所有成员的访问权限在类 D 中保持不变。也就是说,类 B 中的私有成员在类 D
中仍为私有的,类 B 中公有成员在类 D 中仍为公有的。
应当说明的是,这里所讲的访问权限是相对类 D 的“外部”
而言的。即,若为公有派生,则应用程序可以通过类 D 的对象直接访问它从类 B 继承来和公有成员。然而,对类 B 而言,
类 D 也是它的“外部”。即,在类 D 中,仍然不能直接访问类 B 里的私有成员。
// DERICLS.H
#if !defined _DERICLS_H_
#define _DERICLS_H_
#include <iostream.h>
class X {
int x;
public:
X(int a = 0),x(a) {}
void SetX(int a) { x = a; }
int GetX() { return x; }
void Show() { cout << x; }
};
class Y,public X {
int y;
public:
Y(int a = 0),y(a) {}
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << GetX() << '\t' << y; }
};
#endif
注意派生类 Y 中的公有成员函数 Show( ) 的函数体,这里不能写成:
cout << x << '\t' << y;
这是因为,虽然 Y 是 X 的派生类,但它仍属 X 的“外部”。
// TESTDERI.CPP
#include "dericls.h"
void main()
{
X aX(3);
Y aY(5);
aX.Show(); // 输出 3
cout << endl;
aY.SetX(8);
aY.Show() // 输出 8 5
cout << endl;
}
10.1.1.2 私有派生当类 D 是从类 B 私有派生而来时,类 B 中 所有 成员的访问权限在类 D 均变为私有的。也就是说,类 D 中所有从类 B 继承来的成员在类 D 的对象中均为不可访问的。
为此,在类 D 中必须为外部访问它从类 B 继承来的数据成员提供公有的成员函数。
一般而言,类中重要的数据成员之访问权限通常均为私有的,
并在类中定义一些公有的成员函数以便为外部提供访问这些私有成员的接口。若采用私有派生,则由于这些原为公有的接口在派生类中变为私有的,因此派生类中就得为外部定义访问基类中数据成员的公有成员函数。然而,基类的私有成员在派生类中也不能直接访问,因此,派生类中新定义的公有成员函数就只有通过调用基类的公有成员函数来访问它们。这样一来,
在派生类的外部实际上要经过两次函数调用才能访问到派生类从基类继承来的私有成员。若派生的层次深一些,则函数调用的次数将更多。因此,私有派生在现实中极少使用。
例如,将上例的类 Y 改为:
class Y,private X {
int y;
public:
Y(int a = 0),y(a) {}
void SetBX(int a) { SetX(a) }
int GetBX() { return GetX(); }
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << GetBX() << '\t' << y; }
};
这里,成员函数 SetBX( ) 和 GetBX( ) 就是类 Y 为外部访问它从基类 X 那里继承来的数据成员 x 的公有接口。
经过这样的修改,TEST.CPP 中的语句
aY.SetX(8);
就变成一条非法语句,因为它企图调用对象 aY 中的 私有 成员函数 SetX()。若将该语句改为:
aY.SetBX(8);
则程序的输出不变。
由于在私有派生时,派生类通常必须为基类的大多数公有成员函数另外提供一些接口,这不仅使得派生类更加庞大,而且更是大大降低了程序的运行效率。
10.1.1.3 保护的成员类中访问属性为 protected 的成员叫做保护的成员。保护的成员具有双重性质:对于其派生类而言为公有的;而对于外部程序而言则为私有的。这样的性质使得类成员既方便了派生类的访问,又阻止了外部的随意访问。例如,若将 DERICLS.H 中类 X 的 x 成员说明成保护的,则类 Y 中的 Show() 函数将可以写的便加简洁,并且运行效率也得到提高:
class X { class Y,public X {
protected,//...
int x; void Show() {
//… cout << x << '\t' << y;
}; }
};
10.1.1.4 抽象类与保护的成员函数所谓 抽象类 ( Abstract Class)是指不能生成实例(对象)的类。抽象类只能用来派生子类。
有多种因素可以使一个类变为抽象类。
保护的构造函数就是产生抽象类的因素之一。当一个类中包含有保护的构造函数时,由于外部无法访问这样的构造函数,因而也就无法创建这种类的对象。然而,对于其派生类而言,则是可以访问这种函数的。因此派生类可以调用它(们)来为自身所继承来的基类部分分配内存。
保护的析构函数也是产生抽象类的一个因素。当一个类中包含有保护的析构函数时,由于外部无法调用该函数来撤销对象,
从而使得对象永久地占有存储空间。这种现象是不允许出现的。
因此系统压根儿就不允许这样的对象生成。
10.1.2 多重继承
class derivative,<access> base1,…,
<access> basen
{
<private:>
pri_members;
public:
pub_members;
<protected:
pro_members;>
};
注意:说明中每个基类的访问权限仅修饰该基类本身。各个基类的访问权限必须单独列出,若某个基类无访问权限关键字,则缺省为派生类从该基类私有派生。
// MULTDERI.H
#if !defined _MULTDERI_H_
#define _MULTDERI_H_
#include <iostream.h>
class X {
protected:
int x;
public:
X(int a = 0),x(a) {}
void SetX(int a) { x = a; }
int GetX() { return x; }
void Show() { cout << x; }
};
class Y {
protected:
int y;
public:
Y(int a = 0),y(a) {}
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << y; }
};
class Z,public X,Y {
int z;
public:
Z(int a = 0),z(a) {}
void SetZ(int a) { z = a; }
int GetZ() { return z; }
void Show()
{
cout << x << '\t' << y << '\t' << z << endl;
}
void SetBY(int a) { SetY(a); }
int GetBY() { return GetY(); }
};
#endif
10.2 初始化基类成员
<derivative,,> derivative(args),base1(arg1),
…,basen(arg n)
{
func_body;
}
例:在 NULTDERI.H 文件所定义的类 Z 中添加一个构造函数:
Z(int a,int b,int c),X(a),Y(b),z(c) {}
该函数等效于:
Z(int a,int b,int c)
{
x= a; y = b; z = c;
}
注意:在初始化基类成员时,必须保证基类中存在相应的构造函数。
以这里定义的 Z 类构造函数为例,
其基类 X 中必须定义有 X(int),Y
中必须定义有 Y(int) 构造函数。
// TESTMULT.CPP
#include "multderi.h"
void main()
{
Z aZ(3,4,5);
aZ.Show(); // 输出 3 4 5
aZ.SetX(20);
aZ.SetBY(30);
aZ.SetZ(40);
aZ.Show(); // 输出 20 30 40
cout << endl;
}
10.3 二义性、支配规则和赋值兼容规则
10.3.1 二义性由于在派生的过程中会不断地引入新的成员、在多重继承的过程时派生类会继承多个基类的成员,因而难免在派生类中出现成员重名的现象。 如果在对对象的一次访问中所涉及的成员不止一个,则称这种访问是有二义性的 。例如,在类 Z 中就存在
3 个 Show() 函数,因此上述的 aZ.Show(); 语句就存在二义性。
至于该语句为何能够顺利地通过编译并正确地执行,将在
10.3.2 中介绍。
利用成员名限定可以消除二义性 。例:
aZ.X,,Show(); // 调用基类 X 中的 Show() 函数利用成员名限定来消除二义性不仅可以用在对对象的访问中,
也可以用在派生类成员函数的定义中。例如,可以将类 Z 中的 Show() 函数定义成:
void Show()
{
X,,Show();
cout << '\t';
Y,,Show();
cout << '\t';
cout << z;
}
10.3.2 支配规则支配规则是指派生类中的成员支配基类中的同名成员 。也就是说,如果存在二义性,则访问的成员将从派生类开始向基类上朔。
由于支配规则在起作用,所以 aZ.Show(); 就能够正确执行。
因为这时调用的是类 Z 中所定义的 Show() 函数。
10.3.3 继承和对象成员在许多场合下,一个新类既可以通过继承某些已有类来生成,
又可以完全重新定义一个类,而将已有类的对象作为其数据成员。例:
class Z {
private:
int z;
X cx;
Y cy;
//…
};
然而,由于存在二义性,有时通过继承将是行不通的。比如,
定义一个三口之家:
class Family,public Person,public Person,public Person {
//…
public:
Family(char* fn,char* mn,char* cn)
{
Name(fn); // 无法编译
Name(mn);
Name(cn);
}
//…
};
利用对象成员则可以解决该问题:
class Family {
Person Father;
Person Mother;
Person Child;
char *Address;
public:
Famliy() { Address = 0; }
Family(char* fn,char* nm,char* cn),Father(fn),
Mother(mn),Child(cn),Address(0) {}
//…
};
10.3.4 赋值兼容规则赋值兼容规则是指在公有派生情况下,一个派生类的对象可以用在其基类可以使用的场合。例如,设有类定义:
class X { //..,};
class Y,public X { //… };
X x;
Y y;
规则 1 派生类的对象可以向基类对象赋值:
x = y;
注意:这时,对象 y 中仅有从其基类 X 继承来的数据成员被赋值。
规则 2 派生类的对象可以初始化基类的引用:
X rx = y;
规则 3 派生类对象的地址可以赋给基类类型的指针:
X *px = &y;
注意:在这两种情况下,通过 rx 或 px 仅能访问派生类从基类继承来的成员。若欲访问派生类中新增的成员,则必须对它们进行强制类型转换:
(Y*)px->SetY(5); // 设 SetY() 为类 Y 新增的公有成员函数
(Y&)rx.SetY(5);
习题:
22,23,24
<class_name,,>class_name(class_name&)
{
func_body;
}
拷贝初始化构造函数为利用一个已存在的对象来创建另一个对象提供了接口。例如:
Person per1("张三 ",21,'m'),per2(per1);
其中,对象 per2 就是利用对象 per1 来初始化的。因此这两个对象具有其值 完全相同 的数据成员。
注意:前边并没有为 Person 类定义拷贝初始化构造函数,创建 per2 时调用的是什么呢?是缺省的拷贝初始化构造函数。
若类中没有显式地定义拷贝初始化构造函数,则系统将自动生成一个函数体为空的缺省的拷贝初始化构造函数。
然而,对于本例而言,利用缺省的拷贝初始化构造函数却会带来一个严重和问题:当对象 per1 生存期结束时,其析构函数会释放对象中数据成员 pName 所指的动态内存并释放整个对象所占的内存。而当 per2 消亡时,析构函数在释放整个对象所占内存前首先要释放 per2.pName 所指的动态内存,而这一块内存已经在撤消 per1 时释放掉了。这将导致一个“运行时间错误( Run-Time Error)”。
对于那些涉及到动态内存分配的类,必须显式地定义拷贝初始化构造函数 。
从拷贝初始化构造函数的一般形式可以看出,该函数是不允许重载的,因此显式定义的拷贝初始化构造函数将覆盖缺省的拷贝初始化构造函数。
// 在 PERSON.H 中添加公有的成员函数原型:
Person(Person&);
// 在 PERSON.CPP 中添加函数定义:
Person,,Person(Person& per)
{
pName = new char[strlen(per.pName) + 1];
strcpy(pName,per.pName);
uAge = per.uAge;
uSex = per.uSex;
}
经过这样的定义,上述 per2 在创建时将为自身的 pName 申请一块动态内存,并将 per1 中的 pName 所指的字符串复制到该动态内存中。当任一对象消亡时,释放的均为自身的动态内存,从而防止了相应的运行时间错误发生。
9.4 构造函数与对象成员结构变量可以用作另一个结构的成员。同理,对象也可以作为另一个类的数据成员。例:
#include "person.h"
class Family { // 三口之家类
private:
Person Father;
Person Mother;
Person Child;
//…
};
为了初始化类中的对象成员,类中应当定义具有以下一般形式的构造函数:
class_name(args),c1(arg1),c2(arg2)…c n(argn)
{
func_body;
}
其中:各 ci 分别为类中的对象成员名; args 为多个形式参数;
而各 argi 则为 args 的子集。
例:
Family(Person pa,Person ma,Person kid),
Father(pa),Mother(ma),Child(kid) {}
这里,冒号后边的各项就分别调用了 Person 类中的拷贝初始化构造函数。因此,显式定义 Person 类中的拷贝初始化构造函数就显得更为重要。
第 10章 继承和派生类
10.1 继承
OOP 技术的继承是对生物学中分类概念的模拟,如下图所示。
在 C++ 语言中,称一个类继承另一个类的过程为派生一个类。
派生出的类叫做派生类或子类,被继承的类叫做基类或父类。
昆虫无翅 有翅蛾 苍蝇 蝴蝶人工 农 商 学 兵人黄种人 白种人 黑种人,..
OOP 技术支持单一继承和多重继承。单一继承是指一个类从另 一个 类中派生而来;而多重继承则指一个类拥有 多个 父类。
上述的各类均属于单一继承,下图所示则是一个多重继承的例子。
硬件 软件电脑
10.1.1 单一继承单一继承具有以下的一般形式:
class derivative,<access> base
{
<private:>
pri_members;
public:
pub_members;
<protected:
pro_members>
};
其中,derivative 和 base 分别为派生类和基类的类名;可选的 access 是访问控制字,它或者为 private(可以省略),或者为 public,分别表示子类从父类公有派生和私有派生。
10.1.1.1 公有派生当类 D 是从类 B 公有派生而来时,类 B 中所有成员的访问权限在类 D 中保持不变。也就是说,类 B 中的私有成员在类 D
中仍为私有的,类 B 中公有成员在类 D 中仍为公有的。
应当说明的是,这里所讲的访问权限是相对类 D 的“外部”
而言的。即,若为公有派生,则应用程序可以通过类 D 的对象直接访问它从类 B 继承来和公有成员。然而,对类 B 而言,
类 D 也是它的“外部”。即,在类 D 中,仍然不能直接访问类 B 里的私有成员。
// DERICLS.H
#if !defined _DERICLS_H_
#define _DERICLS_H_
#include <iostream.h>
class X {
int x;
public:
X(int a = 0),x(a) {}
void SetX(int a) { x = a; }
int GetX() { return x; }
void Show() { cout << x; }
};
class Y,public X {
int y;
public:
Y(int a = 0),y(a) {}
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << GetX() << '\t' << y; }
};
#endif
注意派生类 Y 中的公有成员函数 Show( ) 的函数体,这里不能写成:
cout << x << '\t' << y;
这是因为,虽然 Y 是 X 的派生类,但它仍属 X 的“外部”。
// TESTDERI.CPP
#include "dericls.h"
void main()
{
X aX(3);
Y aY(5);
aX.Show(); // 输出 3
cout << endl;
aY.SetX(8);
aY.Show() // 输出 8 5
cout << endl;
}
10.1.1.2 私有派生当类 D 是从类 B 私有派生而来时,类 B 中 所有 成员的访问权限在类 D 均变为私有的。也就是说,类 D 中所有从类 B 继承来的成员在类 D 的对象中均为不可访问的。
为此,在类 D 中必须为外部访问它从类 B 继承来的数据成员提供公有的成员函数。
一般而言,类中重要的数据成员之访问权限通常均为私有的,
并在类中定义一些公有的成员函数以便为外部提供访问这些私有成员的接口。若采用私有派生,则由于这些原为公有的接口在派生类中变为私有的,因此派生类中就得为外部定义访问基类中数据成员的公有成员函数。然而,基类的私有成员在派生类中也不能直接访问,因此,派生类中新定义的公有成员函数就只有通过调用基类的公有成员函数来访问它们。这样一来,
在派生类的外部实际上要经过两次函数调用才能访问到派生类从基类继承来的私有成员。若派生的层次深一些,则函数调用的次数将更多。因此,私有派生在现实中极少使用。
例如,将上例的类 Y 改为:
class Y,private X {
int y;
public:
Y(int a = 0),y(a) {}
void SetBX(int a) { SetX(a) }
int GetBX() { return GetX(); }
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << GetBX() << '\t' << y; }
};
这里,成员函数 SetBX( ) 和 GetBX( ) 就是类 Y 为外部访问它从基类 X 那里继承来的数据成员 x 的公有接口。
经过这样的修改,TEST.CPP 中的语句
aY.SetX(8);
就变成一条非法语句,因为它企图调用对象 aY 中的 私有 成员函数 SetX()。若将该语句改为:
aY.SetBX(8);
则程序的输出不变。
由于在私有派生时,派生类通常必须为基类的大多数公有成员函数另外提供一些接口,这不仅使得派生类更加庞大,而且更是大大降低了程序的运行效率。
10.1.1.3 保护的成员类中访问属性为 protected 的成员叫做保护的成员。保护的成员具有双重性质:对于其派生类而言为公有的;而对于外部程序而言则为私有的。这样的性质使得类成员既方便了派生类的访问,又阻止了外部的随意访问。例如,若将 DERICLS.H 中类 X 的 x 成员说明成保护的,则类 Y 中的 Show() 函数将可以写的便加简洁,并且运行效率也得到提高:
class X { class Y,public X {
protected,//...
int x; void Show() {
//… cout << x << '\t' << y;
}; }
};
10.1.1.4 抽象类与保护的成员函数所谓 抽象类 ( Abstract Class)是指不能生成实例(对象)的类。抽象类只能用来派生子类。
有多种因素可以使一个类变为抽象类。
保护的构造函数就是产生抽象类的因素之一。当一个类中包含有保护的构造函数时,由于外部无法访问这样的构造函数,因而也就无法创建这种类的对象。然而,对于其派生类而言,则是可以访问这种函数的。因此派生类可以调用它(们)来为自身所继承来的基类部分分配内存。
保护的析构函数也是产生抽象类的一个因素。当一个类中包含有保护的析构函数时,由于外部无法调用该函数来撤销对象,
从而使得对象永久地占有存储空间。这种现象是不允许出现的。
因此系统压根儿就不允许这样的对象生成。
10.1.2 多重继承
class derivative,<access> base1,…,
<access> basen
{
<private:>
pri_members;
public:
pub_members;
<protected:
pro_members;>
};
注意:说明中每个基类的访问权限仅修饰该基类本身。各个基类的访问权限必须单独列出,若某个基类无访问权限关键字,则缺省为派生类从该基类私有派生。
// MULTDERI.H
#if !defined _MULTDERI_H_
#define _MULTDERI_H_
#include <iostream.h>
class X {
protected:
int x;
public:
X(int a = 0),x(a) {}
void SetX(int a) { x = a; }
int GetX() { return x; }
void Show() { cout << x; }
};
class Y {
protected:
int y;
public:
Y(int a = 0),y(a) {}
void SetY(int a) { y = a; }
int GetY() { return y; }
void Show() { cout << y; }
};
class Z,public X,Y {
int z;
public:
Z(int a = 0),z(a) {}
void SetZ(int a) { z = a; }
int GetZ() { return z; }
void Show()
{
cout << x << '\t' << y << '\t' << z << endl;
}
void SetBY(int a) { SetY(a); }
int GetBY() { return GetY(); }
};
#endif
10.2 初始化基类成员
<derivative,,> derivative(args),base1(arg1),
…,basen(arg n)
{
func_body;
}
例:在 NULTDERI.H 文件所定义的类 Z 中添加一个构造函数:
Z(int a,int b,int c),X(a),Y(b),z(c) {}
该函数等效于:
Z(int a,int b,int c)
{
x= a; y = b; z = c;
}
注意:在初始化基类成员时,必须保证基类中存在相应的构造函数。
以这里定义的 Z 类构造函数为例,
其基类 X 中必须定义有 X(int),Y
中必须定义有 Y(int) 构造函数。
// TESTMULT.CPP
#include "multderi.h"
void main()
{
Z aZ(3,4,5);
aZ.Show(); // 输出 3 4 5
aZ.SetX(20);
aZ.SetBY(30);
aZ.SetZ(40);
aZ.Show(); // 输出 20 30 40
cout << endl;
}
10.3 二义性、支配规则和赋值兼容规则
10.3.1 二义性由于在派生的过程中会不断地引入新的成员、在多重继承的过程时派生类会继承多个基类的成员,因而难免在派生类中出现成员重名的现象。 如果在对对象的一次访问中所涉及的成员不止一个,则称这种访问是有二义性的 。例如,在类 Z 中就存在
3 个 Show() 函数,因此上述的 aZ.Show(); 语句就存在二义性。
至于该语句为何能够顺利地通过编译并正确地执行,将在
10.3.2 中介绍。
利用成员名限定可以消除二义性 。例:
aZ.X,,Show(); // 调用基类 X 中的 Show() 函数利用成员名限定来消除二义性不仅可以用在对对象的访问中,
也可以用在派生类成员函数的定义中。例如,可以将类 Z 中的 Show() 函数定义成:
void Show()
{
X,,Show();
cout << '\t';
Y,,Show();
cout << '\t';
cout << z;
}
10.3.2 支配规则支配规则是指派生类中的成员支配基类中的同名成员 。也就是说,如果存在二义性,则访问的成员将从派生类开始向基类上朔。
由于支配规则在起作用,所以 aZ.Show(); 就能够正确执行。
因为这时调用的是类 Z 中所定义的 Show() 函数。
10.3.3 继承和对象成员在许多场合下,一个新类既可以通过继承某些已有类来生成,
又可以完全重新定义一个类,而将已有类的对象作为其数据成员。例:
class Z {
private:
int z;
X cx;
Y cy;
//…
};
然而,由于存在二义性,有时通过继承将是行不通的。比如,
定义一个三口之家:
class Family,public Person,public Person,public Person {
//…
public:
Family(char* fn,char* mn,char* cn)
{
Name(fn); // 无法编译
Name(mn);
Name(cn);
}
//…
};
利用对象成员则可以解决该问题:
class Family {
Person Father;
Person Mother;
Person Child;
char *Address;
public:
Famliy() { Address = 0; }
Family(char* fn,char* nm,char* cn),Father(fn),
Mother(mn),Child(cn),Address(0) {}
//…
};
10.3.4 赋值兼容规则赋值兼容规则是指在公有派生情况下,一个派生类的对象可以用在其基类可以使用的场合。例如,设有类定义:
class X { //..,};
class Y,public X { //… };
X x;
Y y;
规则 1 派生类的对象可以向基类对象赋值:
x = y;
注意:这时,对象 y 中仅有从其基类 X 继承来的数据成员被赋值。
规则 2 派生类的对象可以初始化基类的引用:
X rx = y;
规则 3 派生类对象的地址可以赋给基类类型的指针:
X *px = &y;
注意:在这两种情况下,通过 rx 或 px 仅能访问派生类从基类继承来的成员。若欲访问派生类中新增的成员,则必须对它们进行强制类型转换:
(Y*)px->SetY(5); // 设 SetY() 为类 Y 新增的公有成员函数
(Y&)rx.SetY(5);
习题:
22,23,24