c语言的常用的预处理指令和条件编译

c语言的常用的预处理指令和条件编译

  • 预处理详解
  • 预定义符号
    • #define
      • #define 定义标识符
      • #define 定义宏
      • 带副作用的宏参数
      • 宏和函数的对比
      • #define命名约定和#undef移除宏
    • # 和 ## 参数插入字符串
      • 字符串的自动连接
      • #宏参数
  • 命令行定义
  • 条件编译
    • #if和#endif
    • 多分支条件编译#if、#elif、#else和#endif
    • #ifdef和#ifndef 判断某符号是否定义

预处理详解

最早接触预处理,还是在一篇文章看懂c语言_如何看懂c语言代码-CSDN博客中介绍了c语言中#define定义的符号在编译阶段会被替换的行为。

这里将我所了解的预处理符号简单做个收集。预处理指令肯定不止这些,曾经遇到过的:

#pragma pack()

也是预处理指令。

详细参考《c语言深度剖析》。

预定义符号

__FILE__	//进行编译的源文件
__LINE__	//文件当前的行号
__DATE__	//文件被编译的日期
__TIME__	//文件被编译的时间
__STDC__	//如果编译器遵循ANSI C,其值为1,否则未定义

输出这些符号可以知道代码的信息。

#include <stdio.h>
#include <windows.h>int main()
{printf("%s\n%d\n%s\n%s\n", __FILE__, __LINE__, __DATE__, __TIME__);printf("%s", __STDC__);//vs下未定义,说明vs不是严格遵循ANSI C标准return 0;
}

在某一个的Devc++5.11的输出:

D:\aDarkwanderor\_01ComputerLearn\_01C_Language_and_C++\_4CppProjectDebug\testC.c
6
Apr 25 2025
21:39:22

#define

#define 定义标识符

#define的用法:

#define name stuff

定义阶段会将name替换成stuffstuff可以是数字,可以是关键字,还可以是语句。

如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。

#define MAX 1000//为 register这个关键字,创建一个简短的名字
#define reg register//用更形象的符号来替换一种实现
#define do_forever for(;;)//在写case语句的时候自动把 break写上
#define CASE break;case  // 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,       \__DATE__,__TIME__ )  

注意staff;需要谨慎,因为符号用到的地方可能是某个语句的一部分。

#define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:

  • 参数列表的左括号必须与name紧邻。

  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

  • 对每个出现的参数和最终的宏,尽量用括号()括起来。

简单使用宏:

#include <stdio.h>#define MUL(x) x*x
#define MUL2(x) (x)*(x)
#define ADD(x) (x)+(x)int main() {int a = 5;printf("%d\n", MUL(a));printf("%d\n", MUL(a + 1));printf("%d\n", MUL2(a + 1));printf("%d\n", ADD(a) * 6);return 0;
}

输出:

25
11
36
35

但我们想要的预期输出很明显是{25,36,36,60},在MULADD两个宏上出现了问题。

将宏的符号替换(预编译之后),并省略stdio.h展开后的所有代码,main函数变成这个样子:

int main() {int a = 5;printf("%d\n", a * a);printf("%d\n", a + 1 * a + 1);printf("%d\n", (a + 1) * (a + 1));printf("%d\n", a + a * 6);return 0;
}

第4行的宏替换后,因为*的优先级高,故先计算1*a,使得结果错误,第6行的宏也是如此。因此对每个出现的参数和最终的宏,尽量用括号()括起来。

#define替换规则进行总结:

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数(或源码)进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复1、2。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能

出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

例如:

x+1;//不带副作用
x++;//带有副作用

这里的MAX宏可以证明具有副作用的参数所引起的问题。

#include <stdio.h>#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main() {int x = 5,y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);return 0;
}

输出:

x=6 y=10 z=9

宏和函数的对比

宏通常被应用于执行简单的运算,比如在两个数中找出较大的一个:

#define MAX(a, b) ((a)>(b)?(a):(b))

不用函数来完成这个任务的原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多(就是函数费时,宏省时)。所以宏比函数在程序的规模和速度方面更胜一筹

  2. 更为重要的是函数的参数必须声明为特定的类型

所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。即宏是类型无关的

此外,宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

宏的缺点:和函数相比宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度

  2. 宏是没法调试的。

  3. 宏由于类型无关,也就不够严谨。

  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

  5. 宏无法实现递归

  6. 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。

用一张表列出二者的差别:

对比项#define 定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

#define命名约定和#undef移除宏

函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是:

  • 把宏名全部大写。

  • 函数名不要全部大写。

此外如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。可以用#undef移除宏。

#define NAME stuff
//...
#undef NAME

# 和 ## 参数插入字符串

字符串的自动连接

案例:

#include <stdio.h>int main() {char* p = "hello ""world\n";char* p2 = "hello"\" wor"\"ld\n";printf("hello"" world\n");printf("%s", p);printf("%s", p2);return 0;
}

输出:

hello world
hello world
hello world

这说明,字符串是有自动连接的特点的。

因此可以利用这个特点继续实现表现更丰富的宏。

#宏参数

在这之前先介绍#宏参数,这里的#宏参数是指把宏参数转换成字符串

例如:

#include <stdio.h>#define STR(x) #xint main() {printf(STR(aasfsdgsg));return 0;
}

输出:

aasfsdgsg

因此利用#宏参数和字符串的自动连接特性,完善的宏如下:

#include <stdio.h>#define PRINT(FORMAT, VALUE) \printf("the value is "FORMAT"\n", VALUE)#define PRINT2(FORMAT, VALUE) \printf("the value of " #VALUE " is "FORMAT "\n", VALUE);int main() {int i = 10;PRINT("%d", 10);//利用字符串的自动连接特性PRINT2("%d", i + 3);//利用#宏参数和字符串的自动连接特性return 0;
}

输出:

the value is 10
the value of i + 3 is 13

##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。

例如:

#include <stdio.h>#define CAT(x,y) x##yint main() {int a = 2025;int b = CAT(a, -3);//将a和-3连接在一起变成a-3printf("%d", b);return 0;
}

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

命令行是一种通过输入文本命令来与计算机系统(特别是操作系统)进行交互的界面,它与图形用户界面(GUI)相对应,具有高效、灵活和强大的特点,在系统管理、软件开发等领域应用广泛。

c语言代码通过编译最终变成的可执行程序,只要满足条件是可以直接运行在操作系统上的。从代码到可执行程序,在一些编译器可以通过输入命令干涉编译过程。

当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。

举个例子,假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。

例如这个代码:

#include <stdio.h>
int main()
{int array[ARRAY_SIZE];int i = 0;for (i = 0; i < ARRAY_SIZE; i++){array[i] = i;}for (i = 0; i < ARRAY_SIZE; i++){printf("%d ", array[i]);}printf("\n");return 0;
}

在封装严密的 IDE 比如Devc++、vs2019等,这个代码可能直接报错。

但如果是在vscode,则可以通过命令行在编译阶段指定ARRAY_SIZE变成我们想要的数值,从而生成不同功能的程序。

例如通过命令gcc -D ARRAY_SIZE=10 testc.c可以指定ARRAY_SIZE变成指定数值10,然后生成可执行程序 a.exe 。再输入.\a.exe即可执行它。

请添加图片描述

也可在原命令的基础上加-o ProgramName.exe来指定生成的可执行程序的程序名。这里让生成的可执行程序名和c语言的代码名保持一致。

请添加图片描述

条件编译

在编译一个程序的时候若要将一条语句(一组语句)编译或者放弃参与编译,可通过条件

编译指令实现。

这些调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

常见的条件编译指令如下。

#if和#endif

#if 常量表达式//...
#endif

常量表达式由预处理器求值。

例如:

#include <stdio.h>
int main()
{
#if 0printf("asdfghjkl\n");
#endifprintf("qwertyyiop");return 0;
}

输出:

qwertyyiop

在模块化编程中的某个头文件用#define定义某个符号,这个符号也能通过#if进行条件编译。

例如,这里将#define和c语言代码放在一起:

#include <stdio.h>#define ABC 1int main()
{
#if ABCprintf("asdfghjkl\n");
#endifprintf("qwertyyiop");return 0;
}

输出:

asdfghjkl
qwertyyiop

多分支条件编译#if、#elif、#else和#endif

#if实现多分支编译的用法:

#if 常量表达式1//...
#elif 常量表达式2//...
#else//...
#endif

其中#elif不可放在#else之后,一般#else是作为所有常量表达式都不满足的情况下,作为最后的选择。

例如:

#include <stdio.h>#define A 1
#define B 2
#define C 3void f1() {
#if A==1&&B!=2printf("1\n");
#elif A==1&&B==2printf("2\n");
#endif
}void f2() {
#if A==1&&B!=2printf("1\n");
#elseprintf("2\n");
#endif
}void f3() {
#if A==1&&B!=2printf("1\n");
#elif A==1&&B==2&&C!=3printf("2\n");
#elseprintf("3\n");
#endif
}int main()
{//f1();//f2();f3();printf("six");return 0;
}

#ifdef和#ifndef 判断某符号是否定义

若定义了某个符号就执行相关语句:

#if defined(symbol)
//...
#endif#ifdef symbol
//...
#endif

若没定义某个符号就执行相关与:

#if !defined(symbol)
//...
#endif#ifndef symbol
//...
#endif

其中symbol表示某一标识符,这个标识符通常由#define预定义。

一般更喜欢用#ifdef#ifndef,因为可以少敲一个单词。

例如,#ifdef的用法:

#include<stdio.h>#define A 1int main()
{
#ifdef Aprintf("A\n");
#endif
#ifdef Bprintf("B\n");
#endifprintf("six");return 0;
}

#ifndef

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS 1
#endif#include <stdio.h>int main()
{int a;scanf("%d", &a);//在vs定义了_CRT_SECURE_NO_WARNINGS,才能正常使用scanf和其他别的函数printf("%d", a);return 0;
}

一般#ifndef#ifdef通常用于避免头文件重复包含。

避免头文件包含还可以在头文件开头加这样一句:

#pragma once

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TTL、RS-232 和 RS-485 串行通信电平标准区别解析

TTL、RS-232 和 RS-485 是三种常见的串行通信电平标准&#xff0c;它们各自有不同的协议特点&#xff0c;适用于不同的应用场景。以下是它们的主要特点对比&#xff1a; ​​1. TTL&#xff08;Transistor-Transistor Logic&#xff09;​​ ​​主要特点​​ ​​单端信号​…

SwinTransformer改进(6):与Dual Cross-Attention结合的视觉模型

在计算机视觉领域,Transformer架构正逐渐取代传统的CNN成为主流。 本文将深入解析一个结合了Swin Transformer和Dual Cross-Attention(DCA)的创新模型实现。 模型概述 这个实现的核心是将Swin Transformer(一种高效的视觉Transformer)与创新的Dual Cross-Attention模块相结…

Dify框架面试内容整理-Dify框架

什么是Dify框架? Dify框架是一个开源的AI应用开发平台,专注于帮助开发者和非技术人员快速构建、部署和管理基于大语言模型(如GPT系列、国产开源模型)的应用。 Dify框架的特点:

道可云人工智能每日资讯|“人工智能科技体验展”在中国科学技术馆举行

道可云元宇宙每日简报&#xff08;2025年4月28日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 《2025年提升全民数字素养与技能工作要点》发布 近日&#xff0c;中央网信办、教育部、工业和信息化部、人力资源社会保障部联合印发《2025年提升全民数字素养与技能…

基于javaweb的SpringBoot新闻发布系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

苍穹外卖心得体会

1 登录认证 技术点&#xff1a;JWT令牌技术&#xff08;JSON Web Token&#xff09; JWT&#xff08;JSON Web Token&#xff09;是一种令牌技术&#xff0c;主要由三部分组成&#xff1a;Header头部、Payload载荷和Signature签名。Header头部存储令牌的类型&#xff08;如JW…

车载功能测试-车载域控/BCM控制器测试用例开发流程【用例导出方法+优先级划分原则】

目录 1 摘要2 位置灯手动控制简述2.1 位置灯手动控制需求简述2.2 位置灯手动控制逻辑交互图 3 用例导出方法以及优先级原则3.1 用例导出方法3.1.1 用例导出方法介绍3.1.2 用例导出方法关键差异分析 3.2 优先级规则3.2.1 优先级划分的核心原则3.2.2 具体等级定义与判定标准 3.3 …

Linux系统基础:基础指令简介(网络概念部分)

简介&#xff1a;Linux 是一种开源的类 Unix 操作系统内核&#xff0c;由 Linus Torvalds 于 1991 年首次发布。经过多年发展&#xff0c;它已成为服务器、嵌入式设备和个人计算机领域的重要操作系统。 网络基础概念 初始协议 简单来说&#xff0c;协议是一种约定&#xff0…

多模态(3):实战 GPT-4o 视频理解

最近&#xff0c;OpenAI 团队的 GPT-4o 模型&#xff0c;在多模态方面的能力有了大幅提升&#xff0c;这次我们就使用 GPT-4o 完成一个视频理解的实战。 1. 环境搭建 1.1 安装 FFmpeg 做视频处理&#xff0c;我们需要用到 FFmpeg 这款功能强大的开源多媒体处理工具。FFmpeg…

(27)VTK C++开发示例 ---将点坐标写入 STL文件

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 此示例使用 vtkSTLWriter 将存储在 vtkPolyData 对象中的 3D 几何数据保存到 STL 文件&#xff0c;并读取stl文件显示…

2. python协程/异步编程详解

目录 1. 简单的异步程序 2. 协程函数和协程对象 3. 事件循环 4. 任务对象Task及Future对象 4.1 Task与Future的关系 4.2 Future对象 4.3 全局对象和循环事件对象 5. await关键字 6. 异步上下文管理 7.异步迭代器 8. asyncio的常用函数 8.1 asyncio.run 8.2 asyncio.get…

智慧园区IOT项目与AI时代下的机遇 - Java架构师面试实战

在互联网大厂的Java求职者面试中&#xff0c;面试官通常会针对实际业务场景提出一系列问题。以下是关于智慧园区IOT项目及AI时代下的机遇的面试模拟对话。 第一轮提问 面试官&#xff1a;马架构&#xff0c;请简要介绍下智慧园区IOT项目的整体架构设计。 马架构&#xff1a;…

论文导读 - 基于特征融合的电子鼻多任务深度学习模型研究

基于特征融合的电子鼻多任务深度学习模型研究 原论文地址&#xff1a;https://www.sciencedirect.com/science/article/pii/S0925400524009365 引用此论文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; NI W, WANG T, WU Y, et al. Multi-task deep learning model f…

AI超级智能体项目教程(二)---后端项目初始化(设计knif4j接口文档的使用)

文章目录 1.选择JDK的版本和相关配置2.添加依赖信息2.1指定lombok版本信息2.2引入hutool工具类2.3了解knif4j依赖2.4引入knif4j依赖 3.contrller测试3.1完成yml文件配置3.2修改默认扫描路径3.3controller具体的内容3.4配置接口和访问路径3.5如何访问3.6调试接口3.6调试接口 1.选…

linux blueZ 第四篇:BLE GATT 编程与自动化——Python 与 C/C++ 实战

本篇聚焦 BLE(Bluetooth Low Energy)GATT 协议层的编程与自动化实践,涵盖 GATT 基础、DBus API 原理、Python(dbus-next/bleak)示例、C/C++ (BlueZ GATT API)示例,以及自动发现、读写特征、订阅通知、安全配对与脚本化测试。 目录 BLE GATT 基础概念 BlueZ DBus GATT 模…

kafka与flume的整合、spark-streaming

kafka与flume的整合 前期配置完毕&#xff0c;开启集群 需求1&#xff1a; 利用flume监控某目录中新生成的文件&#xff0c;将监控到的变更数据发送给kafka&#xff0c;kafka将收到的数据打印到控制台&#xff08;三个node01中运行&#xff09; 1.在kafka中建立topic kafka…

redis高级进阶

1.redis主从复制 redis主从复制1 2.redis哨兵模式 哔哩哔哩视频 redis哨兵模式1 redis哨兵模式2 redis哨兵模式3 3.redis分片集群 redis分片集群1 redis分片集群2 redis分片集群3

uniapp: 低功耗蓝牙(BLE)的使用

在微信小程序中实现蓝牙对接蓝牙秤的重量功能&#xff0c;主要依赖微信小程序提供的低功耗蓝牙&#xff08;BLE&#xff09;API。以下是一个清晰的步骤指南&#xff0c;帮助你完成从连接蓝牙秤到获取重量数据的开发流程。需要注意的是&#xff0c;具体实现可能因蓝牙秤的协议和…

3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目

安装 pnpm install icraft/player-react --saveimport { ICraftPlayer } from "icraft/player-react";export default function MyScene() {return <ICraftPlayer srcyour-scene.iplayer />; }icraft/player-react 为开发者提供了一站式的3D数字孪生可视化解决…

云数据中心整体规划方案PPT(113页)

1. 引言 概述&#xff1a;云数据中心整体规划方案旨在构建弹性、高效的云计算基础设施&#xff0c;通过软件定义数据中心&#xff08;SDDC&#xff09;实现资源虚拟化与管理自动化。 2. 技术趋势与背景 技术革新&#xff1a;随着云计算、虚拟化及自动化技术的发展&#xff0c…