然而 #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(){
...
}