1. 程式人生 > >有點意思的C/C++問題及解答:6-10

有點意思的C/C++問題及解答:6-10

問題 6:非C++內建型別A 和B,在哪幾種情況下B 能隱式轉化為A? 

(1)class A { ...... };  class B : public A { ……} ;  // B 公有繼承自A,可以是間接繼承的

(2)class A { ...... }; class B {  operator A() { return A::A(); } ......};  // B 實現了到A 的型別轉化
(3)class A { A( const B& ); } ; // A 實現了non-explicit 的引數為B(可以有其他帶預設值的引數)建構函式 
提供隱式轉換時,應避免出現二義性。比如上面的幾種方式,如果即定義了(2)又定義了(3),就存在二義性。因為編譯器可以用兩種方式將B轉換為A,一是用A的建構函式,另一種是用B的型別轉換符。最佳實踐:避免二義性最好的辦法是避免編寫互相提供隱式轉換的成對的類。《C++ Primer》。

問題 7:下面這個程式有什麼問題?

[cpp]  view plain copy print ?
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. private:  
  6.     int
     value;  
  7. public:  
  8.     A(int n) { value = n; }  
  9.     A(A other){ value = other.value; }  
  10.     void Print(){ cout << value << endl; }  
  11. };  
  12. int main()  
  13. {  
  14.     A a = 10;  
  15.     A b = a;  
  16.     b.Print();  
  17.     return 0;  
  18. }  

編譯錯誤, A(A other){}  這個複製建構函式定義有誤,該函式的形參只能是本類型別物件的引用,常用const修飾《C++ Primer》。應該改成這樣 A(const A&other) { value = other.value; }。複製建構函式可以用於:
(1)根據另一個同類型的物件顯示或隱式初始化一個物件
string s = "hello"; 
首先會呼叫接受一個C風格字串形參的string建構函式,建立臨時物件,然後呼叫複製建構函式,將s初始化為那個臨時物件的副本。
(2)複製一個物件,將它作為實參傳遞給一個函式
void foo(string s);
(3)從函式返回複製一個物件
string foo();
(4)初始化容器中的元素。
vector<string> svec(5); 
首先呼叫string預設建構函式建立一個臨時值,然後將這個臨時值複製給svec的每一個元素,即呼叫string複製建構函式。
(5)根據元素初始化列表初始化陣列元素
比如下面這個函式,初始化陣列a 時呼叫的是複製建構函式。而初始化陣列b時呼叫的是預設建構函式。而初始化陣列c時,由於它有4個元素,而初始化列表只有2個,因此前兩個元素的初始化呼叫複製建構函式,後兩個元素的初始化呼叫預設建構函式。

[cpp]  view plain copy print ?
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     A()  { value=1;cout<<"A Default Constructor"<<endl;}  
  7.     A(int v) {value=v;cout<<"A Copy Constructor"<<endl;}  
  8.     int value;  
  9. };  
  10. int main()  
  11. {  
  12.     A a[2]={1,2};  
  13.         A b[2];  
  14.         A c[4]={1,2};  
  15.     return 0;  
  16. }  

問題8:建構函式、靜態函式、行內函數可不可以是虛擬函式?

建構函式和靜態函式不可以、行內函數可以
建構函式不可以為虛擬函式:虛擬函式的呼叫是通過一個虛指標來實現的,而這個虛指標是在構造過程中設定的。
靜態函式不可為虛擬函式:靜態函式的呼叫是不需要例項的,而虛擬函式需要通過從一個例項中獲取虛指標,進而獲取函式的地址,從而實現動態繫結。
行內函數可以:內聯是在編譯階段用程式碼換呼叫,而虛擬函式是在執行期動態繫結。雖然可以,但是一般不會這樣做,代價太大了。

問題9:解構函式必須是虛擬函式嗎?

答案是並不是必須的。如果類被設計成能被繼承,解構函式必須是虛擬函式。否則可以不為虛擬函式。解構函式寫成虛擬函式主要是為了在實現多型時不造成記憶體洩露。比如下面這段程式,B的解構函式不會被呼叫,如果將A的解構函式寫成虛擬函式,則B的解構函式可以被呼叫。

[cpp]  view plain copy print ?
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     A() {cout<<"A Constructor\n";}  
  7.     ~A() {cout<<"A Destructor\n";}  
  8. };  
  9. class B:public A  
  10. {  
  11. public:  
  12.     B() { cout<<"B Constructor\n";}  
  13.     ~B() { cout<<"B Destructor\n";}  
  14. };  
  15.    
  16. int main()  
  17. {  
  18.         A *pa=new B();  
  19.     delete pa;  
  20.     return 0;  
  21. }  
問題10:下面這個程式的輸出是什麼? [cpp]  view plain copy print ?
  1. #include <iostream>  
  2. using namespace std;  
  3. struct Point3D  
  4. {  
  5.     int x;  
  6.     int y;  
  7.     int z;  
  8. };  
  9.    
  10. int main()  
  11. {  
  12.     Point3D* pPoint = NULL;  
  13.     int offset = (int)(&(pPoint->z));  
  14.     cout<<offset<<endl;  
  15.     return 0;  
  16. }  
這個程式輸出為8,&(pPoint->z)取成員變數z的地址,並不需要訪問記憶體,所以只要將pPoint指向的地址加上變數z在類中偏移量,結果為8。

如果是&(pPoint->x)和&(pPoint->y),則結果為0和4。

如果pPoint不為空,例如 Point3D* pPoint = new Point3D;  三個成員變數的地址依次相差4。