当前位置: 首页 > 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 行号 "文件名"

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

相关文章:

  • 信誉好的江苏网站建设百度seo可能消失
  • 怎么自己做个免费网站吗网络营销理论基础
  • 软件开发公司职位百度关键词优化软件如何
  • 做tb任务赚钱的网站2022最新时事新闻及点评
  • 国际消息新闻网站优化师
  • 顺义电大网上作业在那个网站做在百度如何发布作品
  • WHAT - React Native 的 Expo Router
  • Redis哈希表Rehash全解析:扩容缩容背后的渐进式智慧
  • ref() 与 reactive()
  • 黑马Day01-03集开始
  • 原子操作(CAS)
  • 《TCP/IP 详解 卷1:协议》第13章:TCP连接管理
  • java-SpringBoot框架开发计算器网页端编程练习项目【web版】
  • 马克思主义基本原理知识笔记
  • MediaMarktSaturn EDI 对接指南:欧洲零售卖场的数字化协同范例
  • 虚幻基础:插槽
  • C++面试6——类和结构体的区别和使用场景
  • 零基础学习RabbitMQ(3)--核心概念
  • 打包上传到Linux部署并启动
  • C++ string类的操作
  • FFMPEG常用函数
  • 应用层协议 HTTP
  • 618风控战升级,瑞数信息“动态安全+AI”利剑出鞘
  • 无人机航电系统之语音通信技术篇
  • elk+filebeat收集springboot项目日志
  • 开疆智能CCLinkIE转ModbusTCP网关连接川崎机器人配置案例