1、函數形參,如:
CreateProcess(
NULL,
cmdbuf,
NULL,
NULL,
bInhH,
dwCrtFlags,
envbuf,
NULL,
&siStartInfo,
&prInfo
);
函數的參數個數最好不要太多,一般來說6個左右就可以了,眾多的函數參數會讓讀代碼的人一眼看上去就很頭昏,而且也不利於維護。如果參數眾多,還請使用結構來傳遞參數。這樣做有利於數據的封裝和程序的簡潔性。
也利於使用函數的人,因為如果你的函數個數很多,比如12個,調用者很容易搞錯參數的順序和個數,而使用結構struct來傳遞參數,就可以不管參數的順序。
而且,函數很容易被修改,如果需要給函數增加參數,不需要更改函數接口,只需更改結構體和函數內部處理,而對於調用函數的程序來說,這個動作是透明的。
寫有參數的函數時,首要工作,就是要對傳進來的所有參數進行合法性檢查。而對於傳出的參數也應該進行檢查,這個動作當然應該在函數的外部,也就是說,調用完一個函數后,應該對其傳出的值進行檢查。
例如輸入參數,如果是指針要先判斷是否為空或非法
對於函數返回值
如果是系統函數,則需要判斷返回值做出相應判斷,而不是默認他為正常的返回
如果是返回布爾值,則應先判斷布爾值的真假
如果是指針,則更應檢查指針的合法性
2、對於選擇語句的使用
如果是正常和出錯的選擇,一般先把出錯選項放在前面,因為出錯選項的分支比較小,也可以讓人清晰的知道各種可能出錯的選項
如果是對於常用的和不常用的選項,一般將常用的選項放在前面,這樣可以減少程序執行時選擇的時間
3、對於錯誤提示的處理
一種是具體情況具體分析,在每次錯誤提示的地方定義錯誤提示的代碼,這種方法雖然比較靈活,但是也比較不規范,第一是每次都要在出錯的地方重復一遍錯誤提示的代碼,另一個是同樣錯誤類型可能會有不太一致的錯誤提示。
另外一種就是在頭文件中通過枚舉或const定義所有可能的錯誤類型,每個變量名用錯誤描述定義,同時定義一個處理錯誤函數,函數的形參是錯誤類型變量,在函數內定義一個選擇語句,每一個分支都對應與一個錯誤的類型。
4、修改別人的代碼
當你維護別人的程序時,請不要非常主觀臆斷的把已有的程序刪除或是修改。我經常看到有的程序員直接在別人的程序上修改表達式或是語句。修改別人的程序時,請不要刪除別人的程序,如果你覺得別人的程序有所不妥,請注釋掉,然后添加自己的處理程序,必竟,你不可能100%的知道別人的意圖,所以為了可以恢復,請不依賴於CVS或是SourceSafe這種版本控制軟件,還是要在源碼上給別人看到你修改程序的意圖和步驟。
5、宏
雖然,宏的執行很快(因為沒有函數調用的開銷),但宏會讓源代碼澎漲,使目標文件尺寸變大,(如:一個50行的宏,程序中有1000個地方用到,宏展開后會很不得了),相反不能讓程序執行得更快(因為執行文件變大,運行時系統換頁頻繁)。
6、static的變量
一個static的變量,其實就是全局變量,只不過他是有作用域的全局變量。比如一個函數中的static變量,但static的最多的用處卻不在這里,其最大的作用的控制訪問,在C中如果一個函數或是一個全局變量被聲明為static,那么,這個函數和這個全局變量,將只能在這個C文件中被訪問,如果別的C文件中調用這個C文件中的函數,或是使用其中的全局(用extern關鍵字),將會發生鏈接時錯誤。這個特性可以用於數據和程序保密。
7、函數長度
函數一般是完成一個特定的功能,千萬忌諱在一個函數中做許多件不同的事。函數的功能越單一越好,一方面有利於函數的易讀性,另一方面更有利於代碼的維護和重用,功能越單一表示這個函數就越可能給更多的程序提供服務,也就是說共性就越多。
一般來說,一個函數中的代碼最好不要超過300行左右,越少越好,最好的函數一般在100行以內。有證據表明,一個函數中的代碼如果超過300行,就會有和別的函數相同或是相近的代碼,也就是說,就可以再寫另一個函數。
雖然函數的調用會有一定的開銷,但比起軟件后期維護來說,增加一些運行時的開銷而換來更好的可維護性和代碼重用性,是很值得的一件事。
8、硬編碼
最好不要在程序中出現數字式的“硬編碼”,例如在數組大小或者循環次數時,盡量不要直接使用數字,而是定義常量來使用,這樣在使用的時候就知道了這個值是干什么用的
9、調試信息
程序在開發過程中必然有許多程序員加的調試信息。我見過許多項目組,當程序開發結束時,發動群眾刪除程序中的調試信息,何必呢?為什么不像VC++那樣建立兩個版本的目標代碼?一個是debug版本的,一個是Release版的。那些調試信息是那么的寶貴,在日后的維護過程中也是很寶貴的東西,怎么能說刪除就刪除呢?
10、字符串常量
對於像
char *p = “test”;
*p = ‘p’;
這樣的賦值操作,雖然可以正常編譯,但是賦值語句卻並不起作用,因為“test”是常量,是不能再被賦值的,編譯器會自動把它定義為常量指針,而在運行時,在vs2008環境下,如果是release模式不會報錯,在debug模式下運行到賦值語句時,程序會直接崩潰。並且如果是兩個指針指向相同的字符串常量,這兩個指針指向的是同一地址的字符串。因此要避免這種定義,如果必須定義一個這樣的常量可以用如下的格式:
const char *p = “test”;
這樣就顯式聲明了p是一個常量指針,若給它賦值編譯器就會報錯。
若想用指針指向一個一般的字符串,可以用下面的方法
char *p = new char[5];
//p = "test";
if (p)
{
strcpy(p, "test");
p[0] = _T('4');
AfxMessageBox(p);
delete[] p;
p = NULL;
}
上面的賦值操作都是正常的
但是如果加上上面那句被注釋的語句,就會出現問題。因為該條語句相當於讓p指向了“test”常量的字符串,而不再指向new分配的地址,這樣下面的賦值語句不起作用,因為它現在是一個指針常量了,使用delete也不在起作用,因為p現在沒有指向new分配的地址。並且造成了內存泄漏。
或者
char a[5] = {0};
strcpy(a, "test");
AfxMessageBox(a);
a[0] = '0';
AfxMessageBox(a);
當然使用string或CStirng類也可以避免上面的問題。
11、對於引用
將引用初始化之后,引用就一直指向這個變量,相當於指針常量,再用別的變量賦值,也是將這個變量的值賦給樂引用初始化對應的變量。
12、字符串函數
strcmp,strlen,strcpy,strcat這些字符串函數不能直接操作空指針,否則就會直接出現內存錯誤,因此在操作之前一定要判斷指針是否為空。
13、定義與初始化
初始化式必須要有存儲空間來進行初始化,如果聲明有初始化式,那么它可被當做是定義,即使聲明標記為extern
頭文件中只能存放變量,函數的聲明和內聯函數的定義,變量默認為extern型,但要有事先聲明,方法是在cpp文件中定義變量,在其對應頭文件中用extern聲明文件,然后在用到該變量的cpp文件中#include包含該變量聲明的頭文件即可,當然若不想該變量被其他文件訪問,可在定義時在其前面加static前綴,這樣即使在頭文件中用extern聲明,它也不能被外部文件訪問
14、指針和引用的區別
1)、 引用必須初始化,不能為空,指針可以為空;引用被賦給一個變量后就不能再被賦值,指針可以隨意更改,所以沒有const引用,指針卻有。指針是解引用,引用不是。指針可以嵌套使用,引用不行。
2)、 用sizeof時,指針得到的是指針的大小,引用是變量的大小
3)、 指針和引用的加減操作不同
15、賦值構造函數和復制構造函數的區別
1)、 拷貝是創建新的對象並對其賦值,賦值是對已經存在的對象進行賦值。
2)、 當對象內有堆分配指針時,注意深拷貝和淺拷貝的區別。當為復制時需要先為新對象分配空間,然后再將要賦的值賦給它,如果只傳遞指針值,就會造成多個指針指向同一塊內存空間,造成重復釋放出現問題。如果是賦值,就要先判斷是不是同一對象,是的話就直接返回,如果不是,就先釋放掉原來的內存,然后分配新的內存並將值寫到新的內存中。
3)、 拷貝函數的形參是引用,無返回值,賦值函數的形參和返回值都是引用。因為拷貝函數是構造函數,構造函數是沒有返回值的,使用引用而不是值傳遞可以避免重復調用拷貝構造函數,生成很多副本,最重要的是使用值傳遞就會造成拷貝函數的無限循環遞歸調用。
綜上所述,需要自定義拷貝構造函數和賦值函數的情形之一就是類中存在動態分配內存的變量。如果賦值函數使用默認函數,就會造成原來的內存地址泄露,同時有多個指針指向同一塊內存,造成重復釋放。使用默認拷貝函數的問題只會將指針值傳遞過去,造成個指針指向同一塊內存,造成重復釋放。
16、虛函數表
沒有虛函數的類沒有虛函數表
在類對象分配內存時,首先分配存儲該類虛函數表地址的指針空間,並且一個類在內存中只有一份虛函數表,該類的所有對象調用的都是這一份虛函數表
在vs2008中,虛函數表的首地址並不就是第一個虛函數的地址,並且該表中只存儲是虛函數的地址,不是虛函數的地址不存儲,因此不同版本的編譯器其定義可能不太一樣
17、C++中的explicit
在C++中,一個參數的構造函數(或者除了第一個參數外其余參數都有默認值的多參構造函數)有兩個作用:第一就是一個形參的構造函數,第二就是隱式的賦值操作函數,但有時想要不允許這樣的隱式轉換,則可在構造函數前面加關鍵字explicit
18、基類和派生類的類型兼容規則
指向派生類的基類的指針不能直接訪問派生類中新增的成員。
需要知道一些常識,一個類所有的函數都是在code代碼區中唯一的存放一份。而數據成員則是每個對象存儲一份,並按照聲明順序依次存放。
類A中有了虛函數就會在類的數據成員的最前面添加一個vfptr指針(void** vfptr),這個指針用來指向一個vtable表(一個函數指針數組)(一個類只有一個該表),該表存儲着當前類的所有虛函數的地址。這樣vfptr就成為了一個類似成員變量的存在。訪問虛函數的時候通過vfptr間址找到vtable表,再間址進而找到要調用的函數。這樣就在一定程度上擺脫了類型制約。
只要vfptr的值不同,那么訪問函數成員的時候使用的vtable表就不同,就可能訪問到不同類的函數成員。B類對象中的vptr指向B類自己的vtable。
當B類繼承A類的時候,因為A中有虛函數,編譯器就自動的給B類添加vfprt指針和vtable表。也可以理解為B類繼承來了A類中的那個vptr指針成員。
當A類指針指向B類對象時,發生假切割。要知道這個過程只是切掉A類中沒有的那些成員,由於vfptr是從A類中繼承來的,所以這個量仍將保留。而對於vfptr的值則不會改變,仍然指向B類的vtable表。所以訪問F1函數的時候是通過B類的vtable表去尋址的,自然就是使用子類的函數。
當B類的指針指向A類的對象時(當B類存在新增數據成員時可能出錯),同理。
而對於普通函數則受類型的制約,(因為沒有vptr指針)使用哪個類的指針調用函數,那么所調用的就是那個類的函數。
總而言之,普通函數通過對象或指針的類型來找所調用的函數,而虛函數是通過一個指針來找到所要調用的函數的。
當將一個派生類對象賦給基類指針時,該基類指針不能訪問派生類中新增的成員,但將基類指針強制轉化為派生類指針后則可以訪問派生類中新增的成員。
19、四種類型轉換
reinterpret_cast
進行二進制形式的轉化,一般不用
const_cast
用於取出const屬性,把const類型的指針變為非const類型的指針,如:const int *fun(int x,int y){} int *ptr=const_cast<int *>(fun(2.3))
static_cast
static_cast其實就是隱式轉化與普通顯示強制轉化,不會進行類型安全檢查,這樣在轉換之后調用就會出問題,例如,將基類對象賦給繼承類指針,用該指針去訪問繼承類的新增成員就會出現錯誤,但在轉化的時候不會出錯誤,在調用的時候才會出錯,有很大的隱患。C語言的類型轉換其實就是這種轉換。
dynamic_cast
利用dynamic_cast則會先進行類型檢查,如果不行就直接報錯,也不會去進行轉化,pd1 = dynamic_cast<D*>(pb),如果成功則pd1指向pb,如果失敗則pd1為空,可以將語句放在條件語句的條件執行中,如果失敗則該表達式為0,就可以得到是執行失敗了,進行失敗的處理。
但要注意的是,使用dynamic_cast的類中要有虛函數,否則就會報錯,這是由於運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表(關於虛函數表的概念,詳細可見<Inside c++ object model>)中,只有定義了虛函數的類才有虛函數表,沒有定義虛函數的類是沒有虛函數表的。
對於引用,因為不存在空引用,所以不可能對引用使用用於指針強制類型轉換的檢查策略,相反,當轉換失敗的時候,它拋出一個 std::bad_cast 異常,該異常在庫頭文件 typeinfo 中定義。可以重寫前面的例子如下,以便使用引用:
void f(const Base &b)
{
try
{
const Derived &d = dynamic_cast<const Derived&>(b);
// use the Derived object to which b referred
}
catch (bad_cast)
{
// handle the fact that the cast failed
}
}
20、數組做函數形參
void Func ( char str[100] )
{
sizeof( str );//結果是4
}
Func ( char str[100] )函數中數組名作為函數形參時,在函數體內,數組名失去了本身的內涵,僅僅只是一個指針;在失去其內涵的同時,它還失去了其常量特性,可以作自增、自減等操作,可以被修改。
數組名的含義如下:
(1)數組名指代一種數據結構,這種數據結構就是數組;
例如:
char str[10];
cout << sizeof(str) << endl;
輸出結果為10,str指代數據結構char[10]。
(2)數組名可以轉換為指向其指代實體的指針,而且是一個指針常量,不能作自增、自減等操作,不能被修改;
char str[10];
str++; //編譯出錯,提示str不是左值
(3)數組名作為函數形參時,淪為普通指針。
