C++二義性

 二義性問題

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類的轉換函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章