C++ How to Program讀書筆記總結,未完。
1、 endl與\n區別在於endl重新整理輸出緩衝區;
2、 static_cast<type>() 用於強制類型轉換;
3、 switch(type),type可為何類型?
4、 int的取值範圍介於short和long之間;
5、 在記憶體有限或要求執行速度的面向效能的情況中,可以考慮用較小的整數長度;
6、 如果程式的機器指令不如自然長度那麼有效(例如要進行符號擴充),則用較小的整數長度會使程式減慢;
7、 輸出格式:域寬,精度,左靠右對齊;
eg. Cout << setiosflag(ios::fixed| ios::showpoint | ios::left)
<<setprecision(精度) <<setw(域寬) <<number << endl;
8、 程式應寫成一組小函數的集合,使得程式更容易編寫、調試、維護和修改,小函數能提高函數的複用性;
9、 rand()產生偽隨機數,srand(unsigned int seed)完成隨機化過程,srand(time(0))使電腦通過時鐘值自動取得種子值;
10、 自動儲存可以節省記憶體,因為自動儲存類變數在進入聲明的塊時產生並在退出這個塊時刪除,自動儲存是最低許可權原則的例子。
11、 任何對內嵌函式的改變都可能要求函數的所有客戶重新編譯。這樣可能在有些程式的開發和維護中影響非常大;
12、 Inline限定符經常使用的小函數;
13、 使用內嵌函式可以減少執行時間,但會增加程式長度;
14、 按值傳遞的一個缺點是,如果有一個大的資料項目需要傳遞,那麼複製這些資料就需要大量的時間和記憶體空間;
15、 按引用傳遞對效能很有協助,它可消除按值傳遞複製大量資料的開銷,但是可能削弱安全性,因為被調用函數可能破壞調用者的資料;
16、 為了傳遞大型物件,使用一個常量引用參數來類比按值傳遞的外觀和安全性,並且避免傳遞大型物件的副本的開銷;
17、 按值傳遞的參數聲明為const時,也只保護原始實參的副本,而不是原始實參本身。因此在按值傳遞時原始實參對於被調用函數所做的修改來說是安全的;
18、 為了清晰性和良好效能起見,許多C++程式員寧願用指標來向函數傳遞可修改的實參,而小的不可修改的參數採用值傳遞,大的不可修改的參數通過使用常量的引用來傳遞給函數;
19、 返回被調用函數中的自動變數的引用會產生邏輯錯誤;
20、 在函數原型和函數頭部中同時指定預設實參會產生編譯錯誤;
21、 在聲明常量變數時沒有給它初始化是一個編譯錯誤,eg聲明為const的指標;
22、 用const限定符實施最小特權的原則。使用最小特權原則適當的設計軟體,可以極大的減少調試時間和不恰當的副作用,並可以使程式更易於修改和維護;
23、 可以講static應用於局部數組聲明,那樣,數組就不會在每次程式調用改函數時都進行建立和初始化,也不會在程式中每次該函數結束時被銷毀。這樣可以提高效能,特別是在使用大型數組時;
24、 如果不用執行時的指派陳述式初始化數組而用數組初始化值列表在編譯時間初始化數組,則程式執行速度更快
25、 函數原型中可以包括變數名,使程式更清晰,但編譯器將忽略這個名稱;
26、 “*”運算子通常稱為間接運算子或間接引用(dereference)運算子,間接引用null 指標通常是致命的執行階段錯誤
27、 將指標傳遞給函數有四種方式,存取權限不同:指向非常量資料的非常量指標(最大存取權限)、指向常量資料的非常量指標(可以被修改以指向任何適當類型的其他資料項目,但是不能通過該指標來修改它所指向的資料)、指向非常量資料的常量指標(始終指向同一個記憶體位置,通過該指標可以修改這個位置上的資料,這就是數組名的預設情況)、指向常量資料的常量指標(最小存取權限);
28、 講函數原型放在其他函數中能保證最低許可權原則,只能從該原型所在函數中正確的調用;
29、 向函數傳遞數組時,同時傳遞數組長度(而不是在函數內部固定數組大小),這樣能使函數有更好的可重用性;
30、 由於sizeof是一個編譯時間的一元運算子,而不是一個運行時的運算子,所以實用sizeof並不會降低執行效能;
31、 將一個類型的指標賦給另一個類型(不是void*類型)的指標,而不先將第一個指標強制轉換為第二個指標的類型,會造成一個編譯錯誤;
32、 Void*指標不能被間接引用,除了將void*指標和其他指標進行比較、將其強制轉換為有效指標類型和將地址賦給void*指標之外,其他對其的操作都導致編譯錯誤;
33、 儘管數組名是指向數組開頭的指標,並且指標是可以在算術運算式中修改的,但是,由於數組名是常量指標,所以不能在算術運算式中修改數組名;
34、 引用數組元素有四種標記法(下標標記法、將數組名作為指標的指標/位移量標記法、指標下標標記法和用指標的指標/位移量標記法);
35、 為了使程式清晰,在運算元組時使用數組標記法,而不要用指標標記法;
36、 函數指標type (*函數名)(),記得括弧,一個常見用法是用在所謂的菜單驅動系統中,用函數指標選擇特定功能表項目所需要調用的函數;
37、 將單個字元作為char*的字串處理,會導致致命的執行階段錯誤。一個char*的字串是一個指標,它很可能是一個大整數,而一個字元是一個小整數(ASCII值範圍0~255)。在許多系統中間接引用一個char值會導致錯誤,因為低的記憶體位址往往被保留做特殊用途,例如有效作業系統的中斷處理常式,因此會發生“記憶體訪問擾亂”的錯誤(?記憶體位址與小整數char值有何關係);
38、 Char *strtok(char *s1, const char *s2)用法;
39、 在類定義中顯示的將類的資料成員初始化是個語法錯誤;
40、 在類定義中(通過函數原型)聲明成員函數而在類定義外定義這些成員函數,可以區分類的介面與實現方法。這樣可以實現良好的軟體工程,類的客戶不能看到類成員函數的實現方法;
41、 普遍原則:避免重複代碼。eg. 如果類的成員函數已經提供了類的建構函式(或其他成員函數)所需要的全部或部分功能,那麼就可以在函數中調用這樣的成員函數。這不僅可以簡化代碼的維護,而且可以減少又修改代碼實現方法所引起出錯的可能性;
42、 對象只包含了資料。如果對一個類的類名或該類的一個對象進行sizeof運算,結果將只報告該類的資料成員的大小。編譯器只建立獨立於類的所有對象的一份成員函數的副本。該類的所有對象共用這份副本。當然,每個對象都需要自己的類資料的副本,因為在對象間這些資料是不同的。函數代碼是不可修改的,也稱為可重新進入代碼或純過程。因此可以被類的不同對象所共用;
43、 類範圍外,類成員是通過一個對象的控制代碼引用,可以是對象名、對象引用或指向對象的指標;
44、 用#ifndef、#define和#endif預先處理指令防止一個程式中多次包含相同的標頭檔;
45、 流操縱符是個“粘性”設定,意味著一旦設定了填充字元,將在所有接下來的顯示地區裡有效;
46、 不要讓類的public成員函數返回對該類private資料成員的非const引用(或指標),返回這種引用會破壞類的封裝;
47、 賦值(=)運算子可以將一個對象賦給另一個類型相同的對象。預設情況下,這樣的賦值通過逐個資料成員賦值的方式進行;
48、 對象可以作為函數的實參進行傳遞,也可以由函數返回。這種傳遞和返回預設情況下是以值傳遞的方式執行的,即傳遞或返回對象的一個副本。這樣的情況下,C++建立一個新的對象,並使用拷貝建構函式將原始對象的值複製到新的對象中;
49、 將變數和對象聲明為const可以提高效能,如今複雜的最佳化編譯器可以對常量提供某些對變數來說不能提供的最佳化;
50、 定義為const的成員函數如果又調用同一類的同一執行個體的非const成員函數,將導致編譯錯誤;
51、 在const對象上調用非const成員函數將導致編譯錯誤;
52、 可以對const成員函數進行非const版本的重載;
53、 常量資料成員(const對象和const變數)和聲明為引用的資料成員必須採用成員初始化器文法形式進行初始化,在建構函式中為這些類型資料賦值是不允許的;
54、 軟體重用性的一個普遍的形式是組成,即一個類將其他類的對象作為成員;
55、 (*this).m_data = data, 注意括弧
56、 This指標的可以防止對象進行自我賦值,還使串聯的成員函數調用成為可能;
57、 當動態分配的記憶體空間不再使用時若不釋放,將導致系統過早的用完記憶體,這有時成為“記憶體泄露”(memory leak);
58、 即使不存在已執行個體化的類的對象,類的static資料成員和static成員函數仍存在並可以使用;
59、 在檔案範圍內定義待用資料成員包含關鍵字static是編譯錯誤;
60、 在static成員函數中使用this指標是編譯錯誤;
61、 將static成員函式宣告為const是編譯錯誤;
62、 在刪除動態分配的記憶體空間後,將指向這片記憶體的指標設定為0,杜絕野指標;
63、 #define NDEBUG忽略所有assert;
64、 描述類的功能而不管其實現細節成為資料抽象,C++的類定義了抽象資料類型(ADT),其包含兩個概念,即資料表達和可在資料上執行的操作;
65、 好的軟體工程有兩個幾本原則:一是介面與實現的分離,二是隱藏實現細節。可向客戶提供代理類,只能訪問類的public介面。
66、 重載一元運算子時,把運算子函數用作類的成員函數而非友元函數。因為友元的使用破壞了類的封裝性;所以盡量避免使用友元函數和友元類;
67、 在衍生類別的建構函式中,採用成員初始化器列表顯示的初始化成員對象和調用基類的建構函式,可以防止重複初始化,即調用了預設建構函式後,又在衍生類別的建構函式中再次修改資料成員;
68、 如果可能,盡量避免在基類中包含protected資料成員。相反,應包含有訪問private資料成員的函數,以保證對象處於可靠狀態;
69、 如果在衍生類別中包含一個與基類中同名但是有不同簽名的成員函數,那麼這個函數會隱藏基類版的那個函數。如果通過衍生類別的對象的public介面試圖調用基類版的這個成員函數,將會產生編譯錯誤;
70、 假設我們建立一個衍生類別對象,這個衍生類別及其基類中都包含其他類的對象。當這個衍生類別的對象被建立時,首先執行基類成員對象的建構函式,然後執行基類的建構函式,接著執行衍生類別成員對象的建構函式,最後執行衍生類別的建構函式。衍生類別對象解構函式的調用順序與相應的建構函式調用順序正好相反;
71、 基類指標和衍生類別指標與基類對象和衍生類別對象的混合匹配有四種可能:
a) 直接用基類指標指向基類的對象;
b) 直接用衍生類別指標指向衍生類別的對象;
c) 用基類指標指向一個衍生類別的對象。但是只能引用基類成員。如果試圖通過基類指標引用只在衍生類別中才有的成員會產生編譯錯誤;除非顯示的把基類指標強制轉換為衍生類別指標,這就是向下強制類型轉換(downcasting),但是該技術是具有潛在危險的操作。
d) 用衍生類別指標指向基類對象,會產生編譯錯誤。衍生類別指標必須先強制類型轉換為基類指標;
72、 區分繼承和組成,組成是把對象作為類的成員;
73、 基類的private成員只能在基類的定義中或由基類的友元訪問;
74、 當從protected基類派生一個類時,基類的public和protected成員都變成protected成員;
75、 當從private基類派生一個類時,基類的public和protected成員都變成private成員;
76、 一旦一個函數被聲明為虛函數,即使重新定義類時沒有聲明虛函數,那麼它從該點之後的繼承階層中都是虛函數;
77、 即使某些函數因類階層中的高層已聲明為virtual而成為隱含的virtual函數,但為了使程式更加清晰可讀,最好還是在類階層的每一級中都把它們顯示的聲明為virtual函數;
78、 如果程式通過指向衍生類別對象的基類指標或引用調用virtual函數,那麼程式會根據所指對象的類型而不是指標類型,動態(即執行時)選擇正確的衍生類別函數。這稱為動態綁定或遲綁定;
79、 當virtual函數通過安名引用特定對象和使用圓點成員選擇運算子的方式被調用時,調用哪個函數在編譯時間就已經決定了(稱為靜態繫結),所調用的virtual函數正式為該特定對象所屬的類定義的(或繼承而來的)函數—這並不是多態性行為;
80、 多態性程式設計可以消除不必要的switch邏輯,使程式看上去顯得很簡單,包含更少的分支邏輯和更簡單有序的代碼。這種簡單化使得程式更易於測試、調試和維護;
81、 抽象類別為類階層中的各種類定義公用的通用介面。因為它通常在繼承層次中做基類,所以叫抽象基類。通過聲明類的一個或多個virtual函數為純virtual函數,可以使類稱為抽象類別;
82、 試圖執行個體化抽象類別的對象,將導致編譯錯誤;具體類則可以;
83、 儘管不能執行個體化抽象基類的對象,但可以聲明指向抽象基類對象的指標和引用。這樣的指標和引用可以用來對執行個體化的具體衍生類別的對象進行多態性的操作;
84、 純虛函數是在它的聲明中“初始化值為0”的函數。Eg. Virtual void draw()const = 0; “=0”為純指示符。純virtual函數不提供函數的具體實現,每個派生的具體類必須重載所有基類的純virtual函數的定義;
85、 如果一個類中含有virtual函數,那麼該類中就要提供一個virtual解構函式,即使該解構函式並不一定是該類需要的。
86、 建構函式不能是virtual函數;
87、 動態綁定需要在運行時把virtual成員函數的調用傳送到恰當類的virtual函數的版本。Virtual函數表簡稱為vtable,是一個包含函數指標的數組。每個含有virtual函數的類都有一個vtable。對於類中的每個virtual函數,在vtable中都有一個包含函數指標的項,此函數指標指向該類對象的virtual函數版本。特定類所用的virtual函數可能是該類中定義的函數,也可能是從類階層中較高層的基類直接或間接繼承而來的函數
88、 當基類提供了一個virtual成員函數時,衍生類別可以重載此函數,但不是必須的。因此,衍生類別可以使用基類版本的virtual函數;
89、 動態綁定使得獨立軟體廠商(ISV)可以不用暴露所有權就發行就緒軟體。發布的軟體可以只包含標頭檔和目標檔案--不需要暴露原始碼。之後軟體開發人員可以通過繼承從ISV提供的類中派生出新類。和ISV提供的類一起啟動並執行軟體也能和衍生類別一起運行,並能夠通過動態綁定使用這些類中提供的重載的virtual函數;
90、 多態性是通過virtual函數和動態綁定實現的;
91、 C++中通過virtual函數和動態綁定實現的多態性非常高效。程式員使用這些功能時對系統效能的影響很小;
92、 dynamic_cast運算子檢查指標所指對象的類型,然後判斷這一類型是否與此指標正在轉換成的類型有一種“是一個”的關係,如果它們之間存在“是一個”的關係,返回對象的地址,否則返回0;
93、 運算子typeid返回包含運算元資料類型資訊的type_info類對象的一個引用,資訊中包括資料類型的名稱。要使用typeid,程式必須包含標頭檔<typeinfo>;
94、 調用時,type_info的成員函數name返回一個基於指標的字串,它包含type_info對象所表示的類型名;
95、 運算子dynamic_cast和typeid是c++運行時類型資訊(RTTI)特徵的一部分,允許程式在運行時判斷對象的類型;
96、 多繼承可以把複雜的事物帶入到系統中。要在設計系統中恰當的使用多繼承必須非常小心。當單繼承能解決問題的時候,盡量不要使用多繼承;
97、 菱形繼承的二義性發生在一個衍生類別從兩個或者多個基類子物件繼承的時候。使用虛繼承可以很好的解決多副本子物件的問題。當使用虛繼承的方式從基類繼承時,只有一個子物件會出現在衍生類別中,這個過程叫做虛基類繼承;
98、 為虛基類提供一個預設的建構函式可以簡化層次設計;
99、 儘管模板提供了軟體重用的優點,請記住在編譯時間多個函數模板特化和類模板特化是在程式(在編譯時間)中進行的(儘管模板唯寫了一遍)。這些副本將消耗相當大的記憶體。然而這通常不是問題,因為為模板特化產生的代碼與程式員不用模板時編寫的獨立重載函數的代碼長度相等;
100、 類模板也稱作參數化型別(parameterizezd type),通過允許將泛型類執行個體化為明確類型的類來實現軟體重用;
101、 在編譯的時候適當的(可以利用一個非類型模板參數)指定一個容器類的大小(比如一個數組類或一個堆棧類),這樣做消除了使用new建立動態空間的執行時間開銷;
102、 在編譯時間制定容器的大小可以避免在new不能獲得足夠記憶體時產生的潛在致命執行階段錯誤;
103、 異常處理用於處理同步錯誤,這個錯誤發生在一個語句正在執行的時候;
104、 通過引用捕獲異常對象能夠去除表示拋出的異常對象的複製開銷;
105、 當沒有異常發生時,異常處理代碼會造成很少甚至沒有效能損失。因此,實現異常處理的程式運行起來比將錯誤處理代碼和程式邏輯混合起來的程式更有效率;
106、 帶有常見錯誤情況的函數應該返回0或者NULL(或者其他合適的值),而不是拋出異常。調用這個函數的程式通過檢查傳回值來確定函數調用是否成功;
107、 在拋出條件運算式(?:)的結果時必須要小心,因為提升規則可能使結果變成非期望的類型。例如當從同一個條件運算式中拋出一個Int型或double型的值時,條件運算式會把int型轉為double型;
108、 如果假設一個異常處理結束後,控制將回到拋出點後的第一條語句,那麼將是一個邏輯錯誤;
109、 下列情況下,catch處理器參數匹配所拋出對象的類型:
a) 實際是同一類型;
b) Catch處理器參數類型是所拋出物件類型的public基類;
c) 處理器參數為基類指標或參考型別,而拋出對象為衍生類別指標或參考型別;
d) Catch處理器為catch(…)
110、 異常處理器可以通過throw;重新拋出異常,將異常處理推給另一個異常處理器。但是執行一個在catch處理器之外的空throw語句將導致函數terminate被調用,程式將放棄異常處理並會立即結束;
111、 拋出在異常說明中沒有聲明過的異常將會導致函數unexpected被調用;
112、 在以下情況中,將會調用terminate函數:
a) 對於拋出的異常,異常機制找不到匹配的catch塊;
b) 解構函式試圖在堆棧展開時拋出一個異常;
c) 在沒有異常要處理時試圖重新拋出異常;
d) 調用函數unexpected將預設調用函數terminate;
113、 當一個異常被拋出但並沒有在一個特定的域內被捕獲時,該函數呼叫堆疊就會展開,並試圖在下一個外部try…catch塊內捕獲該異常;
114、 為了使程式更健壯,使用在失敗時拋出bad_alloc異常的new版本;
115、 auto_ptr能夠防止記憶體泄露,但在一些操作中它的使用是有限制的。例如,auto_ptr不能指向數組和標準容器類;
116、 將捕獲基類對象的catch處理器放在捕獲該基類的衍生類別對象的catch前面是一個邏輯錯誤。基類的catch捕獲所有由基類派生的類對象,所以衍生類別catch將永遠不會執行;
117、 要捕獲所有由try語句塊拋出的潛在異常,可以使用catch(…)。使用這種方式捕獲異常的一個缺點是在編譯時間不知道捕獲到的異常的類型,另外一個缺點是沒有指定的參數,因此沒有辦法在異常處理器中尋找該異常對象;
118、 標準異常層次是一個建立異常的好的出發點。使得程式員建立的程式可以拋出標準異常類,也可以拋出標準異常類的衍生類別,還可以拋出並不是由標準異常類派生的自訂異常類;
119、 由於沒能捕獲到異常而導致程式組件中斷,程式可能繼續佔用資源(例如檔案流或I/O裝置)。在這種情況下,其他程式將不能得到該資源。這就是“資源泄漏”;
120、 其他異常處理和錯誤處理技術:
a) 忽視異常;
b) 中斷程式;
c) 設定錯誤指標;
d) 測試錯誤條件,傳遞錯誤資訊以及調用exit;
e) 使用函數setjump和longjump;
f) 一些特定的錯誤有專用的方法進行處理,eg. New &new_handler;
121、 I/O流類的繼承關係;
122、 檔案開啟模式;
123、 reinterpret_cast的使用是與編譯器相關的,程式在不同平台上運行起來可能並不一樣。所以除非有絕對的必要,都不應該使用reinterpret_cast運算子;
124、 檔案可以在流對象超過了函數範圍或程式執行結束前由ifstream、ofstream、fstream的解構函式關閉,但是比較好的編程習慣是不需要檔案時使用clost顯示關閉;
125、 自引用類包含一個指向與自己相同的類對象的一個指標;
126、 Delete並不刪除指標,只是刪除所指空間;
127、 對在運行時大小動態變化的資料結構使用動態記憶體分配可以節省記憶體。但是要記住,存放地址的指標要佔用空間,而且動態記憶體分配會增加函數調用的開銷;
128、 如果要修改父函數的指標,需要聲明參數為指向指標的指標;
129、 Typedef為內建資料類型建立別名,使得程式更具可移植性;
130、 有時通過宏替換用內聯代碼實現的函數調用。這消除了函數調用的開銷。內嵌函式比宏更好,是因為其提供了函數的類型檢查服務;
131、 運算子”#”把替換文本的標記轉換為帶引號的字串;”##”運算子把兩個標記連在一起;
132、 宏或者符號常量的替換文本是同一行中#define指令標識符之後的所有剩餘內容(和宏的參數列表)。如果替換文本超過一行,那麼在該行最後加上反斜線(\),表示替換文本繼續到下一行;
133、 許多作業系統把命令列參數傳遞給參數列表中包含int argc和char *argv[]的main函數。參數argc是命令列參數的個數,argv是儲存實際命令列參數的字串數組;
134、 STL方法允許編寫通用程式,使得代碼不依賴於底層容器。這樣的編程方法稱為泛型程式設計;
135、 STL一般避免使用繼承和虛函數,而是採用泛型程式設計,利用模板來獲得更高的運行時效率;
136、 使用STL編程可以提高代碼的可移植性;
137、 STL容器的通用函數及只適用於首類容器的通用函數;
138、 首類容器(序列容器和關聯容器)的通用typedef;