C/C++条件编译:#ifdef、#else、#endif等
文章目录
- #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 4
int 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 = 3
i=2, running total = 12
i=3, running total = 31
i=4, running total = 64
Grand 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》
相关文章:
C/C++条件编译:#ifdef、#else、#endif等
文章目录 #undef指令从C预处理器角度看已定义条件编译1.#ifdef、#else和#endif指令 #ifndef指令#ifndef指令通常用于防止多次包含一个文件程序使用#ifndef避免文件被重复包含 #if和#elif指令条件编译还有一个用途是让程序更容易移植 参考 程序员可能要为不同的工作环境准备C程序…...

基于51单片机步进电机节拍步数正反转LCD1602显示( proteus仿真+程序+原理图+设计报告+讲解视频)
基于51单片机步进电机节拍步数正反转LCD1602显示 📑1. 主要功能:📑2. 讲解视频:📑3. 仿真📑4. 程序代码📑5. 设计报告📑6. 设计资料内容清单&&下载链接📑[资料下…...

Vim 从何而来?
Vim 编辑器的创造者、维护者和终身领导者 Bram Moolenaar 为了纪念这位杰出的荷兰程序员,我们今天来聊一聊 Vim 的历史。 Vim 无处不在。它被很多人使用。同时 Vim 可能是世界上 “最难用的软件之一” ,但是又多次被程序员们评价为 最受欢迎的 代码编辑…...
Auto.js 清除指定应用缓存
本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!Auto.js 清除指定应用缓存 环境 win10Pixel4Android13var packageName = ""; // 包名 var resu…...
[EFI]Surface Pro 4电脑 Hackintosh 黑苹果引导文件
硬件型号驱动情况主板Surface Pro 4处理器Intel Core i5-6300U 2.5GHz已驱动内存16GB DDR4 2400Mhz已驱动硬盘Samsung SSD 860 EVO 250G Media (Install on SSD External)已驱动显卡Intel HD Graphics 520 2GBmacOS 13以上自行添加显卡补丁声卡Realtek ALC3269(id 3…...

【Java 进阶篇】深入浅出:JQuery 事件绑定的奇妙世界
在前端的世界里,事件是不可或缺的一部分。用户的点击、输入、滚动等行为都触发着各种事件,而如何在代码中捕捉并处理这些事件是每位前端开发者必须掌握的技能之一。本文将带你深入浅出,探索 JQuery 中的事件绑定,为你揭开这个奇妙…...
Pair用法示例:
这里用到了 org.apache.commons.lang3.tuple.Pair 来封装数据(就是不想自己再写一个 DO 或者 VO 或者 MO) 在Java中,Pair是一种简单的数据结构,用于存储两个相关联的值。它没有特定的内置类,但可以通过自定义实现或使…...
rpc依赖安装
依赖: 0、boost:用于实现多线程等; 1、protobuf:用于实现数据的序列化、反序列化,也用于定义和生成rpc数据及接口; 2、libevent:用于实现基于IO多路复用机制的网络事件循环。 其实可以直接用包…...
文件存储服务 实时通信服务 HTTP通信协议
目录 文件存储服务实时通信服务HTTP通信协议 👍 点赞,你的认可是我创作的动力! ⭐️ 收藏,你的青睐是我努力的方向! ✏️ 评论,你的意见是我进步的财富! 文件存储服务 文件存储服务是一种用于…...

Redis - 订阅发布替换 Etcd 解决方案
为了减轻项目的中间件臃肿,由于我们项目本身就应用了 Redis,正好 Redis 的也具备订阅发布监听的特性,正好应对 Etcd 的功能,所以本次给大家讲解如何使用 Redis 消息订阅发布来替代 Etcd 的解决方案。接下来,我们先看 R…...

Hessian协议详解
前言 Hessian协议是一种基于二进制的轻量级远程调用协议,用于在分布式系统中进行跨语言的通信。它使用简单的二进制格式来序列化和反序列化数据,并支持多种编程语言,如Java、C#、Python等。Hessian协议相对于其他协议的优势在于其简单性和高…...

【AI视野·今日Sound 声学论文速览 第三十六期】Mon, 30 Oct 2023
AI视野今日CS.Sound 声学论文速览 Mon, 30 Oct 2023 Totally 7 papers 👉上期速览✈更多精彩请移步主页 Daily Sound Papers Style Description based Text-to-Speech with Conditional Prosodic Layer Normalization based Diffusion GAN Authors Neeraj Kumar, A…...

Android Jetpack的组件介绍,常见组件解析
jetpack组件有哪些 Android Jetpack是一个集成Android应用程序组件的一站式解决方案。它使开发人员能够专注于他们的应用程序的真正创新部分,而不会受到Android平台特定的限制。Jetpack组件可分为四个类别: 架构组件(Architecture Componen…...

ImportError: cannot import name ‘url_quote‘ from...
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是全栈工…...

一文看分布式锁
为什么会存在分布式锁? 经典场景-扣库存,多人去同时购买一件商品,首先会查询判断是否有剩余,如果有进行购买并扣减库存,没有提示库存不足。假如现在仅存有一件商品,3人同时购买,三个线程同时执…...

Jenkins自动化部署一个Maven项目
Jenkins自动化部署 提示:本教程基于CentOS Linux 7系统下进行 Jenkins的安装 1. 下载安装jdk11 官网下载地址:https://www.oracle.com/cn/java/technologies/javase/jdk11-archive-downloads.html 本文档教程选择的是jdk-11.0.20_linux-x64_bin.tar.g…...

K8S1.23.5部署(此前1.17版本步骤囊括)及问题记录
应版本需求,升级容器版本为1.23.5 kubernetes组件 一个kubernetes集群主要由控制节点(master)与工作节点(node)组成,每个节点上需要安装不同的组件。 master控制节点:负责整个集群的管理。 …...

基于java web的中小型人力资源管理系统
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...
Python学习笔记--Python关键字yield
原文:http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained 注:这是一篇 stackoverflow 上一个火爆帖子的译文 问题 Python 关键字 yield 的作用是什么?用来干什么的? 比如,我正在试图理解下面的代码: def node._get_child_candidates(self,…...
CF 850 C Arpa and a game with Mojtaba(爆搜优化SG)
CF 850 C. Arpa and a game with Mojtaba(爆搜优化SG) Problem - C - Codeforces Arpa and a game with Mojtaba - 洛谷 思路:显然对于每一种质因子来说操作都是独立的 , 因此可以考虑对于每一种质因子求当前质因子的SG &#…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...