C语言的预处理详解

说到预处理,大家绝对不会觉得陌生。因为我们在开始编写一份程序的时候,从键盘录入的第一句话:

#include <stdio.h>

这里就使用了预处理,引入头文件。同时在文件开头我们常见的#define ,#ifdef,#pragma之类的都属于预处理。

那么,我们在此先提出几个问题:

1.预处理是C语言的一部分吗?

2.包含#号的都是预处理吗?

3.预处理指令后面是不是都不需要加“;”呢?

看完本文,相信你就会得出答案。

首先我们来了解一下ANSI标准定义的c语言预处理指令都有哪些?

除了这些之外还有几个没有出现在表中的宏:

_LINE_:正在编译的文件的行号

_FILE_:正在编译的文件的名字

_DATE_:编译时刻的日期字符串

_TIME_:编译时刻的时间字符串

_STDC_:判断该程序是否为标准的c程序

若编译器非标准的,则上述宏有可能支持部分或根本不支持。在此我们将就表中的预处理进行讲解。


  •   1. 宏定义 #define

#define非常方便,所以我们当然都喜欢用它。define常用来定义常量和字符串常量。例如,#define PI 3.1415926

在后续的代码中,你都可以用PI来代替这一串数字了。而且就算我要求PI的精度再提高一些那也没有关系,我们只要在define处修改一下就可以了。若是没有使用宏定义,你就要在代码中处处寻找3.1415926然后去修改,是不是感觉痛苦不堪呢?

虽然它可以给我们带来许多便利,但是在使用它的时候一定要小心再小心,它也经常会在你不注意的时候让你犯下错误。例如下面这段代码。

#define PCHAR char*

PCHAR p1,p2;

这段代码有什么问题呢?编译并不会出错,而且p1确实是char*类型。但是!

大家可以看到文中代码,我对p1和p2分别取地址(不要在意是否是野指针,只是为了演示类型)

照理来说指针再取地址就要用char**类型的变量来接收,所以char**a = &p1这句代码并无问题。

但是char**b = &p2提示了错误:char*类型的值不能用char**来接受。也就是说我们的p2它竟然是一个char类型,而并不是char*类型变量?

所以像这种没有办法一下就发现的错误才是最恐怖的错误。像这种情况我还是建议大家一个个的定义变量。实在不行可以使用typedef,不会出现这种问题。

我们还可以用define定义字符串,一般用于路径:

#define FILE_PATH E:\folder1\1.txt


  • 2.#undef

这个预处理命令也就是undefine的意思,即撤销宏定义。也就是说宏定义的生命周期从#define开始到#undef结束。


  • 3.#include

这是我们最常见的词语了。在编译一个程序的时候,首先第一句话就是#include <stdio.h>啦。它同样也非常重要,是将多个源文件连接成一个源文件进行编译,结果就生成一个目标文件(obj)。常见有两种形式:

1.include <xxx.h>

用尖括号括起来的头文件一般都是系统自带的,表示系统将在指定的路径进行寻找。

2.include "xxx.h"

双引号一般则用于我们自己编写的头文件,系统也会优先在当前目录中查找。如果找不到指定文件名的文件就会和形式1一样在指定的路径进行寻找。


  • 4.条件编译

平常写代码过程中,我们为了实现分支结构会经常使用if else结构,在预处理同样也有类似的功能,即条件编译。我们可以按照不同的条件去编译不同的部分,这对程序的移植和调试有着巨大的帮助。条件编译主要有以下两种形式。

1.#ifdef 标识符1  &&  #ifndef 标识符2

//code1

#else

//code2

#endif

这一段就是经典的条件编译。如果定义了标识符1(或如果没有定义(ifndef)标识符2),执行代码段code1;否则执行code2.

要注意的是#ifdef或#ifndef需要和#endif对应。

2.#if 常量表达式

//code1

#else

//code2

#endif

这段则无限接近我们日常使用的if else了。同样要注意endif。

除此之外还有一个#elif,即是elseif,形成if else_if 阶梯状语句,可以进行多种编译选择。


  • 5.#error预处理

它的作用人如其名,是用来提示错误的,编译程序时如果遇到#error就会生成一个编译错误提示信息并停止编译。关于提示的错误信息都是系统定义好的,这里不再介绍,请查找相关资料。


  • 6.#pragma预处理

#pragma可能是所有预处理指令中最复杂的那个了,因为它可以跟很多参数,而这些参数实现的功能也都大相径庭。当然了,越复杂功能也就越强大,这里挑几个常见的参数进行讲解。

1.#pragma once

在头文件的最开始处加上这句话,就可以避免头文件的重复引用(include)。它的作用就是保证每个头文件只编译一次,再加入同名的头文件也没有关系(反正也不编译,且不会报错)。

2.#pragma message

在我们编写程序时,有时一旦定义了许多宏则有可能忘记了某个关键的宏是否正确的设置了。这时只需要使用这条指令就可以一目了然。

具体用法:

编译时的控制台将显示引号中的内容。

  3.#pragma hdrstop

当出现这条语句代表预编译头文件到此为止,后面的将不再参与预编译。通常这样做用来指定编译优先级,同时也可以加快链接的速度(一次编译太多头文件可能会占很多磁盘空间)。

4.#pragma warning

此指令用于和warning有关的操作(即非致命编程错误的警告)。

具体示例:

#pragma warning (disable:4707)     //屏蔽4707警告

#pragma warning (once:4706)         //只显示一次4706警告

#pragma warning (error:164)           //将164号警告当作一个错误。

也可以三合一写成:#pragma warning (disable:4707;once:4706;error:164)

5.#pragma comment

该指令用于导入库。

例如:#pragma comment (lib,"user32.lib")

将user32.lib 库文件导入本工程中。

6.#pragma pack

本知识涉及到内存对齐,详细讲解请看我的另一篇博客《内存对齐》。

猜你喜欢

转载自blog.csdn.net/czc1997/article/details/81079498
今日推荐