C/C++条件编译:#ifdef、#else、#endif等
时间:2024-04-28 14:35:20 来源:网络cs 作者:焦糖 栏目:运营工具 阅读:
文章目录
#undef指令从C预处理器角度看已定义条件编译1.#ifdef、#else和#endif指令 #ifndef指令#ifndef指令通常用于防止多次包含一个文件程序使用#ifndef避免文件被重复包含 #if和#elif指令条件编译还有一个用途是让程序更容易移植 参考
程序员可能要为不同的工作环境准备C程序和C库包。不同的环境可能
使用不同的代码类型。预处理器提供一些指令,程序员通过修改#define的值
即可生成可移植的代码。#undef指令取消之前的#define定义。#if、#ifdef、
#ifndef、#else、#elif和#endif指令用于指定什么情况下编写哪些代码。#line
指令用于重置行和文件信息,#error指令用于给出错误消息,#pragma指令用
于向编译器发出指令。
#undef指令
#undef指令用于“取消”已定义的#define指令。也就是说,假设有如下定义:
#define LIMIT 400
然后,下面的指令:
#undef LIMIT
将移除上面的定义。现在就可以把LIMIT重新定义为一个新值。即使原
来没有定义LIMIT,取消LIMIT的定义仍然有效。如果想使用一个名称,又
不确定之前是否已经用过,为安全起见,可以用#undef 指令取消该名字的定
义。
从C预处理器角度看已定义
处理器在识别标识符时,遵循与C相同的规则:标识符可以由大写字
母、小写字母、数字和下划线字符组成,且首字符不能是数字。当预处理器
在预处理器指令中发现一个标识符时,它会把该标识符当作已定义的或未定
义的。这里的已定义表示由预处理器定义。如果标识符是同一个文件中由前
面的#define指令创建的宏名,而且没有用#undef 指令关闭,那么该标识符是
已定义的。如果标识符不是宏,假设是一个文件作用域的C变量,那么该标
识符对预处理器而言就是未定义的。
已定义宏可以是对象宏,包括空宏或类函数宏:
#define LIMIT 1000 // LIMIT是已定义的#define GOOD // GOOD 是已定义的#define A(X) ((-(X))*(X)) // A 是已定义的int q; // q 不是宏,因此是未定义的#undef GOOD // GOOD 取消定义,是未定义的
注意,#define宏的作用域从它在文件中的声明处开始,直到用#undef指
令取消宏为止,或延伸至文件尾(以二者中先满足的条件作为宏作用域的结
束)。
另外还要注意,如果宏通过头文件引入,那么#define在文件中的位置
取决于#include指令的位置。
稍后将介绍几个预定义宏,如__DATE__和__FILE__。这些宏一定是已
定义的,而且不能取消定义。
条件编译
可以使用其他指令创建条件编译(conditinal compilation)。也就是说,
可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)
块。
1.#ifdef、#else和#endif指令
我们用一个简短的示例来演示条件编译的情况。考虑下面的代码:
#ifdef MAVIS#include "horse.h"// 如果已经用#define定义了 MAVIS,则执行下面的指令#define STABLES 5#else#include "cow.h" //如果没有用#define定义 MAVIS,则执行下面的指令#define STABLES 15#endif
这里使用的较新的编译器和 ANSI 标准支持的缩进格式。如果使用旧的
编译器,必须左对齐所有的指令或至少左对齐#号,如下所示:
#ifdef MAVIS#include "horse.h" // 如果已经用#define定义了 MAVIS,则执行下面的指令#define STABLES 5#else#include "cow.h" //如果没有用#define定义 MAVIS,则执行下面的指令#define STABLES 15#endif
#ifdef
指令说明,如果预处理器已定义了后面的标识符(MAVIS),则
执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令
就执行到哪里)。如果预处理器未定义MAVIS,且有 #else指令,则执行
#else和#endif指令之间的所有代码。
#ifdef #else很像C的if else。两者的主要区别是,预处理器不识别用于标
记块的花括号({}),因此它使用#else(如果需要)和#endif(必须存在)
来标记指令块。这些指令结构可以嵌套。也可以用这些指令标记C语句块
ifdef.c程序
/* ifdef.c -- 使用条件编译 */#include <stdio.h>#define JUST_CHECKING#define LIMIT 4int main(void){int i;int total = 0;for (i = 1; i <= LIMIT; i++){total += 2 * i*i + 1;#ifdef JUST_CHECKINGprintf("i=%d, running total = %d\n", i, total);#endif}printf("Grand total = %d\n", total);return 0;}
编译并运行该程序后,输出如下:
i=1, running total = 3i=2, running total = 12i=3, running total = 31i=4, running total = 64Grand total = 64
如果省略JUST_CHECKING定义(把它放在C注释中,或者使用#undef指
令取消它的定义)并重新编译该程序,只会输出最后一行。可以用这种方法
在调试程序。
定义JUST_CHECKING并合理使用#ifdef,编译器将执行用于
调试的程序代码,打印中间值。调试结束后,可移除JUST_CHECKING定义
并重新编译。如果以后还需要使用这些信息,重新插入定义即可。这样做省
去了再次输入额外打印语句的麻烦。
#ifdef还可用于根据不同的C实现选择合适的代码块。
#ifndef指令
#ifndef指令与#ifdef指令的用法类似,也可以和#else、#endif一起使用,
但是它们的逻辑相反。#ifndef指令判断后面的标识符是否是未定义的,常用
于定义之前未定义的常量。如下所示:
/* arrays.h */#ifndef SIZE#define SIZE 100#endif
(旧的实现可能不允许使用缩进的#define)
通常,包含多个头文件时,其中的文件可能包含了相同宏定义。#ifndef
指令可以防止相同的宏被重复定义。在首次定义一个宏的头文件中用#ifndef
指令激活定义,随后在其他头文件中的定义都被忽略。
#ifndef指令还有另一种用法。假设有上面的arrays.h头文件,然后把下面
一行代码放入一个头文件中:
#include "arrays.h"
SIZE被定义为100。
但是,如果把下面的代码放入该头文件:
#define SIZE 10#include "arrays.h"
SIZE则被设置为10。这里,当执行到#include "arrays.h"这行,处理
array.h中的代码时,由于SIZE是已定义的,所以跳过了#define SIZE 100这行
代码。鉴于此,可以利用这种方法,用一个较小的数组测试程序。测试完毕
后,移除#define SIZE 10并重新编译。这样,就不用修改头文件数组本身
了。
#ifndef指令通常用于防止多次包含一个文件
#ifndef指令通常用于防止多次包含一个文件。也就是说,应该像下面这
样设置头文件:
/* things.h */#ifndef THINGS_H_#define THINGS_H_/* 省略了头文件中的其他内容*/#endif
假设该文件被包含了多次。当预处理器首次发现该文件被包含时,
THINGS_H_是未定义的,所以定义了THINGS_H_,并接着处理该文件的其
他部分。当预处理器第2次发现该文件被包含时,THINGS_H_是已定义的,
所以预处理器跳过了该文件的其他部分。
为何要多次包含一个文件?
最常见的原因是,许多被包含的文件中都包含着其他文件,所以显式包含的文
件中可能包含着已经包含的其他文件。
这有什么问题?
在被包含的文件中有某些项(如,一些结构类型的声明)只能
在一个文件中出现一次。C标准头文件使用#ifndef技巧避免重复包含。
但是,这存在一个问题:
如何确保待测试的标识符没有在别处定义。通常使用这些方法
解决这个问题:
用文件名作为标识符、使用大写字母、用下划线字符代替文件名中的点字符、
用下划线字符做前缀或后缀(可能使用两条下划线)。
例如,查看stdio.h
头文件,可以发现许多类似的代码:
#ifndef _STDIO_H#define _STDIO_H// 省略了文件的内容#endif
你也可以这样做。但是,由于系统标准库使用下划线作为前缀,所以在自
己的代码中不要这样写,避免与标准头文件中的宏发生冲突。
程序使用#ifndef避免文件被重复包含
names.c程序
// names.h --修订后的 names_st 头文件,避免重复包含#ifndef NAMES_H_#define NAMES_H_// 明示常量#define SLEN 32// 结构声明struct names_st{char first[SLEN];char last[SLEN];};// 类型定义typedef struct names_st names;// 函数原型void get_names(names *);void show_names(const names *);char * s_gets(char * st, int n);#endif
下面程序测试该头文件没问题,但是如果把#ifndef保护删除后,程序就无法通过编译。
doubincl.c程序:
// doubincl.c -- 包含头文件两次#include <stdio.h>#include "names.h"#include "names.h" // 不小心第2次包含头文件int main(){names winner = { "Less", "Ismoor" };printf("The winner is %s %s.\n", winner.first,winner.last);return 0;}
#if和#elif指令
#if
指令很像C语言中的if
。#if
后面跟整型常量表达式,如果表达式为非
零,则表达式为真。可以在指令中使用C的关系运算符和逻辑运算符:
#if SYS == 1#include "ibm.h"#endif
可以按照if else的形式使用#elif(早期的实现不支持#elif)。例如,可
以这样写:
#if SYS == 1#include "ibmpc.h"#elif SYS == 2#include "vax.h"#elif SYS == 3#include "mac.h"#else#include "general.h"#endif
较新的编译器提供另一种方法测试名称是否已定义,即用
#if defined(VAX)
代替
#ifdef VAX
这里,defined是一个预处理运算符,如果它的参数是用#defined定义
过,则返回1;否则返回0。
这种新方法的优点是,它可以和#elif一起使用。
下面用这种形式重写前面的示例:
#if defined (IBMPC)#include "ibmpc.h"#elif defined (VAX)#include "vax.h"#elif defined (MAC)#include "mac.h"#else#include "general.h"#endif
如果在VAX机上运行这几行代码,那么应该在文件前面用下面的代码定
义VAX:
#define VAX
条件编译还有一个用途是让程序更容易移植
条件编译还有一个用途是让程序更容易移植。改变文件开头部分的几个
关键的定义,即可根据不同的系统设置不同的值和包含不同的文件。
参考
《C Primer Plus》
本文链接:https://www.kjpai.cn/news/2024-04-28/163257.html,文章来源:网络cs,作者:焦糖,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!
下一篇:返回列表