关于c++预编译符的使用
(该内容是在观看了孙鑫老师的MFC教程后,觉得有必要记录的)
在学习c++时,我们经常会用到#include,#define两种预编译符,很简单,前者是一种引用文件的方法,后者是宏定义的方法。
引用文件,在你写的源代码前可以引用其他文件,将其他文件中的内容直接进行使用。常见的比如使用数学函数要添加#include <cmath>。
宏定义,是一种下新定义的方法,给出一个新的“名词”表示某个值,通俗地讲,宏定义就是给某个值起个外号,我们可以通过外号直接使用该值。比如说#define pi 3.1415926,我们可以说新定义了一个pi,令它为3.1415926,亦可以说给3.1415926这个值起了个新名字。
在学习MFC时,总可以遇见其他的许多预编译符#error,#ifdef,#endif等等,虽然它们的存在似乎不影响我们写程序,但没有了它们,编译运行程序可能将困难重重。
一、预编译符在编译过程中的哪一步
(图片源自孙鑫老师的MFC视频教程)
预处理发生在编译之前,主要作用就是包含文件,宏替换,条件编译,布局控制。
我认为预编译符就是一种得到完整的需编译的无重复的源代码的方法。以下为我自己的想法:包含文件,利用#include包含各种头文件,使代码完整;宏替换,一种简化、增加可读性的方法;条件控制,有选择的选取编译片段和剔除重复定义的方法;布局控制,还没理解。。。。
百度百科
# 和 ## 操作符是和#define宏使用的. 使用# 使在#后的首个参数返回为一个带引号的字符串. 例如, 命令
#define to_string( s ) # s //将会使编译器把以下命令 cout << to_string( Hello World! ) << endl; //理解为 cout << "Hello World!" << endl;
使用##连结##前后的内容. 例如, 命令
#define concatenate( x, y ) x ## y
int xy = 10; //将会使编译器把 cout << concatenate( x, y ) << endl; //解释为 cout << xy << endl; //理所当然,将会在标准输出处显示'10'.
#define命令用于把指定的字符串替换文件中的宏名称 . 也就是说, #define使编译器把文件中每一个macro-name替换为replacement-string. 替换的字符串结束于行末. 这里是一个经典的#define应用 (至少是在C中):
#define TRUE 1 #define FALSE 0 int done = 0; while( done != TRUE ) { ... }
#define命令的另外一个功能就是替换参数,使它 假冒创建函数一样使用. 如下的代码:
//对参数x取绝对值的宏定义 #define absolute_value( x ) ( ((x) < 0) ? -(x) : (x) ) int x = -1; while( absolute_value( x ) ) { ... }
#error命令可以简单的使编译器在发生错误时停止. 当遇到一个#error时,编译器会自动输出行号而无论message的内容. 本命令大多是用于调试.
#if, #ifdef, #ifndef, #else, #elif, #endif这些命令让编译器进行简单的逻辑控制. 当一个文件被编译时, 你可以使用这些命令使某些行保留或者是去处.
#if expression 如果表达式(expression)的值是"真"(true),那么紧随该命令的代码将会被编译.
#ifdef macro 如果"macro"已经在一个#define声明中定义了, 那么紧随该命令的代码将会被编译.
#ifndef macro 如果"macro"未在一个#define声明中定义, 那么紧随命令的代码将会被编译.
一些小边注: 命令#elif是"elseif"的一种缩写,并且他可以想你所意愿的一样工作. 你也可以在一个#if后插入一个"defined"或者"!defined"以获得更多的功能.这里是一部分例子:
//判断是否宏定义过DEBUG,并依情况输出 #ifdef DEBUG cout << "This is the test version, i=" << i << endl; #else cout << "This is the production version!" << endl; #endif
#include <iostream>本命令包含一个文件并在当前位置插入. 两种语法的主要不同之处是在于,如果filename括在尖括号中,那么编译器不知道如何搜索它. 如果它括在引号中, 那么编译器可以简单的搜索到文件. 两种搜索的方式是由编译器决定的,一般尖括号意味着在标准库目录中搜索, 引号就表示在当前目录中搜索. The spiffy new 整洁的新C++ #include目录不需要直接映射到filenames, 至少对于标准库是这样. 这就是你有时能够成功编译以下命令的原因
#line命令是用于更改 __LINE__ 和 __FILE__变量的值. 文件名是可选的. __LINE__ 和 __FILE__ 变量描述被读取的当前文件和行. 命令
//更改行号为10,当前文件改为"main.cpp". #line 10 "main.cpp"
#pragma命令可以让编程者让编译器执行某些事. 因为#pragma命令的执行很特殊,不同的编译器使用有所不同. 一个选项可以跟踪程序的执行.
#undef命令取消一个先前已定义的宏变量, 譬如一个用#define定义的变量.
预定义变量
下列参数在不同的编译器可能会有所不同, 但是一般是可用的:
-
__LINE__ 和 __FILE__ 变量表示正在处理的当前行和当前文件.
-
__DATE__ 变量表示当前日期,格式为month/day/year(月/日/年).
-
__TIME__ 变量描述当前的时间,格式为hour:minute:second(时:分:秒).
-
_cplusplus 变量只在编译一个C++程序时定义.
-
__STDC__ 变量在编译一个C程序时定义,编译C++时也有可能定义.
末了,#ifdef等的用途
在MFC中,类不可以重复定义,而#include包含头文件会重复编译,于是可能出现如下,出错情形
(Animal.h中) class Animal{...} (Fish.h中) #include "Animal.h" class Fish:public Animal{...} (main.cpp中) #include “Animal.h" #include "Fish.h" void main(){...}
编译代码时,首先从main.cpp中开始,由预编译符#include ”Animal.h“第一次编译Animal类。接着由#include ”Fish.h“第二次编译Animal类,此时重复定义。再然后编译Fish类和main函数。
正确情形
(Animal.h中) #ifndef XXXX #define XXXX //定义,值并不是必需的,此处的定义无使用意义,不需要值 class Animal{...} #endif (Fish.h中) #include "Animal.h" class Fish:public Animal{...} (main.cpp中) #include “Animal.h" #include "Fish.h" void main(){...}
同样的内容,在第一次编译Animal类时,由于没有宏定义过XXXX,所以编译其中内容,第二次由于宏定义过XXXX,不再进行编译,于是不存在错误。