#define详解

1. #define定义标识符

语法:

#define name stuff

举个例子:

#define MAX 1000    //为1000创建一个名字MAX
#define reg register          
//为 register这个关键字,创建一个简短的名字reg
#define CASE break;case        
//在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%stline:%dt 
date:%sttime:%sn" ,
__FILE__,__LINE__ ,       
__DATE__,__TIME__ )   

在define定义标识符的时候,不要在最后加上 ; ,否则容易导致问题

在这里插入图片描述
此处出现了语法错误。


2. #define定义宏

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

下面是宏的申明方式:

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

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

如:

#define SQUARE( x ) x * x
//SQUARE是宏名,x是参数,x*x 是替换的内容

这个宏接收一个参数 x .
如果在上述声明之后,你把

SQUARE( 5 );

置于程序中,预处理器就会用5*5这个表达式替换上面的表达式:

观察下面的代码段:

int a = 5;
printf("%dn" ,SQUARE( a + 1) );

乍一看,你可能觉得这段代码将打印36这个值。事实上,它将打印11.

SQUARE( a + 1) 替换为 a+1*a+1, 即 5 + 1 * 5 + 1 = 11

由替换产生的表达式并没有按照预想的次序进行求值。先乘法再宏定义的加法,乘法运算先于宏定义的加法
宏是先替换再计算,不是先计算再替换。

这个问题,的解决办法是在宏定义表达式加上三对括号就可以了。

#define SQUARE( x)   ( ( x ) + ( x ) )

提示:

所有用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中 的操作符或邻近操作符之间不可预料的相互作用


3. #define 替换规则

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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

4. #和##

#的作用

char* p = "hello ""bitn";
printf("hello"," bitn");
printf("%s", p);     //hell0 bit

这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。
另外一个技巧是:
使用 # ,把一个宏参数变成对应的字符串。

比如:

#include<stdio.h>
#define PRINT(N, FORMAT) printf("the value of "#N" is "FORMAT"n", N)
int main()
{
	float f = 3.14f;
	PRINT(f, "%lf");
	int b = 20;
	PRINT(b,"%d");

	return 0;
}

代码中的 #N 会预处理器处理为:“N”

在这里插入图片描述


##的作用

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。

#include<stdio.h>

#define CAT(Class, Num) Class##Num

int main()
{
	int Class106 = 100;
	printf("%dn", CAT(Class, 106));
	//printf("%dn", Class106);    //预处理后
	return 0;
}

在这里插入图片描述

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


5. 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 5;//6 7
	int b = 4;//5
	int m = MAX(a++, b++);
	//int m = ((a++) > (b++) ? (a++) : (b++));
	
	printf("m=%d ", m);//6
	printf("a=%d b=%dn", a, b);//7 5
	return 0;
}
a和b后置加加,先用后加,
当(a++)和(b++)比较后,(a++)>(b++) , a变为6,b为5,m=(a++)=6,
a++又用了一次,此时a+1变为7.

6. 宏和函数对比

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

那为什么不用函数来完成这个任务?
原因有二:

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

宏的缺点:

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

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