浅谈c语言中的宏定义的运用


前言

在c语言中,以"#"开头的语句叫做预处理命令,如#include,#define等。这些命令通常放在函数之外,放在源文件前,被称为预处理部分。预处理是在编译之前完成的,所以有些语法或逻辑错误可能一开始发现不了。懂得如何聪明地使用预处理命令,对于程序的阅读、修改、和调试非常有利。在这里我整理了一些关于#define预处理命令的运用,虽然不是很深入,但以实用为目的。

一、无参宏定义

一般的形式为:

#define 标识符 字符串

例如:

#define pi 3.14159
#define M (x * x + y * y)
#define hour (clock() / 6000 % 24)

顾名思义,无参宏定义就是不带参数,所作的就是单纯的进行字符替换。
第一行把pi替换成3.14159,避免了使用时的反复输入和可能的输入错误。便于阅读,因为pi比起数字有着更明确的含义。也便于修改参数时的维护等操作。
第二、三行表明,可以用简单的符号来替代程序中反复出现的表达式中。符号最好有明确的意义,比如用hour代表小时,这样便于阅读和修改。
这里值得注意的是,在表达式的两边要加上一对小括号。因为只是简单的字符替换,替换后表达式的运算符号的优先级会影响结果(除非你要的就是这种效果),加上小括号是很好的习惯。举个例子:

#define M x * x + y * y      //如果不加小括号

当使用表达式M / 2,替换后就是x * x + y * y / 2,运算的顺序不能如愿。如果宏定义时有小括号替换之后就是(x * x + y * y) / 2这样就没有改变运算的顺序了。
当使用第二行宏定义时,程序中必须已经定义了x和y变量,也不能对其他的变量进行操作。如果想要更自由的使用,这时就需要带参宏定义了。

二、带参宏定义

一般的形式为:

#define 宏名(参数表) 字符串

例如:

#define Pow(x) ((x) *( x))
#define M(x,y) ((x) * (x) + (y) * (y))
#define Min(a,b) ((a) < (b) ? (a) : (b))

参数的个数可以是一个也可以是两个或者多个。另外,宏名和参数表之间不能有空格。这里再次提醒一下记得加小括号,而且不仅表达式两边要加,变量的两边也要加,不然仍然可能因为运算优先级的问题出现问题。举个例子:

#define Pow(x) (x * x)	//如果变量两边没有括号

当我们这么使用时Pow(2+3),我们期望的结果是5*5=25,然而替换后的结果是(2+3*2+3),计算结果是11。加上小括号之后就对了((2+3)*(2+3))
但是不得不说的是,因为宏定义进行的只是死板字符的替换,有些使用还是会有问题,比如我这么使用第四行的宏定义Min(a++,b),进行替换之后就是((a++)<(b)?(a++):(b)),我们注意到a++有可能被执行两次,这就可能会出问题。

三、代码段定义

当我们想要用宏定义做更复杂的事情的时候,不是一句表达式能完成的时候,我们可以对一段代码进行宏定义,常见有如下两种方法:

#define Swap_1(a,b) do {int t = a; a = b; b = t; } while(0)
#define Swap_2(a,b) { \
	int t = a; \
	a = b; \
	b = t; \
}

第一种方法是将代码段放在一个do-while语句中,这种写法的有个巧妙之处是do-while语句的语法是最后while(0)后面需要一个的,所以我们可以像调用函数一样调用Swap_1(a,b),然后在其后加一个;而不会有语法错误。
但是宏定义每行能写下的代码是有限的(80个字符),当我们需要宏定义更多的代码段时就不能用这种方法。再者,这种写法不利于阅读。所以有了第二种方法。我们可以在每一句的最后加上一个\来将下一行的内容包含进宏定义。
这样的写法就很像函数了,但相比函数有很多优点:程序在预编译阶段就将代码段进行了替换,省去了创建函数、传参和销毁函数的时间空间消耗。

四、# 操作符

这里指的不是用在define前的#,而是指用在#define后面的#操作符。#操作符的作用就是把修饰的参数转换为一个字符串。使用方法和带参宏定义类似,例如:

#define MKSTR(str) #str
#define DOBUG_OUT(var) printf(#var"=%d\n",var)

第一行。#strstr的区别就是,前者将str对应的标识符转换成了字符串,后者只是表示str对应的标识符。所以当我们这么使用printf(MKSTR(baidu.com\n))时,#操作符把baidu.com\n替换成了字符串"baidu.com\n",所以结果就是输出了字符串baidu.com\n。当你需要输出变量名时,这可以是个很好的运用。
第二行。#操作符可以用于输出调试信息。比如我想知道reg的值,可以这么使用DEBUG_OUT(reg),替换后就是printf("reg""=%d\n",reg)。编译时会把两个双引号隔开的字符串当作一个字符串来处理,等效于printf("reg=%d\n",reg)。这样输出信息里包含变量名,从而方便调试。

五、# # 操作符

# #操作符的用途是合并变量名,常用于嵌入式开发的寄存器命名中,例如:

#define PT(X) PT# #X

当我们这么使用PT(A)时,替换后就是PTA
又例如:

#define PT(X,n,REG) BITBAND_REG(PT# #X# #_BASE_PTR-># #REG,n)		//位操作
#define PTA0_out PT(A,0,PDOR)

连用两个宏定义,当我们使用PTA0_out,就会被替换成BITBAND_REG(PTA _BASE_PTR->PDOR,0),从而避免输入太长的代码和错误的风险,极大的方便了编程。

有一点需要注意的就是被## #修饰的宏参数是不支持宏的,我们无法通过宏定义再将被## #修饰的宏参数进行替换


六、预定义的宏

在c语言中,系统封装了一些很好用的宏,我们可以直接调用。有部分宏是是非标准的,有时候有些编译器是没有的。我在这里罗列一些,具体用法就不啰嗦了:

说明
__DATE __日期:Mmm dd yyyy
__TIME __时间:hh:mm:ss
__LINE __行号
__FILE __文件名
__FUNC __函数名/非标准
__PRETTY_FUNCTION __更详细的函数说明/非标准

七、条件式编译

条件式编译常用在代码的调试和代码的裁剪上,以适配不同的环境。此处不做细说,稍作罗列。

函数说明
#ifdef DEBUG是否定义了DEBUG宏,DEBUG不能是变量
#ifndef DUBUG是否定义了DEBUG宏
#if MAX_N == 5宏MAX_N是否等于5
#elif MAX_N == 4否则宏MAX_N是否等于4,elif是else if的简写
#else
#endif只要用到了条件式编译,最后一定要使用这个

总结

宏定义因其特性,用与替代一些简单的函数时可以相比函数效率更高。善于使用宏定义可以使我们的代码更容易阅读,修改,调试,移植和维护。对于一些宏定义使用中的小细节比如加括号等要注意,并养成习惯。还要注意使用时的习惯,避免参数表达式的重复执行等。了解系统预定义的宏,可以方便的实现一些此前难以实现的操作。还有条件式编译虽然不在大学课程中强调,但在工程实践中运用非常广泛。
总之懂得聪明的使用宏定义是很重要的事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值