您的位置:首页 > 编程语言 > C语言/C++

C/C++的条件编译

2009-04-29 13:43 183 查看
以为已经掌握条件编译,预编译的要领了,结果还是存在很多遗漏的地方,所以规整一下:

条件编译:

1
#ifdef _XXXX
...程序段1...
#else
...程序段2...
#endif
这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。

2
#ifndef _XXXX
...程序段1...
#else
...程序段2...
#endif
这里使用了#ifndef,表示的是if not def。当然是和#ifdef相反的状况(如果没有定义了标识符_XXXX,那么执行程序段1,否则执行程序段2)。

3
#if 常量
...程序段1...
#elif<常量表达式2>
...程序段2...
#elif<常量表达式3>
...程序段3...
... ...
#else
...程序段2...
#endif
这里表示,如果常量为真(非0,随便什么数字,只要不是0),就执行程序段1,否则执行程序段2。

例如:
#if defined(_PC) && defined(_SSE)
# ifdef DEBUG
# define __PC_VERIFY_ALIGNMENT__
# endif
# ifdef __PC_VERIFY_ALIGNMENT__
# define PC_VERIFY_ALIGN_ASSERT( ptr ) /
{/
if( ( ( (INT) ptr ) % 16) != 0 ) /
{/
debugf( NAME_Critical, TEXT("Unaligned PC data (0x%X)"), ptr ); /
DebugBreak(); /
}/
}
# else
# define PC_VERIFY_ALIGN_ASSERT( ptr )
# endif
#else
# define PC_VERIFY_ALIGN_ASSERT( ptr )
#endif

再谈到预编译命令:

预处理器指示符
头文件通过include 预处理器指示符preprocessor include directive 而成为我们程序的
一部分预处理器指示符用# 号标识这个符号将放在程序中该行的最起始一列上处理
这些指示符的程序被称做预处理器preprocessor 通常捆绑在编译器中
#include 指示符读入指定文件的内容它有两种格式
#include <some_file.h>
#include "my_file.h"
如果文件名用尖括号< 和> 括起来表明这个文件是一个工程或标准头文件查
找过程会检查预定义的目录我们可以通过设置搜索路径环境变量或命令行选项来修改这些
目录在不同的平台上这些方法大不相同建议你请教同事或查阅编译器手册以获得更进
一步的信息如果文件名用一对引号括起来则表明该文件是用户提供的头文件查找该
文件时将从当前文件目录开始
被包含的文件还可以含有#include 指示符由于嵌套包含文件的原因一个头文件可能
会被多次包含在一个源文件中条件指示符可防止这种头文件的重复处理例如
#ifndef BOOKSTORE_H
#define BOOKSTORE_H
/* Bookstore.h 的内容 */
#endif
条件指示符#ifndef 检查BOOKSTORE_H 在前面是否已经被定义这里BOOKSTORE_H
是一个预编译器常量习惯上预编译器常量往往被写成大写字母如果BOOKSTORE_H
在前面没有被定义则条件指示符的值为真于是从#ifndef 到#endif 之间的所有语句都被包
含进来进行处理相反如果#ifndef 指示符的值为假则它与#endif 指示符之间的行将被忽

为了保证头文件只被处理一次把如下#define 指示符
#define BOOKSTORE_H
放在#ifndef 后面这样在头文件的内容第一次被处理时BOOKSTORE_H 将被定义
从而防止了在程序文本文件中以后#ifndef 指示符的值为真
只要不存在两个必须包含的头文件要检查一个同名的预处理器常量这样的情形这
个策略就能够很好地运作
#ifdef 指示符常被用来判断一个预处理器常量是否已被定义以便有条件地包含程序代
码例如
int main()
{
#ifdef DEBUG
cout << "Beginning execution of main()/n";
#endif
string word;
vector< string > text;
while ( cin >> word )
{
#ifdef DEBUG
cout << "word read: " << word << "/n";
#endif
text.push_back( word );
}
// ...
}
本例中如果没有定义DEBUG 实际被编译的程序代码如下
int main()
{
string word;
vector< string > text;
while ( cin >> word )
{
text.push_back( word );
}
// ...
}
反之如果定义了DEBUG 则传给编译器的程序代码是
int main()
{
cout << "Beginning execution of main()/n";
string word;
vector< string > text;
while ( cin >> word )
{
cout << "word read: " << word << "/n";
text.push_back( word );
}
// ...
}
我们在编译程序时可以使用-D 选项并且在后面写上预处理器常量的名字这样就能在
命令行中定义预处理器常量2
$ CC -DDEBUG main.C
也可以在程序中用#define 指示符定义预处理器常量

........................................................................................................................

编译C++程序时编译器自动定义了一个预处理器名字__cplusplus 注意前面有两个下
划线因此我们可以根据它来判断该程序是否是C++程序以便有条件地包含一些代码
例如
#ifdef __cplusplus
// 不错我们要编译C++
// extern "C" 到第7 章再解释
extern "C"
#endif
int min( int, int );
在编译标准C 时编译器将自动定义名字__STDC__ 当然__cplusplus 与__STDC__
不会同时被定义
另外两个比较有用的预定义名字是__LINE__和__FILE__ __LINE__记录文件已经被
编译的行数__FILE__包含正在被编译的文件的名字可以这样使用它们
if ( element_count == 0 )
cerr << "Error: " << __FILE__
<< " : line " << __LINE__
<< "element_count must be non-zero./n";
另外两个预定义名字分别包含当前被编译文件的编译时间__TIME__ 和日期
__DATE__ 时间格式为hh:mm:ss 因此如果在上午8 点17 分编译一个文件则时间表
示为08:17:05 如果这一天是1996 年10 月31 日星期四则日期表示为
Oct 31 1996
若当前处理的行或文件发生变化则__LINE__和__FILE__的值将分别被改变其他四个
预定义名字在编译期间保持不变它们的值也不能被修改
assert()是C 语台标准库中提供的一个通用预处理器宏在代码中常利用assert()来判断一
个必需的前提条件以便程序能够正确执行例如假定我们要读入一个文本文件并对其
中的词进行排序必需的前提条件是文件名已经提供给我们了这样我们才能打开这个文件
为了使用assert() 必须包含与之相关联的头文件
#include <assert.h>
下面是一个简单的使用示例
assert( filename != 0 );
assert()将测试filename 不等于0 的条件是否满足这表示为了后面的程序能够正确执
行我们必须断言一个必需的前提条件如果这个条件为假即filename 等于0 断言
失败则程序将输出诊断消息然后终止
assert.h 是C 库头文件的C 名字C++程序可以通过C 库的C 名字或C++名字来使用它
这个头文件的C++名字是cassert C 库头文件的C++名字总是以字母C 开头后面是去掉后
缀.h 的C 名字正如前面所解释的由于在各种C++实现中头文件的后缀各不相同因
此标准C++头文件没有指定后缀
使用头文件的C 名字或者C++名字两种情况下头文件的#include 预处理器指示符的
效果也会不同下面的#include 指示符
#include <cassert>
将cassert 的内容被读入到我们的文本文件中但是由于所有的C++库名字是在名字空间
std 中被定义的因而在我们的程序文本文件中它们是不可见的除非用下面的using 指示
符显式地使其可见
using namespace std;
使用C 头文件的#include 指示符
#include <assert.h>
就可以直接在程序文本文件中使用名字assert() 而无需使用using 指示符3 库文件厂
商用名字空间来控制全局名字空间污染即名字冲突问题以避免它们的库污染了用
户程序的名字空间8.5 节将讨论这些细节

一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
条件编译命令最常见的形式为:
#ifdef 标识符
程序段1
#else
程序段2
#endif

它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。
其中#else部分也可以没有,即:
#ifdef
程序段1
#denif

这里的“程序段”可以是语句组,也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行,而不同的计算机又有一定的差异。例如,我们有一个数据类型,在Windows平台中,应该使用long类型表示,而在其他平台应该使用float表示,这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译:
#ifdef WINDOWS
#define MYTYPE long
#else
#define MYTYPE float
#endif

如果在Windows上编译程序,则可以在程序的开始加上
#define WINDOWS

这样则编译下面的命令行:
#define MYTYPE long

如果在这组条件编译命令之前曾出现以下命令行:
#define WINDOWS 0

则预编译后程序中的MYTYPE都用float代替。这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况,可以根据此思路设计出其它的条件编译。
例如,在调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段:
#ifdef DEBUG
print ("device_open(%p)/n", file);
#endif

如果在它的前面有以下命令行:
#define DEBUG

则在程序运行时输出file指针的值,以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的,即在调试时加一批printf语句,调试后一一将printf语句删除去。的确,这是可以的。但是,当调试时加的printf语句比较多时,修改的工作量是很大的。用条件编译,则不必一一删改printf语句,只需删除前面的一条“#define DEBUG”命令即可,这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用,即起统一控制的作用,如同一个“开关”一样。
有时也采用下面的形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif

只是第一行与第一种形式不同:将“ifdef”改为“ifndef”。它的作用是:若标识符未被定义则编译程序段1,否则编译程序段2。这种形式与第一种形式的作用相反。
以上两种形式用法差不多,根据需要任选一种,视方便而定。
还有一种形式,就是#if后面的是一个表达式,而不是一个简单的标识符:
#if 表达式
程序段1
#else
程序段2
#endif

它的作用是:当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。
例如:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输出。
#define LETTER 1
main()
{
char str[20]="C Language",c;
int i=0;
while((c=str[i])!='/0'){
i++;
#if LETTER
if(c>='a'&&c<='z') c=c-32;
#else
if(c>='A'&&c<='Z') c=c+32;
#endif
printf("%c",c);
}
}

运行结果为:C LANGUAGE
现在先定义LETTER为1,这样在预处理条件编译命令时,由于LETTER为真(非零),则对第一个if语句进行编译,运行时使小写字母变大写。如果将程序第一行改为:
#define LETTER 0

则在预处理时,对第二个if语句进行编译处理,使大写字母变成小写字母(大写字母与相应的小写字母的ASCII代码差32)。此时运行情况为:
c language
有人会问:不用条件编译命令而直接用if语句也能达到要求,用条件编译命令有什么好处呢?的确,此问题完全可以不用条件编译处理,但那样做目标程序长(因为所有语句都编译),而采用条件编译,可以减少被编译的语句,从而减少目标的长度。当条件编译段比较多时,目标程序长度可以大大减少。

C++预处理(一)(整理)
C++中有那么多灵活的特性,例如重载、类型安全的模板、const关键字等等,为什么程序员还要写“#define”这样的预处理指令?
典型的一个例子,大家都知道“const int a=100;”就比“#define a 100”要好,因为const提供类型安全、避免了预处理的意外修改等。
然而,还是有一些理由让我们去使用#define。

一、使用预处理宏
1) 守护头文件
为了防止头文件被多次包含,这是一种常用技巧。
#ifndef MYPROG_X_H
#define MYPROG_X_H
// … 头文件x.h的其余部分
#endif
2) 使用预处理特性
在调试代码中,插入行号或编译时间这类信息通常很有用,可以使用预定义的标准宏,例如__FILE__、__LINE__、__DATE__和__TIME__。
3) 编译时期选择代码
A. 调试代码
选择性的输出一些调试信息:
void f()
{
#ifdef _DEBUG
cerr < <”调试信息” < <endl;
#endif
// .. f()的其他部分
}

通常我们也可以用条件判断来代替:
void f()
{
if(_DEBUG)
{
cerr < <”调试信息” < <endl;
}
// .. f()的其他部分
}

B. 特定平台代码
同一函数同一功能在不同的编译平台上可能有不同的表现形式,我们可以通过定义宏来区分不同的平台。
C. 不同的数据表示方式
< <深入浅出MFC>>这本书对MFC框架中宏的使用解析的很透彻,也让我们领略到宏的强大功能。可以参看DECLARE_MESSAGE_MAP(),
BEGIN_MESSAGE_MAP, END_MESSAGE_MAP的实现。
4) #pragma的使用,例如用#pragma禁止掉无伤大雅的警告,用于可移植性的条件编译中。例如,
包含winsock2 lib文件:
#pragma comment(lib,”ws2_32”)
用如下预处理宏,可以使结构按1字结对齐:
#pragma pack(push)
#pragma pack(1)
// … 结构定义
#pragma pack(pop)
禁止掉某些警告信息:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( error : 164 )// 把164号警告作为错误报出
// Some code
#pragma warning( pop )

二、宏的常见陷阱
下面示范如何写一个简单的预处理宏max();这个宏有两个参数,比较并返回其中较大的一个值。在写这样一个宏时,容易犯哪些错误?有四大易犯错误。
1) 不要忘记为参数加上括号
// 例1:括号陷阱一:参数
//
#define max(a, b) a < b ? b : a
例如:
max(i += 2, j)
展开后:
i += 2 < j ? j : i += 2
考虑运算符优先级和语言规则,实际上是:
i += ((2 < j) ? j : i += 2)
这种错误可能需要长时间的调试才可以发现。
2) 不要忘记为整个展开式加上括号
// 例2:括号陷阱二:展开式
//
#define max(a, b) (a) < (b) ? (b) : (a)
例如:
m = max(j, k) + 42;
展开后为:
m = (j) < (k) ? (j) : (k) + 42;
考虑运算符优先级和语言规则,实际上是:
m = ((j) < (k)) ? (j) : ((k) + 42);
如果j >= k, m被赋值k+42,正确;如果j < k, m被赋值j,是错误的。如果给展开式加上括号,就解决了这个问题。
3) 当心多参数运算
// 例3:多参数运算
//
#define max(a, b) ((a) < (b) ? (b) : (a))
max(++j, k);
如果++j的结果大于k,j会递增两次,这可能不是程序员想要的:
((++j) < (k) ? (k) : (++j))
类似的:
max(f(), pi)
展开后:
((f()) < (pi) ? (pi) : (f()))
如果f()的结果大于等于pi,f()会执行两次,这绝对缺乏效率,而且可能是错误的。
4) 名字冲突
宏只是执行文本替换,而不管文本在哪儿,这意味着只要使用宏,就要小心对这些宏命名。具体来说,这个max宏最大的问题是,极有可能会和标准的max()函数模板冲突:
// 例4:名字冲突
//
#define max(a,b) ((a) < (b) ? (b) : (a))
#include <algorithm> // 冲突!
在 <algorithm>中,有如下:
template <typename T> const T&
max(const T& a, const T& b);
宏将它替换为如下,将无法编译:
template <typename T> const T&
((const T& a) < (const T& b) ? (const T& b) : (const T& a));
所以,我们尽量避免命名的冲突,想出一个不平常的,难以拼写的名字,这样才能最大可能地避免与其他名字空间冲突。

宏的其他缺陷:
5) 宏不能递归
容易理解。
6) 宏没有地址
你可能得到任何自由函数或成员函数的指针,但不可能得到一个宏的指针,因为宏没有地址。宏之所以没有地址,原因很显然===宏不是代码,宏不会以自身的形势存在,因为它是一种被美化了的文本替换规则。
7) 宏有碍调试
在编译器看到代码之前,宏就会修改相应的代码,因而,他会严重改变变量名称和其他名称;此外,在调试阶段,无法跟踪到宏的内部。

——《现代C++中的预处理宏》徐东来
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: