C++ Programming language读书笔记
C語言,結構化程序設計。自頂向下、逐步求精及模塊化的程序設計方法;使用三種基本控制結構構造程序,任何程序都可由順序、選擇、循環三種基本控制結構構造。
模塊結構:"獨立功能,單出、入口"
C++ :是C的超集,類,封裝,重載,集成。
? 通用程序設計:容器, vector,list,dequeue,map, set
? ? ? ? ? ? ? ? ? ? ? 算法:排序,查找,,復制,合并,插入,刪除
? ?模板類, STL
?
?
http://blog.163.com/leonary_dy/blog/static/405528602009122103416862/
?
雖然很多人一再強調語言細節不重要,可我還是要花時間重讀經典。上次我認認真真讀這本書還要追溯到兩年前,現在我對C++的理解更深了一層,可以從書中讀到一些兩年前無法領悟的東西。
聲明:本筆記只記錄我以前不了解的部分,請初學者不要作為書籍摘要
我看的是TCPL電子版第三版,June 1997第一次印刷。這是網上最常見的版本,前兩頁書皮雖然寫著special版,其實后面版權信息那里寫的是第三版,書皮應該是有人后加上去的。電子書雖便宜,不過因為是初版,錯誤在所難免,類似auto_ptr的錯誤這種地方(詳見下面鏈接)。如果以前沒看過這本書,我建議買一本新版的紙質書,從權威、厚度、質量 來看性價比都遠超Effective?系列
http://blog.163.com/leonary_dy/blog/static/40552860200891123358999/
2009-2-22
前面三章隨便翻翻跳過,太基礎了
4.4?節開頭,“把unsigned int作為一個(32位的:我注)bit數組很合適,但是為了獲得更大的正數范圍而使用無符號整型并不是好辦法,這可能會因為隱式類型轉換帶來一些莫名其妙的問題”。其實這是有道理的,畢竟最大的unsigned int比signed int只大一倍而已,如果signed int可能溢出,unsigned同樣很危險。可是標準庫里面用的size_t公然定義為unsigned int,給代碼編譯帶來了N多warning,不爽啊不爽
0開頭的表示八進制,0x開頭表示16進制
4.6節,BS爺爺說char至少有8bit,我分明記得NetMM在水木說過標準規定的下限是7bit,鑒于此問題不嚴重,哪天無聊了再去翻翻最新的標準吧。wchar_t在C當中是一個宏,C++當中是一個關鍵字。在VC9當中有一個編譯選項可以開關
頭文件<limits>中可以找到各種上下限
枚舉類型是有長度限制的,對其做算術運算的時候需要留意一下。不過我在VC9上試了一下,/01?參數編譯出來的一個{0,1}?枚舉sizeof是4,/0d?當然也是4了。
4.9.3?講了一段命名規則的問題,使用范圍廣的名字應該長一些,很local的名字應該短一些,或許我們可以考慮通過某個名字的使用范圍和頻率的哈夫曼編碼確定其長度進行重構
4.9.6 Objects和左值,這一段有些莫名其妙,兩者沒有太大關系呀
附錄C3.4的最后結論我并不同意,全部使用char來避免char之間的賦值轉換并不是好辦法,移植的時候還是char的不確定性帶來的問題更多。實際上還是應該徹底避免用char進行運算,在java游戲的時代內存很緊張,不得不省吃儉用,遺留代碼中落下了很多不必要的麻煩。
5.6節最后?"Pointers to functions (§7.7) and pointers to members (§15.5) cannot be assigned to void*s."?為什么?為什么??
2009-2-24
6.1?節BS為我們展示一個計算器,實質上是一個詞法分析器,結構的層次化很嚴謹,從語法的角度來講我看這段代碼應該富富有余,但是實際上沒那么簡單。如何把一個繁瑣并不復雜的東西寫的簡潔清晰,這需要深厚的功底,這類的東西我向來是比較頭大的。
6.2.2?節提到了賦值順序的問題,這其實是一個很嚴肅的問題,BS說類似
v[i] = i++;
這樣的代碼編譯器應該作出警告,但是我把VC9開到W4也沒有警告。
, && ||?這三個操作符可以保證左操作數的賦值先于右操作數,這其實是一個序列點概念(sequence point),BS在這里沒有進一步的展開講解,考慮到這本書的定位和目的。操作符優先級曾經是N多腦殘考官的最愛,大多數都不是問題,例外在于&&?和?||?之間是有順序的,并不像我以前想象的那樣同級關系從左至右依次計算。在a || b && c的時候,該加括號的地方要加括號。
2009-2-25
6.2.8節,提到了?T(v)?相當于static_cast<T>(e),而上面講的(T)e?則是根據上下文選擇三種cast之中的一種(不會dynamic_cast),我一度還認為T(v)?和(T) v?是等價的
6.3.3 BS爺爺說他也跟我一樣不喜歡do-while循環
7.2.1節又是輕描淡寫的提到了一個很詭異的地方,和參數類型不一致的常量在參數傳遞的時候可以綁定到const引用類型的臨時變量上,這是臨時變量的用途之一,涉及到臨時變量的生命周期、隱式類型轉換等問題。
前兩天發現臨時對象的一個問題: 對于T a = v ;??在C++標準當中要求語義上生成一個臨時對象再拷貝構造a,而不是T a(0)這種直接構造a。然而VC擴展當中不檢查T的拷貝構造函數,即使拷貝構造的參數非const也可以編譯通過。打開/za選項就是按照標準的說法給出error
隱式類型轉換當中的類型提升不包括int=》long的轉換,需要注意。BS爺爺在前面說過如果沒事只用int做整型就好,除非歷史遺留問題,不要用short long之類
2009-2-28
8.2.?類名也是namespace的一種,由于類的聲明、定義先入為主,導致后來我接觸namespace時在這一點上我的理解不夠深。namespace?定義的scope和class定義的scope有很多相似之處,聲明必須在scope標記的?{}?當中,定義可以用namespace::name?形式。同一scope內的名字引用時不需要namespace::?前綴
using namespace xxxxxx;?是個不太好的習慣,失去了namespace應有的意義
using xxxxspace::xxxx;才是正確的做法。
BS在8.2.3節肯定了我的看法,但是很遺憾我們現在的代碼中充斥了這樣的用法,也許對于一個十萬行量級的項目來說,這種用法帶來的方便足以抵消引入的問題。在8.2.8節寫到:
理想情況下,namespace應該:
1 xxxxx
2 xxxxx
3?不應該讓用戶使用太過繁瑣
如果很繁瑣,那實際上暴露出設計上的缺陷
8.2.6?很通俗、簡要的介紹了ADL(Argument Dependent Lookup),也叫做Keoniglookup,在C++標準當中唯一一個以人名命名的規則,Andrew Koenig。這個規則實際上在模板部分有著更為深刻的應用背景,但在本節沒辦法講的那么復雜。詳細情況請自行谷歌,譬如下面這個
http://blog.pfan.cn/vfdff/35154.html
2009-3-4
最近兩天看的東西開始讓我懷疑以前究竟有沒有讀過TCPL,很多明明在書上寫著的東西我到現在才知道
9.2?節講了Linkage的一些規則,9.2.1小標題前面提了一句說在C和早期的C++當中static的意思是“internal linkage”,新寫代碼就不要把static這樣用了。非extern的const變量就可以保證internal linkage,如果需要變量可以使用匿名的namespace,當然使用全局變量這本身就是一個很土的做法,很容易被人鄙視的。水木精華區x-5-6-38提到匿名namespace?中的變量是external linkage
http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FCPlusPlus%2Ffaq%2Flinker%2FM.1149790415.l0
ODR(one-definition-rule),本應該規定自定義類型、模板在一個程序中只能定義一次。但是由于頭文件的問題,這個規則在C++當中就變成了同一類型的定義在一個編譯單元中只有一次,在不同編譯單元中必須一致。
2009-3-7
10.2.4節 靜態成員變量可以是自身類型,也就是說:
class Date{
static Date default_data;
};
這樣的代碼看起來很新穎,我以前知道的只有通過指針嵌套,不過這種用法的使用范圍似乎沒有指針那么多。
10.4.6.3?節“Note that the default copy constructor leaves a reference member referring to the same object in both the original and the copied object.? This can be a problem if the object referred to is supposed to be deleted.”??就是我前面博客中寫的“關于引用類型的成員變量”
http://blog.163.com/leonary_dy/blog/static/40552860200871811858224/
看到這里的時候我應該感嘆英雄所見略同,還是自己以前讀TCPL太過膚淺呢。
10.4.9節最后講的使用一個靜態函數初始化,通過一個靜態變量標記是否已經初始化。可是這句話“the really difficult case is the one in which the first operation may be time- critical so that the overhead of testing and possible initialization can be serious.”???我沒看明白這句話說的是什么問題,以及21.5.2相比10.4.9這個辦法優越在哪里。留待以后再說吧,看了一下VC的初始化全局變量cin?的時候使用的也是編譯器提供的特性,21.5.2也說編譯器的實現要更為簡單可靠。
11.2.4?最后這里的描述似乎有些問題,成員函數的操作符重載和全局函數的操作符重載是可以ambiguous的,并不存在成員函數高于全局函數的問題。并且一視同仁也避免了同名函數之間的“隱藏”。
但是有一組operator是例外,那就是new和delete系列,成員operator new和delete的優先級高于全局的new、delete,內存分配操作符還有一點與眾不同之處在于他們都是靜態的,即使沒有顯式static聲明。
11.3.1 操作符操作成員的才是成員函數 (*=. +=), ? (+ - * / 這些不必,可以弄到全局)
11.3.4?節,為什么拷貝構造的參數必須是引用? 因為參數傳遞本身就會涉及到一次拷貝,如果不是引用類型那拷貝構造將成為一個無限遞歸調用!!
下面關于complex x=2;的描述可能有些問題,詳見水木C++版111文,太細節,不看也罷
11.4 Conversion Operators?隱式類型轉換,比較容易出問題的一個地方,但是用于寫一些底層工具的話又非常方便,也不可避免,用的時候需要多加小心。11.3.5最后提到成員操作符只 能用于左值變量,并不會通過隱式類型轉換生成一個臨時對象,這大約是為了避免隱式類型轉換不小心帶來的問題吧。
2009-3-14
11.4.1?最后的那個例子,可以實現某種程度上的按返回值重載函數,詳情見RC的這個帖子
http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FCPlusPlus%2Fcodeandtrick%2FM.1037702476.F0
11.5.1
作為類函數:
1.訪問類的私有部分
2.在類的作用域
3.經由對象指針去操作。
友元只有前兩項.
?最后那個例子似乎有些問題
class?X {
friend?void?f();
int?i;
};
?
void?f() {
X a;
a.i = 0;
}
TCPL上說f()?必須要在聲明為友元之前聲明一次,否則不視為X的友元函數,也就是說a.i?=0那里應該編譯錯誤。但是我查了一下標準中friend一節并未提及這一限制,在VC9和Comeau 4.3.10上編譯也沒有遇到問題。
?
可以進行隱式類型轉換,作為全局的, 類的成員函數,不能運行對象進行轉換 clas A{ A(int a); print()}; 12.print不行,不能進行A(12).print()的轉換。
可以定義全局的,如果再需要訪問類的內部,定義友元。
必須是成員函數的是,構造函數,析構 和虛函數
?
?
12.4.2 “Since Ival_box provides the interface for the derived class, it is derived
using public. Since BBwindow is only an implementation aid, it is derived using protected ”
這里解釋了什么時候應該public繼承,什么時候應該protected繼承。protected繼承和private繼承的時候外界是無法通過子類對象訪問其父類成員的,同時從子類向父類的指針、引用的隱式類型轉換也被禁止。
?
2009-3-21
進入第13章,發現腦細胞有些不夠用。模板和繼承是C++中代碼復用的兩大法寶,很重要的一點是需要搞清楚什么場合用什么技術。13.6.1簡單的分析了一下“我們操作一堆具有共同屬性的對象時,如果這些對象之間有某種繼承關系,就應該使用虛函數完成,反之應該使用模板。”這一段是我進入13章 以來第一次記起來上次曾經看過,前面的特化、偏特化、模板繼承的用法通通沒有印象了。但是這也應該是使用模板最為關鍵的一個問題了,在現實當中,我目前的 做法是:沒有很充分的理由需要使用模板的時候,優先考慮通過繼承的方式抽象。模板帶來的可讀性上的代價必須要考慮進去。對于我來說,同樣的一段代碼用模板 表達出來要比用繼承表達要難懂的多。也許隨著水平的提高模板相對繼承的使用成本會降低一些。
?
這一部分無疑是C++當中最耗費腦細胞的環節,完全沒有前面幾章一馬平川的感覺。我不得不放棄了前面一邊看一邊寫評語的習慣,只能先看過一遍再返回來慢慢理解。
13.2.3節模板參數可以是模板,還可以是常量,通過常量表達式或external linkage的對象/函數的地址表達。常量參數一個很強大的武器,通過模板函數的重載、遞歸某些吃飽了撐著的人開發了各種各樣的編譯期運算的tricks,這里僅舉一個小例子,一個可以返回任意類型數組元素個數的函數
template<class?T,?int?n>
int?Func(T (&X)[n])
{
return?n;
}
更多的例子可以去boost里面找,或者看看那本什么模板元編程的書
?
13.3.2節 在函數重載的時候普通函數比一個模板函數的優先級要高。在進行模板參數推導的時候,涉及推導的參數是不允許進行隱式類型轉換的。但是不涉及推導的部分可以轉化譬如本節最后的例子:
template<class T> class B{};
template<class T> classD : public B<T> {};
template<class T>void Func( B<T>*);//?這里可以進行D<int>*到B<int>*的轉換
//?只要能推導出一個確定的T類型即可
?
與之相反的例子在13.6.3節,模板和類的繼承兩者混用會導致嚴重的問題,一個是運行時一個是編譯期重用。D是B的子類,D*可以隱式轉換到B*并不意味著vector<D*>可以隱式轉換到vector<B*>。如果有這種需求,我們可以使用13.6.2節給出的帶模板的構造函數來進行轉換。
?
13.4.1節,這里書上出現了一個比較大的問題,模板參數的默認值只能用于構建模板類,而不能用于模板函數或者模板類的成員函數,也就是說本節這個compare函數是編不過的。在VC9上編譯時報告error C4519,查了一下潘愛民版的依然存在這個問題。至于原因,我在這里找到了線索
http://www.open-std.org/JTC1/sc22/wg21/docs/cwg_defects.html#226
BS爺爺于2000年提交了一份提案,建議加上模板函數的默認參數,但是被否決了。原因是參數可以推導、函數可以重載,如果還可以默認類型的話問題將變得非常復雜。
?
13.5?模板特化有一個順序問題需要注意一下。如果某個模板在特化的聲明之前已經進行過實例化,會產生一個編譯錯誤。如果在偏特化之前進行過實例化,VC9當中可編譯通過,按照未偏特化的版本進行實例化。
另外GCC實例化的時候還有一個不討人喜歡的"feature",pragma pack按照實例化位置的pack進行pack,而不是定義模板類的pack,具體情況看這里:
http://chen3feng.spaces.live.com/blog/cns!FEF0D083246BBED0!1880.entry
因為實例化可能發生在各種地方,這種順序帶來的問題可能還有。
?
?
13.5.1最后那句話翻來覆去看不懂…… 另外還有兩個問題:
1.?模板函數是否可以偏特化?(ms不可以)
2.?剛才在哪兒看見一眼,模板函數的返回類型也是其signature的一部分,ms還可以通過返回值deduce模板參數(這是在標準上看到的)
?
13章大體上看完了兩遍,現在知道了不少可以怎么用的語法,但還有很多不知可不可以用的語法沒有搞清楚。
13.2
·通過某個實例化的模板來理解、調試代碼比較方便,不至于太抽象
·編譯器保證同樣的模板參數只產生一個實例化的類型/函數
·除了類型,還可以用幾種編譯期常量作為模板參數(但是string literal不可以)
13.3
·函數模板的參數可以在調用的時候做推導
13.5
·模板類可以通過偏特化+繼承減小生成的代碼量
?
2009-03-28
14章 異常處理
14.1.1 C++異常處理只用于同步的異常,硬件中斷等異步的異常不在考慮之內,譬如除以0等情況。
異常處理的設計原則是為了分離錯誤檢測代碼和錯誤處理代碼
通常情況下使用指針或引用類型catch異常,這樣不會在拷貝的時候丟失信息。
?
14.4.1就是大名鼎鼎的RAII原則,盡可能的利用構造函數和析構函數來管理資源的獲取和釋放,避免構造函數拋出異常時未調用析構函數造成的內存泄漏等問題,這也就是通常所說的異常安全。
14.4.2 Auto_ptr這一節的電子版有問題,中文版部分糾正,詳情見我那篇《關于auto_ptr的三個問題》
?
問題:
?
譬如說標準庫當中的異常,使用的代碼當中沒有進行catch,會出現未捕獲的異常嗎?(Yes)
聲明了throw類型的函數和沒聲明的函數是同一個函數嗎?如果不是的話如何重載?(必須保證同一函數的所有聲明、定義的Exception Specification一致,否則將是一個編譯錯誤。但是這個限制比較雞肋,標準規定跨編譯單元的聲明檢查不是必須,否則嚴重影響編譯成本,因此在VC9當中即使同一編譯單元默認也不做這一檢查,打開/Za選項才認為是一個編譯錯誤)
?
14.6.3未捕獲異常的映射,在函數的exception specification中加一個std::bad_exception,如果出現指定以外的異常拋出,則自動轉換為一個std::bac_exception而不是直接terminate()
?
14.11?建議,第四條,并不是每個程序都需要考慮異常安全,不要拿著錘子就想到處都敲一敲,KISS也很重要,一定要因地制宜,考慮實際情況編寫合適的代碼。
?
?
15.2.5
曾經聽人說起在虛基類當中不應該放任何成員變量,但是原因并不能令人信服。這一節提到如果認為虛繼承的性能開銷不可接受的話可以把不含有成員變量的虛基類改成非虛的,不會有任何問題。在設計C++的時候沒有把所有的基類繼承規定為虛的是出于歷史原因的考慮,不應該為不需要的東西付出額外的開銷。現在來看,這個原因造成性能瓶頸的可能性太低了。如果有,這樣的應用基本也還在用C,沒有轉移到C++陣營。
?
15.3.2
對于private繼承,子類向父類的指針轉換并非完全禁止,在類的內部或者友元函數當中還是允許的。
15.3.2.1?如果多重繼承當中有多條路徑可以訪問某個父類的成員,則允許訪問的規則優先于禁止訪問。
?
15.4.1?本節開始的例子指出dynamic_cast在downcast類型不對的時候返回空指針,在upcast訪問權限不夠的時候也返回空指針。不過我在VC里面試了一下,例子給出的那段代碼直接就給出了編譯錯誤。這里可能是委員會后來作了修訂,upcast權限不足的直接給error,看下面的例子
class?Base{
};
class?D :?virtual?protected?Base{};
class?DD :?public?D,?virtual?public?Base
{};
?
?
int?main()
{
DD d;
D* pD = &d;
Base* pB =?dynamic_cast<Base*>(pD);
return?0;
}
因為是私有繼承,通常情況下的D*是不可以轉換為Base*的,但是DD從Base公有繼承了一次,因此可以由DD*轉換為Base*。pD實際指向的是一個DD對象,也就是說按照RTTI的規則來看轉換為Base*比返回0更為合理。然而訪問權限這個東西其實只是一個編譯期的概念,并不存在于運行期,如果到了運行期是無法判斷出是否有足夠的訪問權限。但是在編譯期又無法決定pD究竟是一個D的指針還是DD的指針。
我能想到的就是這個原因,誰有興趣可以去翻一下C++標準或者提案,我不打算深究了。
btw:本節例子之前的那句話描述有些問題,應該是這樣子" if p is of type T* or T is an accessible base class of p"
?
15.4.1?本節后面一個例子的trick有點兒意思,用多繼承的方式把一個concrete類型包在polymorphic類型當中然后再轉回來,這是解決concrete類型不能dynamic_cast的一個workaround,看起來總比static_cast要類型安全一些。
?
15.4.2 dynamic_cast失敗除了因為類型不對,還有可能是多繼承當中virtual?和非virtual并存出現ambiguity。如果在upcast當中出現這種情況,實際上在編譯期就可以知道的,我認為完全沒必要搞到編譯期返回空指針,這個設計欠妥。
?
15.4.2.1?從virtual base做downcast不能用static_cast,既然dynamic_cast可以做這件事,既然你已經不怕麻煩用了static,不妨改成dynamic吧。但有個問題,如果是非polymorphic基類被virtual繼承的話,那就兩種cast都搞不定了。當然基類不polymorphic也是比較失敗的設計,沒事不需要用罷了。
?
15.6.2?構造函數不可以virtual,但是也有辦法作出同樣的效果,那就是本節代碼示范的通過virtual函數clone自身對象,這貌似是設計模式中的一種。
?
15.6 為什么如下虛函數沒有調用子類的呢
class PurvitualBase1{
public:
? ? ? ? virtual PurvitualBase1* new_expr(){return new PurvitualBase1();};
? ? ? ? virtual PurvitualBase1* clone(){return new PurvitualBase1(*this);};
private:
? ? ? ? int b;
};
class??PurvitualDeriv1: public??PurvitualBase1{
public:
? ? ? ? virtual PurvitualDeriv1* new_expr(){return new PurvitualDeriv1();};
? ? ? ? virtual PurvitualDeriv1* clone(){return new PurvitualDeriv1(*this);};
private:
? ? ? ? int bb;
};
void user2(PurvitualBase1 *pb, PurvitualDeriv1 *pd)
{
? ? ? ? PurvitualDeriv1 *pd2 = pd->clone();
? ? ? ? PurvitualDeriv1 *pd3 = pb->clone();
? ? ? ? pd2->output();
? ? ? ? pd3->output();
}
PurvitualDeriv1 testv1;
PurvitualBase1 *ptestpb1 = &testv1;
user2(ptestpb1, &testv1); //為什么這個函數調用時,user2中的虛函數clone沒有用子類的clone,卻用了父類的clone呢?按理說應該是虛函數調用子類的啊
轉載于:https://www.cnblogs.com/virusolf/p/5476616.html
總結
以上是生活随笔為你收集整理的C++ Programming language读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机正确的坐姿教案,礼仪课坐姿教案.d
- 下一篇: 怎么退出自适应巡航_20款奔驰GLE35