C语言详解(预编译)

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言

🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


目录

  • 前言
    • 1、预定义符号
    • 2、#define定义常量和标识符
    • 3、#define定义宏
    • 4、带有副作用的宏参数
    • 5、宏替换的规则
    • 6、宏和函数的对比
    • 7、# 和
      • 7.1 #运算符
      • 7.2 ##运算符
    • 8、命名的约定
    • 9、#undef
    • 10、命令行定义
    • 11、条件编译
    • 12、头文件的包含
      • 12.1 头文件被包含的方式
        • 12.1.1 本地文件包含
        • 12.1.2 库文件包含
      • 12.2 嵌套文件的包含
  • 总结

前言

本篇文章将详细介绍编译过程中预编译的具体细节
在C语言的学习中部分人可能会忽视这一部分的学习,因为像VS这样相对强大的集成开发环境,我们在写好代码后只需要开始执行即可,所以部分人认为这一部分不值得我们花费时间去学习
其实不然,学习C语言预编译过程可以帮助我们更深入地了解C语言的编译过程和语法特性,提高代码编写的效率和质量,以及拓展编程技能


1、预定义符号

C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预编译阶段处理的

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

例如:

在这里插入图片描述


2、#define定义常量和标识符

#define定义的常量和标识符在预编译阶段完成替换

基本语法:

#define name stuff

特别的,为了区分普通常量这个name我们一般用大写形式
比如:

#define MAX 10000
#define REG register

#define后面的代码理论上讲只能写一行,但是如果后面的代码过长,我们可以使用'\'来实现换行,相当于转义转义字符'\'转义了转义字符'\n'

#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n,\__FILE__,__LINE__,\__DATE__,__TIME__)

值得注意的是,行末最好不要加;,在某些场景下是没什么问题,但是在大多数情况下是有语法错误的,所以我们要养成良好的编程习惯,行末不加;


3、#define定义宏

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

#define name(parament_list) stuff

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

注意: 参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

举例:输入一个数,输出它的平方数

#include <stdio.h>
#define SQUARE(x) x*xint main()
{int n = 0;scanf("%d", &n);int ret = SQUARE(n);printf("%d\n", ret);return 0;
}

在这里插入图片描述

上面的代码看似没有什么问题,但当我们想计算n+1的平方数时,就会出现问题:

#include <stdio.h>
#define SQUARE(x) x*xint main()
{int n = 0;scanf("%d", &n);int ret = SQUARE(n + 1);printf("%d\n", ret);return 0;
}

在这里插入图片描述

这是为什么呢?

原因就是带参数的宏在替换的时候括号内的表达式是不做任何计算的

也就是说,上面替换后的形式是:5 + 1 * 5 + 1,为了解决这个问题,我们可以在定义宏的时候给x加上括号:

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

这样替换后的结果就变成了:(5 + 1)*(5 + 1),但是这样给单独的参数加括号的形式在某些场景下还是存在问题,比如:

#include <stdio.h>
#define SQUARE(x) (x)+(x)int main()
{int n = 0;scanf("%d", &n);int ret = 5 * SQUARE(n + 1);printf("%d\n", ret);return 0;
}

在这里插入图片描述

那为了解决这个问题,我们可以(x)+(x)整体加上括号:((x) + (x))

#include <stdio.h>
#define SQUARE(x) ((x)+(x))int main()
{int n = 0;scanf("%d", &n);int ret = 5 * SQUARE(n + 1);printf("%d\n", ret);return 0;
}

在这里插入图片描述

所以,在写宏的时候一定不要吝啬括号


4、带有副作用的宏参数

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

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

上面两个表达式的值是相同的,但是第一个表达式x的本身没有发生改变,而第二个表达式x本身发现了改变,这就是副作用

例如:使用宏实现求两个数的较大值

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

在这里插入图片描述

上面代码中宏参数在宏定义中出现了两次,我们使用MAX(a, b);时没什么问题,但当我们使用MAX(a++, b++);时问题就会出现:

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

在这里插入图片描述

可以发现a和b的值会发生改变,就是表达式求值的时候出现了永久性效果。

与函数对比:

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

请添加图片描述

从上面的代码中可以看出来,带参数的宏替换和函数传参是非常相似的,但是它们的传参是有本质区别的。
带参数的宏替换是直接将参数做整体替换,替换过后的表达式是:((a++)>(b++)?(a++):(b++));而函数参过后的表达式是:(a > b ? a : b)


5、宏替换的规则

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

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果有,它们首先被替换
  • 替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值所替换
  • 最后,再次对结果文件进行扫描,看看是否包含任何由#define定义的符号,如果有,重复上述步骤

例如:

#include <stdio.h>
#define M 10
#define N M + 2
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{int ret = MAX(M, N);return 0;
}

MAX(M, N)首先被替换成:((10)>(M + 2)?(10):(M + 2))
然后((10)>(M + 2)?(10):(M + 2))再被替换成:((10)>(10 + 2)?(10):(10 + 2))

注意:

  • 宏参数和#define定义中可以出现其他#define定义的符号,但宏不能实现递归

比如:#define N M + 2这个是可以的,但#define N N + 2是不行的。

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

比如:

#include <stdio.h>
#define M 10
#define N M + 2
#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{printf("MAX(M, N)");return 0;
}

请添加图片描述

可以看到宏MAX(M, N)并没有展开。


6、宏和函数的对比

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

比如在两个数中找较大数,用宏实现更有优势:

#define MAX(x, y) ((x)>(y)?(x):(y))

那为什么不用函数呢?原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作需要的时间更多,函数调用还需要一些入栈出栈的过程,所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。但宏可以使用于整型、长整型、浮点型等可以用于>来比较的类型,宏参数是无关类型的

但是和函数相比宏还是有劣势的:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是不能调试的
  3. 宏由于无关类型,也就不够严禁,所以宏定义是不够安全的
  4. 宏可能会带来运算符优先级的问题,导致程序容易出错

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

#include <stdio.h>
#define MALLOC(n, type) (type*)malloc(n * sizeof(type))int main()
{//int* p = (int*)malloc(10 * sizeof(int));int* p = MALLOC(10, int);//int *p = (int*)malloc(10 * sizeof(int));return 0;
}

宏和函数的对比:

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

7、# 和

7.1 #运算符

#运算符将宏的一个参数转换为字符串字面量,它仅允许出现在带参数的宏的替换列表中
#运算符所执行的操作可以理解为“字符串化
比如:当我们有一个变量int a = 10;的时候,我们想打印出:the value of a is 10.
下面是常规写法:

#include <stdio.h>int main()
{int a = 10;printf("the value of a is %d\n", a);return 0;
}

如果我们想把打印的这条代码通过宏替换来实现,该怎么做呢?

#include <stdio.h>
#define PRINT(format, n) printf("the value of n is "format"\n", n)int main()
{int a = 10;PRINT("%d", a);//printf("the value of n is ""%d""\n", a);return 0;
}

如果写成上面这种代码很明显并没有解决问题,因为如果我们将n写成%d时并不能打印出a,而只能打印出a的值,那为了能打印出a本身的字面量,我们就可以使用#操作符
如下:

#include <stdio.h>
#define PRINT(format, n) printf("the value of "#n" is "format"\n", n)int main()
{int a = 10;PRINT("%d", a);//printf("the value of "a" is ""%d""\n", a);double b = 3.14;PRINT("%lf", b);//printf("the value of "b" is ""%lf""\n", b);return 0;
}

请添加图片描述

所以我们说:#运算符所执行的操作可以理解为“字符串化”,上面的代码中是将a和b字符串化了。

当n = a的时候,#n 就相当于“a”


7.2 ##运算符

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。##被称为记号粘合
这样的连接必须产生一个合法的标识符,否则其结果就是未定义的。

比如现在有这么一个问题:当我们写一个函数来求两个数的较大值的时候,不同的类型我们就需要写不同的函数,这样写太繁琐了,我们可以使用宏来简化这件事:

#include <stdio.h>
#define GENERIC(type) \
type type##_max(type x, type y)\
{\return ((x) > (y) ? (x) : (y));\
}GENERIC(int)
//int int_max(int x, int y)
//{
//	return ((x) > (y) ? (x) : (y));
//}GENERIC(double)
//double double_max(double x, double y)
//{
//	return ((x) > (y) ? (x) : (y));
//}int main()
{printf("%d\n", int_max(10, 20));printf("%lf\n", double_max(3.14, 6.28));return 0;
}

请添加图片描述

上面的代码中我们利用宏替换来实现创建不同类型的函数,type##_max中的##操作符将type_max连接成了一个新的标识符


8、命名的约定

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

  • 把宏名全部大写
  • 函数名不要全部大写或不大写

9、#undef

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

#include <stdio.h>
#define M 10int main()
{printf("%d\n", M);
#undef Mprintf("%d\n", M);return 0;
}

请添加图片描述

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


10、命令行定义

许多C编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程。
例如:当我们根据同一个源文件想要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个一定长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个较大的数组)

编译指令:

//linux  环境演示
gcc -D ARRAY_SIZE=10 programe.c

11、条件编译

满足条件,就参与编译;不满足条件,就不参与编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令:

1.
#if   常量表达式   //常量表达式由预处理器求值
//...
#endif如:
#define _DEBUG_ 1
int main()
{
#if _DEBUG_printf("a");
#endifreturn 0;
}
2.多个分支的条件编译
#if   常量表达式
//...
#elif   常量表达式
//...
#else
//...
#endif如:
#define M 1
int main()
{
#if M == 1printf("a");
#elif M == 2printf("b"):
#elseprintf("C");
#endifreturn 0;
}
3.判断是否被定义
//如果定义了
#if defined(symbol)
#ifdef symbol如:
#define M 2
int main()
{
#ifdef Mprintf("a");
#endifreturn 0;
}//如果没定义
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#ifdef OS_UNIX#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2():#endif
#endif

条件编译通常用于跨平台性代码的编译


12、头文件的包含

12.1 头文件被包含的方式

12.1.1 本地文件包含

一般指自己创建的头文件

#include "filename.h"

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


12.1.2 库文件包含

一般指标准库中头文件的包含

#include <filename.h>

查找策略:
直接去标准路径下去查找,如果找不到就提示编译错误。
那这样是不是就说明,对库文件也可以使用" "的形式包含呢?
答案是可以的。但是这样查找的效率比较低,也不容易区分是库文件还是本地文件


12.2 嵌套文件的包含

我们已经知道,#include指令可以使另外一个文件被编译,就像它实际出现于#include指令的地方一样。
这种替换的方式很简单:预编译器先删除这条指令,并用包含文件的内容替换
一个头文件被包含几次,就会被实际编译几次,如果重复包含,对编译的压力就比较大

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

如果像上面这样写,test.h文件的内容就会被拷贝5份
如果test.h文件比较大,这样预处理后代码量会剧增,如果工程比较大,有公共使用的文件,被大家都能用,又不做任何的处理,那么后果会不堪设想。
为了解决头文件被重复引入的问题,就要用到条件编译
我们在每个头文件的开头这样写:

#ifndef __FILENAME_H__
#define __FILENAME_H__//...#endif

或者

#pragma once

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


总结

  • 增强对C语言编译过程的整体理解:预编译是C语言编译过程的第一阶段,在预编译阶段可以对源代码进行预处理,如宏定义、头文件包含等。通过学习预编译过程,可以更全面地理解C语言代码的编译过程。
  • 优化代码结构:预编译指令能够简化代码结构、提高代码的重用性和可维护性。学习预编译过程可以帮助程序员更好地利用预编译指令优化代码结构,提高代码的质量。
  • 理解条件编译和跨平台编译:条件编译是预编译指令中的重要功能,可以根据不同条件编译不同的代码。通过学习预编译过程,可以了解如何使用条件编译来实现跨平台编译,提高代码的可移植性。

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

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

相关文章

ESP32S3中使用按键控制LED灯亮灭

// 定义 LED 与 按键引脚 int led_pin 4; int button_pin 5;// 定义 LED 逻辑值 int led_logic 0; // 判断 LED 的状态是否改变过 bool status false;void setup() { pinMode(led_pin, OUTPUT);pinMode(button_pin, INPUT_PULLDOWN); }void loop() {// 按键消抖if (digita…

Java应用中文件上传安全性分析与安全实践

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 一. 文件上传的风险 二. 使用合适的框架和库 1. Spr…

《大道平渊》· 拾叁 —— 失眠?忍不住乱想?不如反其道而行之!

《平渊》 拾叁 "睡觉的时候就是要胡思乱想" 声明&#xff1a;以下内容针对非失眠症人群&#xff0c;如果失眠不是偶尔发生&#xff0c;而是长期存在&#xff0c;以下内容和你无关&#xff0c;请尽早治疗&#xff0c;遵循医嘱。 失眠的本质是什么&#xff1f;心理因素…

stable diffusion中的negative prompt是如何工作的

https://stable-diffusion-art.com/how-negative-prompt-work/https://stable-diffusion-art.com/how-negative-prompt-work/https://zhuanlan.zhihu.com/p/644879268

机器学习常见知识点 2:决策树

文章目录 决策树算法1、决策树树状图2、选择最优决策条件3、决策树算法过程→白话决策树原理决策树构建的基本步骤常见的决策树算法决策树的优缺点 【五分钟机器学习】可视化的决策过程&#xff1a;决策树 Decision Tree 关键词记忆&#xff1a; 纯度、选择最优特征分裂、熵、基…

【源码】校园小情书小程序最新版 校园小程序开发 微信情书小程序 校园小情书小程序源代码

校园小情书微信小程序源码 | 社区小程序前后端开源 | 校园表白墙交友小程序 功能&#xff1a; 表白墙 卖舍友 步数旅行 步数排行榜 情侣脸 漫画脸 个人主页 私信 站内消息 今日话题 评论点赞收藏 服务器环境要求&#xff1a;PHP7.0 MySQL5.7 …

【PB案例学习笔记】-20制作一个超链接按钮

写在前面 这是PB案例学习笔记系列文章的第19篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

PyTorch -- 最常见激活函数的选择

首先&#xff0c;简单复习下什么是梯度&#xff1a;梯度是偏微分的集合 举例说明&#xff1a;对于 z y 2 − x 2 : ∇ z ( ∂ z ∂ x , ∂ z ∂ y ) &#xff08; 2 x , 2 y &#xff09; z y^2-x^2: \nabla z (\frac{\partial z}{\partial x}, \frac{\partial z}{\partia…

第十四篇——互信息:相关不是因果,那相关是什么?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/499cd9af2ea14cbf8d12813f6f…

互联网医院系统源码的创新应用:预约挂号小程序开发实战

预约挂号小程序作为互联网医院系统的创新应用&#xff0c;更加贴近用户需求&#xff0c;实现了预约挂号的便捷化和智能化。本篇文章&#xff0c;笔者将带领读者进入预约挂号小程序开发的实战过程&#xff0c;探索互联网医院系统源码在小程序开发中的创新应用。 一、互联网医院系…

50.Python-web框架-Django中引入静态的bootstrap样式

目录 Bootstrap 官网 特性 下载 在线样例 Bootstrap 入门 Bootstrap v5 中文文档 v5.3 | Bootstrap 中文网 在django中使用bootstrap 新建static\bootstrap5目录&#xff0c;解压后的Bootstrap文件&#xff0c;拷贝项目里就好。 在template文件里引用css文…

Perl语言入门学习

引言 Perl是一种功能强大的编程语言&#xff0c;广泛用于文本处理、系统管理和Web开发。它以其灵活性和强大的正则表达式处理能力著称。本篇博客将介绍Perl的基础知识&#xff0c;并通过多个例子帮助初学者快速上手。 1. 安装Perl 在开始学习Perl之前&#xff0c;您需要确保…

Stable Diffusion: ControlNet Openpose

上一文已经介绍了ControlNet的安装&#xff0c;点击右边的三角箭头。 拖放原始姿态图片。 勾选“启用”&#xff0c;“完美像素模式”&#xff0c;“允许预览” 控制类型选择“OpenPose(姿态&#xff09;” 预处理器选“openpose_full”&#xff0c;会对原始姿态图片做整体分…

opencv roi改进版

点击鼠标左键开始画roi,右键或者回车代表画框完毕 并且做了封装。 import cv2 import numpy as npclass ROIDrawer:def __init__(self, image_path):self.drawing = Falseself.ix, self.iy = -1, -1self.roi = Noneself.image_o = cv2.imread(image_path)self.image = self.…

[NCTF 2018]flask真香

打开题目后没有提示框&#xff0c;尝试扫描后也没有什么结果&#xff0c;猜想是ssti。所以尝试寻找ssti的注入点并判断模版。 模版判断方式&#xff1a; 在url地址中输入{7*7} 后发现不能识别执行。 尝试{{7*7}} ,执行成功&#xff0c;继续往下走注入{{7*7}}&#xff0c;如果执…

【网络编程】基于TCP的服务器端/客户端

TCP是Transmission Control Protocol(传输控制协议)简写。因为TCP套接字是面向连接的&#xff0c;因此又称为基于流的套接字。 把协议分为多个层次&#xff0c;设计更容易&#xff0c;通过标准化操作设计开放式系统 网络层介绍 链路层 链路层是物理连接领域标准化的结果&…

Java从放弃到继续放弃

并发编程 为什么需要多线程&#xff1f; 由于硬件的发展&#xff0c;CPU的核数增多&#xff0c;如果仍然使用单线程对CPU资源会造成浪费。同时&#xff0c;单线程也会出现阻塞的问题。所以&#xff0c;选择向多线程转变。 多线程的使用使得程序能够并行计算&#xff0c;提高计…

问题:以下被纳入代理资产风险分类管理的业务包括() #媒体#知识分享

问题&#xff1a;以下被纳入代理资产风险分类管理的业务包括&#xff08;&#xff09; A&#xff0e;非标准化理财投资业务 B&#xff0e;特定债权投资业务 C&#xff0e;委托债权代理业务 D&#xff0e;非标准化代理销售业务 参考答案如图所示

【C++ | 移动构造函数】一文了解C++的 移动构造函数

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

java多线程临界区介绍

在Java多线程编程中&#xff0c;"临界区"是指一段必须互斥执行的代码区域。当多个线程访问共享资源时&#xff0c;为了防止数据不一致或逻辑错误&#xff0c;需要确保同一时刻只有一个线程可以进入临界区。Java提供了多种机制来实现这一点&#xff0c;例如synchroniz…