C语言-第十章:预处理

传送门:C语言-综合案例:通讯录

目录

第一节:什么是预处理

       

第二节:#define

        2-1.定义符号

        2-2.给类型取别名

        2-3.定义一段代码

        2-4.定义宏

        2-5.#define 的替换规则

        2-6.宏中的#

        2-7.宏中的##

        2-8.#undef 移除一个宏定义

第三节:条件编译

        3-1.#ifdef

        3-2.#ifndef

        3-3.#if

        3-3.#elif 和 #else

第四节:文件包含

        4-1.库文件

        4-2.防止文件被多次包含

结语:


第一节:什么是预处理

        预处理是对代码进行的一些修改,从代码变成可执行程序,就先需要经过预处理。

        之前出现的 #define、注释都与预处理有关。进行预处理时,会将 define 定义的符号替换成预设的符号,注释会被删除。

        还有一些预处理符号,这些符号会被替换成相应的字符串,它们的写法和内容如下:

__FILE__它所在的文件路径

__LINE__

它所在的行号
__DATE__处理它时的日期
__TIME__处理它时的时间
__FUNCTION__它所在的函数名

        请看以下代码:

#include <stdio.h>
int main()
{
	printf("文件路径:%s\n",__FILE__);
	printf("当前行号:%d\n",__LINE__);
	printf("当前日期:%s\n",__DATE__);
	printf("当前时间:%s\n",__TIME__);
	printf("所在函数:%s\n",__FUNCTION__);
	return 0;
}

       

第二节:#define

        #define 定义的符号也需要经过预处理才能发挥作用,它会将代码中的符号替换成预设的符号,#define 有以下几种用法:

        2-1.定义符号

#define M 100 // 预处理时,会将 M替换成100
#include <stdio.h>
int main()
{
	int a = M;
	printf("%d\n",a);
	return 0;
}

        2-2.给类型取别名

#define un_t unsigned int // 给 unsigned int 取了一个短的名字 un_t
#include <stdio.h>
int main()
{
	un_t a = 100;
	printf("%d\n",a);
	return 0;
}

  

        关键字 typedef 也有给类型取别名的作用,它们之间也有区别,请看以下代码:

#define char_ptr char* // 给 char* 取了一个的名字 char_ptr
#include <stdio.h>
int main()
{
	char_ptr a,b;
	printf("a的大小:%u\n",sizeof a);
	printf("b的大小:%u\n",sizeof b);
	return 0;
}

  

typedef char* char_ptr;
#include <stdio.h>
int main()
{
	char_ptr a,b;
	printf("a的大小:%u\n",sizeof a);
	printf("b的大小:%u\n",sizeof b);
	return 0;
}

 

        它们都给char*取了别名,但是 #define 定义出来的变量类型却不一样,这是因为 #define 实行的是,直接替换,替换后的内容为:

char* a,b;

        第一个 * 与 a 结合,a就是指向char的指针,而b没有 * ,它就是char类型的变量。

        而 typedef 是类型重定义,它取的别名会作为一个整体给后面的变量,即:

char* a;char* b;
//或
char* a,* b;

        这是它们之间的一些区别,需要注意。

        2-3.定义一段代码

        例如我们需要知道当前文件路径+行号+日期+时间+函数名,如果用5个 printf,当需要多次调用时就太麻烦了,而且也不能封装成函数,这样的话行号和函数名就是被封装的函数的了,可以用#define,请看以下代码:

#define Print_Info printf("文件路径:%s\n",__FILE__);\
				   printf("当前行号:%d\n", __LINE__);\
				   printf("当前日期:%s\n", __DATE__);\
				   printf("当前时间:%s\n", __TIME__);\
				   printf("所在函数:%s\n", __FUNCTION__)
#include <stdio.h>
#include <Windows.h>
int main()
{
	Print_Info;
    printf("\n");
	Sleep(5); // 休眠5s

	Print_Info;
	return 0;
}

        #define 中的 '\' 是续行符,在内容较多、需要换行时使用

        虽然我们写的是 Print_Info,但在预处理时,它会自动被替换成上述的5个printf。

        在上述代码和结果中,还需要注意的是:虽然程序休眠了5s再打印下一个Print_Info,但是它们的当前时间是一样的,这是因为预处理在程序执行前就完成了,即__TIME__在执行前就完成替换了,之后如何执行与它无关。

        2-4.定义宏

        宏是一种类似函数的结构,它也允许传参,一个简单的宏如下:

#define ADD(x,y) x+y // 宏
#include <stdio.h>
int main()
{
	printf("%d\n",ADD(1,2));
	return 0;
}

 

        它也可以传参,但是与函数传参不同,在传参过程中不会计算,而是直接替换,请看以下代码:

#define MUL((x),(y)) x*y
#include <stdio.h>
int main()
{
	printf("%d\n",MUL(1,2+3));
	return 0;
}

        如果MUL是个函数,传入2和2+3的参数,它会计算成2和5的参数传递进去,那么答案就是10,但是实际上的结果是7:

 

        这是因为宏的参数不会计算,而是直接替换,这个过程如下:

        那么怎么避免这种情况发送呢?,我们可以给宏中的x,y加上括号:

#define MUL(x,y) (x)*(y)

        这就相当于传参时先计算再传入。但是这样还不够保险,情看以下代码:

#define ADD(x,y) (x)+(y)
#include <stdio.h>
int main()
{
	printf("%d\n", ADD(2, 2 + 3)*2);
	return 0;
}

        这是一个加法宏,得到2与2+3的和再乘以2,结果似乎是14,但是实际上的答案是12:

 

        这是因为虽然参数先计算后传入,但是替换后y被2抢占先机了,即ADD(2, 2 + 3)*2替换后为

(2)+(2+3)*2,先乘除后加减,结果就是12了。

        又怎么避免它呢?我们可以给 (x)+(y) 又套上括号,让它们成为一个整体:

#define ADD(x,y) ((x)+(y))

 

        这样的"双重保险"下就几乎不会出错了。

        2-5.#define 的替换规则

                如果有宏,会先替换宏中的参数;

        其次再替换参数名、符号等;

        最后检查是否还有没替换的符号,如果有,继续替换;

        因为#define的替换规则是循环的,这代表着#define中可以嵌套其他#define的符号,请看以下代码:

#include <stdio.h>
#define ADD(x,y) ((x)+(y))
#define M 2
#define N 3
#include <stdio.h>
int main()
{
	printf("%d\n", ADD(M, M + N)*M);
	return 0;
}

 

        我们在宏中嵌套使用了#define的M、N,这是被允许的。

        但是宏不允许递归,因为没有结束条件,它会无限替换下去。

        还需要知道的是#define不是遇到M、N就替换,它无法替换字符串中的符号:

#define M 2
#define N 3
#include <stdio.h>
int main()
{
	printf("%s\n", "M,N");
	return 0;
}

 

        2-6.宏中的#

        #的作用是将一个字符插入字符串中,例如:

#define PRINT(x) printf("the value of "#x" is %d\n",x);
#include <stdio.h>
int main()
{
	int a = 10;
	PRINT(a);
	return 0;
}

  

        它的替换过程如下:

        2-7.宏中的##

        ##可以将两个部分连接成一个整体:

#define cat(x,y) x##y
#include <stdio.h>
int main()
{
	int a101 = 10;
	printf("%d\n",cat(a,101));
	return 0;
}

 

        它的替换过程如下:

        #和##几乎用不上,至少在我的学习过程中从来没有用过。

        2-8.#undef 移除一个宏定义

        它的使用方法如下:

#define M 100
#undef M // 移除M的宏定义
#include <stdio.h>
int main()
{
	int a = M;
	printf("%d\n",a);
	return 0;
}

 

第三节:条件编译

        条件编译与选择语句很像,它需要满足条件才编译这部分代码,可以跳过一些不需要编译的代码,节约了时间。

        3-1.#ifdef

        #ifdef 是如果定义了这个宏,就执行代码,否则不执行:

#define M 100// 定义了M
#include <stdio.h>
int main()
{
	
	#ifdef M
	printf("%d\n",M);
	#endif // 结束
	return 0;
}

  

        当然也可以不要后面预设的符号:

#define M// 定义了M
#include <stdio.h>
int main()
{
	
	#ifdef M
	printf("定义了M\n");
	#endif // 结束
	return 0;
}

  

        3-2.#ifndef

        ifndef与ifdef相反,不定义才执行,定义了反而不执行。

        3-3.#if

        #if后跟常量表达式,而不是宏。

        3-3.#elif 和 #else

        #elif 与之 #ifdef,如同 else if 与之 if,如何使用else if 就如何使用 #elif;#else 同理。

第四节:文件包含

        4-1.库文件

        文件包含使用 #include,它可以跟<>包含库文件,例如#include<stdio.h>;也可以跟""包含本地文件,例如我们写通讯录时的#include"Address.h"。

        它们也有差异:

        #include<stdio.h>会直接到标准库去查找,找不到就报错;

        #include"Address.h"会先去本地库中查找,找不到再去标准库查找,还没有就报错。

        虽然冒号可以查找本地库和标准库,但是库文件还是用<>,因为库文件查找就不需要去本地浪费时间了。

        4-2.防止文件被多次包含

        (1)#prama once

        将它放在头文件的最前面,那么该头文件不会被重复包含;

        (2)#ifndef 宏

                 #define 宏

                 #endif

        第一次包含该文件时,没有文件中的宏,就编译该文件,如果再次包含这个文件,因为有了文件中的宏就不再编译,这样也可以防止文件被多次包含。

结语:

        至此,我学到关于C语言的知识就已经完成了,但是我学习编程的道路还没有结束,实际上,我还欠很多博客没有写,比如数据结构、C++、Linux操作系统……接下来我也将努力完成它们。我不是计算机专业的学生,而是药学专业的学生,在大一下册的时候偶然接触到计算机编程,从此一发不可收拾,用了一万块报名了课外机构——比特就业课。至此每天上完课就写算法题,或者写博客,亦或者上编程课。

        大三寒假我想试一下找实习,所以我需要尽快完成博客。

        诸君,共勉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值