预处理:
预处理是在编译器编译之前进行的操作。预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。所以预处理过程先于编译器对源代码进行处理,这样做的好处是经过处理后的代码会变的很精短。
在很多编程语言中,并没有任何内在的机制来完成如下一些功能:
在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码(防止重复包含某些文件)。
要完成这些工作,就需要使用预处理程序。
目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。
预处理指令写法:
预处理指令以 # 号开头。#号必须是某一行的第一个字符。#后面是指令关键字,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
C/C++常见的预处理指令如下:
1. #include --- 包含头文件
2. #define --- 定义宏
3. #undef --- 取消已定义的宏
4. #if --- 如果给定条件为真,则编译下面相应代码
5. #ifdef --- 如果宏已经定义,则编译下面相应代码
6. #ifndef --- 如果宏没有定义,则编译下面相应代码
8. #elif --- 如果前面的#if给定条件不为真,当前条件为真,则编译下面相应代码
9. #endif --- 结束一个#if……#else条件编译块
10. #error --- 停止编译并显示错误信息
条件编译:
决定那些代码被编译,而哪些不被编译。
条件编译(#ifdef,#else,#endif,#if等)用法:
使用#ifdef指示符,可以区隔一些与特定头文件、程序库和其他文件版本有关的代码。
1、#ifdef,#else,#endif
用法:
#ifdef _Test
…程序段1…
#else
…程序段2…
#endif
分析:如果标识符_Test已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。
例1:
#include "iostream"
using namespace std;
// #define DEBUG
int main()
{
#ifdef DEBUG
cout<<"DEBUG 被定义"<<endl;
#endif
return 0;
}
运行结果为:Press any key to continue
改写代码:去掉上面注释,即加上 #define DEBUG
运行结果为:DEBUG 被定义
Press any key to continue
例2:
#define ORDER
#include "iostream"
using namespace std;
#define ORDER
int main()
{
#ifdef ORDER
cout<<"ORDER在上面已被定义!"<<endl;
#else
cout<<"ORDER没有被定义过!"<<endl;
#endif
return 0;
}
分析:如果程序开头有#define ORDER这行,即ORDER有定义,碰到下面#ifdef ORDER的时候,将会执行第一个cout。否则执行第二个cout。
应用:可以很方便的开启/关闭整个程序的某项特定功能。
一般的情况下,#define语句是包含在一个特定的头文件中。
例如:新建头文件head.h 、define.cpp
head.h :
#ifndef DEBUG
#define DEBUG
#endif
define.cpp:
#include "iostream"
#include "head.h"
using namespace std;
int main(){
#ifdef DEBUG
cout<< "Beginning execution of main()"<<endl;
#endif
return 0;
}
运行结果:
Beginning execution of main()
Press any key to continue
因为头文件中已定义 #define DEBUG
2、#ifndef #else #endif:
#ifndef _GO
…程序段1…
#else
…程序段2…
#endif
分析:#ifndef,(和#ifdef相反):
如果没有定义了标识符_GO,那么执行程序段1,否则执行程序段2)。
3、#if 常量 #else #endif:
#if 常量
…程序段1…
#else
…程序段2…
#endif
分析:如果常量为真(非0),执行程序段1,否则执行程序段2。
应用:可以将测试代码加进来,当需要测试的时候,只要将常量变为 1即可,
当不需要测试的时候,只要将常量变为0即可。
4、#else
#include"iostream"
using namespace std;
#define DEBUG //定义宏时,自动屏蔽下面#else中语句
//#define DEBUG //注释后,自动屏蔽DEBUG下面语句
int main()
{
#ifdef DEBUG
cout<<"Debuging"<<endl;
#else
cout<<"Not Debuging"<<endl;
#endif
return 0;
}
5、#elif
#elif预处理综合了#else和#if的作用
#include"iostream"
using namespace std;
#define ONE 1
#define TWO 2
int main()
{
#ifdef ONE
cout <<"1"<<endl;
#elif TWO
cout <<"2"<<endl;
#else
cout <<"3"<<endl;
#endif
}
/*
该条件判断相当于if...else if ...else
所以只有
cout <<"1"<<endl;
被执行
*等同于没有 #define TWO 2 该句
#elif预处理综合了#else和#if的作用
如果屏蔽掉 #define ONE 1 ,结果为:2
如果 #define ONE 1 、#define TWO 2都屏蔽掉,结果为:3
*/
ifdef、#ifndef 等用法注意:
头件中的#ifndef很关键。比如有两个C文件,都include了同一个头文件。在编译时,这两个C文件要同时编译成一个可运行文件,于是就会产生大量的声明冲突。
所以不管头文件会不会被多个文件引用,建议还是把头文件的内容都放在#ifndef和#endif中。
格式:
#ifndef <标识>
#define <标识>
//……
//其他代码
//……
#endif
<标识>可以自由命名,但每个头文件的“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h
#ifndef STDIO_H
#define STDIO_H
……
#endif
#pragma once
常用指令,只要在头文件的最开始加入这条指令就能够保证头文件只被编译一次。
#pragma once --- 防止某个头文件被多次include,
#ifndef,#define,#endif --- 防止某个宏被多次定义。
注意:
#pragma once是编译相关,即这个编译系统上能用,但在其他编译系统不一定可以,
也就是说移植性差,不过现在基本上每个编译器都有这个定义了。
注意: #ifndef,#define,#endif是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。
#include包含头文件
1. #include < xxx.h >
这种格式告诉预处理程序在编译器 自带的或外部库 的头文件中搜索被包含的头文件。
2. #include“xxx.h”
这种格式告诉预处理程序在当前、被编译的应用程序的 源代码文件中 搜索被包含的头文件,
如果找不到,再搜索编译器自带的头文件。
为什么采用两种不同的包含格式?
编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个
应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含
格式使得编译器能够在很多头文件中区别出一组公共的头文件。
#define定义宏
C++强调使用const来定义常量。宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。
如:
1. 用#define实现求最大值和最小值的宏
#include "iostream"
using namespace std;
#define MAX(x,y) (((x)>(y))?(x):(y)) //注意:三元运算符要比if,else效率高
#define MIN(x,y) (((x)<(y))?(x):(y))
int main(void)
{
#ifdef MAX //判断这个宏是否被定义
cout<< "10和100两数中最大值是:" << MAX(3,5)<<endl;
#endif
#ifdef MIN
cout<< "10和100两数中最小值是:" << MIN(3,5) <<endl;
#endif
return 0;
}
结果:
10和100两数中最大值是:5
10和100两数中最小值是:3
Press any key to continue
#define宏的使用要格外小心,参数最好用括号括起来,因为宏只是简单的文本替换,如果不注意,容易引起歧义错误。
如:
#include "iostream"
using namespace std;
#define SQR(x) (x*x)
int main()
{
int b=3;
#ifdef SQR//只需要宏名就可以了,不需要参数,有参数的话会警告
cout<< "a =" << SQR(b+2) <<endl; //3+2*3+2 = 2*3+3+2 = 11
#endif
return 0;
}
/*
这个宏的定义是错误的。并没有实现程序中的B+2的平方
预处理的时候,替换成如下的结果:b+2*b+2
正确的宏定义应该是:#define SQR(x) ((x)*(x)) = (3+2)*(3+2) = 25
*/
错误的写法和结果:
正确的写法和结果:
用宏取得一个字的高字节或低字节
一个字2个字节:
获得低字节(低8位),与255(0000,0000,1111,1111)按位相与
获得高字节(高8位),右移8位即可。
用宏取得一个数组所含元素的个数
#include "iostream"
using namespace std;
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0]))) //总的大小除以每个类型的大小
int main()
{
int array[100];
#ifdef ARR_SIZE
cout<< "数组中元素个数为:"<< ARR_SIZE(array)<<endl;
#endif
return 0;
}
结果:
数组中元素个数为:100
Press any key to continue
其他:
#error --- 将使编译器显示一条错误信息,然后停止编译。
#line --- 可以改变编译器用来指出警告和错误信息的文件号和行号。
#pragma --- 指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。