C++預處理命令#define巨集(macro)展開的若干用法
然而 #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(){
...
}