当前位置: 首页 > news >正文

C预处理详解2

四、#undef

这条指令用于移除一个宏定义

#undef NAME

如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

例如:如果定义一个宏MAX为100,则打印这个宏的结果就是100

#define MAX 100
int main()
{printf("%d\n",MAX);return 0;
}

而如果再用 #undef 指令对这个宏名进行重新定义,那么它之前的就会被移除,那么这时候运行时就会报错,因为此时的宏MAX未定义

五、命令行定义

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

例如:当我们根据同⼀个源文件要编译出⼀个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了⼀个某个长度的数组,如果机器内存有限,我们需要⼀个很小的数组,但是另外⼀个机器内存大些,我们需要⼀个数组能够大些。

例如:(使用VS code gcc)

我们写一个打印数字的代码,创建一个变长数组,此时我们可以在命令行输入命令:

gcc test.c -D sz= 10 -o test.exe

我们可以自行规定数组的长度,此时所设置的长度是10,那么运行起来(在命令行输入 .\test.exe)时就会打印 0~9 十个数字

而我们也可以规定其他的长度,例如100等

我们也可以打开 test.i 文件查看(以sz=100为例),发现它确实变成了我们想要的长度:

在命令行输入 gcc test.c -D sz=100 -E -o test.i

六、条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很方便的,因为我们有条件编译指令

比如如说:

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

例如以下的代码,我们写一个宏PRINT,使用 #ifdef #endif 这一对条件编译指令进行选择性编译,如果运行时发现这个宏存在被定义为0或非0的数字,甚至是不定义都可以),就会打印 hehe,

而如果发现不存在这个宏,则 #ifdef 这条指令下的语句就不会进行打印,颜色也会变灰

我们也可以通过 gcc 观察:

常见的条件编译指令

1.

#if 常量表达式

        //……

#endif

例如:#if 的条件可以是一个常量、常量表达式,如果为0,条件为假,就不执行

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

例如:

创建一个宏M,宏的值可以为常量或常量表达式,如果为0,则 #if 的条件为假,不执行

(注意与 #ifdef 和 #endif 进行区分,这一对指令是只要宏存在,无论它的条件是什么都可以)

2.多个分支的条件编译

#if 常量表达式

        //……

#elif 常量表达式

        //……

#else

        //……

#endif

例如:

宏M的值为多少就执行哪一条指令

3.判断是否被定义

#if defined(symbol)                                       #if !defined(symbol)

        //……                                                            //……

#endif                                                            #endif

或者这样写:                                                或者这样写:

#ifdef symbol                                                 #ifndef symbol

        //……                                                             //……

#endif                                                            #endif

#if !defined(X) ≡ #ifndef X(检查是否未定义)【如果宏X已定义,则执行#if 和 #endif 之间的代码;如果未定义,则不执行内部代码】
#if defined(X) ≡ #ifdef X(检查是否已定义)

例如:

4.嵌套指令

#if defined(S1)

        #ifdef OPTION1

                //……

        #endif

        #ifdef OPTION2

                //……

        #endif

#elif defined(S2)

        #ifdef OPTION2

                //……

        #endif

#endif

例如:

如果宏S1存在,那就执行此条指令下的代码,然后再判断P1,P2哪一个宏存在;而如果是宏S2存在,就执行 #elif defined 这条指令下的代码

七、头文件的包含

7.1 头文件被包含的方式

7.1.1 本地文件包含

#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头文件。 如果找不到就提示编译错误。

例如:创建一个 test.h 头文件

源文件所在目录下查找:

在标准位置查找头文件:

如果在当前目录未找到test.h这个头文件,就去标准位置查找

VS2022环境的标准头文件路径:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt

7.1.2 库文件包含

#include <filenname.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使⽤ " "的形式包含?

答案是可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

7.2 嵌套文件包含

我们已经知道, #include 指令可以使另外⼀个文件被编译。就像它实际出现于 #include指令的地方⼀样。 这种替换的⽅式很简单:

预处理器先删除这条指令,并用包含文件的内容替换。 ⼀个头文件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。

例如:

test.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{return 0;
}

test.h

void test();
struct Stu
{int id;char name[20];
};

如果直接这样写,test.c 文件中将 test.h 包含5次,那么test.h文件的内容将会被拷贝5份在test.c中。如果test.h 文件⽐较大,这样预处理后代码量会剧增。如果⼯程比较大,有公共使用的头文件,被大家都能使用,又不做任何的处理,那么后果真的不堪设想。

如何解决头文件被重复引入的问题?

答案:条件编译

每个头文件的开头写:

#ifndef __TEST_H__

#define __TEST_H__

//头文件的内容

#endif

或者:

#pragma once

就可以避免头文件的重复引入。

而在 VS2022 中,头文件在开头会默认有:

八、其他预处理指令

8.1 #error

语法:

#error "错误消息"

作用:强制中断编译过程,并输出自定义错误消息。

典型用途:
检查不满足的编译条件(如依赖的宏未定义)
防止使用不兼容的编译器或环境
提示开发者修复配置问题

例如:

int main()
{
#if !defined(__cplusplus)
#error "本代码必须使用C++编译器!"     
#endifreturn 0;
}//如果使用C编译器(而非C++),编译将中断并显示错误消息。

8.2 #pragma

语法:

#pragma 指令内容

作用:向编译器传递特定指令

典型用途:
控制内存对齐(#pragma pack)
禁用编译器警告(#pragma warning(disable: xxx))
确保头文件只包含一次(#pragma once)
其他编译器优化或特殊功能

8.3 #line

语法:

#line 行号 "文件名"

作用:修改编译器报告的行号和文件名

http://www.lqws.cn/news/505693.html

相关文章:

  • 桌面小屏幕实战课程:DesktopScreen 7 文件系统
  • 01-StarRocks安装部署FAQ
  • HOW - 图片的一倍图、二倍图和三倍图
  • 【Pandas】pandas DataFrame merge
  • 鸿蒙开发 一 (八)、自定义绘制
  • 3DSwiper 好看的走马灯轮播图
  • Meson介绍及编译Glib库
  • 顺序表整理和单项链表01 day20
  • 对人工智能的厌倦感是真实存在的,而且它给品牌带来的损失远不止是参与度的下降
  • 【sklearn】K-means、密度聚类、层次聚类、GMM、谱聚类
  • Flutter 学习 之 mixin
  • CFDEM 介绍和使用指南
  • CUDA12.1+高版本pytorch复现Mtrans环境
  • FastMCP+python简单测试
  • 全面掌握 Nginx的功能和使用方法
  • Ingress-Nginx简介和配置样例
  • 最方便的应用构建——利用云原生快速搭建本地deepseek知识仓库
  • 程序猿成长之路之数据挖掘篇——聚类算法介绍
  • uniapp实现远程图片下载到手机相册功能
  • redis的安装及操作
  • 支持向量机(SVM):原理、实现与应用
  • Python核心库Pandas详解:数据处理与分析利器
  • 传输层协议TCP
  • 随机森林详解:原理、优势与应用实践
  • 【apache-maven3.9安装与配置】
  • C++ string类的操作
  • Python与Web3.py库交互实践
  • ref() 与 reactive()
  • Android中Navigation使用介绍
  • 跟着AI学习C#之项目实践Day5