C++ 二义性问题

来源:互联网 发布:软件合作开发合同范本 编辑:程序博客网 时间:2024/05/02 04:21

 二义性问题

1.在继承时,基类之间、或基类与派生类之间发生成员同名时,将出现对成员访问的不确定性——同名二义性。

2.当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生另一种不确定性——路径二义性。


同名二义性

同名隐藏规则——解决同名二义的方法

             当派生类与基类有同名成员时,派生类中的成员将屏蔽基类中的同名成员。

                         若未特别指明,则通过派生类对象使用的都是派生类中的同名成员;

                         如要通过派生类对象访问基类中被屏蔽的同名成员,应使用基类名限定(::)。

多继承同名隐藏举例

[cpp] view plain copy
  1. //多继承同名隐藏举例  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B1//声明基类B1  
  5. {   
  6. public:  
  7.     int nV;  
  8.     void fun() {cout<<"Member of B1"<<endl;}  
  9. };  
  10. class B2//声明基类B2  
  11. {   
  12. public:  
  13.     int nV;  
  14.     void fun() {cout<<"Member of B2"<<endl;}  
  15. };  
  16. class D1: public B1, public B2  
  17. {   
  18. public:  
  19.     int nV;//同名数据成员  
  20.     void fun(){cout<<"Member of D1"<<endl;} //同名函数成员  
  21. };  
  22.   
  23.   
  24. void main()  
  25. {  
  26.     D1 d1;  
  27.     //用“对象名.成员名”访问子类成员。  
  28.     d1.nV=1;  
  29.     d1.fun();  
  30.     //加“作用域分辨符标识”, 可访问基类被屏蔽的成员  
  31.     d1.B1::nV=2;  
  32.     d1.B1::fun();  
  33.     d1.B2::nV=3;  
  34.     d1.B2::fun();  
  35. }  



同名二义性的解决方法

解决方法一:用类名来限定c1.A::f() 或c1.B::f()

解决方法二:同名覆盖,再造接口在C 中再声明一个同名成员函数f(),该函数根据需要调用A::f() 或B::f()


路径二义性





为了解决路径二义性问题,引入虚基类

–用于有共同基类的多继承场合(多层共祖)

声明
–以virtual修饰说明共同的直接基类
例:class B1: virtual public B
作用
–用来解决多继承时可能发生的对同一基类继承多次和多层而产生的二义性问题.
–为最远的派生类提供唯一的基类成员,而不重复产生个副本。
注意:
–在第一级继承时就要将共同基类设计为虚基类。

虚基类举例
class B { public: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtualpublic B { private: int b2;};
class C: public B1, public B2{ private: float d;};
在子类对象中,最远基类成分是唯一的。于是下面的访问是正确的:
C cobj;
cobj.b;


使用最远基类成员原则

[cpp] view plain copy
  1. //使用最远基类成员原则  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B0//声明基类B0  
  5. {   
  6. public://外部接口  
  7.     int nV;  
  8.     void fun(){cout<<"Member of B0"<<endl;}  
  9. };  
  10. class B1: virtual public B0 //B0为虚基类,派生B1类  
  11. {   
  12.     public://新增外部接口  
  13.     int nV1;  
  14. };  
  15. class B2: virtual public B0 //B0为虚基类,派生B2类  
  16. {   
  17.     public://新增外部接口  
  18.     int nV2;  
  19. };  
  20.   
  21. class D1: public B1, public B2//派生类D1声明  
  22. {  
  23.     public://新增外部接口  
  24.     int nVd;  
  25.     void fund(){cout<<"Member of D1"<<endl;}  
  26. };  
  27. void main()//程序主函数  
  28. {  
  29.     D1 d1;//声明D1类对象d1  
  30.     d1.nV=2;//使用最远基类成员  
  31.     d1.fun();  
  32. }  
运行结果:

Member of B0
Press any key to continue


有虚基类时的构造函数的调用次序:

无论虚基类与产生对象的派生类相隔多远,首先调用虚基类的构造函数;

然后按继承次序调用直接基类的构造函数;

如果有包含的对象,再按声明次序调用所包含对象类的构造函数;

最后才是普通类型数据成员的初始化。


有虚基类时的构造函数举例

[cpp] view plain copy
  1. //有虚基类时的构造函数举例  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B0//声明基类B0  
  5. {   
  6.     public://外部接口  
  7.     B0(int n){ nV=n;cout<<"B0's constructor called \n";}  
  8.     int nV;  
  9.     void fun(){cout<<"Member of B0"<<endl;}  
  10. };  
  11. class B1: virtual public B0  
  12. {   
  13. public:  
  14.     B1(int a) : B0(a) {cout<<"B1's constructor called \n";}  
  15.     int nV1;  
  16. };  
  17. class B2: virtual public B0  
  18. {   
  19. public:  
  20.     B2(int b) : B0(b) {cout<<"B2's constructor called \n";}  
  21.     int nV2;  
  22. };  
  23. class D1: public B1, public B2  
  24. {  
  25. public:  
  26.     D1(int c) : B0(c), B1(c), B2(c),b1(c),b2(c) {cout<<"D1's constructor called \n"; }  
  27.     int nVd;  
  28.     void fund(){cout<<"Member of D1"<<endl;}  
  29. private:  
  30.     B1 b1;  
  31.     B2 b2;  
  32. };  
  33. void main()  
  34. {  
  35.     D1 d1(1);  
  36.     d1.nV=2;  
  37.     d1.fun();  
  38. }  
运行结果:

B0's constructor called
B1's constructor called
B2's constructor called
B0's constructor called
B1's constructor called
B0's constructor called
B2's constructor called
D1's constructor called
Member of B0
Press any key to continue


“二义性”的回顾

凡是编译器访问或调用时有多于一项的合法选择,这种会让编译器举棋不定的代码叫具二义性的代码。
二义性的发生场合:
1.带默认形参值的函数与同名的重载函数相遇时;

[cpp] view plain copy
  1. //带默认形参值的函数与同名的重载函数相遇时  
  2. #include <iostream>  
  3. using namespace std;  
  4. void funct (int i, long =2,float f=3);     //函数原型中默认值可省去形参名  
  5. void funct (int i, long l)    {     l=i+l;    }  
  6.   
  7. void main()  
  8. {  
  9.     funct(2);           //调用void funct(int i,long =2,float f=3);  
  10.     /* 
  11.     funct (2,3)可以匹配3个形参的funct (int i, long=2,float f=3) 
  12.     也可以匹配2个形参的funct(int i,long l)。因此程序在编译时出现提示 
  13.     */  
  14.     //funct(2,3);     // error take place  here,  
  15.     funct(2,3,4);    //调用void funct (int, long, float);       
  16. }  
  17.   
  18. void funct (int i, long l,float f)  
  19. {  
  20.     cout << "funct(int i, long =2,float f=3)"<<endl;  
  21. }  

2.继承时的同名二义;
3.多层共祖的路径二义;
4.形实结合时的类型兼容;
[cpp] view plain copy
  1. //形实结合时的类型兼容;  
  2. #include <iostream>  
  3. using namespace std;  
  4. void fun ( int a){cout << "fun(int)" <<endl;}  
  5. void fun ( char c){cout << "fun(char)" <<endl;}  
  6.   
  7. void main()  
  8. {  
  9.     double d = 100.618;  
  10.     //fun ( d ); // 到底调用哪个函数?  
  11.     //解决:  
  12.     fun( int(d) );   
  13.     fun( char(d) );  
  14.     //或  
  15.     fun( static_cast<int>(d) );  
  16.     fun( static_cast<char>(d) );    
  17. }  
运行结果:

fun(int)
fun(char)
fun(int)
fun(char)
Press any key to continue

5.用户自定义类型的转换时。

class B;

class A 

public: 

A (const B &) {} //构造函数

};

class B 

public: 

operator A ( ) const {} //类型转换函数

};

void fun ( const A &);

B b;fun ( b ); // 到底调用哪个函数?  类型转换函数还是构造函数

解决:1。给A的构造函数前加explicit ,关闭自动转换功能;2。不提供或不直接提供B类的转换函数。

0 0
原创粉丝点击