计算机预处理详解

前言:

前面的博客中我们已经讲过了预处理是什么,本期我们就来详细的讲述一下预处理这个概念。

预定义符号

_FILE_ //进行编译的源文件 _LINE_ //文件当前的行号 _DATE_ //文件被编译的日期 _TIME_ //文件被编译的时间 _SDTC_ //如果编译器遵循ANSI C,其值为1,不然未定义

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

代码语言:javascript
复制
#include<stdio.h>
int main()
{
	printf("file:%s line:%d\n", __FILE__, __LINE__);
	printf("data:%s time:%s\n", __DATE__, __TIME__);
	return 0;
}

#define定义标识符

#define MAX 100 //定义常量MAX为100

#define reg register //使关键字用另一种符号替代
#define do_forever for( ; ;) //一种替换
#define CASE break;case
//如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。

#define MAX 100;//在定义标识符的时候加上了一个分号,在正常的语句下,连续使用两个分号,编译器会认为后面那个是一个空语句,但是在下面的场景会出现问题: if(condition) max = MAX; else max = 0;

if语句不加中括号,默认只会跟着一条语句,这里相当于是有两条语句了,下面再出现else的时候就会出现语法错误。

#define 定义宏

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

宏的定义方式

#define name(parament-list) stuff parament-list为参数 注: 1.参数列表的左括号必须与name紧邻,否则可能会被当成stuff 2.我们在定义宏用来求值的时候尽量都加上括号,避免在进行替换之后会因为优先级问题,导致结果不是我们预期想要的结果。

.#和##

代码语言:javascript
复制
char* p = "hello ""world\n";
printf("%s", p);

输出结果是不是“hello world”?

是的 我们发现字符串有自动连接的特点。

再来看一段代码

代码语言:javascript
复制
#define PRINT(FORMAT,VALUE)\
                    printf("the value is "FORMAT\n",VALUE);
//在C语言以反斜杠 ( ' \ ' ) 表示断行,跟在反斜杠后的字符自动连到前一行。反斜杠后不能跟空格。

PRINT("%d",10);

就变成了:

代码语言:javascript
复制
printf("the value is "%d"\n", 10);

## 的作用

## 可以把位于它两边的符号和成一个符号
它允许宏定义从分离的文本片段创建标识符
#define ADD_TO_SUM(num, value)
sum##num += value;
int sum5 = 0;
ADD_TO_SUM(5, 10);
printf("%d\n", sum5);
替换为
sum5 += 10;

带副作用的宏参数

代码语言:javascript
复制
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);

//输出的结果是什么?

替换结果为:

代码语言:javascript
复制
z = ((x++) > (y++) ? (x++) : (y++));

我们分析下执行过程,首先x和y比较

代码语言:javascript
复制
1 : x > y // 5 > 8?
2 : x++ // x = 6
3 : y++ // y = 9

根据三目运算符的结果,我们得到

代码语言:javascript
复制
1 z = y++ // z = 9
2 y++ // y = 10

所以输出结果为

代码语言:javascript
复制
x = 6 y = 10 z = 9

宏和函数的对比

这两者有什么区别呢?为什么有的时候用宏,有的时候却用函数。

宏通常被用于执行简单运算。比如用于一个数翻倍。

代码语言:javascript
复制
#define DOUBLE(x) ((x) + (x))
代码语言:javascript
复制
int double(int x)
{
return x+x;
}

为什么不用函数呢?

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 宏与类型无关。

当然,宏相比函数也有缺点

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

宏和函数的区别:

1、宏做的是简单的字符串替换,不受类型限制;而函数是参数的传递,受到参数类型的限制。
2、宏体替换宏名是在编译之前就完成的,函数参数的调用是在函数执行时将实参传给形参的。
3、宏参数的替换是不经过计算的,有可能会带有副作用,所以我们在写宏体的时候一般在能加括号的地方都不要吝啬括号,但有时候这也不能解决副作用的问题。函数在传参时传的是值,不会产生副作用。
4、因为函数是在执行期间调用的,所以可以进行调试;宏在编译前完成的,所以不可以进行调试。
5、函数支持递归,宏不支持。
6、函数在调用时会产生时间和空间上的开销;宏在调用时则没有,因为宏进行的只是简单的字符串替换。
7、如果使用宏比较多,宏体在展开时会产生大量的代码,大大降低运行时间。

#undef

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

代码语言:javascript
复制
#undef NAME
代码语言:javascript
复制
#define MAX 100
#undef MAX

MAX; // 报错

文件包含

我们在初学阶段,一般用#include 指令调用头文件和自己定义的文件

建议不要使用下面这段代码去查找库文件

代码语言:javascript
复制
#define "stdio.h"

会大大降低查找效率

我们在进行一段工程的时候,会经常出现嵌套文件的存在,这个时候一个文件底下可能调用了多次同样的头文件或者本地文件,这样会大大降低效率,避免出现这种问题,可以加上:

代码语言:javascript
复制
#pragma once
//这段代码是防止头文件被重复引入