侯捷《C++/OOP/GP/DP》講座心得

導讀:作者通過侯捷的講座,總結了一些學習心得,對一些常用的代碼和設計做了非常通俗易懂的剖析,非常有幫助。文章內容如下:

很高興侯捷老師又來公司了,給我們上了四天非常生動的技術講座,受益匪淺,現在我簡要介紹一下我的學習心得,與大家分享。這次講座主要集中在《C++/OOP/GP/DP 》主題,針對有一些編程基礎的工程師,對一些常用的代碼和設計做了非常通俗易懂的剖析,非常有幫助。當然更深入的理解還需要結合多種技術名著來學習,我結合我的理解以及自己的學習和開發的經驗介紹一下C++/OO/Template 以及Design Pattern的理會,考慮到講座的性質,我並不直述本次講座的內容,歡迎批評指正。

侯捷老師的講座基本是講述他多年來在C++領域的研究成果,基本大部分都可以在他的書籍和網站上能讀到,但是考慮到最近幾年軟件技術的蓬勃發展,如Design Pattern的更廣泛應用,又有許多心得,基本上是較爲泛的基礎的層面,並結合實際代碼和應用,對實際項目開發非常有益。下面我逐個主題泛泛地講一遍。

面向對象中的合成(Composition)和繼承(Inheritance)關係

通常擴展一個類的功能主要有兩種方式,一種是大家很熟悉的繼承(inheritance),另一種就是合成(composition),很多初學OO(面向對象)並有一些經驗都很容易搞混這個的區別,其實很簡單,繼承是解決Is-a的問題,而合成是解決Has-a的問題。比如說小鳥有兩個翅膀,就是合成,而鳥是一種飛禽,就是繼承了,設計一個“小鳥”的類,它繼承自”飛禽”,就具有“飛”的特性,但要用合成的方法“包含”一個“翅膀”的類才具有真正“飛”的功能。

別看這兩個定義很簡單,其實很多人都犯過錯誤,包括Java類庫的設計者,他們就把Properties直接“繼承”自Hashtable了,這裏其實應該用“合成”。

講到合成,就應該說說聚合(Aggregation),它是描述整體和局部的關係,合成其實是一種“強烈”的聚合,它與局部具有相同的生命週期,“容納”局部的“對象”,而聚合只是“容納”局部的一個“指針”。比如說,人和腦袋就是合成,而汽車與發動機就是聚合,改裝汽車可以任意替換更好的發動機,而人的腦袋就不行(目前是這樣:)

聚合在UML中是以空心棱形的箭頭表示,合成是以實心棱形的箭頭表示。

還有一種關係叫委託(Delegation),委託是一種讓合成(composition)變得像繼承(inheritance)的複用能力一樣強大的方式。(a way of making composition as powerful for reuse as inheritance [Lie86, JZ91] )在委託中,兩個對象在處理一個請求的時候發生關聯:一個接收的對象委派操作給它的委託對象。這跟子類(subclass)延遲請求(deferring requests)給它的父類(parent class)來實現類似。但是在繼承裏,一個被繼承的操作(inherited operation)通過this成員變量能夠經常引用到那個接收的對象。爲了在委託裏達到同樣的效果,接受者傳遞它自己給它的委託者,以便被委託的操作能夠引用到這個接收者。

再說一下繼承(Inheritance),它是將基類(base-class)所有一切(包括 private )都繼承下來,所以假如你想實現一個新的類,只想繼承一部分,就用合成(Composition)別用繼承。或者更進一步來講,如果你想改造一個類,想改造一些接口(interface),也建議用合成,通過轉調內部對象的方法實現,別用虛函數(virtual function)。這是非常符合最基本的OCP設計原則(Open-Closed Principle ,開閉原則)的方式了。

類的構造(Constructor)和析構(Destructor)

類的構造和析構是最基礎的知識了,任何一個類的對象產生和銷燬都必須有這兩個步驟,但是它們是如何工作的,編譯器是如何製造缺省的ctor和dtor的,估計少有人關注了。

一個類的對象的產生,會依次從它最裏面的類開始構造,同一個類會跟據內部類成員定義的順序依次構造。類對象的銷燬的過程則相反。基類的構造器會在用戶定義的ctor之前調用,基類的析構則是在用戶定義的dtor之後進行。熟悉這些過程,非常有利於設計出優秀的類庫,也不容易出現內存泄露和資源耗盡等問題。下面舉個例子更容易理解:

 

class A { public: A(); ~A(); };

class B { public: B(); ~B(); };

class C { public: C(); ~C(); };

class D : public A, B {

public: D() { init(); } ~D() { release(); }

private: void init(); void release(); C c;

};

 

上面的定義中D類的ctor構造過程如下:

A::A();

B::B();

c.C::C();

D::init();

 

D類的dtor析構過程如下:

 

D::release();

c.C::~C();

B::~B();

A::~A();

 

更復雜的繼承關係以及多重繼承的構造和析構過程類似,有興趣的人可以寫程序測試:)

還有一個問題,編譯器會在什麼時候自動產生ctor和dtor的呢,又是如何產生的呢

其實很簡單,當你沒有寫缺省構造函數(default constructor)和缺省析構函數(default destructor)的時候,編譯器就會給你自動生成一個,換句話說,任何類都有構造函數和析構函數,雖然有時候什麼都不做,還有複製構造函數(copy ctor)也會自動生成。但是如何產生會跟你的類的成員有關。如果成員都是原生類型,還有如果類成員也全部爲原生類型,ctor將只會跟普通變量定義的初始化一樣,給一個初值,dtor則什麼都不做,copy ctor則會使用內存複製(memcpy)的方式複製對象。如果成員包含一個或多個類成員,而且至少有一個類成員定義有缺省構造方法,則產生的ctor會依次調用每個成員的ctor。dtor和copy-ctor產生方法類似。(詳見《Inside the C++ Object Model》)

多態(Polymorphism)和虛函數(Virtual function)

多態是面向對象的基本特性,C++裏是通過virtual關鍵詞來提供的,它是通過在類對象里加入vtbl虛函數表來實現的,這一點相信大部分程序員都很清楚,不過怎麼做到多態功能估計瞭解的不多了。要詳細瞭解,還請閱讀《 Inside the C++ Object Model》一書,下面簡單介紹一下原理。

一般編譯都會給包含有virtual function的類頭部(有的編譯也會放到底部,比如 VC )增加一個成員vptr指針,指向一個 vtbl 虛函數表,爲定長數組,大小是所有帶virtual的函數數目再加 1 。虛函數指針從 vtbl[1] 開始,按照定義順序,指向特定的函數實現。如果子類定義了父類中帶virtual的函數,則vtbl相應的指針指向子類的函數實現,否則就指向父類的實現。另外再說明一點,其中vtbl[0]是有別的用途,用來存放類型信息,做dynamic_cast 用途。

仍以上面的例子爲例,如下的代碼編譯器是如何處理:

A *p = new D(); // up-cast

p->vfunc1(); // 編譯器會轉化爲如下代碼

(*(p->vptr))[n](p); // n 爲編譯期確定的固定數,即相應 virtual function

// 所在位置

需要牢記一點,總是讓 base class 擁有 virtual destructor 。因爲當如下操作時

delete p;

如果A和B的析構函數不是虛函數,則不會調用子類D的dtor,就有可能造成內存泄露或者資源沒有釋放等嚴重問題。如果給base class加了virtual dtor,由於有多態的特性,就會自動調用subclass的dtor,接下來就會上面的介紹,依次調用各個base class的 dtor,因而就沒有問題了。

C++ template即模板技術是實現泛型編程技術的,能夠使得寫一份代碼可以應用到類似用途的不同地方。模板技術其實原理比較簡單,但是使用還是比較複雜的,看看 STL 源碼就知道了,如果還不相信,再看看Boost代碼好了,會把你搞得暈頭轉向。候捷老師把這個技術講解得非常清楚易懂,還具體分析了 STL 裏各個大組件的運作原理,我這裏就不講述了,基本都是源碼的剖析,請閱讀候捷老師的《STL源碼剖析》一書。

C++template和STLcontainers

在講解STL中用模板如何實現function class(實現函數功能的類,在stl_functions.h)中,有這樣一段代碼

 

template <class _Operation>

class binder1st

: public unary_function<typename _Operation::second_argument_type,

typename _Operation::result_type> {

protected:

_Operation op;

typename _Operation::first_argument_type value;

public:

binder1st(const _Operation& __x,

const typename _Operation::first_argument_type& __y)

: op(__x), value(__y) {}

typename _Operation::result_type

operator()(const typename _Operation::second_argument_type& __x) const {

return op(value, __x);

}

};

 

有人提出上面_Operation op;爲什麼不定義爲引用,如_Operation &op;呢。我的想法如下,因爲構造方法爲

binder1st(const _Operation& __x, // 這裏爲const類型

const typename _Operation::first_argument_type& __y)

傳入的參數爲const類型,這時不應在本調用方法(這裏是構造方法)之外使用引用或指針指向它,因爲帶const T &t的參數一般情況都視爲臨時對象,很有可能是在方法調用的時候臨時產生的,比如說自動轉型產生的臨時對象都是const T &類型,它的生命週期都在此方法調用期間內,方法調用結束即被銷燬,所以就不能在方法外部用引用或指針之類指向它了。舉例來說,可能比較容易理解,比如大家常用的string類,假如有一個方法和調用如下:

void func(const string &s);

func("abcdfd");

這個時候就會出現自動轉型行爲,編譯器會做如下處理

func(string("abcdfd"));

即產生一個臨時的string對象,這個對象是以const類型傳入的。假如你的方法定義改成如下

void func(string &s);

現在大部分編譯器嚴格的處理都會報錯,以前的VC6就不會,但是好像最新的VC2005 也報錯了。

這是其中一個原因,還有一個原因我認爲是_Operation類只是一個 function class,沒有成員,所以做複製構造也不會有多大的開銷,基本不會影響效率。再加模板和inline方法的處理,編譯器經過優化,應該都不會產生臨時對象了,所以也不必用引用了。不過我覺得最重要是上面第一個原因。

內存池和小對象分配器(memory pool,small object allocator)

候捷老師在內存池方面也有很豐富的研究經驗,他基本將目前主流的內存池實作都剖析了一遍,介紹了它們各自的特點,以及如何與上層框架的配合。內存池是一個非常基礎也非常關鍵的底層庫,一般大型的框架自己都帶有一個內存池庫,比如STL、MFC等。即使在目前內存比較便宜的今天,內存資源也是最寶貴的系統資源之一,設計一個優秀的內存池對提高系統的效率和穩定性都非常有幫助,尤其是設計專門針對小內存對象(一般低於128字節)的分配器非常重要,因爲這樣對象分配和釋放非常頻繁,只用簡單的malloc()和free()來處理非常影響效率,不是一個優秀的設計。下面我簡要介紹一下目前主流內存池設計的特點,以及我自己的想法,另外再加一個候捷老師沒提到ACE中的內存池管理器的設計特點。

SGI STL中的內存分配器(allocator)

SGI STL 的 allocator 應該是目前設計最優秀的 C++ 內存分配器之一了,它的運作原理候捷老師在《STL源碼剖析》裏講解得非常清楚。基本思路是設計一個free_list[16]數組,負責管理從8 bytes到128 bytes 不同大小的內存塊(chunk),每一個內存塊都由連續的固定大小(fixed size block)的很多chunk組成,並用指針鏈表串接起來。比如說

free_list[3]->start_notuse->next_notuse->next_notuse->...->end_notuse;

當用戶要獲取此大小的內存時,就在free_list的鏈表找一個最近的free chunk回傳給用戶,同時將此chunk從free_list裏刪除,即把此chunk前後chunk指針鏈結起來。用戶使用完釋放的時候,則把此chunk放回到free_list中,應該是放到最前面的start_free的位置。這樣經過若干次allocator和deallocator後,free_list中的鏈表可能並不像初始的時候那麼是chunk按內存分佈位置依次鏈接的。假如free_list中不夠時,allocator會自動再分配一塊新的較大的內存區塊來加入到free_list鏈表中。

可以自動管理多種不同大小內存塊並可以自動增長的內存池,這是 SGI STL 分配器設計的特點。

Loki中的小對象分配器(small object allocator)

Loki的分配器與 SGI STL的原理類似,不同之處是它管理free_list不是固定大小的數組,而是用一個vector來實現,因此可以用戶指定fixed size block的大小,不像 SGI STL是固定最大128 bytes的。另外它管理free chunks的方式也不太一樣,Loki是由一列記錄了free block位置等信息的Chunk類的鏈表來維護的,free blocks則是分佈在另外一個連續的大內存區間中。而且free Chunks也可以根據使用情況自動增長和減少合適的數目,避免內存分配得過多或者過少。

Loki的分配器使用也不太一樣,可以直接調用,如下

SmallObjAllocator myAlloc(2048, 256); // 參數 1 爲 chunk size

// 參數 2 爲 max fixed size block size

// 可以用於小於 256 bytes 的各種大小內存的分配

void *p1 = (void*)myAlloc.Allocate(20);

void *p2 = (void*)myAlloc.Allocate(100);

void *p3 = (void*)myAlloc.Allocate(256);

void *p4 = (void*)myAlloc.Allocate(300); // 大於 256 將轉交給系統處理

myAlloc.Deallocate(p1,20);

myAlloc.Deallocate(p2,100);

myAlloc.Deallocate(p3,256);

myAlloc.Deallocate(p4,300);

MFC的CPlex和CPtrList(扮演memory pool角色)

CPlex任務比較簡單,只負責管理一大塊memory並串接起來,用戶每次獲取都返回一大塊。分割由使用者(如Collection classes , CFixedAlloc)將這一大塊切割爲一個個小的內存塊。

CPtrList 則負責管理這些切割後的小內存塊,這一點有點類似 Loki 中的 free Chunks ,不過要簡單多了。

MFC還有一個類叫CFixedAlloc,它是提供給應用類來分配固定大小(根據具體應用類的大小)的內存分配器。通過在應用類中定義 DECLARE_FIXED_ALLOC(Foo)和IMPLEMENT_FIXED_ALLOC(Foo)兩個宏來實現。

Boost的object_pool

Boost中的object_pool也是一個可以根據用戶具體應用類的大小來分配內存塊的,也是通過維護一個 free nodes 的鏈表來管理的。可以自動增加 nodes塊,初始是32個nodes,每次增加都以兩倍數向 system heap 要內存塊。 object_pool 管理的內存塊需要在其對象銷燬的時候才返還給 system heap 。

ACE 中的 ACE_Cached_Allocator 和 ACE_Free_List

ACE 框架中也有一個可以維護固定大小的內存塊的分配器,原理與上面講的內存池都差不多。它是通過在ACE_Cached_Allocator中定義個Free_list鏈表來管理一個連續的大內存塊的,裏面包含很多小的固定大小的未使用的區塊(free chunk),同時還使用ACE_unbounded_Set維護一個已使用的chuncks,管理方式與上面講的內存池類似。也可以指定chunks的數目,也可以自動增長,定義大致如下所示:

 

template<class T>

class ACE_Cached_Allocator : public ACE_New_Allocator<T> {

public:

// Create a cached memory pool with @a n_chunks chunks

// each with sizeof (TYPE) size.

ACE_Cached_Allocator(SIZET n_chunks = ACE_DEFAULT_INIT_CHUNKS);

T* allocate();

void deallocate(T* p);

private:

// List of memory that we have allocated.

Fast_Unbounded_Set<char *> _allocated_chunks;

// Maintain a cached memory free list.

ACE_Cached_Free_List<ACE_Cached_Mem_Pool_Node<T> > _free_list;

};

 

設計模式

最後一個主題重點講講設計模式,設計模式現在已經應用很廣泛了,可以說是無處不在。設計模式現在對程序員是非常的重要,甚至到了不懂設計模式就不算真正的程序員一樣。不過設計模式卻又是非常高階的理論,需要有多年的編程經驗才能真正領悟,所以學習起來非常頭痛。因爲它道理非常簡單,但是卻非常抽象,候捷老師通過一大堆實際案例給我們逐個講述了幾個常用的模式的區別和用法。設計模式最經典最權威當屬著名的有字天書 GoF 的《 Design Patterns 》了,我結合自己學習和實踐的體會介紹一下幾個模式。

結構型模式之Composite(合成模式)

GoF的定義:Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects.翻譯爲中文大致意思是:將對象 (s) 組成爲樹狀結構,用以表示“局部 - 整體”的層次體系,使得讓 clients 可以以一致的方式對待“單個對象”和“合成對象”。

比較典型的例子就是文件系統中“文件”和“目錄”的關係,還有Windows窗口系統也是,在一個窗口中還可以開另一個窗口,多個窗口組合成的窗口還可以當作一個窗口放入另一個窗口中,比如在Word中打開多個文檔就是這種情況。Composite模式的好處就是使得clients調用簡單,可以用一致的接口處理單個對象或者多個單一對象組合成的對象。

實例:Java swing library 中 Component , Label , Container就是Composite模式的應用。其中 Label 和 Container 都繼承自 Component ,但是Container中只是一個存放Component的數組,所以Container中就可以放很多Component,比如ScrollPane就是繼承自Container,它可以放Label,還有List,Scrollbar等等,甚至還可以放一個ScrollPane,所以就達到了Composite模式的效果,簡化了 client的使用。

結構型模式之 Decorator(裝飾模式)

GoF 的定義: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 翻譯爲中文大致的意思是:以動態的方式給一個對象添加一些額外的職責,使得不必進行 subclassing 就能擴展功能。

Decorator模式與Composite 模式的區別就是它只內含一個 component object field ,而 Composite 則內含一個 collection of component field 。 Decorator 負責將一個對象“裝飾”起來,做一些“改造或者擴展”,提供額外的功能,它只針對一個 class 。而 Composite 是一組“類似”的對象及其容納它們的容器一視同仁,使得 client 更簡單地處理單個對象和一組對象。它們目的不一樣。

實例: Java IO library 中 BufferedReader , Reader 之間使用的就是 Decorator 模式,其中 BufferedReader 繼承自 Reader ,同時它內部含有一個 Reader 引用,它是通過另一個 Reader 對象構造而來,因此就爲 Reader 提供更多的功能,如帶緩衝的 Reader 。使用非常簡單,只需要如此定義:

Reader in = new BufferedReader(new FileReader("test.txt"));

就爲文件讀取增加了帶緩衝的 IO 功能,非常方便。還可以多個 Decorator 的類組合使用,可以提供更強大的功能,多使用一下 Java IO library 就會體會到。

行爲模式之Observer(觀察者模式)

GoF的定義: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻譯爲中文大致意思是:在 objects 之間定義一個“一對多”的依賴關係,使得當這個 object 改變狀態時,所有依賴它的 objects 都能獲得通知並自動更新。

Observer是用於做“通知”用途的,就像“ publish-subscribe ”,它能夠做到註冊需要通知的對象,並自動通知它們來更新,它們都是被動地被通知,而不是主動觀察。

實例:MFC 裏 CView 和 CDocument 之間就是一個觀察者模式, CView 是 Observer 即觀察者, CDocument 是 Observable 即被觀察者,當 CDocument 修改後會自動通知所有的 CView 對象來自動更新它們顯示的內容,這一點可以用 Word 很容易試出來。還有最新的 Windows 圖形界面框架 WinForm 中的窗口之間消息傳遞等用的也是 Observer 模式,一個窗口發生改變時會自動通知所有與它有關係的窗口,來自動更新信息等,這一點 Jeffrey Richter 可以作證 J

行爲模式之Template Method(模板方法)

GoF的定義:Define the skeleton of an algorithm in an operation, deferring somesteps to subclasses. Template Method lets subclasses redefine certain steps ofan algorithm without changing the algorithm's structure. 翻譯爲中文大致意思是:定義一個算法的骨幹,延緩其中某些步驟以便在 subclasses 中定義它們。 Template Method 使得 subclasses 在不改變算法的體系結構的前提下得到重新定義算法內的某些步驟。

Template Method其實最常用了,在C++裏的應用就是使用 virtual function 實現的,給一個base class 定義一個virtual方法,但是不實現,而是在它的subclasses 中實現它,這就是Template Method。這個模式的好處顯而易見,就是在你設計base class的時候並不知道這個方法具體如何實現,但是卻需要在base class裏某個地方需要調用它以完成一個完整的算法,這時候就是需要用這個模式了。

實例:例子太多了,到處都是。

行爲模式之Strategy(策略模式)

GoF的定義: Define a family of algorithms, encapsulate each one, and make theminterchangeable. Strategy lets the algorithm vary independently from client that use it. 翻譯爲中文大致意思是:定義一族算法,把每個算法封裝起來,並使它們可以相互替換。 Stategy 使得算法給 client 使用的時候變得完全獨立。

Strategy模式完全符合 OCP ( Open-Closed Principle ,開閉原則),可以在不需要做 subclass 更不需要修改 base class 的情況下在 Runtime 的時候擴展系統的功能,它不像 Template Method 需要 subclass 才能擴展新的功能。

實例: Strategy 典型的應用是在薪資系統中,當定義 Employee 類的時候,如果使用 Template Method 模式,就無法更改員工領薪資的方式等,比如員工晉升的時候。這時候就需要用 Strategy 模式,在 Employee 中定義一個指針指向具體的 PaymentMethod 對象,如果需要更改領薪資的方式的時候,只需要將它指向新的 PaymentMehtod 實現就可以了。

結構模式之Adapter(適配器模式)

GoF的定義: Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. 翻譯爲中文大致意思是:將一個 class 的接口轉換爲另外一種 clients 所期望的接口,使得這些 classes 可以更好地協同工作起來,而不至於因爲別的不兼容的問題所影響。

Adapter 模式屬於結構型模式,需要有 Adaptee (被適配者)和 Adaptor (適配器)兩個對象。一般情況下,我們通過繼承( Inheritance )或者合成( Composition )的方式擴展類的功能後,會產生很多接口形式不同的類,但是我們想用同樣的方式調用它們,又不想改造它們(否則違反 OCP 原則),怎麼辦呢?這種情況下就需要用到 Adapter 模式了,這些被“改造”的類就叫做 Adaptee ,而我們就需要寫個 Adaptor 類來轉調它們,把調用的接口改成統一的形式。

實例:Java IO library 裏的InputStreamReader 就是一個 Adapter ,它負責將 InputStream 對象轉換爲 Reader 對象的接口形式,使得用戶可以像使用其它 Reader 一樣的方式來使用InputStream對象,比如:

 

InputStreamReader isr = new InputStreamReader(

new FileInputStream("test.txt"));

BufferedReader br = new BufferedReader(isr);

String line = br.readLine();

 

這就做到了在不改造原來設計的類的接口的情況,擴展了類的應用範圍。一般情況下, Adapter 模式需要結合 Factory 模式和 Proxy 模式來使用,使得接口的訪問更加一致性,更容易改造系統。

結構模式之Facade(外觀模式)

GoF的定義: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use. 翻譯爲中文大致意思是:爲一個子系統的一批接口提供一個統一標準的接口, Facade 定義更高層次的接口,使得子系統更容易使用。

Facade 模式我感覺是一個更高層次的 Adapter ,它是用來理順系統之間的關係,降低系統間的耦合度的常用方法,其實我們可能在設計系統的時候就在不知不覺地應用這個模式了。大部分將業務邏輯層和表示層分離的框架都是應用 Facade 模式實現的,使得上層使用者完全不必理會底層的實現,卻又有統一的使用界面。

實例: J2EE EJB 中 Session Bean 就是一種典型的 Facade 模式,即 Session Facade 。它完全將業務對象封裝起來, EJB 客戶端訪問 Session Bean 來代替訪問業務對象。這就大大簡化了 EJB 的系統結構, Session Bean 就是相當於一個外觀,也相當於一個總管,把業務對象都管理起來,不讓客戶端直接“接觸”到它們。

《設計模式》一書中還有很多模式這裏就不一一介紹了。其實我們大家都可以自己設計一種模式,但是肯定沒有 GoF 的那 23 個著名了。不過現在還有很多別的也很有名的模式,比如說 Wrapper 模式(相當於 Decorator )、 DAO 模式( Data Access Object 模式, OR 映射系統裏常用到)、 MVC 模式(著名的 Model-View-Control 架構模式)等等。我的一個觀點,只有大量地實踐,持續地學習,就會不斷提升自己設計模式的層次,如果想靠一兩年的學習就想精通它,是不可能的任務 J

寫了這麼多,終於寫完了,呵呵。寫完後,有兩點體會: 1, 寫源碼剖析類的文章,就像是給代碼做翻譯,把代碼翻譯成圖片或文字。 2 ,寫 OO/DP 之類理論的文章,則相當於創造,不同的人有不同的體會,技術學習層面不同的理解也不同。還是那句話:理論需要從實踐中來,多學習多實踐。歡迎指正!

最後我列幾本候捷老師推薦的書:

1 , C++ 程序員必備的書(我以爲,即時你不看,也建議備上,充門面也好,爭吵也好都很用)

a) 《 C++ Programming Language 》 Bjarne Stroustrup

b) 《 C++ Primer 》 Stanley B Lippman

c) 《 Effective C++ 》 Scott Meyers

d) 《 Design Patterns 》 GoF

2, 提高或學習的書

a) 《 Inside The C++ Object Model 》 Stanley B Lippman

b) 《 Thinking in C++ 》 Bruce Eckel

c) 《 More Effective C++ 》 Scott Meyers

d) 《 STL 源碼剖析》侯捷

e) 《 Modern C++ Design 》 Andrei Alexandrescu

f) 《 Small Memory Software - Patterns for memory management 》

原文鏈接:http://www.cppblog.com/javenstudio/articles/17057.html

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