再上一篇:第十一章 继承和派生类
上一篇:11.2 初始化基类成员
主页
下一篇:11.4 虚基类
再下一篇:第十二章 类的其它特性
文章列表

11.3 冲突、支配规则和赋值兼容性

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

11.3.1 冲突

若一个公有的派生类是由二个或多个基类所派生,当基类中成员的访问权限为public,且不同基类中的成员具有相同的名字时,出现了重名的情况。这时在派生类使用到基类中的同名的成员时,出现了不唯一性,这种情况称为冲突。

例11.7 有冲突的例子。

#include

class A{

public:

int x;

void Show(){cout <<"x="<

A(int a){x=a;}

A(){ }

};

class B{

public:

int x;

void Show(){cout <<"x="<

B(int a){x=a;}

B(){ }

};

class C:public A,public B{

int y;

public:

void Setx(int a){ x=a;} //D

void Sety(int b){y=b;}

int Gety() {return y;}

};

void main(void)

{

C c1;

c1.Show(); //E

}

上面程序的D行中,派生类C中访问由基类继承来的变量x时,编译器无法确定是要访问属于基类A中的x还是属于基类B中的x。这时出现了编译错误。同样地,在程序的E行中,调用成员函数Show()时,也是无法确定是要调用从类A继承来的成员函数Show(),还是调用从类B中继承来的成员函数Show()。这也导致编译错误。

解决这种冲突的方法有三种:一种方法是使得各基类中的成员名各不相同,显然,这不是好的解决办法;第二种方法是只适合于数据成员,在各个基类中,均把数据成员的访问权限说明为私有的,并在相应的基类中提供成员函数来对这些数据成员进行操作;第三种方法是,使用作用域运算符来限定所访问成员是属于哪一个基类的。作用域运算符的一般格式为:

类名::成员名

其中类名可以是任一基类或派生类的类名,成员名只能是数据成员名(变量名)或成员函数名。

例11.8 用作用域运算符来确定所访问的成员。

#include

class A{

public:

int x;

void Show(){cout <<"x="<

A(int a){x=a;}

A(){ }

};

class B{

public:

int x;

void Show(){cout <<"x="<

B(int a){x=a;}

B(){ }

};

class C:public A,public B{

int y;

public:

void SetAx(int a){ A::x=a;} //对类A中x置值

void SetBx(int a){ B::x=a;} //对类B中x置值

void Sety(int b){y=b;}

int Gety(){return y;}

};

void main(void)

{

C c1;

c1.SetAx(35);

c1.SetBx(100);

c1.Sety(300);

c1.A::Show(); //调用类A中的成员函数

c1.B::Show(); //调用类B中的成员函数

cout << "y="<

}

当把派生类作为基类,又派生出新的派生类时,这种限定作用域的运算符不能嵌套使用,如下的形式的使用方式是不允许的:

ClassName1::ClassName2::...::EleName

即限定作用域的运算符,只能直接限定其成员。

例11.9 多重继承中的冲突。

#include

class A{

public:

int x;

void Show(){cout <<"x="<

};

class B{

public:

int x;

void Show(){cout <<"x="<

};

class C:public A,public B{

int y;

public:

void SetAx(int a){ A::x=a;}

void SetBx(int a){ B::x=a;}

void Sety(int b){y=b;}

int Gety(){return y;}

};

class D:public C{

int z;

public:

void Setz(int a){z=a;}

int Getz() {return z;}

};

void main(void)

{

D d1;

d1.SetAx(35);

d1.SetBx(100);

d1.Sety(300);

d1.C::A::Show(); //E

d1.C::B::Show(); //F

d1.Setz(500);

cout << "y="<

cout <<"z="<

}

在编译时,指出E行和F行语法错。作用域运算符不能连续使用。其解决办法是在类C中增加成员函数:

void ShowA( ){ cout<<”x=”<

void ShowB( ){ cout<<”x=”<

将E行和F行改为:

d1.ShowA();

d1.ShowB();

11.3.2 支配规则

在 C++中,允许派生类中新增加的成员名与其基类的成员名相同,这种同名并不产生冲突。当没有使用作用域运算符时,则派生类中定义的成员名优先于基类中的成员名,这种优先关系称为支配规则。

例11.10 支配规则例。

#include

class A{

public:

int x;

void Show(){cout <<"x="<

};

class B{

public:

int y;

void Show(){cout <<"y="<

};

class C:public A,public B{

public:

int y;

};

void main(void)

{

C c1;

c1.x=100;

c1.y=200; //给派生类中的y赋值

c1.B::y=300; //给基类B中的y赋值

c1.A::Show();

c1.B::Show(); //用作用域运算符限定调用的函数

cout <<"y="<

cout <<"y="<

}

当派生的成员名与基类的成员名同名时,在派生类中或在派生类外要使用基类中的这种成员名时,仍要使用作用域运算符。如上例中,在主函数中使用B类中的变量y时,应写成B::y。

11.3.3 基类与对象成员

任一基类在派生类中只能继承一次,否则,会造成成员名的冲突。如:

class A{

public: float x;

......

};

class B:public A,public A{

...

};

这时在派生类B中包含了二个继承来的成员x,在使用上产生无法区分存取哪一个x,即产生冲突。为了避免这种冲突,C++中规定同一基类继承二次或二次以上都是不允许的。上面定义的派生类B,在语法上是错误的。

若在B类中,确实要有二个类A的成员,则可用类A的二个对象作为类B的成员。如:

class B{

A a1,a2; //或A a[2];

......

};

把一个类作为派生类的基类或把一个类的对象作为一个类的成员,从程序的执行效果上看是相同的,但在使用上是有区别的:在派生类中可直接使用基类的成员(访问权限允许的话),但要使用对象成员的成员时,必须在对象名后加上成员运算符“.”和成员名。

例11.11 基类成员与对象成员在使用上的差别。

#include

class A{

public:

int x;

A(int a=0) { x=a;}

};

class B{

public:

int y;

B(int a=0) { y=a;}

};

class C:public A{

int z;

B b1;

public:

C(int a,int b,int m):A(a),b1(b)

{ z=m; }

void Show( )

{

cout<<"x="<

cout<<"y="<

cout<<"x="<

}

};

void main(void)

{

C c1(100,200,300);

c1.Show();

}

11.3.4 赋值兼容规则

在同类型的对象之间可以相互赋值,那么派生类的对象与其基类的对象之间能否互相赋值呢?这就是要讨论的赋值兼容规则。简单地说,对于公有派生类来说,可以将派生类的对象赋给其基类的对象,反之是不允许的。如设有说明:

class A{

public:

int x; ......

};

class B{

public:

int y;......

};

class C:public A,public B{

public:

int y; ......

};

C c1,c2,c3;

A a1, *pa1;

B b1, *pb1;

赋值兼容与限制可归结为以下四点:

1、派生类的对象可以赋给基类对象,系统是将派生类对象中从对应基类中继承来的成员赋给基类对象。如:

a1= c1;

将c1中从类A中继承来的对应成员(如x等)分别赋给a1的对应成员。

b1= c1;

将c1中从类B中继承来的对应成员(如y等)分别赋给b1的对应成员。

2、不能将基类的对象赋给派生类对象。如:

c2= a1; c3= b1;

这是不允许的。

3、可以将一个派生类对象的地址赋给基类的指针变量。如:

pa1 =&c2; pb1=&c3;

4、派生类对象可以初始化基类的引用。如:

B &rb=c1;

注意:在后二种情况下,使用基类的指针或引用时,只能访问从相应基类中继承来的成员,而不允许访问其它基类的成员或在派生类中增加的成员。