AJIOY的专栏

曾经做过C++开发,写过python,现做ruby 3d开发。酷爱电影CG特效。好记忆不如烂笔头,学习应有札记。...

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(){

...

}



 

阅读更多
版权声明:转载时请标注来自于"AJIOY的专栏" https://blog.csdn.net/ajioy/article/details/6867526
个人分类: C++
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭