使用const定義常量的注意點

條款1:儘可能用const和inline而不用#define 
這個條款最好稱爲:「儘可能用編譯器而不用預處理」,由於#define常常被認爲好象不是語言自己的一部分。這是問題之一。再看下面的語句: 

#define   ASPECT_RATIO   1.653 

編譯器會永遠也看不到ASPECT_RATIO這個符號名,由於在源碼進入編譯器以前,它會被預處理程序去掉,因而ASPECT_RATIO不會加入到符號列表中。若是涉及到這個常量的代碼在編譯時報錯,就會很使人費解,由於報錯信息指的是1.653,而不是ASPECT_RATIO。若是ASPECT_RATIO不是在你本身寫的頭文件中定義的,你就會奇怪1.653是從哪裏來的,甚至會花時間跟蹤下去。這個問題也會出如今符號調試器中,由於一樣地,你所寫的符號名不會出如今符號列表中。 
解決這個問題的方案很簡單:不用預處理宏,定義一個常量: 

const   double   ASPECT_RATIO   =   1.653; 

這種方法頗有效。但有兩個特殊狀況要注意。 
首先,定義指針常量時會有點不一樣。由於常量定義通常是放在頭文件中(許多源文件會包含它),除了指針所指的類型要定義成const外,重要的是指針也常常要定義成const。例如,要在頭文件中定義一個基於char*的字符串常量,你要寫兩次const: 

const   char   *   const   authorName   =   "Scott   Meyers "; 

關於const的含義和用法,特別是和指針相關聯的問題,參見條款21。 

另外,定義某個類(class)的常量通常也很方便,只有一點點不一樣。要把常量限制在類中,首先要使它成爲類的成員;爲了保證常量最多隻有一份拷貝,還要把它定義爲靜態成員: 

class   GamePlayer   { 
private: 
static   const   int   NUM_TURNS   =   5;   //   constant   eclaration   
int   scores[NUM_TURNS];	 //   use   of   constant 
... 
}; 

還有一點,正如你看到的,上面的語句是NUM_TURNS的聲明,而不是定義,因此你還必須在類的實現代碼文件中定義類的靜態成員: 

const   int   GamePlayer::NUM_TURNS;	//   mandatory   definition; 
//   goes   in   class   impl.file 

你沒必要過於擔憂這種小事。若是你忘了定義,連接器會提醒你。 

舊一點的編譯器會不接受這種語法,由於它認爲類的靜態成員在聲明時定義初始值是非法的;並且,類內只容許初始化整數類型(如:int,   bool,   char   等),還只能是常量。 
在上面的語法不能使用的狀況下,能夠在定義時賦初值: 

class   EngineeringConstants   {   //   this   goes   in   the   class 
private:	 //   header   file 
static   const   double   FUDGE_FACTOR; 
... 
}; 
//   this   goes   in   the   class   implementation   file 
const   double   EngineeringConstants::FUDGE_FACTOR   =   1.35; 

大多數狀況下你只要作這麼多。惟一例外的是當你的類在編譯時須要用到這個類的常量的狀況,例如上面GamePlayer::scores數組的聲明(編譯過程當中編譯器必定要知道數組的大小)。因此,爲了彌補那些(不正確地)禁止類內進行整型類常量初始化的編譯器的不足,能夠採用稱之爲「借用enum」的方法來解決。這種技術很好地利用了當須要int類型時可使用枚舉類型的原則,因此GamePlayer也能夠象這樣來定義: 

class   GamePlayer   { 
private: 
enum   {   NUM_TURNS   =   5   }	//   "the   enum   hack "   —   makes 
//   NUM_TURNS   a   symbolic   name   
//   for   5 
int   scores[NUM_TURNS];//   fine 
}; 

除非你正在用老的編譯器(即寫於1995年以前),你沒必要借用enum。固然,知道有這種方法仍是值得的,由於這種能夠追溯到好久之前的時代的代碼但是不常見的喲。 

回到預處理的話題上來。另外一個廣泛的#define指令的用法是用它來實現那些看起來象函數而又不會致使函數調用的宏。典型的例子是計算兩個對象的最大值: 

#define   max(a,b)   ((a)   >   (b)   ?   (a)   :   (b)) 

這個語句有不少缺陷,光想一想都讓人頭疼,甚至比在高峯時間到高速公路去開車還讓人痛苦。 
不管何時你寫了象這樣的宏,你必須記住在寫宏體時對每一個參數都要加上括號;不然,別人調用你的宏時若是用了表達式就會形成很大的麻煩。可是即便你象這樣作了,還會有象下面這樣奇怪的事發生: 

int   a   =   5,   b   =   0; 
max(++a,   b);//   a   的值增長了2次 
max(++a,   b+10);   //   a   的值只增長了1次 

這種狀況下,max內部發生些什麼取決於它比較的是什麼值! 
幸運的是你沒必要再忍受這樣愚笨的語句了。你能夠用普通函數實現宏的效率,再加上可預計的行爲和類型安全,這就是內聯函數(見條款33): 

inline   int   max(int   a,   int   b)   {   return   a   >   b   ?   a   :   b;   } 
不過這和上面的宏不大同樣,由於這個版本的max只能處理int類型。但模板能夠很輕巧地解決這個問題: 

template <class   T> 
inline   const   T&   max(const   T&   a,   const   T&   b) 
{   return   a   >   b   ?   a   :   b;   } 

這個模板產生了一整套函數,每一個函數拿兩個能夠轉換成同種類型的對象進行比較而後返回較大的(常量)對象的引用。由於不知道T的類型,返回時傳遞引用能夠提升效率(見條款22)。 

順便說一句,在你打算用模板寫象max這樣有用的通用函數時,先檢查一下標準庫(見條款49),看看他們是否是已經存在。好比說上面說的max,你會驚喜地發現你能夠後人乘涼:max是C++標準庫的一部分。 
有了const和inline,你對預處理的須要減小了,但也不能徹底沒有它。拋棄#include的日子還很遠,#ifdef/#ifndef在控制編譯的過程當中還扮演重要角色。預處理還不能退休,但你必定要計劃給它常常放長假。
相關文章
相關標籤/搜索