<b>1.7 預處理</b>
c++提供的預處理功能主要有以下4種:宏定義、檔案包含、條件編譯和布局控制。檔案包含在前面已描述過,下面重點描述宏定義、條件編譯和布局控制,其中又着重講述常用宏定義指令、do…while(0)的妙用、條件編譯及extern"c"塊的應用知識。
1.?常用宏定義指令
#def?ine指令是一個宏定義指令,它用來将一個辨別符定義為一個字元串,該辨別符被稱為宏名,被定義的字元串稱為替換文本。該指令有兩種格式:一種是簡單的宏定義,另一種是帶參數的宏定義。
簡單的宏定義的聲明格式如下所示:
#define 宏名 字元串
例:#def?ine pi 3.1415926
帶參數的宏定義的聲明格式如下所示:
#define 宏 (參數表列) 宏
例:#def?ine a(x) x
使用宏定義中,要注意以下問題。
(1)在簡單宏定義的使用中,當替換文本所表示的字元串是一個表達式時,需要加上括号,否則容易引起誤解和誤用。
【例1.17】 簡單宏定義不加括号容易引起誤用。
#include<iostream>
#define n 2+9
using namespace
std;
int main(){
int a=n*n;
cout<<a<<endl;
return 0;
}
程式的執行結果是:
29
例1.17中就出現了問題:在此程式中存在着宏定義指令,宏n代表的字元串是2+9,在程式中有對宏n的使用,一般同學在讀該程式時,容易産生的問題是先求解n為2+9=11,然後在程式中計算a時使用乘法,即n*n=11*11=121,其實該題的結果為29,為什麼結果有這麼大的偏差?因為宏展開是在預處理階段完成的,這個階段把替換文本隻是看作一個字元串,并不會有任何的計算發生,在展開時是在宏n出現的地方隻是簡單地使用串2+9來代替n,并不會增添任何的符号,是以對該程式展開後的結果是a=2+9*2+9,計算後結果為29。要程式如之前想要的結果,隻需要寫成#def?ine
n (2+9),即加上括号就行。
(2)類似地,在帶參數的宏定義的使用中,也容易引起誤解。例如當需要使用宏替換來求任何數的平方,這時就需要使用參數,以便在程式中用實際參數來替換宏定義中的參數。初學者容易寫成如例1.18中的形式。
【例1.18】 帶參數的宏定義不加括号容易引起誤用。
#define area(x)
x*x
int main()
{
int y = area(2+2);
cout<<y<<endl;
8
表面上看,給的參數是2+2,所得的結果應該為4*4=16,但該程式的實際結果為8。宏定義中要遵循先替換後計算的原則,在上面的程式裡,2+2即為宏area中的參數,應該由它來替換宏定義中的x,即替換成2+2*2+2=8了。那如果遵循(1)中的解決辦法,把2+2括起來,即把宏體中的x括起來,是否可以解決呢?#def?ine area(x) (x)*(x),對于area(2+2),替換為(2+2)*(2+2)=16,可以解決,但是對于area(2+2)/area(2+2)又會怎麼樣呢,有人一看到這道題馬上給出結果1,因為分子分母一樣,那麼這樣就又錯了。遵循先替換再計算的規則,這道題替換後會變為(2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除運算規則,結果為16/4*4=4*4=16。解決這類問題的方法是在整個宏體上再加一個括号,即#def?ine area(x) ((x)*(x)),不要覺得這沒必要,沒有它是不行的。
要想能夠真正使用好宏定義,在讀别人的程式時,一定要記住先将程式中對宏的使用全部替換成它所代表的字元串,不要自作主張地添加任何其他符号,完全展開後再進行相應的計算,就不會求錯運作結果。
如果是自己在程式設計時使用宏替換,則在使用簡單宏定義時,當字元串中不隻一個符号時,加上括号表現出優先級,如果是帶參數的宏定義,則要給宏體中的每個參數加上括号,并在整個宏體上再加一個括号。
2.?do...while(0)的妙用
大家都知道,do{…}while(condition)可以表示循環,但你有沒有遇到在一些宏定義中可以不用循環的地方,也用到了do{…}while,比如有這樣的宏:
#define foo(x)
do{\
statement one;\
statement two;\
}while(0) // 這裡沒有分号
粗看會覺得很奇怪,既然循環裡面隻執行了一次,那要這個看似多餘的do...while(0)有什麼意義呢?再來看這樣的宏:
{\
這兩個看似一樣的宏,其實是不一樣的。前者定義的宏是一個非複合語句,而後者卻是一個複合語句。假如有這樣的使用場景:
if(conditon)
foo(x);
else
...;
因為宏在預處理的時候會直接被展開,采用第2種寫法,會變成:
if(condition)
statement one;
statement two;
...///
這樣會導緻else語句孤立而出現編譯錯誤。加了do{...}while(0),就使得宏展開後,仍然保留初始的語義,進而保證程式的正确性。
3.?條件編譯
一般情況下,源程式中所有行的語句都參加編譯。但是有時程式員希望其中一部分内容隻在滿足一定條件時才進行編譯,也就是對一部分内容指定編譯的條件,這就用到了“條件編譯”。
條件編譯指令最常見的形式為:
#ifdef 辨別符
程式段1
#else
程式段2
#endif
它的作用是:當辨別符已經被定義過(一般是用#def?ine指令定義),則對程式段1進行編譯,否則編譯程式段2。其中#else部分也可以沒有,即:
下面這樣的形式則是當指定的表達式值為真(非零)時就編譯程式段1,否則編譯程式段2。可以事先給定一定條件,使程式在不同的條件下執行不同的功能。
#if 表達式
這裡的“程式段”可以是語句組,也可以是指令行。
有時候程式中的某些調試代碼,隻需要在調試的時候被編譯,而不希望在程式的正式發行版中被編譯,你可能會看到類似例1.19這樣的代碼段。
【例1.19】 調試代碼巧用條件編譯。
#define _debug_
int x=10;
#ifdef _debug_
cout<<"file:"<<
__file__<<",line:"<<
__line__<<",x:"<<x<<endl;
#else
printf("x = %d\n", x);
cout<<x<<endl;
#endif
程式的運作結果是:
file:test.cpp,line:7,x:10
當_debug_沒有被定義的時候,僅編譯#else與#endif之間的代碼;當定義了_debug_符号之後,則會編譯#ifdef與#else之間的代碼。要想定義一個符号很簡單,隻需要在檔案頭部加上像這樣的一條語句:
#define _debug_
用#def?ine指令的目的不在于用_debug_代表一個字元串,而隻是表示已定義過_debug_,是以_debug_後面寫什麼字元串都無所謂,甚至可以不寫字元串。
4.?extern
"c"塊的應用
經常能在c與c++混編的程式中看到這樣的語句:
#ifdef
__cplusplus
extern "c" {
...
#ifdef __cplusplus
}
其中,__cplusplus是c++的預定義宏,表示目前開發環境是c++。在c++語言中,為了支援重載機制,在編譯生成的彙編代碼中,會對函數名字進行一些處理(通常稱為函數名字改編),如加入函數的參數類型或傳回類型等,而在c語言中,隻是簡單的函數名字而已,并不加入其他資訊,如下所示:
int func(int
demo);
int func(double
c語言無法區分上面兩個函數的不同,因為c編譯器産生的函數名都是_func,而c++編譯器産生的名字則可能是_func_fi和_func_fd,這樣就很好地把函數差別開了。
是以,在c/c++混合程式設計的環境下,extern
"c"塊的作用就是告訴c++編譯器這段代碼要按c标準編譯,以盡可能地保持c++與c的相容性。例1.20說明了__cplusplus的使用方法。
【例1.20】 __cplusplus的使用方法。
#include<stdio.h>
int main() {
#define to_literal(text) to_literal_(text)
#define to_literal_(text) #text
#ifndef __cplusplus
/* this translation unit is being treated
as a c one */
printf("a c program\n");
/*this translation unit is being treated
as a c++ one*/
printf("a c++ program\n__cplusplus
expands to \""
to_literal(__cplusplus)
"\"\n");
a c++ program
expands to "1"
例1.20中程式的意思是:如果沒有定義__cplusplus,那麼目前源代碼就會被當作c源代碼處理;如果定義了__cplusplus,那麼目前源代碼會被當中c++源代碼處理,并且輸出__cplusplus宏被展開後的字元串。