C语言预处理详解

1.预定义符号:

预处理符号是C标准规定的宏定义符号,总共有五个

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

应用:

在文件输入输出以及输出日志等场合下我们会用到这些预处理符号,例如

2.#define:

2.1#define定义标识符:

举个例子:

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

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

例如这个例子:

#define MAX 1000;
int main() {
	int max = 0;
	if (1)
		max = MAX;
	else
		max = 0;
}

原因是在替换宏之后,就变成了max=1000;;,从而导致后面的else语句找不到能匹配的if语句

2.2#define定义宏:

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

2.2.1定义的格式:

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

注意事项:
参数列表的左括号必须与name紧邻

如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

宏存在的问题,观察下面这个代码:

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
    int a = 5;
    printf("%d\n" ,SQUARE(a+1));
    return 0;
}

不仔细看,大家可能认为答案不就是36嘛,这么简单,但是打印出来确实11,这是为什么呢?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

这样大家明白了为什么嘛?

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

2.3#define的替换规则:

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

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

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

注意事项:

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

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

2.4带副作用的宏参数:

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

#include<stdio.h>
//x + 1;//不带副作用
//x++;//带有副作用
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
	return 0;
}

这个代码的结果是x=6,y=10,z=9,大家想到了吗?

解释:z=((x++)>(y++)?(x++):(y++))

先比较x和y的值,结果为假,然后x和y都自增1,再把y自增一次的值传给z,y再自增一次 ,我们用了这个宏,就导致x和y变量的值发生了永久性的变化,大多时候这都不是我们想要的,所以在有这样效果的宏定义时,要谨慎使用

2.5宏和函数的比较:

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

宏并不是一定比函数好,所以我们要学会的是在不同的环境下去运用他们,来提高我们代码的执行速度

2.6命名约定:

把宏名全部大写,函数名不要全部大写(可以用大驼峰的方式),这样我们能更加方便的区别二者,以免用错
3.#undef:

格式:

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

好了,今天的分享到此结束了,非常开心能给大家分享这些知识,希望能给大家带来帮助,欢迎大家在评论区发言,大家看完觉得还不错,请关注博主,博主后续会有更好的博文分享

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值