孔乙己学C语言(4)

1.3.2宏定义

我们经常不会用名字去称呼自己比较亲近的人,我就经常管一些在企业里工作的同学叫唐总,张总之类的,虽然他们离这个称号还有很长的路要走。在C语言中其实也同样存在这样一个用法,那就是#define,作为出场率很高的预处理语句,关于它的考察题目也是常见的。对于初学者来说,最容易写出来的一厢情愿的代码莫过于#define N(x)  x+100#define ToString(a)  "a" 了。

问题描述:

通常我们在编写C语言程序时,允许用一个标识符来表示一个字符串,称为。而这种标识符我们称为“宏名”。比如下面语句就是一个合法的宏:

#define PI 3.1415926

C编译器对程序进行预处理的时候,就会将将程序中出现的PI全部替换成为3.1415926。使用宏最直接的好处就是可以程序更简洁,而且如果使用的PI需要改变的时候,不需要一个个去修改,直接修改宏就可以了。以下面这个程序为例:

/*example1_3_5.c*/

#include<stdio.h>

#define PI 3.1415926

int main(void)

{

 float Radius,Area;

 scanf("%f",&Radius); /*输入半径的值*/

 Area=PI*Radius*Radius;

 printf("%f\n",Area); /*输出圆的面积*/

 return 0;

}

如果上面程序中的PI精度不要求这么高的话,只需要把这条语句改为#define PI 3.14就可以了。尤其是在编写大型程序的时候,这样使用便于程序的维护和修改。

实例分析:

#define的另一种用法,就是用宏定义来代替一个表达式,这个表达式可以有参数,也可以没有参数。通常一个实现很简单功能的函数,往往写成一个宏定义会更节省系统的开销。

带参宏定义的一般形式为:#define 宏名(形参表) 字符串

使用宏的好处就是可以提高程序运行的效率,但这是对函数调用时而言,比如:

/*example1_3_6.c*/

#include "stdafx.h"

#include<stdio.h>

#define PI 3.1415926

float ComputeArea(float x){

         return PI*x*x;

}

int _tmain(int argc, _TCHAR* argv[])

{

         float Radius,Area;

scanf("%f",&Radius);

Area= ComputeArea(Radius);

printf("%f\n",Area);

         return 0;

}

/*example1_3_7.c*/

#include "stdafx.h"

#include<stdio.h>

#define PI 3.1415926

#define ComputeArea( x) (PI*x*x)

int _tmain(int argc, _TCHAR* argv[])

{

         float Radius,Area;

    scanf("%f",&Radius);

    Area= ComputeArea(Radius);

         printf("%f\n",Area);

         return 0;

}

 

example1_3_6.cexample1_3_7.c相比,一个采用了函数的写法,另一个采用了宏定义的方式。这两种方法看起来都很容易阅读,但是区别是否很大呢

/*example1_3_6.c*/

float ComputeArea(float x){

004113A0  push        ebp 

004113A1  mov         ebp,esp 

004113A3  sub         esp,0C4h 

004113A9  push        ebx 

004113AA  push        esi 

004113AB  push        edi 

004113AC  lea         edi,[ebp-0C4h] 

004113B2  mov         ecx,31h 

004113B7  mov         eax,0CCCCCCCCh 

004113BC  rep stos    dword ptr es:[edi] 

         return PI*x*x;

004113BE  fld         dword ptr [x] 

004113C1  fmul        qword ptr [ __real@400921fb4d12d84a (415740h)] 

004113C7  fmul        dword ptr [x] 

004113CA  fstp        dword ptr [ebp-0C4h] 

004113D0  fld         dword ptr [ebp-0C4h] 

}

004113D6  pop         edi 

004113D7  pop         esi 

004113D8  pop         ebx 

004113D9  mov         esp,ebp 

004113DB  pop         ebp 

004113DC  ret 

   Area= ComputeArea(Radius);

00411429  push        ecx 

0041142A  fld         dword ptr [Radius] 

0041142D  fstp        dword ptr [esp] 

00411430  call        ComputeArea (4111B8h) 

00411435  add         esp,4 

00411438  fstp        dword ptr [Area] 

/*example1_3_7.c*/

/*由于采用了宏定义的写法,因此避免了函数调用时使用的大量资源,两个程序相对比可以发现,采用函数的方式比宏定义的方式多产生了20多条语句*/

 

 

 

 

 

 

 

Area= ComputeArea(Radius);

004113C9  fld         dword ptr [Radius] 

004113CC  fmul        qword ptr [ __real@400921fb4d12d84a (415740h)] 

004113D2  fmul        dword ptr [Radius] 

004113D5  fstp        dword ptr [Area] 

 

 

example1_3_6.c将圆面积的计算公式采用函数的方法,这样使得程序看起来容易阅读,但是这样的程序加大了系统的开销,在函数调用时要消耗一部分系统资源,如果函数完成的功能很复杂,那么这段开销可以忽略不计。但如果像上文这样简单的功能,如果使用函数的话,就有些得不偿失了。这样的话,如果既想程序模块分明,又想减少程序开销的话,就可以选择example1_3_7.c的这种写法:

深入剖析:

现在来看几个常见的#define问题

第一个常见问题,如果做出这个定义,系统是否会接受

#define printf  “hello world”吗?

这里先不要去管宏名是否大写了,大写只是建议并不是规定!小写的宏名同样可以通过编译器的检查。

这个定义是有问题的,但是确实是可以通过编译的。在系统的预处理阶段结束以后,C编译器可以将所有出现的printf都替换为hello world,也就是说宏名是可以用系统的保留字的。但是通常我们不要这样去做。

第二个常见问题,当我们按照下面定义以后

#define PI 3.1415926以后

程序中出现了:

Int CPI

这里面的变量名CPI也包含有PI,那么该语句会不会被替换为

Int C3.1415926呢?

答案显然是不会,这里面预编译器的查找与替换指的的都是完全匹配,就是必须完全一样,不能多也不能少。

第三个常见问题,括号里面的内容是否会被替换同样是

#define PI 3.1415926以后

程序中出现了:

char[] p=”PI”的话

那么预编译器会不会把这个语句替换成下面这个样子

char[] p=” 3.1415926”

预编译器是不会做这个替换的,也就是说,预编译器是不会对程序中引号里面的内容进行替换的

最后一个常见的问题,这也是很多人喜欢出的程序员面试的一道题目

如果我这样定义了这样一段程序,程序的功能是将参数转换为对应字符串,比如参数为5,那么希望的结果为“5”。

/*example1_3_8.c*/

#include "stdafx.h"

#define ToString(a) "a"

int _tmain(int argc, _TCHAR* argv[])

{

         char p[]=ToString(5);

         printf("%S\n",p);

         return 0;

}

那么这段语句执行完成以后,输出的会是什么呢?

出乎很多人意料的是,这里输出的结果是“a”,也就是ToString(5)以后程序的结果是“a”。

事实上,无论ToString()里面的是什么,这段语句执行完以后结果都只能是“a”。

/*example1_3_8.c*/

char p[]=ToString(5);

0041139E  mov         ax,word ptr [string "a" (415740h)]  // 这里编译器将字符“a”赋值给寄存器ax

004113A4  mov         word ptr [p],ax                 //将寄存器中的值赋给了数组p中的第一个元素

根据char p[]=ToString(5);汇编得到的代码可以看出来,结果和ToString()中的参数好像没有任何关系,实际上无论我们将参数设置为多少,结果都会是一样的。

在宏定义中,如果宏名后面是一个字符串的话,那么宏定义是不会去改变字符串里面的内容。实现字符串转换的方法需要采用这种写法:

#define ToString(x) #x

这里面的#x指的就是将变量x转化为字符串,比如程序example1_3_8.c的正确写法因该是

/*example1_3_9.c*/

#include "stdafx.h"

#define ToString(a)  #a

int _tmain(int argc, _TCHAR* argv[])

{

         char p[]=ToString(5);

         printf("%S\n",p);

         return 0;

}

#define中,标准只定义了###两种操作。#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串。

例如#define Join(x,y) x##y

这个宏定义的作用就是将两个变量连接成一个字符串,比如

Join(123,100)的结果就是123100

 

总结:

宏定义中最容易犯的错误还是运算优先性的问题,比如

#define N(x)  x+100

这种定义方式,乍看之下视乎没有任何问题,但是如果在程序中出现了下面的语句:

Int x=2*N(20)

本来我们的目的是希望预处理结束后,该语句变为:

Int x=2*(x+100) 

希望计算的结果是240

但是实际上替换完成以后确是:

Int x=2*x+100

实际计算的结果是140

出现以上的问题,其实只要见过类似的情形,以后就都不会犯这样的错误了。这里还是需要重点提到的就是,最好在宏定义外面加上一个括号,免得在运行中被其他语句改变了初衷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值