C语言宏定义的巧妙用法
在我学习32的过程中发现了这样一段代码:
/*信息输出*/
#define EEPROM_DEBUG_ON 1
#define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...) do{\
if(EEPROM_DEBUG_ON)\
printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
这种代码是参考Linux内核写出来,方便我们在32使用过程中检测信息,返回错误,以及程序调试。我相信大部分人会发现,对于宏的了解绝对没有自己想像中的那么多。
下面就来讲讲宏:
C语言中的宏定义
#define是预处理器处理的单元实体之一
#define定义的可以出现在程序的任意位置
#define定义之后的代码都可以使用这个宏
C语言中的宏常量
#define定义的宏常量可以直接使用
#define定义的宏常量本质为字面量
1、宏可以像大多数函数一样被定义,我记得某公司出过这样一道面试题,要求用宏定义来比较大小,输出最大值。
这道题可能看似很简单我们只需要定义一个宏和两个参数就可以了。
像这样:#define MAX(x,y) x > y ? x : y
但要是只能写出这样的代码只能说明我们有相应的基础,但这还远远不够。
要知道预处理器遇到宏定义时原则上会全部展开,这样运行上面的代码似乎没有什么问题。
但当我们运行下例代码时:printf("max=%d",MAX(1!=1,1!=2));
我们会发现输出的结果跟我们的预期是不一样的,这是因为printf();函数展开后是这样的:printf("max=%d",1!=1>1!=2?1!=1:1!=2);
我们可以将宏改为#define MAX(x,y) (x) > (y) ? (x) : (y)
到此这才算是一个可以运行的宏;
但此宏还是有问题,本文主要讲解“#”和“##”用法。
想要了解如何更好的写出近乎完美的宏定义可以参考:C语言仅凭自学能到什么高度?.
2、当宏自己调用自己时会发生什么:
例:#define ADD(x) (ADD(x)+x)
如上宏定义调用时会直接展开,我们可能会认为当前宏会对自己无限递归。但C语言为了防止无限递归造成死循环。语法规定,当宏在遇到自己时,就停止展开当前宏,也就是说,ADD(1)
的结果为:“ADD(1)+1”
3、在我使用中经常用到的宏:条件编译
先让我们来看一段代码:
#ifndef __I2C_EE_H
#define __I2C_EE_H
#include "stm32f10x.h"
/**************************I2C参数定义,I2C1或I2C2********************************/
#define EEPROM_I2Cx I2C1
#define EEPROM_I2C_APBxClock_FUN RCC_APB1PeriphClockCmd
#define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
#define EEPROM_I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define EEPROM_I2C_GPIO_CLK RCC_APB2Periph_GPIOB
#define EEPROM_I2C_SCL_PORT GPIOB
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6
#define EEPROM_I2C_SDA_PORT GPIOB
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7
#endif /* __I2C_EE_H */
这基本上是在我创建用户函数时一定会用到它。因为,条件编译可以解决头文件重复编译的错误。
#if... #else #endif
则可以让我们有选择的去执行我们想要其执行的代码段。
4、我们不曾知道的“#”,“##”。
“#”符号可以把一个符号直接转换为字符串。
例:#define STRING(x) #x
char * str = STRING( string );
这段的意思就是str中的内容是字符串string。
而“##”则会连接临近的符号从而产生新的符号。
例:#define CONS (x,y) (x**e**y)
printf("CONS IS %d\n",CONS(2,3));
因为“##”会将相近的符号连接起来,所以这段代码则会输出 “CONS IS 2000”;
5、当宏参数是另一个宏时会发生什么?
宏参数是另外一个宏时,会先展开作为参数的宏,当展开的宏参数被放进宏体时,预处理器将会对新展开的宏体进行展开。
例:#define A(x) x
#define CONS (x,y) (x**e**y)
A(CONS(2,3));
2和3作为宏CONS的参数对CONS进行展开为2000,再将展开的结果作为参数放入A(x)。
例外情况:当宏主体对参数使用了“#”或“##”时,宏将不会对参数进行展开。
例:
#define TOSRTING(x) #x
#define STRING(x) TOSRTING(x)
#define A(x) #x
#define CONS (x,y) (x**e**y)
char * str = STRING(A(CONS(2,3)));
可以试想一下str中存储的字符串为多少。
6、变参宏,可以让你定义类似的宏:
#define EEPROM_ERROR(format, ... ) printf("<<-EEPROM-INFO->> "format"\n",__VA_ARGS__)
EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);
__VA_ARGS__为系统预定义宏,会被自动替换为参数列表。
在看完上述文章后:我想大家应该对宏定义有一个比较深刻的认识了,再回来看这段代码:
/*信息输出*/
#define EEPROM_DEBUG_ON 1
#define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
//EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);如何调用
#define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...) do{\
if(EEPROM_DEBUG_ON)\
printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
相信大家可以很容易的知道,printf函数中各参数的意思了。