C语言#define宏定义及其相关注意事项详解

预处理过程

预定义符号(两边分别是两个下划线)

__FILE__		//进行编译的源文`在这里插入代码片`件
__LINE__		//文件当前的行号
__DATA__		//文件被编译的日期
__TIME__		//文件被编译的时间
__STDC__		//如果编译器遵循ANSI C,其值为1,否则未定义
__FUNCTION__	//当前函数名

VS编译器不支持ANSI C,所以没有STDC预定义符号,在Linux的gcc编译器中则有

日志的一种简单写法

int main()
{
	FILE* pf = fopen("log.txt", "a+");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		fprintf(pf, "%s %s %d %s %s %d\n", __FILE__, __FUNCTION__, __LINE__, __DATE__, __TIME__, i);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

#define定义符号

#define MAX 1000
#define reg register			//为register这个关键字,创建一个简短的名字
#define do_forever for(; ;)		//用更形象的符号来替换一种实现
#define CASE break; case		//在写case语句的时候自动把break写上。
//如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("fi1e:%s\tline:%d\t \
						date:%s\ttime:%s\n",\
						__FILE__,__LINE__,\
						__DATE__,__TIMEl__)

在define定义标识符的时候,最好不要在最后加上分号

如果普通的赋值语句,可能没有问题:

#define M 1000;
int a = M;		//在预处理阶段会被替换成int a = 1000;;	
//相当于
int a = 1000;
;				//这里多了一条空语句

如果在if else语句中,则会出现问题

#define M 1000;
int b;
if(a>1)
{
	b = M;		//预处理替换后就变成 b = 1000;;	则这是两条语句,else就会报错,编译不通过
}else{		
	b = 1;
}

#define 定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

下面是宏的申明方式:
#define name( parament-list ) stuff

其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中

注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

使用:

#define SQUARE(x)  x * x
int main()
{
	printf("%d\n", SQUARE(3));	//3*3 = 9
	printf("%d\n", SQUARE(3 + 1));	//3+1*3+1 = 7	先替换再计算
	return 0;
}

所以参数不同宏可能会产生意想不到的结果,为了避免这种情况出现,尽量在宏体中加上括号,表示整体

#define SQUARE(X) ((x)*(x))
int main()
{
	printf("%d\n",SQUARE(3+1));		//4*4 = 16
	return 0;
}

#define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤:

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。(即看参数是否是另一个#define定义的宏名)

⒉替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:
1.宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。

2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。如:

#define M 1000
int main()
{
	printf("M = %d\n",M);		//格式化打印的M不会被识别为宏,而后面的M会被替换为1000
	return 0;
}

#和##

# -> 字符串化 可以将传进来的宏参数直接变成字符串

/*要实现传不同的值打印出来的信息也是不同的,用函数很难实现
下面宏定义中,参数x在宏体中使用了#,表示将传入的a,b,c,d变成字符串加入到打印语句中给,这样就可以实现不同的变量名有不同的输出语句
如需按不同的格式打印,则可以再为格式定义一个参数,传入到输出语句中
*/
#define print(x,FORMAT) printf("the value of "#x" is " FORMAT "\n",x)		

int main()
{
	int a = 1;
	print(a,"%d");		//the value of a is 1
	int b = 2;
	print(b,"%d");		//the value of b is 2
	int c = 3;
	print(c,"%d");		//the value of c is 3
	float d = 3.14f;
	print(d,"%f");		//the value of d is 3.140000
	return 0;
}

链接符号 -> 可以把位于它两边的符号合成一个符号

它允许宏定义从分离的文本片段创建标识符。

注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

#define DAY(x) myday##x
#define CAT(x,y) x##y
int main()
{
    int myday1 = 10;
    int myday2 = 20;
    printf("%d\n",DAY(1));  //10
    printf("%d\n",DAY(2));  //20
    int class101 = 100;
    printf("%d\n",CAT(class,101));	//100	class符号和101符号合并起来
}

带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。(如b = ++a,这里b被赋值了,但a自身被改变了,所以a这种写法是有副作用的)

a+1 不带副作用

a++ 带副作用

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 3;
	int b = 5;
	int m = MAX(a++, b++);	//宏替换后其实变成这样子	((a++)>(b++)?(a++):(b++))
    //因为是后置++,所以3>5不成立,a++变为4,b++变为6后返回b,m被赋值为6,之后b++变为7
	printf("%d\n", m);		//6
	printf("a = %d  b = %d\n", a, b);		//a = 4  b = 7
	return 0;
}

宏和函数对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个.

#define MAX(x,y) ((x)>(y)?(x):(y))

那为什么不用函数来完成这个任务?

原因有二:

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
⒉.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏则可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的

当然和宏相比函数也有劣势的地方:

1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没法调试的。
3.宏由于类型无关,也就不够严谨。
4.宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define	MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	//int* p = malloc(10 * int);	//函数做不到
	int* p = MALLOC(10, int);		//定义宏可以实现直接传参数
	return 0;
}
属性#define宏定义函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

总结:简单的功能用宏来写,复杂的功能用函数来写

拓展:在C++中,有内联函数(inline):其结合了宏的优点和函数的优点

命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是:

把宏名全部大写

函数名不要全部大写

#undef

这条指令用于移除一个宏定义。

#define M 100
int main()
{
	int a = M;		//a可以正常赋值
#undef M			//移除宏定义
    int b = M;		//b不能被赋值,编译运行会报错,M符号未定义
	return 0;
}

命令行定义

许多C的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们只能定义小一点的数组,但是另外一个机器内存大些,我们定义的数组就比较大。)

例如:

在Linux环境中:gcc -D :在执行指令时会在.c文件里自动加入宏定义,

gcc -DABC1 == #define ABC1

如果c程序里写了#ifdef ABC1,则为真,执行后面的语句,没定义则执行#else后面的语句

条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为可以使用条件编译指令。

比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

#define P 1			//2.宏定义后,则下面输出语句经过编译
int main()
{
#ifdef	P			//1.如果没宏定义P符号,则输出语句不经过编译
	printf("hello");
#endif
	return 0;
}

常见的条件编译指令:

1.
#if 常量表达式
	//...
#endif
//常量表达式由预处理器求值。:
#define _DEBUG_ 1
#if _DEBUG_
	// ..
#endif

2.多个分支的条件编译
#if	常量表达式
	//...
#elif	常量表达式
    //...
#endif
    
3.判断是否被定义
#if defined(symbol)
//如果symbol被定义,则执行后续语句
    
//形式1
#ifdef symbol		
    //...
#endif
    
//形式2
#if defined(symbol)
    //...
#endif
    
//如果symbol未被定义,则执行后续语句
    
//形式1
#ifndef symbol
    //...
#endif
    
//形式2
#if !defined(symbol)
    //...
#endif
4.嵌套指令
    #if defined(OS_UNIX)
    	#ifdef OPTION1
    		//...
    	#endif
    	#ifdef OPTION2
    		//...
    	#endif
    #elif defined(OS_MSDOS)
    	#ifdef OPTION3
    		//...
    	#endif
   #endif
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值