预处理指令是以#
号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
部分预处理指令:
指令说明#空指令,无任何效果#include包含一个源代码文件#define定义宏#undef取消已定义的宏#if如果给定条件为真,则编译下面代码#ifdef如果宏已经定义,则编译下面代码#ifndef如果宏没有定义,则编译下面代码#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码#endif结束一个#if……#else条件编译块预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的,程序员在程序中用预处理命令来调用这些功能。
使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
宏定义(#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*类型变量?
其实这个地方替换完之后是char *p1,p2,*是跟着p1的,所以p2是char类型。
所以像这种没有办法一下就发现的错误才是最恐怖的错误。像这种情况我还是建议大家一个个的定义变量。实在不行可以使用typedef,不会出现这种问题。
我们还可以用define定义字符串,一般用于路径:
#define FILE_PATH E:\folder1\1.txt
撤销宏定义(#undef)这个预处理命令也就是undefine的意思,即撤销宏定义。也就是说宏定义的生命周期从#define开始到#undef结束。
包含(#include)文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
这是我们最常见的词语了。在编译一个程序的时候,首先第一句话就是#include 啦。它同样也非常重要,是将多个源文件连接成一个源文件进行编译,结果就生成一个目标文件(obj)。常见有两种形式:
1、include
用尖括号括起来的头文件一般都是系统自带的,表示系统将在指定的路径进行寻找。
2、include "xxx.h"
双引号一般则用于我们自己编写的头文件,系统也会优先在当前目录中查找。如果找不到指定文件名的文件就会和形式1一样在指定的路径进行寻找。
条件编译条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。
平常写代码过程中,我们为了实现分支结构会经常使用if else结构,在预处理同样也有类似的功能,即条件编译。我们可以按照不同的条件去编译不同的部分,这对程序的移植和调试有着巨大的帮助。条件编译主要有以下两种形式。
#ifdef 标识符1 && #ifndef 标识符2
//code1
#else
//code2
#endif
这一段就是经典的条件编译。如果(ifdef)定义了标识符1 并且 如果没有定义(ifndef)标识符2,执行代码段code1;否则执行code2.
要注意的是#ifdef或#ifndef需要和#endif对应。
#if 常量表达式
//code1
#else
//code2
#endif
这段则无限接近我们日常使用的if else了。同样要注意endif。
除此之外还有一个#elif,即是elseif,形成if else_if 阶梯状语句,可以进行多种编译选择。
#error预处理它的作用人如其名,是用来提示错误的,编译程序时如果遇到#error就会生成一个编译错误提示信息并停止编译。关于提示的错误信息都是系统定义好的。
#pragma注释,编译指示#pragma可能是所有预处理指令中最复杂的那个了,因为它可以跟很多参数,而这些参数实现的功能也都大相径庭。当然了,越复杂功能也就越强大,这里挑几个常见的参数进行讲解。
1.#pragma once
在头文件的最开始处加上这句话,就可以避免头文件的重复引用(include)。它的作用就是保证每个头文件只编译一次,再加入同名的头文件也没有关系(反正也不编译,且不会报错)。
2.#pragma message
在我们编写程序时,有时一旦定义了许多宏则有可能忘记了某个关键的宏是否正确的设置了。这时只需要使用这条指令就可以一目了然。
具体用法:
编译时的控制台将显示引号中的内容。
3.#pragma hdrstop(header stop)
当出现这条语句代表预编译头文件到此为止,后面的将不再参与预编译。通常这样做用来指定编译优先级,同时也可以加快链接的速度(一次编译太多头文件可能会占很多磁盘空间)。
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
本知识涉及到内存对齐。