1. 程式人生 > >C++預處理命令#define巨集(macro)展開的若干用法

C++預處理命令#define巨集(macro)展開的若干用法

           提及#define,你會不會馬上聯想到自己時時用#define MAX 100用於標記一個數組的長度?這樣做無非兩個原因,一來,#define是編譯時的巨集展開,對執行時間毫無影響。其二,使用諸如MAX這類見名知其意的識別符號代替生硬的數學符號使程式更加清晰,而且更改起來著實方便,一改全改。試想一下,一個幾萬行的程式碼工程中有1000多個200,500這樣的數字,你逐一改下去,即便再有耐心也會抓狂吧。(MSDN:You can use the#define directive to give a meaningful name to a constant in your program.)

然而 #define巨集展開的用法遠不止這一點,它還有其他更多讓人意想不到的方式,好好利用,可以提升你的程式碼質量。筆者在此作一個小小的總結,以便大家查閱。

#define指令一共有兩種語法定義

Syntax

#define identifier token-stringopt 

#define identifier[(identifieropt,... ,identifieropt)]token-stringopt

第一種是形如#define MAX 100之類不帶引數的定義,而第二種就是筆者今天要重點提及的形式,帶引數形式巨集展開。

#define WIDTH       80
#define LENGTH      ( WIDTH + 10 ) //不帶引數巨集定義

話不多說,咱看例子Example1:

#include <iostream>
#define Error(n) std::cout<<"Error "#n<<std::endl;//巨集展開 
#define Joint(a,b,c) a##b##c  //連線字串a,b,c 
int main(){
	Error(123);
	unsigned int Link = Joint(12,06,3);
	std::cout << link << std::endl;
    return 0;	
}

執行結果:

Error 123

12063

這種用法是把一個類似於函式形式的表示式巨集展開,Error(n) 在編譯時會用std::cout<<"Error "#n<<std::endl語句替代,這句話的作用就是輸出字串"Error",接著用"#"連線n,n的值需要在程式編寫時指定,而不能用類似於函式的形式接受"使用者"的賦值。

如果在Error(123);這句之前加上

  int test;
 std::cin >> macroT;
 Error(macroT);

這裡不會發生奇蹟。你輸入一個123或者任何其他的數字,它執行結果只會是這樣

Error macroT

而不是Error 123(或其他數字)

給你5秒鐘想想為什麼?

。。。

            有些人會說,這句巨集展開的樣子長得實在太像函數了,就像雙胞胎一樣。

然而它們的本質卻確完全不一樣。一個是編譯時執行(#define Error(n)),而一個是執行時執行(void Error(n))。

            仔細想一下,編譯是在執行之前執行的,而std::cin>>macroT是在執行時才執行的,所以它們是配不到一塊去的。至於為什麼會輸出"test"這樣的字串,

通過解釋這句程式碼

std::cout << "Compile:";
 Error(macroT);

結合巨集展開

Error(n) std::cout<<"Error "#n<<std::endl;可以推斷它Error(macroT)作用實際是

輸出字串"Error"+"macroT",沒錯,這裡的macroT它不是那個整型變數macroT,只是一個字串型引數,即n,n後面"#"的作用是把字串n連線到之前的"Error "字串中。

還不明白?下面這個例子會加深你的理解。

#include <iostream>
#define Error(n) std::cout<<"Error "#n<<std::endl;//巨集展開 
#define Joint(a,b,c) a##b##c  //連線a,b,c字串
void ErrorRun(int n){   //函式呼叫方式
    std::cout << "Error " << n << std::endl;
}
int main(){
	int macroT;
	std::cin >> macroT;
	
	std::cout << "Compile:";
	Error(macroT);// macroT只是一個引數,名字跟之前定義的int型變數相同而已,代表的東西不同
	             //   macroT在編譯時就進行了連線,先於int型變數。
	std::cout << "Run:    ";
	ErrorRun(macroT);//通過函式呼叫才是int型的變數macroT,它是使用者在執行程式時指定的
	unsigned int Link = Joint(12,06,3);
	std::cout << "Joint:  " Link << std::endl;
    return 0;	
}

輸入123

執行結果:

Compile:Error macroT
Run:    Error 123
Joint:  12063

明白了吧。我這麼笨瓜的人都懂了。這個弄懂了下面的Joint(a,b,c)就稍好說了。首先,兩條巨集指令大體上相同,不過Joint(a,b,c)後面不是一個輸出這樣的動作型語句了,而是a##b##c。那麼##是連線什麼型別?int?char?char*?還是什麼?假設你不知道,可以經過多次試驗證實##只連線int 型。

#define Joint2(a,b,c) a#b#c

  char Link2 = Joint2('12','06','3');
 std::cout << Link2 << endl;  //通不過編譯

所以不能想當然地“造句”。

#define Joint(ab,c) a##b##c

unsigned int Link = Joint(12,06,3);

作用:先將Joint(12,06,3);展開為12063,這個數是int型,再將其賦值給Link,,最後輸出。

NOTE:巨集展開只佔編譯時間,不佔執行時間。函式呼叫會影響執行時間,需要分配單元,保留現場,值傳遞,返回。

再舉個例子

Example2:

#include <iostream>
using namespace std;
// Macro to get a random integer with a specified range 
#define getrandom(min, max) \
    ((rand()%(int)(((max) + 1)-(min)))+ (min)) //得到隨機數的公式
 inline int GetRandom(int min,int max){ //內聯地展開 
      return (rand()%(int)(max+1-min)+min);
 }
int main(){
	srand((unsigned)time(NULL));//時間種子,每次執行時得到的隨機數都不相同
	cout << "Get Random number through #define:" << endl;
	//通過巨集展開方式得到10個隨機數 
	for(int i = 0; i != 10; ++i){ 
    int tmp = getrandom(2,100);
    cout << tmp << ' ';
	}
	cout << endl;
	
	cout << "Get Random number through function:" << endl;
	//通過函式呼叫方式得到10個隨機數 
	for(int i = 0; i != 10;  ++i){
	   int temp = GetRandom(2,100);
	   cout << temp << ' ';	
	}
	cout << endl;
	return 0;
}

這裡定義GetRandom(int,int)為行內函數,由於筆者今天重點在巨集上,所以對行內函數不作詳解(感興趣的話點選我)。行內函數類似於巨集展開,形式像函式,但是它是編譯時直接展開的。有一點很奇怪,GetRandom(int,int)可以接收在執行時得到的min和max(這一點巨集展開絕對不允許,但行內函數做到了)。如果你知道原因的話請留個言告訴我吧。

         int min,max;
	cin >> min >> max;
	for(int i = 0; i != 10;  ++i){
	   int temp = GetRandom(min,max); //接收執行時的值,不明白為什麼還能正常工作。
	   cout << temp << ' ';	
	}

注意:在巨集展開語句#define getrandom(min, max) \
    ((rand()%(int)(((max) + 1)-(min)))+ (min)) 中的公式((rand()%(int)(((max) + 1)-(min)))+ (min)) (max)和(min)一定要加上括號,不然易出錯。

如:

#define PI 3.1415926

#define DEMO(r) PI * r * r

...

area =DEMO(3);

顯然這樣是沒有什麼問題,但是,如果是area = DEMO(a+b);(假定a,b已經在程式設計時賦值)呢?巨集展開是PI * a + b * a + b,這與我們的意願不符,所以,應該設計成以下方式

#defien DEMO(r)  PI * ( r )  * (  r )

這樣會展開為PI * ( a + b) * (a + b),不容易出錯。養成良好的習慣非常重要。

函式可以用過引用的方式返回多個值:

如int DEMO(int a,int &b,int &c){

...

return a;

}

可以返回a,b,c三個值。

巨集展開也可以返回多個值:

Example3:

#include <iostream>
#include <iomanip>  //用於保留小數點位數 
using namespace std;
#define PI 3.1415926
#define A 100
#define B 200
#define RADIUS 50
#define AREA(LINE,RECTANGLE,TRIANGLE,CIRCLE)\
        LINE = 0; RECTANGLE = A * B;\
        TRIANGLE = 0.5 * RECTANGLE; CIRCLE = PI * RADIUS * RADIUS;//RECTANGLE 先於TRIANGLE正確 

#define CIRCLE(R,L,S,V) L = 2 * PI * R; S = PI * R * R; V = 4.0/3.0 * PI * R * R * R;
int main(){
       float line,rectangle,triangle,circle;
       AREA(line,rectangle,triangle,circle);
       cout << line << "\t" << rectangle << "\t" \
       << triangle << "\t" << circle << endl;
       
       float r,l,s,v;
       cin >> r;
       CIRCLE(r,l,s,v); //返回多個值
       cout << r << "\t" << l << "\t" << s << "\t" << v << endl;
       cout.precision(2);
       cout<<setiosflags(ios::fixed)<<setprecision(2)<<s<<endl; // 保留兩位小數
       return 0;
}


這個程式比較好理解,大家可以自己琢磨一下。關於巨集展開的用法就交流到這裡吧。相信看完此篇文章,你會對#define指令有一個更深層次的理解。


 附錄:

可以用#undef命令終止巨集定義的作用域。

int main(){

...

}//#define有效範圍截止於此

#undef

f1(){

...

}