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)構造函數、析構函數能否調用虛函數? 其實這個問題,我一直都沒明白有啥意義。構造函數、析構函數調用虛函數是可以的,可是調用的是所屬類的版本。