參考自李健的《編寫高質量代碼--改善C++程序的150個建議》
0 不要讓main函數返回void
操作系統將main作爲程序入口,main函數執行程序代碼,最後返回程序的退出狀態。標準的C/C++中並不支持void main(),VC支持,gcc則不支持;
但是C++中有一個好壞難定的規定:
在main函數中,return語句用於離開main函數(析構掉所有的具有動態生存時間的對象),並將其返回值作爲參數來調用exit函數。如果函數執行到結尾而沒有遇到return語句,其效果等同於執行了return 0;。
也就是說編譯器會協助完成返回值的問題。
然而,這樣的壞處就是,當編譯器不支持這個規定的時候,程序就會出錯,比如
int main()
{
}
在VC下編譯是成功的,在g++下編譯,就會提示錯誤:‘main’必須返回’int‘
1 區分0的4個面孔
1 整型0
2 空指針NULL
3 字符串結束標記'\0'
' \0'是一個字符,佔8位,其二進制位表示是 00000000,在C/C++中,'\0'作爲字符串的結束標記,是唯一的結束標記
4 邏輯FALSE/false
false/true是標準C++語言裏新增的關鍵字,而FALSE/TRUE是通過#define定義的宏:
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#defineTRUE 1
#endif
也就是說FALSE/TRUE是int類型,而false/true是bool型,兩者是不一樣的,bool在C++裏是佔用1個字節
2 避免那些有運算符引起的混亂
=和== :在if語句中 比較兩個表達式是否相等,
if( nValue == 0)
{ ... }
如果程序員出現疲勞或精神不集中,把’==‘寫成’=‘,這樣if(nValue = 0),那麼這個if條件是恆成立,並且nValue被賦值爲0,並且不會提示錯誤
這恐怕不是程序員想要的。。。
但如果把常量寫在前面,如if(0 == nValue){ },這樣如果程序員寫成if(0 = nValue){ },編譯器會直接的提示錯誤
但是對於&和&& 、|和|| 這類運算符,就需要平時養成謹慎的習慣了。
3 對表達式計算順序不要想當然
讓我們來看一個例子
if(nGrade & MASK == GRAND_ONE)
{...}
我們的本意是,通過nGrade和MASK取與,然後在比較是否等於GRAND_ONE,
可是,實際上上面的代碼的真實效果是if( nGrade & (MASK == GRAND_ONE)){}
這個建議的核心是,不要吝嗇括號,讓語義表達的更準確,這樣可以減少出錯
如: a = p() + q() * r();
三個函數p() 、q() 、r()的執行順序可能是6種組合中的一個,所以a的值是不確定的,在這種情況下,就需要明確一種執行順序,
int x=p();
int y = q();
a = x+y*r();
類似的: expr1 ? expr2 : expr3
4 小心宏#define使用中的陷阱
定義宏時,要使用完備的括號:
比如定義兩個參數相加, #define ADD(a,b) ((a)+(b))是一個安全的方式
使用宏時,不允許參數發生變化,比如,ADD( a++, --b )
定義宏時,用大括號將宏所定義的多條表達式括起來,比如 #define INIT { int a=1;int b=2;int c=3; }
5 不要忘記指針變量的初始化
局部變量的指針未初始化,可能導致程序崩潰
全局變量的指針,編譯器會悄悄完成變量的初始化(0)。
6 明晰逗號分隔表達式的奇怪之處
逗號表達式是從C繼承來的,其中每個表達式都會被執行,不過,整個表達式的值僅是最右邊表達式的結果
如:if(++x,--y,x<20 && y >0),該語句返回的是“ x<20 && y>0”與0比較的結果
另外,逗號表達式既可以用作左值,也可以用作右值。
7 時刻提防內存溢出
C語言中的字符串庫沒有響應的安全保護措施,strcpy、strcat等函數操作時沒有檢查緩衝區大小,容易引起安全問題
好的方法是,定義一個函數,並傳遞確定的長度,如:
const int DATA_LENGTH = 16;
void DataCopy(char* src ,int len)
{
char dst[DATA_LENGTH];
for(int i=0;src[i] != 0 && i<DATA_LENGTH; i++)
cout<<src[i]<<endl;
if(len < DATA_LENGTH)
strcpy(dst,src);
}
這裏如果,src的長度是10,當i=10時,src[10]就已經越界了。。。
8 拒絕晦澀難懂的函數指針
比如 void (*p[10]) (void (*)() );
1 聲明一個無參數、返回空的函數指針的typedef,如:typedef void (*pfv)();
2 聲明一個指向參數爲pfv且返回空的函數指針, 如 : typedef void (*pFun_taking_pfv) (pfv));
3 定義數組,pFun_taking_pfv p[10];
9 防止頭文件重發包含
方式一:
#ifndef __TEST_H__
#define __TEST_H__
......
#endif
方式二:
#pragma once
優缺點:方式一的缺點是宏導致的編譯時間長
方式二的缺點是隻是針對物理路徑相同的文件,而不是文件的內容
10 優化結構體中元素的佈局
瞭解結構體中元素的對齊規則,合理地對結構體元素進行佈局,可以有效的結餘空間,還可以提高元素的存取效率
首先看兩個成員相同的結構體:
struct A {
int a;
char b;
short c;
};
struct B {
char b;
int a;
short c;
};
在內存中的排列是
struct A
struct B
註釋:A中前4個字節存放a,第5個字節存放b,最後兩個字節存放c,白色的表示系統填充的字節
B中第一個字節存放b,緊接着3個字節爲編譯器填充字節,第5到8個字節存放a,9到10存放c,最後兩個字節爲編譯器填充。
結構體的對齊規則:
1. 結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2. 結構體的每個成員相對於結構體首地址的偏移量都是該成員自身大小的整數倍,比如上面的結構體B中的3個變量的相對於首地址的偏移量都是自身大小的整數倍;
3. 結構體的總大小應爲結構體中最寬基本類型大小的整數倍,如果需要,編譯器會在最後一個成員後填充字節,如上面的struct B的末尾。
11 將強制轉型減到最少
C風格的強制轉換語法:
( Type ) exp 或者 Type ( exp )
標準C的強制類型轉換可能導致內存的擴張和截斷
擴張:把char型的變量強制轉換成int
截斷:把int型轉換成char型,
特殊的情況:
unsigned int i = 65535;
int j = (int) i;
這樣j就變成了-1
在C中,void類型的指針可以與其他類型的指針互相轉換
在C++中,不允許不同類型的指針轉換,而提供了四個轉換方法:
const_cast<T*>(a)
它用於從一個類中去除:const、volatile和__unaligned屬性
dynamic_cast<T*>(a)
它將a值轉換成類型爲T的對象指針,主要用來實現類層次結構的提升,也被稱做"安全向下轉型",即基類轉向派生類是嚴格和安全的
reiniterpret_cast<T*>(a)
它用於轉換不相干的類型,多用於指針類型的轉換,類似C的強制類型轉換,是不安全的
static_cast<T*>(a)
它多用於基本類型的轉換,在轉換過程中不進行類型檢查,不能確保轉換是安全的
12 優先使用前綴操作符
對於整型和長整形,前綴和後綴操作的性能區別可以忽略
對於自定義的類型,前綴操作省去了臨時對象的構造,效率上優於後綴
建議13 變量定義的位置和時機
儘可能的推遲變量的定義,直到不得不需要定義時爲止;
儘可能的縮小變量的作用域,減少變量名污染,提高程序可讀性
14 小心使用typedef
typedef與define的不同:typedef不是簡單的字符替換,是對類型的整體封裝,就好像類型的別名一樣;define只是簡單的字符串替換,沒有任何的其他操作
typedef在語法上是一個存儲類的關鍵字,類似auto、extern、mutable、static、register;雖然它不會是真正影響對象的存儲特性,但:
typedef staticint iInt;這行代碼編譯器會提示“指定了一個以上的存儲類型”
下面是來自http://www.cnblogs.com/kerwinshaw/archive/2009/02/02/1382428.html
陷阱:
記住,typedef是定義了一種類型的新別名,不同於宏,它不是簡單的字符串替換。比如:
先定義:
typedef char* PSTR;
然後:
int mystrcmp(const PSTR, const PSTR);
const PSTR實際上相當於const
char*嗎?不是的,它實際上相當於char*
const。
原因在於const給予了整個指針本身以常量性,也就是形成了常量指針char*
const。
簡單來說,記住當const和typedef一起出現時,typedef不會是簡單的字符串替換就行。
15 避免使用可變參數的函數
編譯器對可變參數函數的原型檢查不夠嚴格,所以容易引起問題,難於排錯:
儘量使用C++中的函數重載代替可變參數函數
16 慎用goto
在程序中的一組嵌套循環中,滿足某種退出循環的情況時,會用到goto
goto語句破壞了程序的結構性,影響了程序的可讀性,因此應儘可能少的使用goto語句
17 提防隱式轉換帶來的麻煩
在C中void*可以與其他類型的相互轉換,C++中void* 與其他類型的轉換是單向的,即只允許其他類型轉換成void*
儘量控制隱式轉換的發生:通常採用的方式包括:
(1) 使用非C/C++關鍵字的具名函數,用operateor as_T()替換operator_T()(T爲C++的數據類型)
(2) 爲單參數的構造函數減少explicit 關鍵字
18 正確區分void與void*
void是無類型的意思,它不是一種數據結構
void*表示無類型指針,即它可以指向任何類型的數據