预处理深入详解:预定义符号、宏、命名约定、命令行定义、条件编译、头文件的包含

article/2025/7/19 8:48:15

目录

一、预定义符号

二、#define定义常量

三、宏

(一)#define定义宏

(二)带有副作用的宏参数

(三)宏替换的规则

(四)宏和函数的对比

四、#和##

(一)#运算符

(二)##运算符

五、命名约定

六、#undef

七、命令行定义

八、条件编译

1、情况1

2、多个分支的条件编译

3、判断是否被定义

4、嵌套指令

九、头文件的包含

(一)头文件被包含的方式

1、本地文件包含

2、库文件包含

(二)嵌套文件包含

十、其他预处理指令

结尾  


🔥个人主页:艾莉丝努力练剑

🍓专栏传送门:《C语言》

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平  



前言:前面几篇文章介绍了c语言的一些知识,包括循环、数组、函数、VS实用调试技巧、函数递归、操作符、指针、字符函数和字符串函数、C语言内存函数、数据在内存中的存储、结构体、联合和枚举、动态内存管理、文件操作、编译和链接等,在这篇文章中,我将开始介绍预处理的一些重要知识点!对预处理感兴趣的友友们可以在评论区一起交流学习!


一、预定义符号

C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

都有哪些预定义符号呢?我们在这里展示一下:

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

我们这里举个例子:

printf("file:%s line:%d\n", __FILE__, __LINE__);

 我们创建一个文件,写入预定义符号,可以得到这样的效果:

#include<stdio.h>int main()
{printf("%s\n", __FILE__);printf("%s\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);return 0;
}

二、#define定义常量

基本语法:

#define name stuff

举个例子:

#define M 100
#define STR "hehe"
#define reg register
//register - 寄存器int main()
{printf("%d\n", M);int a = 5 + M;printf("%s\n", STR);return 0;
}

再举个例子:

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

思考一下:在define定义标识符的时候,需要不需要在最后加上";"呢?

比如:

#define MAX 1000;
#define MAX 1000

到底要不要加呢?好像加不加都可以,又好像不可以。

这里我们建议不要加;,这样容易导致问题,我们光这样说空口无凭 ,举个例子:

比如下面这种场景:

if (condition)max = MAX;
elsemax = 0;

如果是加了分号的情况,等替换后,if和else之间就是两条语句,而没有大括号的时候,if后边只能有一条语句。这里会出现语法错误。

三、宏

(一)#define定义宏

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

下面是宏的声明方式:

#define name ( parament-list ) stuff

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

注意:

参数列表的左括号必须与name紧邻,因为如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分,结果将大不相同。

举个例子:

#define SQUARE(x) x*x

这里这个宏接受了一个参数x,如果在上述声明之后,我们把SQUARE(10) ; 置于程序中,预处理就会用下面这个表达式替换上面这个表达式了:10*10

警告:这个宏存在一个bug,我们观察下面的代码:

int a = 5;
printf("%d\n", SQUARE(a + 1));

粗略一看,你可能会想:这不就是100吗?实际上它打印出来的结果是21!这是为什么呢?

其实,在替换文本的时候,参数x被替换成了a+1,因此这条语句实际上变成了:

printf("%d\n", a + 1 * a + 1);

这样一来就清晰了,由替换产生的表达式并没有按照预想的次序进行求值,才产生了bug。

我们只要在宏定义上加上两个括号就能解决问题了:

#define SQUARE(x) (x) * (x)

 这样进行预处理之后就能达到我们预期的效果了:

printf("%d\n", (a + 1)* (a + 1));

此处还有一个宏定义:

#define DOUBLE(x) (x) + (x)

 定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 10;
printf("%d\n", 10 * DOUBLE(a));

 这次又会打印什么值呢?看上去好像会打印200,但实际上打印的是110,我们发现替换之后:

printf("%d\n", 10 * (5) + (5));

 乘法运算先于宏定义的加法,因此出现了110的值。

这个问题有办法解决吗?有的有的。我们只要在宏定义表达式的两边加上一对括号就可以了。

#define DOUBLE(x) ((x)*(x))

注意:

因此,对于数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符和邻近操作符之间不可预料的相互作用。

(二)带有副作用的宏参数

当宏参数在宏的定义出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就有可能出现危险,导致不可预料的后果,副作用即是表达式求值的时候出现的永久性效果。

例如:

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

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

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

 此处我们得知道预处理之后的结果是什么?

z = (x++) > (y++) ? (x++) : (y++);

因此输出的结果就是:

x=6  y=10  z=9 

(三)宏替换的规则

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

1、在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们会最先被替换;

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

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

注意: 

1、宏参数和#define定义中可以出现其它#define定义的符号,但对于宏而言,不能出现递归;

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

(四)宏和函数的对比

宏通常被应用于执行简单的运算。

比如要求在两个数中找出比较大的一个的时候,写成下面的宏,会更有优势些:

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

那为什么不用函数来完成呢?其实有两个原因:

1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作需要的时间要多得多,因此宏比函数的规模和速度方面要更胜一筹

2、更为重要的是,函数的参数必须声明为特定的类型,因此函数只能在类型合适的表达式上使用。反之这个宏怎么可以适用于整型、长整型、浮点型等可以用于>来比较的类型。宏的参数是类型无关的

和函数相比宏的劣势:

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

2、宏是没法调试的;

3、宏由于跟类型无关,也不够严谨;

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

宏有时候可以做到函数做不到的事情:宏的参数可以出现类型,但函数做不到(正因如此,就导致了上面所说的同函数相比宏的第3个劣势)。

#define MALLOC(num, type)\(type )malloc(num sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数 //预处理器替换之后: 
(int*)malloc(10 sizeof(int));

为了直观地对比宏和函数,这里我们列了一个表格:

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

四、#和##

(一)#运算符

#运算符将宏的一个参数转换为字符串字面量;

它仅允许出现在带参数的宏的替换列表中;

#运算符所执行的操作可以理解为“字符串化”。

当我们有一个变量 int  a  = 10;的时候 ,我们想打印出:the value of a is 10 的时候就可以这么写:

#define PRINT(n) printf("the value of "#n"is %d",n);

当我们按照下面的方式调用的时候:

PRINT(a);//当我们把a替换到宏的体内时,就出现了#a,而#a就是转换为"a"此时一个字符串代码就会被预处理为 :

printf("the value of "a" is %d", a);

 此时运行代码就能够在屏幕上打印:

the value of a is 10

(二)##运算符

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

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

这里我们思考一下,如果要求写出一个函数以求两个数的较大值的时候,不同的数据类型就得写不同的函数。

打个比方,像是这样:

int int_max(int x, int y)
{return x > y ? x : y;
}float float_max(float x, float y)
{return x > y ? x : y;
}

可这样写起来太繁琐了,现在我们试着这样写代码:

//宏定义 #define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \return (x>y?x:y); \
}

友友们还记得吗,这个“\”就是我们前面介绍过的续航符,这里又出现了。

使用宏,定义不同函数:

GENERIC_MAX(int) //替换到宏体内后int##_max 生成了新的符号 int_max做函数名 
GENERIC_MAX(float) //替换到宏体内后float##_max 生成了新的符号 float_max做函数名 int main()
{//调用函数 int m = int_max(2, 3);printf("%d\n", m);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}

完整代码演示:

#include<stdio.h>//宏定义 #define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \return (x>y?x:y); \
}GENERIC_MAX(int) //替换到宏体内后int##_max 生成了新的符号 int_max做函数名 
GENERIC_MAX(float) //替换到宏体内后float##_max 生成了新的符号 float_max做函数名 int main()
{//调用函数 int m = int_max(2, 3);printf("%d\n", m);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}

输出结果:

 在实际开发过程中##使用的很少,这个例子已经是能举出的相对贴切的例子啦。

五、命名约定

一般来讲,函数和宏的使用语法很类似,因此语言本身没法帮我们区分二者。

我们平时有一个习惯是:

| 把宏名全部大写;

| 函数名不要全部大写。

六、#undef

#undef的这条这里用于移除一个宏定义。

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

七、命令行定义

许多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){printf("%d", array[i]);}printf("\n");return 0;
}

编译指令:

这里是在linux环境下的演示,在上一篇文章中,我推荐友友们下载VS Code,配置好环境来实现一些代码,这里最好是能和远程的一台机器交互。我们就直接演示指令吧:

//Linux环境演示
gcc - D ARRAR_SIZE = 10 programe.c

八、条件编译

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

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

代码演示:

#include<stdio.h>
#define __DEBUG__int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i < 10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d\n", arr[i]);//为了观察数组是否赋值成功#endif//__DEBUG__}return 0;
}

我们这里把几种常见的条件编译指令全部列举出来了,常见条件编译指令如下:

1、情况1

#if 常量表达式//...#endif//常量表达式由预处理器求值。 如:#define __DEBUG__ 1
#if __DEBUG__//..#endif

2、多个分支的条件编译

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

3、判断是否被定义

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

4、嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1
unix_version_option1();
#endif#ifdef OPTION2unix_version_option2();
#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();
#endif#endif

九、头文件的包含

(一)头文件被包含的方式

1、本地文件包含
#include "filename"

查找策略:

(1)先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置处查找头文件;

(2)如果找不到,就提示编译错误。

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

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

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

/usr/include

注意:我们要按照自己的安装路径去找。 

2、库文件包含
#include<filename.h>

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

 那么这样是不是可以这么说:对于库文件也可以使用""的形式包含了?

没错,可以。只是这样进行查找效率会低一些,而且这样也不容易区分到底是库文件还是本地文件了。

(二)嵌套文件包含

已知,#include指令可以使另外一个文件被编译,就好像它实际出现于#include指令的位置一样。

这种替换的方式其实很简单——预处理器先删除这条指令,并且用包含文件的内容替换。

一个头文件假如被包含了10次,那就是说实际被编译了10次,如果包含重复,对编译的压力就比较大了。代码演示如下:

(1)test.c

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

(2)test.h 

#pragma oncevoid test();
struct Stu
{int id;char name[20];
};

如果我们直接这样写,test.c文件中将test.h包含6次,那么test.h文件的内容将会被拷贝6份在test.c中。如果test.h文件比较大,这样进行预处理后代码量会剧增,假如工程量比较大的话,有公共使用的头文件,大家都可以使用,还不做任何处理,后果不堪设想。

我们在VS环境的编译器下观察可能看不明显,可以在VS Code环境观察:将会看到成百上千行代码,这还只是重复包含了几次,如果这个程序工程量较大的话,比如有两三万行代码,这个代码量是非常大的,后果不堪设想。。。

说完了后果,我们来介绍一下该如何解决头文件被重复引用的问题——答案是:条件编译

在每个头文件的开头写上这么几行代码:

#ifdef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif  //__TEST_H__

或者:

#pragma once

这样就可以避免头文件的重复引用了:只会包含第一个头文件,剩下的会直接跳过。 

十、其他预处理指令

#error
#pragma
#line
...
//其它的预处理指令大家可以自行去了解一下
#pragma pack()//我们在结构体部分介绍过这个,也是预处理指令

大家可以参考 《C语言深度解剖》这本书学习。


结尾  

往期回顾:

深入详解编译与链接:翻译环境和运行环境,翻译环境:预编译+编译+汇编+链接,运行环境

【掌握文件操作】(下):文件的顺序读写、文件的随机读写、文件读取结束的判定、文件缓冲区

【掌握文件操作】(上):二进制文件和文本文件、文件的打开和关闭、文件的顺序读写

【动态内存管理】深入详解:malloc和free、calloc和realloc、常见的动态内存的错误、柔性数组、总结C/C++中程序内存区域划分

【详解自定义类型:联合和枚举】:联合体类型的声明、特点、大小的计算,枚举类型的声明、优点和使用

【自定义类型:结构体】:类型声明、结构体变量的创建与初始化、内存对齐、传参、位段

结语:本篇文章就到此结束了,本文为友友们分享了预处理相关的一些重要知识点,如果友友们有补充的话欢迎在评论区留言,在这里感谢友友们的关注与支持!  


http://www.hkcw.cn/article/HrjRBxWAcg.shtml

相关文章

深度解析:跨学科论文 +“概念迁移表” 模板写作全流程

跨学科论文速通&#xff01;融合“概念迁移表”的写作导航模板 你的论文是否曾被导师皱眉评价为“四不像”&#xff1f;不同学科的术语在稿纸上打架&#xff0c;核心逻辑若隐若现&#xff1f; 别让心血沦为学术混搭的牺牲品。一张精心设计的 概念迁移表&#xff0c;能将两个看…

Linux安装及管理程序

1 Linux应用程序基础 1.1 Linux 命令与应用程序的关系 在 Linux 操作系统中&#xff0c;一直以来命令和应用程序并没有特别明确的区别&#xff0c;从长期使用习惯来看&#xff0c;可以通过以下描述来对两者进行区别&#xff1a; 应用程序命令的执行文件大多比较小&#xff0…

历年南京大学计算机保研上机真题

2025南京大学计算机保研上机真题 2024南京大学计算机保研上机真题 2023南京大学计算机保研上机真题 在线测评链接&#xff1a;https://pgcode.cn/school Count Number of Binary Strings 题目描述 Given a positive integer n n n ( 3 ≤ n ≤ 90 3 \leq n \leq 90 3≤n≤…

酒店管理系统设计与实现

本科毕业设计(论文) 设计(论文)题目 酒店管理系统设计与实现 学生姓名 学生学号 所在学院 专业班级 校内指导教师 李建 企业指导教师 毕业设计(论文)真实性承诺及声明 学生对毕业设计(论文)真实性承诺 本人郑重声明:所提交的毕业设计(论文)作品是本人在指导教师的指…

Java web学习路径预览

Java web学习路径预览 &#xff08;图源&#xff1a;黑马程序员&#xff09; 目录 Java web学习路径预览 一、HTML、CSS、JS 1. HTML (HyperText Markup Language): 网页的骨架 2. CSS (Cascading Style Sheets): 网页的皮肤 3. JavaScript (JS): 网页的行为 二、Ajax、…

QEMU/KVM课程大纲暨学习路线(1)

一、背景 去年(2024年)10月份,在CSDN上有一位网友联系到我,说有需要我帮忙的地方。加了微信之后,他说了要帮助的事情。原来是他看到了我的QEMU/KVM相关文章,阅读之后觉得符合他们的要求,所以想让我帮他们开发QEMU/KVM相关的课程。 经过沟通和了解,他们之前请了一位老师…

得物前端面试题及参考答案(精选50道题)

浏览器强制缓存和协商缓存的机制及区别 浏览器缓存机制用于减少网络请求、提升页面加载性能&#xff0c;强制缓存和协商缓存是其中两种核心策略。 强制缓存的机制&#xff1a;当浏览器请求资源时&#xff0c;首先检查该资源在本地缓存中的有效期。有效期由响应头中的Cache-Con…

动态IP与区块链:重构网络信任的底层革命

在数字经济蓬勃发展的今天&#xff0c;网络安全与数据隐私正面临前所未有的挑战。动态IP技术与区块链的深度融合&#xff0c;正在构建一个去中心化、高可信的网络基础设施&#xff0c;为Web3.0时代的到来奠定基础。 一、技术碰撞&#xff1a;动态IP与区块链的天然契合 动态I…

PCB设计实践(三十)地平面完整性

在高速数字电路和混合信号系统设计中&#xff0c;地平面完整性是决定PCB性能的核心要素之一。本文将从电磁场理论、信号完整性、电源分配系统等多个维度深入剖析地平面设计的关键要点&#xff0c;并提出系统性解决方案。 一、地平面完整性的电磁理论基础 电流回流路径分析 在PC…

使用vscode进行c/c++开发的时候,输出报错乱码、cpp文件本身乱码的问题解决

使用vscode进行c/c开发的时候&#xff0c;输出报错乱码、cpp文件本身乱码的问题解决 问题描述解决方案问题1的解决方案问题2解决方案 问题描述 本篇文章解决两个问题&#xff1a; 1.当cpp文件出现错误的时候&#xff0c;编译时报错&#xff0c;但是报错内容缺是乱码&#xff0…

信息化项目验收测试:MES 系统验收测试的测试重点

在工业4.0与智能制造转型中&#xff0c;MES系统作为连接计划层与执行层的枢纽&#xff0c;其验收测试的专业性直接影响企业数字化成效。第三方检测机构需从核心功能、性能、集成能力等维度&#xff0c;为企业提供科学的验收测试方案。 一、核心功能验证&#xff1a;打通生产执行…

Prometheus + Grafana + Cadvisor:构建高效企业级服务监控体系

在现代软件开发和运维领域&#xff0c;容器化技术的应用越来越广泛&#xff0c;其中 Docker 作为最受欢迎的容器化解决方案之一&#xff0c;其容器的监控管理变得至关重要。本文将详细介绍如何使用 cadvisor、Prometheus 和 Grafana 来监控 Docker 容器的状态。 一、安装镜像 …

Kotlin-特殊类型

文章目录 数据类型枚举类型匿名类和伴生对象单例类伴生对象 数据类型 声明一个数据类非常简单: //在class前面添加data关键字表示为一个数据类 data class Student(var name: String, var age: Int)数据类声明后,编译器会根据主构造函数中声明的所有属性自动为其生成以下函数…

大疆上云API+流媒体服务器部署实现直播功能

根据官网文档上云API&#xff0c;先将官方提供的Demo部署起来&#xff0c;后端和前端服务环境搭建请参考官方文档。因为官方文档没有对直播这块的环境搭建进行说明&#xff0c;所以下面主要对直播功能环境搭建做一个记录&#xff0c;仅供参考&#xff0c;如有不足之处&#xff…

大模型-attention汇总解析之-GQA

从上面的图可以看出&#xff0c;MHA是一个attention 头有自己独立的kv cache 缓存&#xff0c;这样子的计算效果是最好的&#xff0c;同时kv cache 也是最完善的&#xff0c;意味着也是最占用内存的。MQA 进行了极致的kv cache 共享&#xff0c;那么能不能对多头进行分组&#…

Haption在危险、挑战性或受限环境中操作的情况提供了一种创新的遥操作解决方案

Haption Virtuose 6D TAO是一款拥有7个主动自由度的触觉设备&#xff0c;专为虚拟现实环境交互而设计。 它与Virtuose的一系列软件解决方案兼容&#xff0c;可让您直接在CAD软件中使用该设备进行装配仿真&#xff0c;并在3D游戏引擎中使用该设备&#xff0c;从而打造更加逼真的…

[STM32问题解决(2)]STM32通过串口与PC通信,打开串口助手后无法在打开状态下下载程序和复位STM32

问题回顾 最近学习STM32单片机&#xff0c;经常使用STM32通过USART1串口与PC的串口助手进行通信。为了简单便捷&#xff0c;通常在打开串口的状态下下载程序。这样子下载程序后&#xff0c;STM32发出的信号&#xff0c;PC马上可以收到。 但是&#xff0c;突然出现了一个问题&a…

JVM内存模型(运行时数据区)

目录 编者想说 1、内存模型图 2、栈 3、程序计数器 3、堆 4、方法区&#xff08;元空间&#xff09; 5、本地方法栈&#xff08;Native Method Stack&#xff09; 编者想说 通过上一篇文章的对JVM的体系结构以及它的演化&#xff0c;我们对JVM有了一个比较清晰的认识&…

突破铁芯CT局限:罗氏线圈的“无磁饱和”技术深度解读

罗氏线圈互感器&#xff1a;关键应用场景解析 罗氏线圈&#xff08;Rogowski Coil&#xff09;互感器以其独特的性能优势&#xff0c;成为测量交流电流&#xff08;尤其适用于变化快、幅度大或频率范围广的电流&#xff09;的理想选择。其核心优势在于宽频带、无磁饱和、尺寸灵…

Oracle数据仓库在医院的应用场景

2025年3月28日&#xff0c;我作为会议组织者&#xff0c;在宁波组织了数据仓库在医院的应用场景&#xff0c;会议主要议题如下&#xff1a; 1、解析医院多源异构数据&#xff08;HIS/LIS/EMR/PACS&#xff09;实时整合的技术方案 2、Oracle exadata在构建全院级数据仓库的性能优…