文章目录
前言
一、预定义符号
注意:下面这些预定义符号都是语言内置的,可以直接拿来用。
__FILE__      // 进行编译的源文件
__LINE__      // 文件当前的行号
__DATE__      // 文件被编译的日期
__TIME__      // 文件被编译的时间
__STDC__      // 如果编译器遵循ANSI C,其值为1,否则未定义
__func__      // 获取当前所在函数举例:
#include<stdio.h>
int main()
{
	printf("name: %s, file: %s, line: %d, date: %s, time: %s\n", __func__, __FILE__, __LINE__, __DATE__, __TIME__);
	return 0;
}
预定义符号的作用
- 我们可以将上面这些文件的信息写到日志里面,如果程序出现错误,我们可以很好的定位到是哪个文件的哪个函数出错了,并且知道文件是在什么时候编译的。
举例:
#include<stdio.h>
int main()
{
	FILE* pf = fopen("log.txt", "a"); // 打开log.txt这个文件
	if (pf == NULL)
	{
		return 1;
	}
	for (int i = 0; i < 10; ++i)
	{
		// 将所有文件信息写入log.txt文件
		fprintf(pf, "name: %s, file: %s, line: %d, date: %s, time: %s, i=%d\n", __func__, __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	return 0;
}我们打开log.txt文件,这时就将所有的文件信息都写了进来。

__STDC__ 预定义符号
举例:
int main()
{
	printf("%d\n", __STDC__);
	return 0;
}在Windows的vs2019下
- 编译器报错:未定义标识符“__STDC__”
- 说明vs2019不遵循ANSI C

在Linux的gcc下
- 打印出来__STDC__的值为1
- 说明gcc遵循ANSI C


二、#define
1 #define 定义标识符
语法:
#define name stuff功能:
- 在预处理阶段,将代码中所有的name替换成stuff。
举例:
#define NUM 666
#define STR "hello"
int main()
{
	int num = NUM;
	char* str = STR;
	return 0;
}在预处理阶段,上面的代码将会被替换成:
int main()
{
	int num = 666;
	char* str = "hello";
	return 0;
}怎么验证?
- 点击视图,然后打开解决方案资源管理器。

- 右击此处。

- 点击属性。

- 点击C/C++,然后进入预处理器,将预处理到文件这里的选项改成“是”

- 将当前文件编译一下,然后去当前路径下的Debug文件夹里面可以找到一个test.i文件(这就 是预处理完后生成的文件),然后将其打开。


- 现在就可以看到替换前后的区别了。

提问:
如果我们在刚刚define定义标识符的最后加上" ; " ,那么预处理后的结果将会是下面这样,这就会出现语法错误。

当然define还可以定义其他标识符,可以是个关键字,也可以是一段代码。
#define reg register           //为 register这个关键字,创建一个简短的名字
#define do_forever 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__ ) 2 #define 定义宏
宏的申明方式:
- #define name( parament-list ) stuff
- 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
- 参数列表的左括号必须与name紧邻。
- 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
举例:
// 用于求两数中的较大值 
#define MAX(x, y) (x > y ? x : y)
int main()
{
	int a = 10;
	int b = 20;
	int c = MAX(a, b);
    printf("%d\n", c);
	return 0;
}
替换后的结果为:

注意:如果你想运行当前代码,需要将刚才的设置改回来才行,因为经过刚才的设置后,编译器将代码预处理完就会停下来。

刚才代码的运行结果。

注意:定义宏的时候必要的括号不能少,因为宏的本质还是替换。
举例:
我们写一个求平方的宏:
#define SQUARE( x ) x * x这个宏接收一个参数 x ,
如果在上述声明之后,你把
SQUARE( 5 );置于程序中,预处理器就会用下面这个表达式替换上面的表达式:
5 * 5注意: 这个宏存其实在一个问题,观察下面的代码段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );乍一看,你可能觉得这段代码将打印36这个值。 事实上,它将打印11,为什么?
这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。
解决办法:在宏定义上加上两个括号,这个问题便轻松的解决了:
#define SQUARE(x) (x) * (x)这样预处理之后就产生了预期的效果:
printf ("%d\n",(a + 1) * (a + 1) );这里还有一个宏定义:
#define DOUBLE(x) (x) + (x)定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));这将打印什么值呢?
看上去,好像打印100,但事实上打印的是55,我们发现替换之后:
printf ("%d\n",10 * (5) + (5));乘法运算先于宏定义的加法,所以出现了55的结果。
解决办法:在宏定义表达式两边加上一对括号就可以了:
#define DOUBLE(x)   ( ( x ) + ( x ) )所以我们这里就可以将上面写的求较大值的宏优化一下。
#define MAX(x, y) ((x) > (y) ? (x) : (y))提示:
3 #define 替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
举例:
#define DOUBLE(x) ((x) + (x))
#define NUM 66
int main()
{
	int a = DOUBLE(NUM);
	return 0;
}
这里就会先将:
int a = DOUBLE(NUM);替换成:
int a = DOUBLE(66);然后再替换成:
int a = ((66) + (66));注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
举例:
这里的NUM将不会被替换。
#define NUM 66
int main()
{
	printf("NUM is a macro\n");
	return 0;
}
4 #和##
我们先来看一段代码:
int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of b is %d\n", b);
	return 0;
}运行结果:

- 我们发现上面的两行代码十分是相似,运行的结果也只有两处不同,那么我们可以把刚刚那两行代码(printf那两行)封装成一个函数吗?这样我们就不用每打印一个变量,都要单独写一个printf函数了。
- 答案是可以,但是十分的复杂,使所以这时就需要用我们的宏了。
使用#
作用:把一个宏参数变成对应的字符串。(比如:N是一个宏参数,我们使用#N,然后传过去a,那么a就会自动变成字符串 "a")
举例:
#include<stdio.h>
#define PRINT(N) printf("the value of " #N " is %d\n", N)
int main()
{
	int a = 10;
	PRINT(a); // printf("the value of a is %d\n", a);
	int b = 20;
	PRINT(b); // printf("the value of b is %d\n", b);
	return 0;
}
这里的:
PRINT(a);将会被替换成: (提示:#N被替换成了 "a",N被替换成了 a)
printf("the value of " "a" " is %d\n", a);运行结果:
和上面用两个printf打印出来的结果一模一样。

额外补充:
举例:
int main()
{
	printf("san lian\n");
	printf("san"   " "   "lian\n");
	return 0;
}
运行结果:

如果我们想要打印不同类型的变量,可以像下面这样:
#include<stdio.h>
#define PRINT(N, format) printf("the value of " #N " is " #format "\n", N)
int main()
{
	int a = 20;
	PRINT(a, %d); // printf("the value of a is %d\n", a);
	double pai = 3.1415926;
	PRINT(pai, %lf); // printf("the value of pai is %lf\n", pai);
	return 0;
}
运行结果:

## 的作用:
举例:
#include<stdio.h>
#define CAT(name1, num) name1##num
int main()
{
	int sanlian333 = 666;
	printf("%d\n", CAT(sanlian, 333));
	return 0;
}这里的:
printf("%d\n", CAT(sanlian, 333));
将会被替换成:
printf("%d\n", sanlian333);
运行结果:

注意:像上面这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
5 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
int x = 1;
int a = x+1; // 不带副作用(x的值没有改变)
int b = ++x; // 带有副作用(x的值被改变)MAX宏可以证明具有副作用的参数所引起的问题。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main()
{
	int a = 5;
	int b = 8;
	int c = MAX(a++, b++); // 被替换成 int c = MAX((a++) > (b++) ? (a++) : (b++));
	// 你能不运行,说出下面的结果吗?
	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	return 0;
}
运行结果:
最后的结果是不是意料之外,却又在情理之中。(其实稍微细心点还是很容易看出答案的,但是稍不留神就有可能出错哦)

提示:所以为了避免出现不可预测的后果,在写宏的时候,参数部分尽量不要随便写这种带副作用的参数。










