预定义符号和#define

预定义符号

预定义符号解析
__FILE__进行编译的源文件
__LINE__ 文件当前的行号
__DATE__ 文件被编译的日期
__TIME__文件被编译的时间
__STDC__如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的

例如:

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}

运行后:
在这里插入图片描述
但是__STDC__预定义符号无法再VS编译器上执行
在这里插入图片描述

VS编译器上不完全遵循ANSI C

#define

#define 定义标识符

语法:#define name stuff

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

  • 防止发生语法错误,例如:
  • #define MAX 1000;表示MAX=1000;
  • 出现max=MAX;等价于max=MAX;;发生语法错误

举例:

#define M 20
#define STR "abcd"
#define FOR for(;;)

int main()
{
	printf("%d\n", M);
	printf("%s\n", STR);
	FOR
	{
		printf("hehe");
	};
	return 0;
}
  1. #define M 20表示M=20
  2. #define STR "abcd"表示STR="abcd"
  3. #define FOR for(;;)表示FOR是一个for(;;)循环(此循环为死循环)

运行后:
在这里插入图片描述

#define 定义宏

#define name( parament-list ) stuff

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

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

例题分析:

#define SQUARE(x) ((x)*(x))

int main()
{
	int a = SQUARE(5);
	printf("%d\n", a);
	return 0;
}

运行结果为:25

#define SQUARE(x) ((x)*(x))

  1. 需要将后面的每个参数都用括号括起来

#define SQUARE(x) x*x
SQUARE(5 + 5);预计结果为10*10=100
实际结果
在这里插入图片描述
算式在机器中表示为5+5*5+5=35

  1. 将整体的参数也括起来

#define SQUARE(x) (x)+(x)
10 * SQUARE(5);预计结果为100
实际结果
在这里插入图片描述
算式在机器中表示10*(5)+(5)=55

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

#define 替换规则

在程序中扩展#define定义符号和宏时的步骤

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

先进行M替换为(3+5)
再将(3+5)替换为SQUARE(x),计算式为((3+5)*(3+5))

注意:

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

#和##

首先请先看代码1:

int main()
{
	int a = 100;
	printf("the value of a is %d\n", a);
	int b = 25;
	printf("the value of b is %d\n", b);
	float f = 3.14f;
	printf("the value of f is %f\n", f);
	return 0;
}

在代码1中,重复打印很多,但是又不得不每个都进行打印一遍
在这里插入图片描述

但是通过代码2
在这里插入图片描述
可以发现字符串是有自动连接的特点的

那么上面的代码1就可以使用#,把一个宏参数变成对应的字符串

#define PRINT(n,format) printf("the value of "#n" is "format"\n",n)

int main()
{
	int a = 100;
	PRINT(a, "%d");
	int b = 25;
	PRINT(b, "%d");
	float f = 3.14f;
	PRINT(f, "%f");
	return 0;
}

运行后,与代码1打印的结果相同
在这里插入图片描述

##可以把位于它两边的符号合成一个符号

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

例如代码:

#define CAT(x,y) x##y

int main()
{
	int Mylove = 520;
	printf("%d\n", CAT(My, love));
	printf("%d\n", Mylove);
	return 0;
}

运行后,打印结果相同
在这里插入图片描述
注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的

带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候

  • 如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果
  • 副作用就是表达式求值的时候出现的永久性效果
    x+1-----不带副作用,x不变
    x++-----带副作用,x有变化

例如:

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

int main()
{
	int a = 5;
	int b = 6;
	int c = MAX(a++, b++);
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	printf("c=%d\n", c);
	return 0;
}

运行后
在这里插入图片描述

int c = MAX(a++, b++);等价于int c = ((a++)>(b++)?(a++):(b++))

  1. (a++)>(b++)先进行比较在进行后置++5>6为假,所以运行b++
  2. 此时的a=6,b=7,这时原式子等价于int c=b++;
  3. 所以先将b的值赋给c,在进行b++,此时c=7,b=8

宏和函数对比

宏通常被应用于执行简单的运算

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

宏的缺点:

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

宏的参数可以出现类型,但是函数做不到
例如:

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p = (int*)malloc(126 * sizeof(int));
	int* p = MALLOC(126, int);
	return 0;
}

命名约定

平时的一个习惯是:

  • 把宏名全部大写
  • 函数名不要全部大写

#undef

用于移除一个宏定义

#undef NAME 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

例如:

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

int main()
{
	int c = MAX(3, 5);
	printf("%d\n", c);
#undef MAX
	c = MAX(3, 5);
	printf("%d\n", c);
	return 0;
}

#undef MAX表示移除MAX的宏定义,在此指令之后,表示MAX已经没有作用了

这段代码运行时会报错
在这里插入图片描述

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值