[轉]C++重讀二:C++基本語法(下)

http://blog.csdn.net/alvachien/archive/2005/08/05/446759.aspx

0. 類型轉換中的提示實際上就是內置數據類型的提升,如char轉換爲int,bool轉換爲int,float轉換爲double等。

1. 類型轉換中的標準轉換有五種類型:(1)整值類型轉換(不包括提升);(2)浮點轉換;(3)浮點-整值轉換;(4)指針轉換和(5)bool轉換。前三種轉換是有潛在危險的轉換。所有的標準轉換都是等價的。一些注意點:0可以被轉換爲任何指針類型,這樣創建的指針稱爲空指針值,同時0也可以是任何整型常量表達式。常量表達式0L及0x00都屬於整值類型因此能夠被轉換爲int *的空指針值。指針轉換允許任何指針類型的實參轉換成void *,但是函數指針不能用標準轉換爲void *。

 2. 對於引用參數來說,如果實參是該引用的有效初始值,則爲精確匹配,否則爲不匹配。

3. 模板非類型參數代表了一個常量表達式,由一個普通的參數聲明構成,表示該參數名代表了模板定義種的一個常量。在實例化時,該常量會被一個編譯時已知的常量值代替。

 4. 在模板中,爲了支持類型表達式,必須使用typename;如typename Param::name *p;定義了一個指針p;如果不使用typename則該表達式爲乘法。另外,模板函數也可以被聲明爲inline或extern,必須把指示符放在參數模板後。如template inline T fun1(){…}

5. 函數實參的推演中三種類型轉換是允許的:(1)左值轉換;(2)限定轉換(const和volatile指示符);(3)到一個基類的轉換:如果實參是一個類,它有一個從被指定爲函數參數的類模板實例化而來的基類;如下面的代碼:template class Array {}; template T min(Array &array) {…};

6. 可以使用顯式指定模板參數,模板參數被顯式指定在逗號分隔的列表中,緊跟在函數模板實例的名字後面;如min(a, b);代碼強行指定模板參數以unsigned int轉換。模板顯式指定參數類似於默認參數,只能省略後面的參數。

7. C++支持兩種模板編譯模式:包含模式和分離模式。包含模式下,在每個模板被實例化的文件中包含函數模板的定義,並且往往把定義放在頭文件中;分離模式下,函數模板的聲明被放在頭文件中,它的定義放在另外一個實現文件中,函數模板在實現文件中的定義必須使用關鍵字export。

8. C++支持模板的顯式實例化,在顯式實例化聲明所在的文件中,函數模板的定義必須被給出。如template T f(T t); 顯式實例化爲int *類型,template int *f(int *);

9. C++支持模板的顯式特化定義,如:template T max(T t1, T t2){…}顯式特化const char *類型的定義:template <> const char *max(const char *c1, const char *c2);顯式特化隱藏了通用模板對於該類型的實例。如果模板實參可以從函數參數中推演出來,則模板實參的顯式特化可以從其中取消。如上面的例子: template <> const char *max(const char *, const char *);其省略與顯式指定模板參數一樣。

10. 拋出異常可通過throw表達式來實現。如拋出一個popOnEmpty(類對象)的異常,表達式爲throw popOnEmpty();同時在catch子句中,爲了防止大型類對象的複製,可以將catch子句的異常聲明改爲引用。

11. 在查找用來處理被拋出異常的catch子句時,因爲異常而退出複合語句和函數定義,這個過程稱爲棧展開。C++保證,隨着棧的展開,儘管局部類對象的生命期是因爲拋出異常而結束,但是所有的析構函數將被調用。要重新拋出接受到的異常,使用語句throw;重新拋出的過程中希望對接受到的異常對象進行修改,必須將catch子句的異常聲明改爲引用。

12. 異常規範跟隨在函數參數表之後,用throw指定,後面是異常類型表。如void pop(int &value) throw(popOnEmpty);異常規範保證不會拋出任何沒有出現在異常類型表中的異常。如果在運行時拋出一個沒有出現在異常類型表中的異常,則系統調用C++標準庫中定義的函數unexpected,該函數直接調用了terminate函數結束程序。空的異常規範不會拋出任何異常。

13. 異常規範可以在函數聲明中指出,當有異常規範的指針被初始化時,被用作右值的指針異常規範必須比用作左值的指針規範一樣或者更嚴格,當然,參數類型表必須相同。

14. 使用函數指針來使得template適應更多的場合。內置的函數指針聲明的模板定義,代碼如下: template< typename Type, bool (*Comp)(const Type&, const Type &)> const Type& min(const Type *p, Comp comp);該方案的主要缺點是函數調用使它無法內聯。使用函數對象來替代函數指針,函數對象是類實現,它重載了調用操作符,即operaotr ()函數。有兩個優點:(1)如果被重載的調用操作符是inline,則編譯器能執行內聯編譯;(2)函數對象可以擁有任意數目的額外數據來緩衝結果。改寫模板使之能同時接受函數指針和函數對象(缺點是沒有任何的原型檢查),template min(const Type *p, Comp comp);

15. 函數對象一般有三個來源,(1)系統預定義的算術、關係和邏輯函數對象,包含頭文件;(2)預定義的函數適配器,它容許對預定義的函數對象進行特殊化或者擴展;(3)自定義的函數對象。

16. 標準庫提供的函數適配器分爲兩類:(1)綁定器(binder),通過把二元函數對象的一個實參綁定到一個特殊的值上,將其轉換爲一元函數對象,有兩種綁定器,bind1st和bind2nd,分別將值綁定到第一個參數和第二個參數上;如下代碼: count_if(vec.begin(), vec.end(), bind2nd(less_equal(), 10));(2)取反器(negator),將一個函數的值翻轉的適配器,標準庫提供了兩個not1和not2。

17. 函數對象類定義的簡單形式是包含一個被重載的函數調用操作符。如 class less_equal_ten { public: bool operator() (int val) {return val <= 10;} } 擴展該類,使之容許提供一個遇每個元素比較的值。如: class less_equal_value { public: less_equal_value(int val) : _val(val) {} bool operator() (int val) { return val <= _val; } private: int _val; } 使用上面這個類:count_if(vec.begin(), vec.end(), less_equal_value(25)); 另外一種拓展該類的方法,不使用構造函數,它根據被比較值對類參數化,如: template class less_equal_value2 { public: bool operator() (int val) { return val <= _val; } } 使用該模板類: count_if(vec.begin(), vec.end(), less_equal_value2<25>());

18. 標準庫提供了一組(一共是三個)插入iterator的適配器函數,它們返回特定的插入iterator:(1)back_inserter,使用容器的push_back方法替代賦值操作符;(2)front_inserter,使用容易的push_front方法替代賦值操作符;(3)inserter,使用容器的insert替代賦值操作符,它需要兩個實參,容器本身以及它的一個iterator指示起始插入位置。

19. 標準庫爲輸入和輸出iostream的iterator提供支持。類istream_iterator類支持在一個istream或其派生類上的iterator操作,同理ostream_iterator支持ostream及其派生類。使用頭文件。

 20. 標準庫定義了五種iterator: (1)InputIterator,用來讀取元素,不保證支持寫入操作;(2)OutputIterator,用來寫入元素,但不保證讀入;(3)ForwardIterator,用來以某一個遍歷方向向容器讀或寫;(4)BidirectionIterator,從兩個方向讀或寫一個容器;(5)RandomAccessIterator,出來支持BidirectionIterator所有功能外,還提供在常數時間內訪問容器的任意位置。其中(1)和(2)是最基本的兩個iterator,需要這兩個iterator的地方可以使用(3)(4)(5)的iterator作爲替代;需要(3)的可以使用(4)(5);能夠替代(4)的只有(5)。需要(3)的常用GA有:adjacent_find(),swap_range()和replace()等;需要(4)的GA有inplace_merge(), next_permutation()和reverse();需要(5)的GA有binary_search(),sort_heap()和nth_element()。map,set和list維護了一個(4)類型iterator,所以它們不能使用需要(5)的GA中。而vector和queue維護了(5)類型的iterator,可以使用於所有的GA。

21. 所有泛型算法的前兩個實參都是一對iterator,通常成爲first和last,標誌出要操作容器或者內置數組中的元素範圍。一些算法支持多個版本,一個用內置操作符,而第二個接受函數對象或函數指針,通常在函數名稱後加上_if,比如count_if();對修改所操作容器的算法,一般有兩個版本,一個替換版本,它改變被應用的容器,另外一個返回帶有這些變化的容器複本,是拷貝版本,通常在函數名稱後加上_copy,如replace_copy()。頭文件是,在使用下列4個算術函數adjacenet_difference, accumulate, inner_product和partial_sum,必須使用頭文件。

22. 泛型算法的一些限制:(1)關聯容器(map或set等)在內部維護了元素的排序關係,不容許在關聯容器上應用重新排序的泛型算法,如sort和partition等。(2)list不支持隨機訪問,所以merge, remove, reverse, sort和unique等算法最好不要使用在list上。list提供了上述這些泛型算法的成員函數。

 23. 在類體外定義inline函數,可以顯式在類體的聲明中使用關鍵字inline,或者在類體外的函數定義上顯式使用關鍵字inline,或者兩者都有。但是內聯函數必須在調用它的每個文本文件中被定義,所以在類體外被定義的內聯函數也必須被放在類定義出現的頭文件中。

24. 只有被聲明爲const的成員函數才能被一個const類對象調用。關鍵字const被放在成員函數的參數表和函數體之間,必須在聲明和定義中同時指定const關鍵字。const函數不能修改類數據成員,但如果類中含有指針,則const函數能修改指針指向的內容。const函數可以被相同參數表的非const函數重載。volatile關鍵字類似於const。

 25. 爲了允許修改一個類的數據成員,即使是const對象的數據成員,可以吧數據成員聲明爲mutable。

 26. 類的靜態數據成員對每個類類型都只有一個拷貝,由該類類型的所有對象共享訪問;它比全局對象有兩個優點:(1)靜態數據成員沒有進入全局名字空間;(2)可以實施數據隱藏,比如設置爲private的,而全局變量則不行。一般而言,靜態數據成員在該類定義之外被初始化,靜態成員的名字必須被其類名限定修飾,如int Account::a=90;和全局變量一樣,靜態數據成員也只能提供一個定義,也就是初始化不能放在頭文件中。特別的是,有序型的const靜態數據成員可以在類體中用常量值初始化。如 static const int Account::nameSize = 5;用常量值初始化的靜態數據成員是一個常量表達式,同時該成員還必須在類定義之外被定義,即nameSize要起作用,必須在類定義再定義,可以不賦值。數組類型不屬於有序,不能再類體中初始化。訪問靜態成員可以通過類對象使用成員訪問操作符或者用被類名修飾的名字直接訪問(Account::nameSize)。

27. 靜態數據成員的一些獨特方式的使用:(1)靜態數據成員的類型可以是其所屬類,而非static的數據成員只能聲明爲類對象的指針或者引用。如: class A { public: static Bar mem1; // 合法的。 Bar *pMem2; // 合法的 Bar mem3; // 錯誤 } (2)靜態數據成員可以被作爲類成員函數的缺省實參,而非static成員不能。

 28. 靜態成員函數的聲明除了在類體中的函數聲明前加上關鍵字static,以及不能聲明爲const或volatile之外,與非static成員函數相同。靜態成員函數沒有this指針。

29. 函數指針不能被賦值爲成員函數的地址,即使返回類型和參數表完全匹配。成員函數指針的聲明需要擴展的語法,如int (Account::*test)();聲明瞭一個Account類,返回int的無輸入參數的成員函數指針。類成員的指針必須總是通過特定的對象或指向該類類型的對象的指針來訪問。如類的指針對象pobj調用test函數(pobj->*test)();或者類對象obj調用test函數(obj.*test)();調用中必須有括號;同理,類成員的指針也必須使用特殊的語法,如typedef int Account::*member; member=&Account::size;其中member就是Account類中size的指針,使用它也必須使用指針對象或者類對象。

 30. 指向類的靜態成員的指針是普通的指針,包括函數指針。

31. 聯合是一種特殊的類,一個聯合中的數據成員在內存中的存儲是相互重疊的,每個數據成員都在相同的內存地址開始,分配給聯合的存儲區數量是“要包含它最大的數據成員”所需的內存數。默認情況下,聯合中所有數據都是public的。union不能有靜態數據成員或引用成員,如果一個類類型定義了構造函數、析購函數或拷貝賦值操作符,則它不能成爲union的成員類型。

 32. 使用union的風險是通過一個不恰當的數據成員意外的獲取到當前存儲在union中的值,一般的解決方法是定義一個額外對象,來跟蹤當前被存儲在union中的值的類型,成爲union的判別式(discriminet)。一個比較好的經驗是爲所有union數據類型提供一組訪問函數。如:union TokenValue { int _iVal; char _cVal; } enum TokenKind { ID, Constant }; class Token{ TokenKind tok; TokenValue val; }這裏的TokenKind就是判別式。union的名字是可選的,特殊的情況下union實例可以沒有名字(定義在類體內部)。

 33. 位域是一種特殊的類數據成員,它可以被聲明用來存放特定數目的位,位域必須是有序數據類型,可以有符號,也可以無符號。位域標誌符後面跟一個冒號,然後是一個常量表達式指定位數,如 unsigned int mode : 2; 位域的訪問方式和數據成員相同。取地址操作符不能應用在位域上。

 34. 嵌套類是指一個類在另一個類中定義。嵌套類可以通過外圍類的訪問控制關鍵字進行控制,比如設置爲private可以控制嵌套類無法被直接訪問。嵌套類也可以定義在外圍類之外,比如ListItem的外圍類是List,可以定義class List::ListItem {};這樣的代碼可以保護嵌套類的代碼不公開。嵌套類訪問外圍類的非靜態成員必須通過對象或指針,但可以直接訪問外圍類的靜態成員,類型名和枚舉值(假定這些成員是公有的),類型名是一個typedef名字、枚舉類型名或者一個類名。

 35. 枚舉值在定義枚舉的域內可以被直接訪問,因爲枚舉定義並不像類定義一樣,它沒有維護自己相關的域。

 36. C風格的顯式初始化表有兩個注意缺點:(1)它只能被應用到所有數據成員都是公有的類的對象上;(2)它增加了意外和錯誤的可能性,比如弄錯了初始化的順序。如Data loc = { “aaa”, 0}; // struct Data的定義是int ival; char* pstr;

37. 爲構造函數指定實參有三種等價形式,(1) Account acct1(“Anna Press”); (2) Account acct2 = Account(“Anna Press”); (3) Account acct3 = “Anna Press”;第三種形式只能被用於指定單個實參的情形,一般推薦使用(1)。代碼Account acct4(); 其實聲明瞭一個函數形式。

 38. 使用初始化表和在構造函數內使用數據成員的賦值,兩種實現的最終結果是一樣的,區別取決於數據成員的類型。構造函數可以認爲分爲兩個階段:(1)顯式或隱式的初始化階段;(2)一般的計算階段;計算階段由構造函數內的所有語句構成。初始化階段是隱式或者顯式的取決於是否存在成員初始化表,隱式初始化階段按照聲明的順序依次調用所有基類的缺省構造函數,然後是所有成員類對象的缺省構造函數。對於內置類型的成員,除了兩個例外,初始化表和在構造函數內初始化在結果和性能上是等價的。兩個例外是指const和引用數據成員,它們必須在初始化列表中被初始化。每個成員在初始化表中只能出現一次,初始化的順序不是由名字在初始化表的順序決定,而是由成員在類中被聲明的順序。如下的初始化列表會導致錯誤:class X { int I; int J; X(int v) : j(val), i(j){} … }這裏i的值將會是未定義。

39. 用一個類對象初始化另一個類對象,稱爲缺省的按成員初始化。通常發生在下列情況:(1)用一個類對象顯式初始化另一個類對象;(2)把一個類對象作爲實參傳遞給一個函數或者作爲一個函數的返回值;(3)非空順序容器類型的定義,如vector svec(5);創建一個臨時對象,然後通過string的拷貝構造函數將該臨時對象依次拷貝到vector的5個元素中。(4)將一個類對象插入容器中。

 40. 由於構造函數分爲兩個階段,所以成員類對象的初始化最好在成員初始化表中初始化。如下代碼: Account::Account(const Account &rhs) { _name = rhs._name; }這裏_name是一個類對象,這行代碼需要兩次重複工作,首選初始化_name,然後用rhs._name來賦值。修正方法是使用初始化表。

 41. C++中類對象的初始化總是比賦值更有效率。如Matrix c = a+b; 的效率比Matrix c; c = a+b; 更有效率。在循環中使用臨時對象初始化也更有效率,因爲一般情況下不能直接用被返回的局部對象代替賦值的目的對象。

42. 操作符重載,只有在操作數是類類型的對象時,才能將該操作符作爲類成員重載。但是同樣可以聲明非類成員的操作操作符。除此之外,C++要求,賦值(=),下標([]),調用(())和成員訪問(->)操作符必須定義爲類成員操作符。對於內置類型的操作符,不能被重載,也就是隻能爲類類型和枚舉類型的操作數定義重載操作符。

43. 重載的operator()必須被聲明爲成員函數,它的參數表可以有任意數目的參數。

44. 爲了區分前置操作符和後置操作符的聲明,重載的遞增(++)和遞減(--)的後置操作符的聲明有一個額外的int類型的參數。如: Screen &operator ++(); // 前置操作符 Screen &operator ++(int); // 後置操作符

45. 如果一個類提供了兩個分別稱爲操作符new()和操作符delete()的成員函數,那麼它就可以承接自己的內存管理權。類成員操作符new()的返回類型必須是void *型,並且有個size_t類型的參數。類成員操作符delete()的返回類型必須是void,並且第一個參數的類型是void *。爲一個類類型定義的delete操作符,如果它是被delete表達式調用的,則它可以有兩個參數,第一個仍然必須是void *,第二個必須是預定義類型size_t,該參數值被自動初始化,其值等於所需內存的字節大小。例如:void operator delete(void *, size_t);操作符new()和delete()是類的靜態成員,它們被自動成爲靜態函數。

46. 重載針對數組分配的操作符new[]()和delete[]()的重載。new[]()操作符,返回類型是void *,並且第一個參數類型是size_t。如 void *operator new[](size_t); 當調用如下代碼時:Screen *ps = new Screen[10];操作符new[]()的size_t參數被自動初始化,其值等於存放10個Screen對象的數組所需內存的字節大小。通用可以定義定位的new和delete操作符。如void *operator new(size_t, Screen *);

 47. 用戶自定義轉換函數可以在類類型和轉換函數中指定的類型之間的轉換。如:operator int();關鍵字operator之後的名字不一定必須是內置類型的名字,可以使用typedef定義的名字或者類名稱等,但是不能爲數組或者函數類型。注意,沒有返回值。

 48. 在一個類的構造函數中,凡只帶一個參數的構造函數,都定義了一組隱式轉換,把構造函數的參數類型轉換爲該類的類型。可以使用關鍵字explicit來阻止編譯器使用該構造函數進行隱式類型轉換,但是該函數還是可以被用來執行類型轉換,只要顯式以強制轉換。如類Number定義這樣一個構造函數 Number(const SmallInt &); 有這樣的函數void func(Number); 則代碼在未使用explicit下可以使用SmallInt si; func(si); 在使用explicit後可以使用func(Number(si))或者func(static_cast(si));

49. 類模板參數也可以是一個非類型模板參數,綁定給非類型模板參數的表達式必須是一個常量表達式,即它能在編譯時被計算。非const對象的值不是一個常量表達式,不能被用作非類型參數的實參,但是名字空間中任何對象的地址是一個常量表達式。如int i = 5;中i不能用作非類型模板參數,但是&i可以。

 50. 被定義在類模板定義之外的成員函數必須使用特殊的語法,來指明它是一個類模板的函數。如類Queue類的構造函數:template Queue ::Queue() {…}。類模板的成員函數本身也是一個模板,標準C++要求這樣的成員函數只有在被調用或者取地址時才被實例化,當類模板被實例化時,類模板的成員函數並不自動被實例化。

51. 有三種友元聲明可以出現在類模板中:(1)非模板友元函數或者友元類;(2)綁定的友元類模板或者函數模板。如有模板函數和模板類如下: template class foobar {…} template void foo(QueueItem); template class Queue { void bar(); … } 在類QueueItem增加以上的友元聲明: template class QueueItem { friend class foobar; friend void foo(QueueItem); friend void Queue::bar(); …} (3)非綁定的友元類模板或者函數模板。如下代碼: template class QueueItem { template friend class foobar; template friend void foo(QueueItem); template friend void Queue::bar(); … }

 52. 模板類成員也可以聲明靜態成員變量,類模板的每個實例都有自己的一組靜態數據成員,靜態數據成員的模板定義必須出現在類模板定義之外,定義語法類似與類模板定義外的成員函數定義語法。只有當程序使用靜態數據成員時,它才從模板定義中被真正實例化。類模板的靜態成員本身就是一個模板,靜態數據成員的模板定義不會引起任何內存被分配,只有對靜態數據成員的某個特定的實例纔會分配內存。

53. 類模板的嵌套類型,當外圍類被實例化時,它的嵌套類不會自動被實例化,只有當上下文環境缺省需要嵌套類的完整類型時,嵌套類纔會被實例化。

54. 函數模板或者類模板可以是一個普通類的成員,也可以是一個類模板的成員,成員模板的定義類似與一般模板的定義。而一個成員模板的定義在模板外部時,如: template class Queue { template void assign(Iter first, Iter last); … } template template void Queue::assign(Iter first, Iter last) {…}

55. 模板類的顯式實例聲明,實例代碼如下: template class Queue {…} template class Queue ; // 顯式實例了int類型的Queue 模板類的特化,類似與函數模板的特化,爲指定類型的做特別的處理,如 template<> class Queue {…} 模板類的特化,支持部分特化,即,如果類模板有一個以上的模板參數,指定其中一個或者幾個(非全部)的參數,稱爲部分特化。如: template class Screen {…}; template class Screen {…}

56. 當基類成員名在派生類中被重用時,基類的成員將被隱藏,因爲基類的成員屬於基類的域。對於成員函數,可以使用using聲明來導入基類的成員以構成函數重載。另外一個注意事項是,派生類可以訪問其基類的protected的成員,但是不能訪問其他的基類成員。如代碼 bool NameQuery::compare(const Query *pquery); 該函數可以訪問自己基類的成員,但是無法訪問pquery的protected的成員,這種形式的訪問限制不適用於自己類的其他對象,也就是bool NameQuery::compare(const NameQuery *pquery)中可以訪問pquery的protected成員。

57. 派生類由一個或多個基類子對象以及派生類部分構成。派生類的構造函數次序永遠是(1)基類構造函數;(2)成員類對象構造函數;(3)派生類構造函數。這個次序跟構造函數初始化列表中的順序無關。析構函數的次序正好與構造函數次序相反。

58. 當用類域操作符調用虛擬函數時,改變了虛擬機制,使得虛擬函數在編譯時刻被靜態解析。如Query是基類,NameQuery是派生類,代碼:Query *pquery = new NameQuery(); pquery->isA(); pquery->Query::isA(); 則兩個isA函數調用不同,前一個使用虛擬機制調用NameQuery的函數isA,而後一個直接調用基類的函數。有定義的純虛擬函數也支持被靜態調用。

59. 如果通過基類指針或引用調用派生類實例,則傳遞給它的缺省實參由基類指定的。

60. 如果用基類的指針來刪除一個派生類的對象,爲了能正確執行,必須把基類的析構函數設置爲virtual的。

61. 雖然不能把new操作符聲明爲虛擬的,但是可以提供一個代理new操作符,來負責對象分配並拷貝到空閒存儲區,通常成爲clone,以下是一個例子:virtual Query *clone() {return new NameQuery(*this); }

62. 派生類的賦值操作符operator =調用基類的賦值操作符,可以有兩個語法:(1) this->Query::operator =(rhs); (2)(*static_cast(this))=rhs;

63. 多繼承中,基類構造函數被調用的順序以類派生表中被聲明的順序爲準。析構函數的調用順序總是與構造函數順序相反。

64. public派生被稱爲類型繼承,派生類是基類的子類型,派生類反應了一種is-a關係;private派生被成爲實現繼承,派生類不直接支持基類的接口,它把基類的整個共有接口在派生類中變成private,但是它希望重用基類的實現。Protected派生把基類的所有公有成員變成protected。可以通過using指示符來還原基類的訪問等級,但是不能比基類中原定的級別更嚴格或者更不嚴格。組合是一種has-a的關係,在只是簡單重用實現時,可以按引用(使用一個指針)來組合,組合分爲按值組合(通過類的實際對象被聲明爲一個成員)和按引用組合(通過類對象的引用或者指針成員)。

65. 虛擬繼承是一種可替代“按引用組合”的繼承機制(普通繼承是按值組合),在虛擬繼承下只有一個共享的基類。通過用關鍵字virtual修改一個基類的聲明可以將它指定爲被虛擬派生。

66. 在非虛擬派生中,派生類只能顯式初始化其直接基類;而在虛擬派生中,只有最終派生類可以直接調用其虛擬基類的構造函數,中間派生類對基類構造函數的調用被自動抑制了,必須由最終派生類提供。構造函數順序上,虛擬基類在所有非虛擬基類構造前被構造。在虛擬派生下,對於虛擬基類成員的繼承比“該成員後來重新定義的實例(即中間派生類重載了該成員)”的權值小,所以中間派生類的成員優先,而在非虛擬派生下則產生二義性。

67. 當一個類模板被用作基類時,必須用它完整的參數表對其進行修飾,如代碼:template class derived : public Base {…}

68. RTTI(運行時刻類型識別),有兩個操作符,dynamic_cast操作符:容許在運行時刻進行類型轉換,它可以把一個類類型對象的指針轉換成同一類層次結構中的其他類的指針,如果指針轉換失敗則結果爲0,如果引用轉換失敗則拋出異常;typeid操作符,指出了指針或引用指向對象的實際派生類型。

69. dynamic_cast操作符一次執行兩個操作,它檢驗所請求的轉換是否有效,只有有效時才進行轉換,而檢驗過程是發生在運行時刻。它被用來執行從基類指針到派生類指針的安全轉換,是一種安全的向下轉換。當用於引用時,它拋出std::bad_cast異常。

70. typeid操作符用於獲取一個表達式的類型,它必須與表達式或者類型一起使用,可以是內置的類型或者類類型。可以對typeid的結果進行比較,它區分指針和對象,如class A; A a; typeid(&a)和typeid(a)不同,前者是一個指針,判斷指針是typeid(A *),後者是類對象。它其實返回一個類型爲type_info的類對象,該類型定義在。該類的構造函數都是private,無法自己創建該類的對象,只能通過typeid操作符。該類的實現依賴於編譯器。

 71. 拋出異常:throw popOnFull(value);執行的步驟:(1)throw表達式通過調用類類型pushOnFull的構造函數創建一個該類的臨時對象;(2)創建一個pushOnFull類型的異常,並傳遞給異常處理代碼,該異常對象是第一步throw表達式創建的臨時對象的拷貝;(3)在開始查找異常處理代碼之前,第一步中的臨時對象被銷燬。

72. 派生類中虛擬函數的異常規範必須與基類的對應的虛擬函數的異常規範一樣或者更嚴格。

 73. 通常情況下,在“被拋出的異常的類型”和“異常規範中指定的類型”之間不容許進行類型轉換;但是,當異常規範指定一個類類型或者類類型的指針時,則可以拋出“從該類公有繼承的類類型”。

 74. C++標準庫中的異常層次的根類爲exception,包含在頭文件。它包含一個what虛擬成員函數。C++標準庫將錯誤分爲兩大類,邏輯錯誤和運行時刻錯誤,並提供了一些從exception繼承的類。通過new分配錯誤會拋出bad_alloc異常。

75. 輸出操作符<<默認對C風格的字符串(即類型const char *)做了特殊處理,如果想輸出字符串的地址,必須進行強制轉換。ostream_iterator和istream_iterator會簡化操作。

76. 其它幾種常用的輸入/輸出操作符:(1)get(char &ch)從輸入流中提前提取一個字符,包括空白字符,它返回被應用的istream對象。對應的ostream提供了put(),寫入一個字符。(2)get的第二個版本和第一個版本功能一樣,只是返回值不同,它返回字符值,返回類型是int因爲可能返回文件尾標誌,該標誌通常爲-1。(3)get的第三個版本get(char *sink, streamsize size, char delimiter=’/n’); 讀取size個字符,除非遇到字符delimiter或者遇到文件尾。該函數不把delimiter字符讀入,而是將該字符繼續留在流中,所以必須使用ignore來丟棄該字符。(4)getline函數類似與get的第三個版本,除了它自動丟棄了delimiter字符。想確定讀入了多少字符,使用gcount函數。(5)istream的read函數和ostream的write函數讀取指定長度的字符,除非遇到文件尾。

77. 由於不正確的格式而導致失敗,istream應該把狀態標誌爲false,is.setstate(ios_base::fallbit);對於錯誤狀態的iostream,提取和插入操作沒有影響,但是對它的測試將爲false。

 78. 通過seekg和seekp函數,可以對fstream類對象重新定位(g表示爲了getting字符而定位,而p表示爲了putting而定義)。類似的,tellp和tellg獲取當前的輸出和輸入流的位置。

79. 每個流對象都維護了一組條件標誌,通過這些條件標誌,可以監視當前流的狀態:(1)當一個流遇到文件結尾,eof返回true;(2)如果試圖做一個無效操作,如seek的操作超出文件尾,則bad返回true;(3)如果操作不成功,比如打開文件流對象失敗或者遇到無效的輸入格式,則fail返回true;(4)如果其他條件都不爲true,則good()爲true。顯式修改流對象的條件狀態有兩種方式:(1)clear函數,可以把條件狀態復位到一個顯式的值;(2)使用setstate函數在現有的條件狀態再增加一個條件。可用的條件值是ios_base::badbit, ios_base::eofbit, ios_base::failbit, ios_base::goodbit。同時rdstate函數可以讀取當前狀態值,返回類型是ios_base::iostate。

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