c++ tips 100(有些可能不屬於c++)

來源:互聯網
上載者:User

這是以前(什麼時候?)做的一些零碎筆記(應該是在看c++ primer的時候),不一定正確,但可供參考~~

比較好的一個學習方法:
<1>.通過一個一個的執行個體學習(可以是每一章一個執行個體,始於一個簡單的例子,最後變成一個參考價值較高的完善例子)
<2>.像下面一樣記錄tips,作為備忘(通過自己的語言描述出來,可以加深理解和映像)
<3>.無它,手熟爾.

1.extern "C":c++編譯器能對其進行識別;被其標註的部分將按c的方式進行編譯
個人認為,extern "C"被標註的函數將會有兩個別名,所以這個函數在c++和c的代碼中都能被引用。

2.我們在定義枚舉時可以這樣做:
typedef enum
{
 enum1=0,
 enum2,
}MY_ENUM;
也即給第一個枚舉量賦值,之後的依次累加1

3.注意:c中並沒有在編譯層次支援函數重載,所以c++中的重載函數只能有一個用extern "C"描述。

4.被申明為內聯的函數,不一定在編譯期間被內嵌到引用處。inline關鍵字只是對編譯器提出建議。
內嵌函式的好處:
可以把一個經常出現的操作或代碼定義成一個函數,方便維護修改也方便閱讀理解。所以內嵌函式一般要求短小精悍!
與非inline函數不同的是,inline函數必須在調用該函數的每個文字檔中定義。而且必須相同。所以最好的方式是把它定義在標頭檔中。

5.引用與指標的區別,引用不可為空,不能修改指向~~,優點是能提供操作的直觀性,如果用指標的話,很可能讓代碼失去這種直觀性。尤其是在重載操作符的時候。

6.如下申明
void aaa(int[10]);
被c++編譯器看成
void aaa(int *);
所以,如下三個聲明等價:
void aaa(int[10]);
void aaa(int *);
void aaa(int[]);

7.c的struct只是變數的集合體,不可以隱藏資料,不可以封裝操作。c++的struct與class的差別是struct預設為public。

8.枚舉類型的優點:不能把數字常量(整數)賦給枚舉變數。這樣就防止了誤操作(比如當你用枚舉變數作為參數時,傳入該變數的值是限定的);
可以用枚舉類型來代替宏定義,如:
enum{
 MAX_LEN=100,
 TYPE1=1,
 TYPE2=2
};

9.結構體的兩種定義法:
當結構體定義在標頭檔或某個模組中而需要被其他模組使用時,最好使用typedef:
typedef struct _STRUCT_A{
 int i;
 int j;
}STRUCT_A,*pSTRUCT_A;
而當結構體只需在本模組使用時,可以直接定義並申明:
struct struct_s{
 int i;
 int j;
}mystruct1,mystruct2,mystructs[100],*pmystruct;

10.為什麼要使用volatile關鍵字:因為編譯器會對程式變數進行寄存器最佳化。比如一個經常被使用的變數可能儲存在寄存器中。而volatile修飾的變數很可能在編譯器能控制的範

圍外被修改,比如它是一個寄存器相關值,被外部中斷或始終更新。這時候要告訴cpu該變數不能進行寄存器最佳化,每次都要去讀原值。
   volatile用法與const用法相似。volatile和const修飾的都是他們右邊的變數.

11.complex類型與pair類型:
   #include <complex>  |   #include <utility>
   using: complex<double> r1(11,11.22);
  pair<string,string> p1("aa","bb");
   技巧: 可以使用typedef pair<string,string> PP;

12.指標的合理寫法:char *ch1,ch2;而不是char* ch1,ch2.

13.為什麼要定義宏或常量?
   方便修改;防止誤操作(比如:if(MAXLEN=1));意義明了(可以取一個名字)

14.引用必須初始化指向一個對象。一經指定,就不能再指向其他對象。注意一下const引用的特點。引用的內部存放一個對象的地址,它是該對象的一個別名。對於不可定址的值

,如文字常量,編譯器會產生一個臨時對象,但是使用者無法訪問該對象。
看下面引用:
const int ival=1024;
const int *const &pi_ref=&ival;
在c++中,參考型別主要被用作函數的形式參數。
引用必須指向一個對象,如果:char &ch=0;
則編譯器把它翻譯成:char temp=0;char &ch=&temp;

15.一個重要的錯誤源:
在c++中,類只是一個智能的資料結構。當一個對象拷貝給另一個對象,而該對象包含一個或多個指標時(指標的地址覆蓋了,而不是指標指向的內容發生拷貝)。...

16.可以用一個數組(或部分)來初始化一個vector,比如:
int aa[8]={1,1,1,1,1,1,5,6};
vector<int> v(aa+3,aa+8);
vector有兩種用法:內建數組用法和STL用法。
學會STL的設計模式和思維方法不是一件壞事~~

17.成員函數指標  R (T::*)(...)

18.系統為每個程式執行時可用的記憶體池。這個可用記憶體池被稱為程式的空閑儲存區(free store)或堆(heap)。dynamic memory allocation。

19.c++中有bitset操作,#inclued<bitset>非常的好用,用著試試就知道。

20.c++架構內的強制轉換是不同於c的,類型轉換被稱為cast,有如下一些cast:
static_cast:
dynamic_cast:
const_cast:
reintepret_cast:
任何非const類的指標都可以轉換成void*型的指標。

21.程式員必須顯示地告訴編譯器停止執行switch中的語句。break。大多數情況下,故意省略break標籤的case語句應提供一條注釋。我們的程式不光是要能夠編譯執行,而且對於

以後負責修改和擴充的程式員來說,也應該是可以理解的。
省略break也是必要的,當我們case一個集合時:
switch(i){
case 1:case 2:case 3:
 do 123;
 break;
case 4:case 5:
 do 45;
 break;
case 6:
 break;
default:
 do default;
}

22.可變參數,如:
printf(const char*,...);
print(...);
關於可變參數的解析另行參考~~

23.函數指標的最大用途是用於回調設計。這通常涉及到“函數註冊”。
對一個函數如:void FF(void){retrun;}
定義一個函數指標void (*PFF)(void);
則PFF=FF;PFF=&FF都是可取的。
函數指標數組~~

24.不同程式文字檔的名字空間定義是可以積累起來的。名字空間支援嵌套。
c++中可以用未命名的名字空間聲明一個局部於某一檔案的實體。如:
namespace{
 void func(void){};
}
如果在c檔案中,解決辦法是為函數定義加上首碼static。

25.標準c++庫中的所有組件都是在一個被稱為std的名字空間中聲明和定義的。在標準標頭檔中聲明的函數、對象和類模板都被聲明在名字空間std中。

26.關於函數模版。函數模版提供了一種用來產生各種類型函數執行個體的演算法。對函數介面中的全部或部分類型進行參數化。同樣的,類模版也與之類似。

27.c++的容器包括:sequence container和associative container;
前者:list和vector,deque
後者:map,set,multimap,multiset
容器支援比較操作符。

28.為了提高效率,像vector並不是每次插入元素時都要增加自己的長度。它需要增加自己的長度是,它總是預先多分配一些。所以它有兩個屬性:一是capacity而是size。實際表

明是按指數增長的。
遍曆順序容器時:iterator可以進行指標的任何操作,因為順序容器支援隨機訪問記憶體是連續的。比如:iterator++;iterator--;iterator+N
而對list等記憶體不連續的容器:iterator不能進行iterator+/-N的運算,但可以進行++/--操作(應該是進行了操作符重載)

29.效能問題:當資料元素的長度比較小時vector的效能優勢明顯(insert),反之list更有優勢。vector的增長機制是: 每次分配需要大小的二倍,然後把舊資料拷貝到新分配

的資料區。list的開銷主要是記憶體配置,每插入一個,哪怕資料元素很小,也要進行資料分配。如果要提高vector的效能,當資料類型的長度比較大時,應該通過指標間接儲存該

複雜的資料類型(這顯然是通常的也是極為有效一個做法)。

30.迭代器:a lot of thing!!

31.父類與子類是可以相互轉化的,但不建議從父類強制轉換為子類,以免產生不可知的操作。

32.通過編寫一個簡單的c++程式編譯成exe後用反組譯碼軟體反組譯碼,能夠分析出c++的內部實現以及c++編譯器的一些命名法則。比如:"cout<<"在編譯器內部的名字是

“ostream::operator<<”

33.try{}模組內定義的變數由於局部性不能在catch塊中引用。好的編程模式是try{}catch(A){}catch(B){}...
如果要捕捉所有的異常,則catch(...){}即可(這有點類似可變參數)

34.理解c++的異常拋出機制。可以把異常拋出機制理解為動態函數調用。區別在於:函數調用在編譯時間確定,但是拋出則完全未知。如果檢測到一個錯誤,則調用throw語句,該

語句複製一份異常對象,然後尋找對應的處理函數(catch語句),一層層展開堆棧找到對應的catch語句,沒有則調用預設的terminate函數。catch(Exp exp)相當於帶傳值參數的

函數,如果寫成catch(Exp &exp)則相當於傳址。實際上,後者是相當推薦使用的,因為它可以防止大的異常對象拷貝開銷,同時也方便修改exp對象進行二次拋出。假如不採用引

用方式,則在如下代碼catch(Exp exp){exp.value=1;throw;}的二次拋出中,拋出對象實際上並沒有改變。terminate預設的調用了abort()函數中指程式。

35.異常規範:假如我寫一個庫,裡面有很多函數,那麼庫的使用者如何知道哪些函數會拋出哪些異常呢?異常規範提供了一種解決方案,它能夠隨著函數申明列出函數可能拋出的

異常。形如:void pop(int &value) throw(popOnEmpty);這也就限定了該函數在運行期只能拋出popOnEmpty函數,否則將調用unexpected()->terminate()強行中止程式。但是有

幾點需要澄清:函數內部拋出不符合異常規範的異常但內部把它解決了,不算違例;或者有那麼一條拋出違例異常的語句但是永遠不會執行,編譯器不會報錯;一個函式宣告附帶

了異常規範,則所有聲明都必須附帶。

36.異常與程式設計:雖然c++支援異常,但是並不是說一定要使用異常,程式還可以使用多種排錯方式,比如:返回錯誤碼;局部處理(使用if語句)等等。一切實事求是。

37. 關於assert宏:
#if defined NDEBUG
#define assert(condition) ((void) 0)
#else
#define assert(condition) _assert((condition), #condition, __FILE__, __LINE__)
#endif
值得學習的地方:_assert本身是宏,再在外麵包裝一層宏使之因與編譯選項不同而定義不同,這是很多開關量的慣用方式。

38.assert永遠出現在調試版本,不會出現在發行版本。所以要提高警惕了。在調試版本中某些錯誤可以簡單的用assert來診斷,因為它只給開發人員看;但是在發行版本,要麼這些

錯誤不被允許,要麼你建立代碼能修正錯誤,assert診斷出來的錯誤你必須在發行前解決掉。

39.通常,編譯器用EBP來指示當前活動的棧幀,如果main()->add();則堆棧先是main的棧幀後是add的棧幀,EBP指向add棧幀的開始處,ESP指向棧頂,add函數中的局部變數被解析

成相對與EBP的固定位移,由於堆棧向下生長,所以多表示為EBP+N的形式(很有意思,當你壓棧時,ESP實際上是減小的)。編譯器編譯一個函數時,會在它的開頭添加一些代碼來

為其建立並初始化棧楨,這些代碼被稱為序言(prologue);同樣,它也會在函數的結尾處放上代碼來清除棧楨,這些代碼叫做尾聲(epilogue)。
一般情況下,序言是這樣的:
Push EBP ; 把原來的棧楨指標儲存到棧上
Mov EBP, ESP ; 啟用新的棧楨
Sub ESP, 10 ; 減去一個數字,讓ESP指向棧楨的末尾:10是指函數所要用到的所有局部變數和臨時對象大小和。編譯時間,編譯器知道函數的所有局部對象的類型和“體積”。
尾聲所做的正好和序言相反,它必須把當前棧楨從棧上清除掉:
Mov ESP, EBP
Pop EBP ; 啟用主調函數的棧楨
Ret ; 返回主調函數(CALL指令執行時自動把其後指令的地址壓入堆棧,RET恰好相反)

40.類的成員只能在建構函式內初始化,如果沒有顯式的初始化,會有一個預設的初始方法。(靜態成員在構造前初始化,因為相當於全域變數)
而且在建構函式內部諸如a=1;b=2;的“初始化”過程並不是真正的初始化,而是賦值,真正的初始化是使用參數化列表的方式。也即在執行建構函式裡面的語句前已經初始化了類

對象。

41.VC6.0的反編譯功能真是棒極了!如果你想研究下c++編譯器原理,比如其如何支援類?編個小程式反編譯一下就豁然開朗了。c++相對於c表現的很龐大,很智能,但是我們必須

非常清楚的是,c++的這些新特性完全是依賴於編譯器的強大,編譯到機器碼級(彙編級),c與c++沒什麼兩樣。為什麼說c是半機器語言呢,因為它的文法概念相對於彙編來說並

沒有太多擴充,而c++就不同,他有很多抽象的概念。最簡單的,類的概念,其實你這樣定義一個類class A{int a,int b};並產生一個執行個體A aa;對編譯器而言,{A aa}={int

aa.a,int aa.b}。從某種意義來講,c++只是簡化了c(增加了語義,降低了程式編寫難度),然後把複雜性交給了編譯器。

42.再定義一個類時,通常把public區放前面,private區放後面

43.請一定要理解編譯過程,嚴格區分編譯與串連各自做了些什麼,這樣協助自己在寫程式時不犯錯且在排錯時能準確分析原因。比如,申明影響編譯,定義會影響串連

44.一個類被編譯後是這樣的,就是一推函數體,這堆函數加了一個該對象的指標this。比如:
有代碼:
class A{
public:
int a;
void add(){a++;}
}
那麼編譯後將會有類似於add(A *this){(*((int *)((char *)this-4)))++}的函數出現,由於類裡面個成員變數相對於類指標所指地址的位移是固定的,所以對成員變數的操作也

即對某個位移上按某種類型進行操作。至於靜態成員變數,壓根就是全域變數,只是名字上加了首碼而已。
可以這麼認為:類的實體是一對函數+靜態變數;而對象的實體是一個結構體。也就是一堆結構體共用一組函數。
深切理解這句話:每個類對象都講維護一份類資料成員的拷貝,但是成員函數只有一份拷貝。

45.關於const成員函數的局限性:如果類含有指標成員,則const成員函數能修改該指標所指的對象(編譯器不會報錯)
另:const成員函數能被非const同名函數重載
被申明為const的對象只能調用被const修飾的成員函數

46.mutable(易變的)修飾符。於const相對,用來抵制const的“霸道與迂腐”。
const,volatile修飾的成員函數實際上修飾的是this指標。因為修飾的是this,所以實際上修飾了所有的資料成員。所以在const函數中返回成員時返回的是const this->memberX

47.如果每個成員函數都返回(*this)的引用,那麼對某個對象的操作可以連成串,just like this:
test.int().move(1,2).show();//這樣對三個函數的調用在一條語句裡面就完成了。

48.作為特例,有序型的const待用資料成員可以在類體中用一常量進行初始化。
如:
static const int namesize=16;
static const char name[namesize];

49.函數指標的一種應用模式:有很多操作An,它們有類似的介面,現在還有一個函數B需要調用這些操作,那麼可以把那些操作函數An以指標的方式傳給B使用。
當然,函數指標有很多用處,在很多有效設計模式中使用它能有效增加效率也降低結構的複雜性。
還有一個問題,為什麼成員函數指標不能與定義在類外部的一個同結構的函數指標對等,其實這個最簡單了,因為成員函數含有一個this指標。成員函數首先必須被綁定到一個對

象或指標上,才能得到被調用對象的指標,才能調用指標所指的成員函數上。雖然普通函數指標和成員函數指標都被稱作指標,但它們是不同的事物。

50.關於類成員指標與類成員函數指標還要囉嗦幾句。
class A(){public:int a; void f(){}}
則成員指標申明:int A::pa=&A::a;
成員函數指標申明:void (A::*pf)()=&A::f;
今有A ca;則可如下使用指標:
int i=ca.*pa;(ca.*pf)();其編譯器內部過程請參閱有關書籍~~
解釋一下:void (A::*pf)()這個很容易理解,告訴編譯器pf函數要帶一個A *this的指標參數,而(ca.*pf)中,ca.的意思告訴編譯往函數傳入&ca。

51.靜態類成員的指標與非類成員的指標相同,關於這一點如果從編譯器的角度來理解將很簡單。

52.c++中的union與c中的union的差別。union通常用在類似於
{
 int ID;
 union value{
  ....
 }
}的結構中。有一種特殊的union叫做匿名union。匿名union去掉了一層成員訪問操作符,所以匿名union不能有私人或保護成員,也不能定義成員函數。在全域域申明的命名union

必須申明在未命名的名字空間中(或者被申明為static)

53.bitset模板,c++建議在使用位操作的應用中使用bitset類模板,位操作通常可以和枚舉,常量定義聯絡在一塊,因為沒有人願意去記住每一位是什麼意思。

54.關於局部類與嵌套類這些比較偏的東西,平時很少用到,所以無需仔細瞭解,但要認識到c++編譯器支援這些性質。

55.建議使用預設參數的建構函式,因為可以減少建構函式的個數。千萬別錯誤的認為如果沒有預設的建構函式,編譯器會自動添加。沒有就是沒有。
還有一個容易想當然的是,建構函式必須是共有的。實際上,私人的建構函式有它特定的用途。

56.一個小問題,我們永遠不要這樣去定義一個對象Class1 class1();去掉後面的括弧就行了,否則編譯器會認為這是一個函數申明。呵呵,你以前犯過這樣的低級錯誤嗎?
還有就是,當我們定義了建構函式,我們在定義對象執行個體時就必須調用其建構函式,除非有個建構函式帶預設參數(這樣的話,編譯器會自動添加預設建構函式)
當要一次聲明大量對象的時候,我們當然希望能有個預設的建構函式,這頁是預設最好提供一個卻性建構函式的原因。

57.反編譯顯示,vc++的編譯器把參考型別轉化為指標,但是你如果用sizeof去取得其大小,則不是指標的大小,而是指向的對象的大小。

58.拷貝建構函式。單獨拿出來講是因為它很c++。它的參數是指向該類對象的引用(通常被申明為const)。
注意:即使你沒有申明拷貝建構函式,對一些簡單的類還是可以用一個對象去初始化另一個對象的,編譯器將執行預設的按成員初始化。

59.解構函式:當你動態分配了記憶體,或是使用了作業系統的資源(比如互斥鎖,比如Handle),解構函式就必須存在。但解構函式的功能不僅在於釋放資源。要充分利用它的最後

必然執行這個特點。編譯器記錄一個類(在其生存期內)在哪個地方最後被調用,然後插入它的解構函式。

60.c++語言內部保證,不會用delete操作符刪除不指向任何對象的指標。所以如下的判斷是沒必要的:if(pt) delete pt;這個判斷編譯器做了。

61.c++的強大在於指標,c++的災難往往也是指標。所謂成也蕭何敗也蕭何,因此市面上有很多商用的智能指標,可以關注一下它們的實現技術。不過c++本身提供了一個小巧的智

能指標auto_ptr,auto_ptr所做的事情,就是動態指派至以及當對象不再需要時自動執行清理。鑒於這樣一個事實,c++出現異常時,堆棧展開的過程當中會調用棧內對象的析構

函數。但是不會去清理new出來的對象。比如,a=new A();...delete a;很有可能在這兩句間發生異常從而delete從來沒有執行,這是一個典型的記憶體流失case。所以如果把指標封

裝到一個類中,然後在它的解構函式內清理所指對象,那問題就可以很好的解決,new之後都不需要寫delete。所以平時要多動動腦子~~

62.什麼時候需要顯式調用解構函式?因為delete一個對象時,不但調用了一個對象,也刪除了堆中這塊記憶體。有時候我們不希望如此,特別是多個對象共用某塊記憶體的時候。那麼

我們可以不delete,只是調用解構函式。

63.對象數組的初始化:
Account pols[]={
Account(...),
Account(...),
Account(...)
}

64.vc++的反編譯結果表明,程式編譯後會產生一張函數表:
jmp D::D(32 bits address)
jmp ...
這些跳到指定函數入口的指令緊密排列跟中斷向量一樣,所以我們call一個函數時,並不是直接跳到函數入口處,而是先通過該函數的編號(編譯器編譯時間會進行)為索引跳到“

函數表”的對應jmp指令,通過jmp指令跳到目標函數。

65.vc++反編譯表明,類成員函數的this指標是通過ecx寄存器,而不是使用參數壓棧的方式,實際上函數在入口位置把ecx值作為棧內第一個臨時變數,存放在ebp-4的位置。

66.成員初始化表對效能的影響。不要錯誤的認為,使用成員初始化表總是會提高建構函式的效能。這應該根據具體情況而定。先來瞭解下編譯器到底做了些什麼。如果一個函數含

有類對象成員(而不只是c++的簡單對象如int等),那麼在建構函式體執行前,編譯器會產生隱式初始化表,會調用類成員的預設建構函式。所以如果在構造體內對其賦值,則調

用的是拷貝建構函式,開銷很大。但是對於簡單變數則沒必要使用成員初始化列表。
總之,對於類對象,在初始化和賦值之間的區別是很大的。其它倒無所謂,但使用初始化列表好像更清晰,特別的,const和引用成員必須使用初始化列表初始化。
一個一般性的規則是:在成員初始化列表中初始化所有的成員對象。

67.初始化順序:與在列表中的順序無關,與成員申明順序相關。我們如果要屏蔽掉按成員初始化,在聲明一個拷貝建構函式但是不對其定義。

68.重載賦值操作符時,應該注意一點:最好不要自己對自己拷貝,所以加一句if(this!=&copy)

69.c++語言不能有效返回一個類對象,這被視為c++語言的一個重大缺陷。清理解這句話,並且想想除了從編譯器的角度,有沒有較好的解決辦法。

70.操作符重載函數。很多情況下都要返回一個引用(其實很多成員函數也可以),目的是為了連續操作。比如cout<<a<<b<<c<<endl;為什麼<<操作符能連續調用?因為前一個<<返

回的正是cout<<;又比如:(c=(a+=b)).showmenber();
只有在c++預定義操作符集中的操作符才能被重載。而且每個操作符的意義都應與預定義相關。
比較特殊的幾個操作符是->和()。

71.根據c++編程規範,盡量使錯誤發生在編譯時間,盡量減少執行階段錯誤的可能性。比如:充分利用c++的語言特性。如果一個變數不應改變,那麼最好就是把它申明為const使編譯

器認識到這一點,從而如果出現誤操作或疏忽(在一個比較大的程式中這是難免的),編譯器就能把它揪出來。所以,好的編碼習慣很重要。

72.c++如何解釋->操作符,這是一個遞迴解釋過程,對語言元素A->,c++先尋找A的語義,如果A是一個對象或引用,在調用它的成員操作符->取得返回對象,如果返回對象依然是

一個對象或引用,則遞迴這個過程。直到得到一個指標類型。然後->就變成了取成員的內定義語義。

73.模板定義的嵌套。之前很疑惑iterator是如何?的。網上的人說是使用模板與操作符重載,操作符重載自不必說,但模板的使用上還是有點技巧的。
第一,就是定義在一個類內部申明,用public關鍵字拋出,比如:
class A{
public:
  typedef struct _B{} B;
}  然後可以用A::B來定義某個對象;
第二,模板嵌套,比如:
template <class T> class A{};
template <class T> class B{};
定義或申明時,使用B<A<int>>。iterator就是這麼做的。

74.try...catch的簡單使用,try塊中可以拋出一個字串,在catch塊中列印該字串,並優雅退出(這是個調試的好方法)

75.c++的容器與泛型演算法的分離。還有函數對象。
考慮find演算法,我們要解決三個問題:
a.需要某種方式來遍曆;使用iterator
b.需要對元素進行比較;採用兩個版本,一是使用元素底層的=操作符,二是使用函數指標來讓使用者自訂。
c.需要一個公用類型來表示元素在容器中的位置;

76.泛型演算法接受普通指標或ieterator。a lot of things!

77.explicit,和建構函式一起使用.  
  explicit constructor指明建構函式只能顯示使用,目的是為了防止不必要的隱式轉化.  
  舉個例子:  
  class A{  
  public:  
        A(int){};  
  };     int Test(const   A&){} 
  Test(2);   //正確  
  如果建構函式被聲明為explicit,將會失敗。

78.野指標,野指標是指向垃圾記憶體的指標!!不是NULL指標。比如,你在棧類定義了一個臨時對象,把這個對象的地址傳給了域外,則函數退出時那個指標就成了野指標。

79.函數對象。不錯的好東東。沒有資料成員,只有成員函數。所以不需要執行個體化。多用在泛型演算法當中~~
不僅如此,而且它可以被看成是一個“萬能操作符”。

80.預定義函數對象:#include<functional>
算術,關係,邏輯

81.剛剛看到Bjarne Stroustrup(c++之父)的論斷,盡量少用宏,“如果看到了宏,說明程式語言、程式碼抑或是這個程式員存在問題”。
用const,enum,inline和模板來替換原來使用宏的地方。本人非常認同!
但是,宏仍然是幾個重要任務的惟一解決方案,比如#include保護符(guard)),條件編譯中的#ifdef和#if defined,以及assert的實現

81.何時不用泛型演算法。不允許在關聯容器中應用重新排序的泛型演算法。因為關聯容器為了提高速度,關聯容器內部有個結構不能亂的。第二,鑒於list儲存的非連續性,很多演算法

list都以成員函數的方式實現了,故不應使用泛型演算法。

82.什麼時候需要allocator,預設的c++alloctor也即new和delete,new對分配小記憶體效率和記憶體利用率都不是很高。為提高效能,這時需要自己定義allocator,比如

map<int,int>的時候,然後在自訂的allocator中使用c的malloc和free函數來分配。

83.我們的設計原則:程式員不能顯示的管理很多的東西。特別是一個合作項目,那麼模組的劃分,以及類的architecture,介面的定義等問題都是關鍵。什麼叫程式員不應該顯示

的管理太多的東西?其實質就是封裝,而且是巧妙的封裝。讓整個程式結構更加“智能”。充分利用好c++編譯器所支援的c++特性,盡量使程式更易於擴充,模組更便於使用。
比如,物件導向程式設計的典型特點就是:它把類型解析的負擔從程式員轉移到了編譯器上。

84.再次強調一下多態與動態綁定。第一次感到多態的威力,是看BCM的代碼的時候,後來分析MFC的時候也深有體會!再後來,自己在做一個項目的時候應用了多態技術,使得我寫

的上層代碼可擴充性非常強(實現了功能的類外掛程式化)!
繼承階層的好處是:我們可以針對抽象基類編程,而不是針對組成繼承層次的個別類型(簡化編程也方便擴充與升級)。
所謂c++的多態性,主要是指基類指標可以指向任意衍生類別的能力。(所以多態離不開指標和引用~~)
由程式員來管理的“多態”不是多態。c++編譯器作為最複雜的編譯器,那就不要浪費!

85.在派生一個類前,必須在其前向聲明中定義了基類。光聲明是不行的(因為類不是內定義的,編譯器不知其大小,這跟在類中以本類做成員一樣)。

86.物件導向設計的一個主要形式是抽象基類的定義以及它的公有派生。注意:基於對象與物件導向之間還有個鴻溝。
很多初學者(沒有實際開發經驗的),認為使用類就是物件導向。大錯特錯!
學軟體工程的時候,說物件導向的特點:封裝,繼承,多態,訊息;沒錯,四者缺一不可。其中封裝是最基本的(也是很多初學者所理解的物件導向)。而後面三個特徵沒有實際

的開發經驗觀看書不寫碼是不可能真正一會的。

87.在基類申明為虛的函數,其子類不需要再聲明為虛函數了,其子孫們自然繼承了虛擬特性!

88.基類與子類名字域的獨立性:在基類CA中定義了int i,在CA子類CA1中定義int i;二者並不衝突。它們都限定在自己的類域中~~
同樣的成員函數名也是如此。不要指望基類和衍生類別的成員函數構成一個重載函數集。重載
我們可以這樣來調用基類的函數:CA1 ca1; ca1.CA::func();

89.假如有基類CA,從CA派生CA1,二者都定義了一個函數void test(){}(不是虛函數)。則:
CA *pca=new CA1();pca->test();
這種情況下,pca調用的是CA的test函數。這個如何理解?
讓我們來看編譯器怎麼做:首先,new CA1()說明在記憶體中產生的是CA1對象(其VFT只指向的自然是CA1的),然後CA *pca=,編譯器它認定pca是CA類的指標,它會忘記其實這是一

個CA1類的指標,所以你調用pca->test();編譯器會把其轉為CA::test(CA* pca);而如果調用虛函數呢?回去先查pca指向的對象的第一個元素(那個指向虛函數表的指標),根據

該指標去查調用的函數,查到的自然會是CA1這個類的。
所以c++的發明者很聰明~~

90.關於.操作符合。.操作符就是讓編譯器把.之前的對象的地址以指標的方式傳入到函數(如果後面是函數)。

91.參考88. 我們如何讓基類的成員函數與子類的成員函數構成一個重載集?最好的方式是使用using申明,比如:
class CA{void test(int i);}
class CA1:public CA{using CA::test;void test(string s);}
using申明的函數不能帶參數。所以using把基類的重載集整個添加到了子類中~~

92.當一個基類對象定義了一個protected成員變數。而在該類對象的派生對象的某個成員函數中傳入了該基類對象的引用,那麼派生對象不能操作該成員變數。但如果傳入的是派

生對象的引用,則可以(想想拷貝建構函式)

93.友元,友元,友元。蠻有用的,呵呵。建立曖昧關係的鑰匙。

94.建構函式的調用順序:
 基類的建構函式,如果有多個基類,按其在派生表中出現的順序,不是成員初始化表中的順序。
 如果有成員類對象,按它們申明的順序進行構造。
 然後才是衍生類別對象的構造(就是那個建構函式的執行體)。
這個與資料在對象體中的記憶體排列順序相關,先從記憶體小的開始構造,這符合我們的習慣。可以這樣認為,基類子物件是衍生類別對象特殊的資料成員。

95.作為一般規則,不應該在衍生類別建構函式體中直接向一個基類對象的成員賦值,而應把值傳遞給基類對象的建構函式(在初始化參數列表中)。這是一種好的習慣,否則,基類

與子類變成緊耦合的,不利於代碼修改,維護。

96.虛函數的越權。有衍生類別CA1的執行個體ca1,ca1想調用其基類CA的虛函數怎麼辦?很好辦,加上域限制符CA::就可以了。

97.聲明為純虛的成員函數可以有定義。那麼它的定義有意義嗎。有的。可以在它的子類中以CLASSNAME::的靜態方式進行調用(從某種程度上抑制了虛擬機器制)。
而聲明其為純虛函數的目的是使該類變為抽象類別,不能執行個體化。

98.涉及到多態的話,別忘了把解構函式聲明為虛擬函數(嘿嘿,否則你會死得很慘的~~)
析構的順序與構造的順序相反。可以作一小程式,以列印字串的方式(或跟蹤調試)實驗之。
編譯器會自動往衍生類別的解構函式添加其基類和成員的解構函式。實際上你不寫解構函式,預設的析構過程是會進行的。關於這個地方,應該進一步對編譯器進行瞭解~~
瞭解清了寫專題啊,呵呵(有小題大作的嫌疑~~)

99.對上層c++的概念一定要從彙編級來認識,這樣你才知道編譯器做了什麼,c++為什麼可以這樣設計。你也不用去記很多文法細節,因為你瞭解內部機制。

100.反組譯碼結果表明(vc6.0)。對象調用虛擬函數時並沒有使用vftable,只有在對象指標調用虛擬函數時才採用所謂的動態綁定。 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.