本文主要介绍c语言中条件编译相关的预编译指令,包括  #define、#undef、#ifdef、#ifndef、#if、#elif、#else、#endif、defined。

#define            定义一个预处理宏
#undef            取消宏的定义

#if                   编译预处理中的条件命令,相当于C语法中的if语句
#ifdef              判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef            与#ifdef相反,判断某个宏是否未被定义
#elif                若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else              与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif             #if, #ifdef, #ifndef这些条件命令的结束标志.
defined          与#if, #elif配合使用,判断某个宏是否被定义

二、条件编译

条件编译是根据实际定义宏(某类条件)进行代码静态编译的手段。可根据表达式的值或某个特定宏是否被定义来确定编译条件。

最常见的条件编译是防止重复包含头文件的宏,形式跟下面代码类似:

1 #ifndef ABCD_H
2 #define ABCD_H
3
4 // ... some declaration codes
5
6 #endif // #ifndef ABCD_H

在实现文件中通常有如下类似的定义:

 1 #ifdef _DEBUG2 3 // ... do some operations4 5 #endif6 7 #ifdef _WIN328 9 // ... use  Win32 API
10
11 #endif

这些都是条件编译的常用情境。

三、条件编译中使用的预编译指令

#define            定义一个预处理宏
#undef            取消宏的定义

#if                   编译预处理中的条件命令,相当于C语法中的if语句
#ifdef              判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef            与#ifdef相反,判断某个宏是否未被定义
#elif                若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else              与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif             #if, #ifdef, #ifndef这些条件命令的结束标志.
defined          与#if, #elif配合使用,判断某个宏是否被定义

四、预编译指令应用举例

1. #define、#undef

#define命令定义一个宏:
#define MACRO_NAME[(args)] [tokens[(opt)]]
之后出现的MACRO_NAME将被替代为所定义的标记(tokens)。宏可带参数,而后面的标记也是可选的。

宏定义,按照是否带参数通常分为对象宏、函数宏两种。
对象宏: 不带参数的宏被称为"对象宏(objectlike macro)"。对象宏多用于定义常量、通用标识。例如:

// 常量定义
#define MAX_LENGTH 100
// 通用标识,日志输出宏
#define SLog printf
// 预编译宏
#define _DEBUG

函数宏:带参数的宏。利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源。 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率。但多数c++程序不推荐使用函数宏,调试上有一定难度,可考虑使用c++的inline代替之。例如:

// 最小值函数
#define MIN(a,b) ((a)>(b)? (a):(b))
// 安全释放内存函数
#define SAFE_DELETE(p) {if(NULL!=p){delete p; p = NULL;}}

#undef可以取消宏定义,与#define对应。

2. defined

defined用来测试某个宏是否被定义。defined(name): 若宏被定义,则返回1,否则返回0。
它与#if、#elif、#else结合使用来判断宏是否被定义,乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef。defined可用于在一条判断语句中声明多个判别条件;#ifdef和#ifndef则仅支持判断一个宏是否定义。

#if defined(VAX) && defined(UNIX) && !defined(DEBUG) 

和#if、#elif、#else不同,#ifdef、#ifndef、defined测试的宏可以是对象宏,也可以是函数宏。

3. #ifdef、#ifndef、#else、#endif

条件编译中相对常用的预编译指令。模式如下:

#ifdef ABC
// ... codes while definded ABC
#elif (CODE_VERSION > 2)
// ... codes while CODE_VERSION > 2
#else
// ... remained cases
#endif // #ifdef ABC 

#ifdef用于判断某个宏是否定义,和#ifndef功能正好相反,二者仅支持判断单个宏是否已经定义,上面例子中二者可以互换。如果不需要多条件预编译的话,上面例子中的#elif和#else均可以不写。

4. #if、#elif、#else、#endif

#if可支持同时判断多个宏的存在,与常量表达式配合使用。常用格式如下:

#if 常量表达式1
// ... some codes
#elif 常量表达式2
// ... other codes
#elif 常量表达式3
// ...
...
#else
// ... statement
#endif

常量表达式可以是包含宏、算术运算、逻辑运算等等的合法C常量表达式,如果常量表达式为一个未定义的宏, 那么它的值被视为0。

#if MACRO_NON_DEFINED // 等价于#if 0

在判断某个宏是否被定义时,应当避免使用#if,因为该宏的值可能就是被定义为0。而应当使用#ifdef或#ifndef。
注意: #if、#elif之后的宏只能是对象宏。如果宏未定义,或者该宏是函数宏,则编译器可能会有对应宏未定义的警告。

五、总结

本文主要介绍c语言中有关预编译的指令。撰写本文的目的在于理清相关概念调用,在后续预编译使用时可以找到最合适的指令及格式。比如同时满足多个宏定义的预编译、多分支预编译、#elif和#else指令的配合等。

一、if条件编译,选择编译

(1)

#if ()

//*******

#endif

(2)

#if ()

//******

#else

//******

#endif

(3)

#if ()

//******

#elif ()

//******

#elif ()

//******

#endif

二、注意此处不能加“()”不然会把括号也视为宏定义的字符串

#define DBG

三、

#define  A  C(0、1...)

四、注意此处不能加“()”不然会把括号也视为宏定义的字符串

#define  DBG

#undefine DBG

五、注意此处不能加“()”不然会把括号也视为宏定义的字符串

(1)

#ifdef  DBG

#define UNDBG

#define UNDBG1

#endif

(2)

#ifundef  DBG

#define UNDBG

#define UNDBG

#endif

六、符合条件“&& 和 ||”复合条件下必须加上“()”标准形式如: # if (define (DBG)) || (define (DBG1))

(1)

# if define DBG || define DBG1 || define DBG2

//******

#endif

(2)

#if !define DBG || !define DBG2

//******

#endif

1、条件编译

请看下面一个例子:

  1. #include<stdio.h>

  2. #define BB

  3. #ifdef AA

  4. #define HELLO "hello world"

  5. #elif BB

  6. #define HELLO "hello CC"

  7. #endifint main()

  8. {

  9. printf("%s\n",HELLO);

  10. return 1;

  11. }

如果你觉得这个打印会是hello CC.那你就和我犯了一样的错误了。如果你用gcc -E hello.c -o hello.i 编译,(这条是预编译命令,下面会讲到。)会出现:error: #if with no expression的错误。原因是BB虽然定义了,但是定义的是空值,放在#elif后面就不行。因为#elif不仅仅是检查后面的宏有没有定义,还会检查其值。但是#ifdef就只是检查后面的宏是否定义,而不管其值为多少。读者可以把#define

BB改成#define AA试一下,结果就会打印hello world了。

读者如果有兴趣,也可以把#define BB改成#define BB   0  试一试,这时用gcc -E hello.c -o hello.i预编译可以编译通过,但是编译过程就不行了,因为#elif   0为假,HELLO没有定义。

这几个宏是为了进行条件编译。一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部 分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
    条件编译命令最常见的形式为:
    #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语句也能达到要求,用条件编译命令有什么好处呢?的确,此问题完全可以不用条件编译处理,但那样做目标程序长(因为所有语句都编译),而采用条件编译,可以减少被编译的语句,从而减少目标的长度。当条件编译段比较多时,目标程序长度可以大大减少。

浅谈#ifdef在软件开发中的妙用

  笔者从事UNIX环境下某应用软件的开发与维护工作,用户分布于全国各地,各用户需要的基本功能都是一样的,但在某些功能上要随着需求变化,不断加以升级,要想实现全国各地用户的升级工作是很困难的,而我们则只是利用E-mail发送补丁程序给用户,这些补丁程序都是在一套软件的基础上不断地修改与扩充而编写的,并由不同的标志文件转入到不同的模块,虽然程序体积在不断扩大,但丝毫不影响老用户的功能,这主要是得益于C程序的#ifdef/#else/#endif的作用。

  我们主要使用以下几种方法,假设我们已在程序首部定义#ifdef DEBUG与#ifdef TEST:

  1.利用#ifdef/#endif将某程序功能模块包括进去,以向某用户提供该功能。

  在程序首部定义#ifdef HNLD:

  #ifdef HNLD

  include"n166_hn.c"

  #endif

  如果不许向别的用户提供该功能,则在编译之前将首部的HNLD加一下划线即可。

  2.在每一个子程序前加上标记,以便追踪程序的运行。

  #ifdef DEBUG

  printf(" Now is in hunan !");

  #endif

  3.避开硬件的限制。有时一些具体应用环境的硬件不一样,但限于条件,本地缺乏这种设备,于是绕过硬件,直接写出预期结果。具体做法是:

  #ifndef TEST

  i=dial();

  //程序调试运行时绕过此语句

  #else

  i=0;

  #endif

  调试通过后,再屏蔽TEST的定义并重新编译,即可发给用户使用了。

# ifdef  #ifndef 等用法(转)

  头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

#ifndef <标识>
#define <标识>

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

#endif

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

#ifndef _STDIO_H_
#define _STDIO_H_

......

#endif

2.在#ifndef中定义变量出现的问题(一般不定义在#ifndef中)。

#ifndef AAA
#define AAA
...
int i;
...
#endif
里面有一个变量定义
在vc中链接时就出现了i重复定义的错误,而在c中成功编译。

结论:

(1).当你第一个使用这个头的.cpp文件生成.obj的时候,int i 在里面定义了当另外一个使用这个的.cpp再次[单独]生成.obj的时候,int i 又被定义然后两个obj被另外一个.cpp也include 这个头的,连接在一起,就会出现重复定义.

(2).把源程序文件扩展名改成.c后,VC按照C语言的语法对源程序进行编译,而不是C++。在C语言中,若是遇到多个int i,则自动认为其中一个是定义,其他的是声明。

(3).C语言和C++语言连接结果不同,可能(猜测)是在进行编译的时候,C++语言将全局
变量默认为强符号,所以连接出错。C语言则依照是否初始化进行强弱的判断的。(参考)

解决方法:

(1).把源程序文件扩展名改成.c。

(2).推荐解决方案:
.h中只声明 extern int i;在.cpp中定义

<x.h>
#ifndef __X_H__
#define __X_H__
extern int i;
#endif //__X_H__
<x.c>
int i;

注意问题:

(1).变量一般不要定义在.h文件中。

条件编译#ifdef的妙用详解相关推荐

  1. C语言中-条件编译#ifdef的妙用详解_透彻

    本文主要介绍c语言中条件编译相关的预编译指令,包括  #define.#undef.#ifdef.#ifndef.#if.#elif.#else.#endif.defined. #define     ...

  2. python中requests库的用途-python中requests库session对象的妙用详解

    在进行接口测试的时候,我们会调用多个接口发出多个请求,在这些请求中有时候需要保持一些共用的数据,例如cookies信息. 妙用1 requests库的session对象能够帮我们跨请求保持某些参数,也 ...

  3. python中session的使用方法_python中requests库session对象的妙用详解

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

  4. Javascript中关于Array.filter()的妙用详解

    强烈推荐30个原生JavaScript的demo,包括canvas时钟特效.自定义视频播放器.搜索栏快速匹配.fetch访问资源.console调试技巧等,先fork后学习,详见点击打开链接,欢迎点赞 ...

  5. python编程入门与案例详解-quot;Python小屋”免费资源汇总(截至2018年11月28日)...

    原标题:"Python小屋"免费资源汇总(截至2018年11月28日) 为方便广大Python爱好者查阅和学习,特整理汇总微信公众号"Python小屋"开通29 ...

  6. C语言之详解#ifdef等宏

    C语言之详解#ifdef等宏 这几个宏是为了进行条件编译.一般情况下,源程序中所有的行都参加编译.但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是&qu ...

  7. 武林c语言,详解C语言中条件编译

    预处理器提供条件编译,程序的不同部分可以在不同的条件下编译,从而产生不同的目标代码文件,这对于程序移植和调试非常有用,本文是武林技术频道小编给为大家带来的详解中条件编译,一起来了解一下吧! 通常情况, ...

  8. #ifdef,#else,#endif,#if用法详解(转)

    #ifdef,#else,#endif,#if用法详解(转) 2011-04-22 10:11 预处理就是在进行编译的第一遍词法扫描和语法分析之前所作的工作.说白了,就是对源文件进行编译前,先对预处理 ...

  9. 【Scripts系列】之Makefile中条件分支ifeq/else/endif/else ifeq/ifneq/ifdef/ifndef用法详解

    DATE: 2019-2-22 前言       条件分支的用法在任何编程语言和脚本以及逻辑中都广泛使用.前文讲述过C语言中预处理的条件编译#ifdef/#if/#elif/#else/#endif用 ...

最新文章

  1. 257. Binary Tree Paths
  2. 你还傻傻的分不清“和服和浴衣吗?
  3. 【转载】图论 500题——主要为hdu/poj/zoj
  4. ArcGIS实验教程——实验九:矢量数据提取
  5. 存放80000000学生成绩的集合,怎么统计平均分性能高
  6. 前端学习(2240):构造Vue的的利器-脚手架vue-cli3
  7. 【OS学习笔记】二十二 保护模式六:保户模式下操作系统内核如何加载用户程序并运行 对应的汇编代码之用户程序
  8. 苹果暗黑模式_【蜜茶原创】微信最终妥协苹果,暗黑模式正式上线!
  9. Pycharm中出现ImportError:DLL load failed:找不到指定模块的解决方法(终极方法,不成功来找我~)
  10. 使用SQL编程创建100万条数据测试索引
  11. 标准模板库(STL)之 map 列传
  12. 关于《啊哈!算法》相关资源
  13. Arduino与Proteus仿真实例-HC-SRF04超声波测距仿真
  14. 虾神段子小剧场:人人都该学点数据分析(1):达五十万的电竞人才缺口?
  15. android 传感器应用
  16. 【搞定Go语言】第2天6:Go语言基础之切片
  17. elementui穿梭框数据不更新
  18. MySQL数据库程序设计套题⑤【真题截图+原理解析】
  19. BAT 机器学习 1000 题 201-300(转)
  20. [分享]低工作电流超低功耗LCD液晶显示驱动芯片-VKL144A/VKL144B-TSSOP48/QFN48 6mm*6mm超小体积

热门文章

  1. 节能信标组雌雄双煞 - 来自于洛阳理工学院信标组的车模
  2. 2021年春季学期-信号与系统-第三次作业参考答案-第二道题
  3. 人工智能创意赛复选赛成绩单
  4. java 创建对象的init_Java的对象实例是什么时候被创建
  5. eclipse 右键项目为什么没有properties菜单_只需几步,从零开始搭建SSM项目
  6. kernel vim阅读 设置tags的标签
  7. 计算机osi模型的功能osi,OSI基本参考模型是什么 OSI基本参考模型各层功能介绍【图文】...
  8. 猜拳游戏c语言代码流程图,这是一个猜拳游戏的程序 大家有更好的解决方法么?...
  9. win2003系统服务器,windows 2003 server服务器蓝屏
  10. java 国际化 properties_java处理国际化和读取properties文件代码片段