程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++中幾個值得分析的小問題

C++中幾個值得分析的小問題

編輯:C++入門知識

C++中幾個值得分析的小問題


下面有3個小問題,作為C++ Beginner你一定要知道錯在哪裡了。   1、派生類到基類的引用或指針轉換一定“完美”存在?   一般情況,你很可能會認為:派生類對象的引用或指針轉換為基類對象的引用或指針是一件很正常的事。那要是不一般情況呢?請看下面這個例子:  
class Person
{
public:
    Person(const string& str = "Normal Person") : ID(str) {}
    string ID;                                //作為一般的人身份是“普通人”,作為學生身份是“學生”
};

class Student : private Person
{
public:
    Student(const string& str = "Student") : Person(str) {}
};
 

 

  仍然是以普通人和學生作為例子,學生是private派生自普通人的。ID表示身份狀態(我將它聲明為public的,完全是為了方便,我仍然強烈建議聲明為private)。  
void PrintID(const Person& p)            //打印ID
{
    cout << p.ID << endl;
}

 

我有下面這樣的調用:   Person p; Student s; PrintID(p);            //好的,沒有問題 PrintID(s);            //編譯出錯 好的,我已經注釋出來了。編譯出錯,出錯信息是:error C2243: “類型轉換”: 從“Student *”到“const Person &”的轉換存在,但無法訪問。是不是有點跟你想的不大一樣呢?   分析:   很多時候,我們都默認public繼承,public繼承勾勒出一種is-a的關系,就是派生類是一種基類,從這個語義上理解似乎從派生類轉換為基類的自動的(引用或指針方式)。我這裡說的是private繼承,private繼承勾勒出一種“根據某物實現出”的關系。假設D以private派生於B,意思是D可以根據B對象實現得到。編譯器不會自動將派生類對象轉換為基類對象。   另外一點疑問,你可能注意到了PrintID如果傳入的是Student對象,似乎訪問了私有成員ID(private繼承),是不是這個原因導致編譯錯誤呢?我們將其改為下面這樣:  
void PrintID(const Person& p)            //打印ID
{
    cout << "ID Infor" << endl;
//    cout << p.ID << endl;
}

 

同樣編譯錯誤。所以,要注意:派生類到基類的引用或指針轉換一定“完美”存在,前提是public繼承,不能是private或protected繼承。   說明:還要補充說明一下,在類的繼承層次中public為主,private有時候在設計上可以提供一點“便利”,protected我暫時還不知道有什麼好的用處。       2、關鍵字typename導致的編譯時錯誤。   這個問題,可能很多人都知道,也許像我一樣知道的“不全”。首先需要明確一點是:關鍵字typename與class在聲明template類型參數時完全一樣。我其實要說的是typename泛型(模板)中的使用,它可是大有所為的。   template內出現的名稱如果依賴於某個template參數,稱之為從屬名稱。如果從屬名稱在class內呈嵌套狀,稱之為嵌套從屬名稱。  
template <typename T>
void PrintElement(const T& vec)
{
    T::const_iterator* x;            //const_iterator是個嵌套從屬名稱
...
}

 

我們可能認為聲明了一個指針,指向一個T::const_iterator。實際上,T::const_iterator也可能是個名字為const_iterator的static變量,上面那行代碼就定義了一個乘法。所以,嵌套從屬名稱可能導致解析困難。   C++解析器有個簡單的規則:在template中遇到一個嵌套從屬名稱,它會假設這個名稱不是個類型,這是缺省情況,除非你用關鍵字typename主動告訴編譯器。  
template <typename T>
void PrintElement(const T& vec)
{
    typename T::const_iterator iter;            //這樣聲明就對了
...
}

 

這塊知識,基本每個人都知道。我其實想說的是,使用typename時,有幾個小點注意一下,因為typename的位置不僅在函數內。比如:  
template <typename T>
void Print(typename T::const_iterator iter)        //形參表一定要使用typename

 

下面再舉個復雜一點的例子:    
template <class T>
class Base 
{
public:
    class Nested 
    {
    public:
        Nested(int a) : x(a) {}
    private:
        int x;
    };
};

template<class T>
class Derived : public Base<T>::Nested                    //繼承列表,你不能使用typename
{
public:
    explicit Derived(int a) : Base<T>::Nested(a){}        //構造函數初始化列表,你不能使用typename
};
 

 

  注意,代碼中注釋的部分,就是typename兩個不能使用的地方。我再擴展一下這個問題:  
template<class T>
void func(const typename Base<T>::Nested& test)            //使用typename,很好的
{
    cout << "Very Good" << endl;            
}
我想問的是:Derived<int> d(5);怎麼調用那個函數呢?正確是策略是:

func<int> (d);                //正確的
func(d);                    //錯誤的

 

錯誤的用法,編譯器會提醒你:無法解析T。       3、構造函數、析構函數能否為虛函數?構造函數、析構函數能否調用虛函數?   這個小問題,真是源遠流長了。標准答案是不是都有了呢?我再不耐其煩的說一下。(我沒有把各家說法收集全,就是說下自己的理解)   (1)構造函數、析構函數能否為虛函數?   構造函數不能為虛函數。   回答:有好幾個點可以說說,我說的不全。   1、虛函數的作用主要是為了執行期根據Base指針尋址到“正確”的函數。既然是執行期,必然已經跨過編譯期了,那勢必對象都建立起來了,構造函數是初始化用的,那要構造函數何用。   2、如果我沒記錯的話,構造函數有一個功能是建立vptr,注意不是vtbl(編譯期搞定的),假設是虛函數,誰來建立vptr,此時構造函數應該在vtbl裡待著呢。   3、虛函數是為了派生類對其進行改造的,但構造函數不能被繼承,談何改造。   等等…   析構函數能不能為虛函數?   如果像STL那樣堅決不做派生類,我勸你別把它當虛函數,vptr好歹也是4個字節的存儲呢。如果你要它作為派生類,我勸你一定要設置為虛函數,否則內存洩露會找上你。   (2)構造函數、析構函數能否調用虛函數?   其實這個問題,我一直都沒明白有啥意義。構造函數、析構函數調用虛函數是可以的,可是調用的是所屬類的版本。    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved