【C语言】详解程序的预处理

目录

1.什么是预处理

预处理的作用

2.预定义符号 

3.宏定义   #define

3.1 #define 定义标识符

3.2 #define 定义宏

3.2.1用法 

3.2.2警告:

3.3 # 和 ##

3.3.1 # 把宏参数替换成对应的字符串

3.3.2 ## 合并两个片段

3.4 避免将带有副作用的参数传递给宏

3.5 宏和函数的对比 

3.6 undef 

4.条件编译 

例一

例二 

例三 

头文件包含问题

方法1

方法2


1.什么是预处理

        C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。

预处理的作用

        在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:
●将源文件中以”include”格式包含的文件复制到编译的源文件中。
●用实际值替换用“#define”定义的字符串。
●根据“#if”后面的条件决定需要编译的代码。
         具体可见这篇文章1.2预编译部分:【C语言】你知道.c文件是如何变成.exe文件的吗_

2.预定义符号 

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

          这些预定于符号都是语言内置的,如下代码和运行结果:

#include

int main()
{
	printf("line:%d\n", __LINE__);
	printf("date:%s\n", __DATE__);
	printf("time:%s\n", __TIME__);

	return 0;
}

 【C语言】详解程序的预处理_第1张图片

3.宏定义   #define

3.1 #define 定义标识符

语法:  #define  name  stuff

        例如: #define N 100      

        如果有了这个宏定义,那么此程序中,所有的 N 都会 在预编译阶段 被替换成100。

        又例如: 

         如下代码和运行结果:

#include

#define N 10
int main()
{
	int m = N;
	printf("%d\n", m);
}

【C语言】详解程序的预处理_第2张图片

3.2 #define 定义宏

3.2.1用法 

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

        下面是宏的申明方式:
        #define name( parament-list ) stuff
        其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

        如下,#define MAX(x,y) (x>y?x:y)    这就相当于把MAX(x,y) 直接替换成了 (x>y?x:y) 这个三目运算符,相当于  printf("%d\n", (a>b?a:b));    SQUARE同理,得到的结果也和预期一样:

#include

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

#define SQUARE(x) x*x

int main()
{
	int a = 10;
	int b = 20;

	printf("%d\n", MAX(a,b));
	printf("%d\n", SQUARE(5));

	return 0;
}

【C语言】详解程序的预处理_第3张图片

        还可以把鼠标放到宏上面,观察扩展情况,如下:
【C语言】详解程序的预处理_第4张图片

【C语言】详解程序的预处理_第5张图片

3.2.2警告:

        宏定义不一定总是能够得到完美的结果,如下,目的是输出6*6=36,但是实际运行结果却不是:

#include

#define SQUARE(x) x*x

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

【C语言】详解程序的预处理_第6张图片

        这是因为宏定义是直接替换,所以这里替换成了 5+1*5 +1  ,结果是11,如下:
【C语言】详解程序的预处理_第7张图片

        在宏定义里加上两个括号就可以解决这个问题:

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

int main()
{
	printf("%d\n", SQUARE(5 + 1)); //给宏里面的x加上括号就会避免这个问题
	return 0;
}

【C语言】详解程序的预处理_第8张图片

        但是,这样就完美了吗?我们来看下面的代码,本意是10*12=120,但是实际输出结果如下图:

#include
#define ADD(x) (x)+(x)

int main()
{
	printf("%d\n", 10*ADD(5 + 1)); 
	return 0;
}

【C语言】详解程序的预处理_第9张图片

        这又是为什么呢?我们看到下面的扩展,再仔细联想,就可得知,宏将ADD(5+1) 替换成了 (5+1)+(5+1) 那么10*ADD(5+1) 就变成了 10*(5+1)+(5+1),所以出错:

【C语言】详解程序的预处理_第10张图片

        所以,最终还要在宏的外层添加一个括号才可,这样才是最好的写法:

#include
#define ADD(x) ((x)+(x))

int main()
{
	printf("%d\n", 10*ADD(5 + 1)); 
	return 0;
}

        最后总结,用#define定义宏,不仅要将每个参数单独括号,还要将最外层加一个括号,逐层括号。 

3.3 # 和 ##

3.3.1 # 把宏参数替换成对应的字符串

        如下,想要打印不同数据类型的数据,但是其输出格式大体上是一样的,如下代码写起来就很麻烦,冗余较多。

#include

int main()
{
	int a = 10, b = 20;
	float c = 23.44;
	printf("The value of a is:%d \n", a);
	printf("The value of b is:%d \n", b);
	printf("The value of c is:%f \n", c);
	return 0;
}

        此时就想要写一个宏,实现打印各种数据,且格式保持不变,如果如下的代码这样写,就会导致"The value of val is" 这段字符串里面,val并不会对应地变成 a 或者b 或者c这些变量名,而会一直是"val" 这个字符串,不符合格式要求。那么就一定要把val 放到双引号外面,此时#的作用就来了:

#define PRINT(val,format) printf("The value of val is:"format"\n",val)

         如下,这样子写,#val 是把 val 当作一个字符串来的,会自动加上引号,相当于 #val== "val" 这样子无论val是什么变量名,都可以完美替换。

#define PRINT(val,format) printf("The value of "#val" is:"format"\n",val)

        如下代码和运行结果:

#include
#define PRINT(val,format) printf("The value of "#val" is:"format"\n",val)
int main()
{
	int a = 10, b = 20;
	float c = 23.44;
	PRINT(a, "%d");
	PRINT(b, "%d");
	PRINT(c, "%f");

	return 0;
}

【C语言】详解程序的预处理_第11张图片

3.3.2 ## 合并两个片段

        作用如下,目前而言基本上没有用到,就做简单介绍吧。

#include

#define CAT(a,b) a##b  //把a,b合并起来了
int main()
{
	int Class1 = 10;
	int Class2 = 20;
	printf("%d\n", CAT(Class, 1));
	printf("%d\n", CAT(Class, 2));
	return 0;
}

3.4 避免将带有副作用的参数传递给宏

        如下代码和运行结果, int m = MAX(++a, ++b);   被宏替换成了: int m = ((++a) > (++b) ? (++a) : (++b)); 实际上变成了上面一行这样运算,  4>5  ,是错的,所以冒号后面运算,冒号前面不运算,返回的值是++5,为6,所以m=6,b=6,a=4。传入这样的参数,会导致结果不可控,产生一些难以预测的后果。

#include

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 3;
	int b = 4;
	int m = MAX(++a, ++b);
	//m = ((++a) > (++b) ? (++a) : (++b));

	printf("%d %d %d\n", m, a, b);
	return 0;
}

【C语言】详解程序的预处理_第12张图片

3.5 宏和函数的对比 

        宏通常被应用于执行简单的运算,比如在两个数中找出较大的一个。那为什么不用函数来完成这个任务?
        原因有二:
        1. 用于 调用函数和从函数返回的代码 可能比 实际执行这个小型计算工作 所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
        2. 更为重要的是函数的参数必须声明为特定的类型。(所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等。宏是类型无关的。)

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

3.6 undef 

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

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

4.条件编译 

例一

        在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。比如说调试性质的代码,删除可惜,不删碍事,这时用条件编译就可以解决问题。如下,#ifdef  和 #endif  是配套的,它的意思是,如果#define _DEBUG_  那么就执行在#ifdef  和 #endif 之间的代码,否则在这之间的代码,在预编译阶段就不会被保留。(具体可见这篇文章的1.2.4 部分(174条消息) 【C语言】你知道.c文件是如何变成.exe文件的吗_Austerlitzl的博客-CSDN博客_c语言怎么导出exe)

#include
//#define _DEBUG_

int main()
{
	int arr[10] = { 0 };
	for (int i = 0;i < 10;i++)
	{
		arr[i] = i;
#ifdef _DEBUG_
		printf("%d\n", arr[i]);
#endif // DEBUG

	}
	return 0;
}

         我将#define _DEBUG_   注释起来了,如下运行结果,并没有打印什么内容。 

 【C语言】详解程序的预处理_第13张图片

例二 

        条件编译不仅仅是有 #ifdef  和 #endif   语句,也有 #if 语句,如下。从代码颜色深浅也可以看出,printf("hehe\n"); 是要被编译的代码,颜色更明亮一些。实际上也是如此。

【C语言】详解程序的预处理_第14张图片

 【C语言】详解程序的预处理_第15张图片

例三 

        条件编译也可以有嵌套指令,如下是由 #if    #elif   #else  #endif 嵌套而成的指令。如图,#define  CASE2 和 #define CHOICE2  那么顺着来看,应该被编译的语句是printf("choice2\n");   。由颜色深浅也可以一眼看出,printf("choice2\n");  是被编译的。

【C语言】详解程序的预处理_第16张图片

 【C语言】详解程序的预处理_第17张图片

头文件包含问题

        头文件包含有两种写法 #include ""  和 #include <>   ,前者是先在次工程项目的文件里面寻找,有没有 ""  里面的头文件,如果有则包含,没有则在库里面寻找; 后者是直接在库里面寻找头文件。

        在写项目时,经常要一个头文件被其他多个文件包含,那么在预编译阶段,就会造成头文件的内容多次出现在整个代码中,冗余且重复,很不方便,这时用条件编译语句就可以很方便地解决问题。

方法1

         第一种方法,在头文件里面,使用条件编译语句,如下。#ifndef 地意思是 if not define ,下面条件编译的意思是 :  如果没有 #define _TEST_h_   ,那么就 #define _TEST_h_   ,并且将头文件的内容包含进去。  如果已经 #define _TEST_h_   那么自然就直接到#endif  头文件不会被包含。(那么第一次之后  再有包含该头文件的代码时,由于第一次已经#define _TEST_h_   ,后面就不会再包含

//方法1,如下,这种方法比较老
//如果没有define _TEST_h ,那么define一个,并且把头文件内容包含进去
//下一次引用该头文件时,已经定义了_TEST_h_ 所以不会包含头文件进去了

#ifndef _TEST_h_
#define _TEST_h_

	//这中间是头文件原本的内容,这里简单写一下就好    
int Add(int x, int y);

#endif // !_TEST_h_

方法2

        在头文件顶部写一行 #pragma once  即可。这是比较新的写法。(VS2019环境下,如果建立一个.h 的头文件,会自动在第一行生成这个。其他版本没试过,2019之后的应该也会自动生成,之前就不知道了)

        关于C语言的预处理知识,本文就介绍到这里,欢迎在评论区指正错误!!!

你可能感兴趣的:(C语言学习之路,c语言,c++,开发语言)