字节码学习之常见java语句的底层原理
文章目录
- 前言
- 1. if语句
- 字节码的解析
- 2. for循环
- 字节码的解析
- 3. while循环
- 4. switch语句
- 5. try-catch语句
- 6. i++ 和++i的字节码
- 7. try-catch-finally
- 8. 参考文档
前言
上一章我们聊了《JVM字节码指令详解》 。本章我们学以致用,聊一下我们常见的一些java语句的特性底层是如何实现。
1. if语句
if语句是我们最常用的判断语句之一,它的底层实现原理是什么呢?可以通过反编译字节码来分析一下。
假设我们有以下的java代码:
public class IfStatement {public static void main(String[] args) {int a = 10;if (a > 0) {System.out.println("a is positive");} else {System.out.println("a is negative or zero");}}
}
可以使用javap命令来反编译字节码:
javap -c IfStatement.class
输出结果如下:
public class IfStatement {// 构造方法public IfStatement();Code:0: aload_0 // 将局部变量表中的第0个元素(通常是this引用)入栈1: invokespecial #1 // 调用父类Object的构造方法4: return // 方法返回// main方法public static void main(java.lang.String[]);Code:0: bipush 10 // 将10压入栈顶2: istore_1 // 将栈顶元素(10)存入局部变量表的第1个位置3: iload_1 // 将局部变量表的第1个位置的元素(10)入栈4: ifle 17 // 判断栈顶元素(10)是否小于等于0,如果是则跳转到指令177: getstatic #2 // 获取System类的out字段,类型是PrintStream10: ldc #3 // 将字符串"a is positive"压入栈顶12: invokevirtual #4 // 调用PrintStream的println方法输出字符串15: goto 23 // 无条件跳转到指令2318: getstatic #2 // 获取System类的out字段,类型是PrintStream21: invokevirtual #5 // 调用PrintStream的println方法输出局部变量表第1个位置的元素(10)24: return // 方法返回
}
字节码的解析
- 在main方法中,首先将10压入栈顶,然后将其存入局部变量表的第1个位置。然后将局部变量表的第1个位置的元素(10)入栈,判断其是否小于等于0,如果是则跳转到指令17,否则执行下一条指令。在指令7-15中,它获取System的out字段,将字符串"a is positive"压入栈顶,然后调用println方法输出这个字符串,最后无条件跳转到指令23。在指令18-21中,它获取System的out字段,然后调用println方法输出局部变量表第1个位置的元素(10)。最后,main方法返回。
2. for循环
for循环是我们常用的循环语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。
假设我们有以下的java代码:
public class ForLoop {public static void main(String[] args) {for (int i = 0; i < 10; i++) {System.out.println("i = " + i);}}
}
可以使用javap命令来反编译字节码:
javap -c ForLoop.class
这是一个包含for循环的Java类ForLoop的字节码输出,下面是中文注释:
public class ForLoop {// 构造方法public ForLoop();Code:0: aload_0 // 将局部变量表中的第0个元素(通常是this引用)入栈1: invokespecial #1 // 调用父类Object的构造方法4: return // 方法返回// main方法public static void main(java.lang.String[]);Code:0: iconst_0 // 将0压入栈顶1: istore_1 // 将栈顶元素(0)存入局部变量表的第1个位置2: iload_1 // 将局部变量表的第1个位置的元素(0)入栈3: bipush 10 // 将10压入栈顶5: if_icmpge 19 // 如果局部变量表的第1个位置的元素(0)大于等于栈顶元素(10),则跳转到指令198: getstatic #2 // 获取System类的out字段,类型是PrintStream11: new #3 // 创建一个StringBuilder类的对象14: dup // 复制栈顶元素,此时栈顶有两个相同的StringBuilder对象引用15: invokespecial #4 // 调用StringBuilder类的构造函数初始化对象18: ldc #5 // 将字符串"i ="压入栈顶20: invokevirtual #6 // 调用StringBuilder的append方法将字符串添加到StringBuilder23: iload_1 // 将局部变量表的第1个位置的元素(0)入栈24: invokevirtual #7 // 调用StringBuilder的append方法将数字添加到StringBuilder27: invokevirtual #8 // 调用StringBuilder的toString方法将StringBuilder转化为字符串30: invokevirtual #9 // 调用PrintStream的println方法输出字符串33: iinc 1, 1 // 将局部变量表的第1个位置的元素(0)增加136: goto 2 // 无条件跳转到指令2,形成循环39: return // 方法返回LineNumberTable:line 3: 0line 4: 8line 3: 33line 6: 39StackMapTable: number_of_entries = 2frame_type = 252 /* append */offset_delta = 2locals = [ int, int ]stack = []frame_type = 250 /* chop */offset_delta = 36
}
字节码的解析
可以看到,for循环的底层实现是通过if_icmpge指令来实现的。在本例中,当i小于10时,会执行第8行的输出语句;否则,会跳转到第39行,结束循环。
- 在main方法中,首先将0压入栈顶,然后将其存入局部变量表的第1个位置。接下来是一个循环,循环条件是局部变量表的第1个位置的元素小于10。在循环体中,它首先获取System的out字段,然后创建一个StringBuilder对象并初始化,然后将字符串"i ="和局部变量表的第1个位置的元素添加到StringBuilder,然后将StringBuilder转化为字符串,然后调用println方法输出字符串。在循环体结束时,它将局部变量表的第1个位置的元素加1,然后无条件跳转到指令2,形成循环。当循环结束时,main方法返回。
3. while循环
while循环是我们常用的循环语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。
假设我们有以下的java代码:
public class WhileLoop {public static void main(String[] args) {int i = 0;while (i < 10) {System.out.println("i = " + i);i++;}}
}
可以使用javap命令来反编译字节码:
javap -c WhileLoop.class
输出结果如下:
public class WhileLoop {public WhileLoop();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: bipush 105: if_icmpge 198: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;11: new #3 // class java/lang/StringBuilder14: dup15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V18: ldc #5 // String i =20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;23: iload_124: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;27: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V33: iinc 1, 136: goto 239: returnLineNumberTable:line 3: 0line 4: 2line 5: 8line 6: 33line 5: 36line 8: 39StackMapTable: number_of_entries = 2frame_type = 252 /* append */offset_delta = 2locals = [ int, int ]stack = []frame_type = 250 /* chop */offset_delta = 36
}
可以看到,while循环的底层实现也是通过if_icmpge指令来实现的。在本例中,当i小于10时,会执行第8行的输出语句;否则,会跳转到第39行,结束循环。
4. switch语句
switch语句是我们常用的分支语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。
假设我们有以下的java代码:
public class SwitchStatement {public static void main(String[] args) {int i = 2;switch (i) {case 1:System.out.println("i is 1");break;case 2:System.out.println("i is 2");break;case 3:System.out.println("i is 3");break;default:System.out.println("i is neither 1, 2 nor 3");break;}}
}
可以使用javap命令来反编译字节码:
javap -c SwitchStatement.class
输出结果如下:
public class SwitchStatement {public SwitchStatement();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_21: istore_12: iload_13: tableswitch { // 1 to 31: 282: 403: 52default: 64}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String i is 133: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: goto 7140: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;43: ldc #5 // String i is 245: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V48: goto 7152: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;55: ldc #6 // String i is 357: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V60: goto 7164: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;67: invokevirtual #7 // Method java/io/PrintStream.println:()V70: return71: returnLineNumberTable:line 3: 0line 4: 2line 5: 28line 6: 40line 7: 52line 8: 64line 9: 70line 7: 71StackMapTable: number_of_entries = 5frame_type = 252 /* append */offset_delta = 28locals = [ int ]frame_type = 252 /* append */offset_delta = 11locals = [ int ]frame_type = 252 /* append */offset_delta = 12locals = [ int ]frame_type = 252 /* append */offset_delta = 12locals = [ int ]frame_type = 252 /* append */offset_delta = 3locals = [ int ]
可以看到,switch语句的底层实现是通过tableswitch指令来实现的。在本例中,当i等于1时,会执行第28行的输出语句;当i等于2时,会执行第40行的输出语句;当i等于3时,会执行第52行的输出语句;否则,会执行第64行的输出语句。
5. try-catch语句
try-catch语句是我们常用的异常处理语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。
假设我们有以下的java代码:
public class TryCatchStatement {public static void main(String[] args) {try {int[] arr = new int[3];arr[4] = 5;} catch (ArrayIndexOutOfBoundsException e) {System.out.println("Array index out of bounds!");}}
}
可以使用javap命令来反编译字节码:
javap -c TryCatchStatement.class
输出结果如下:
public class TryCatchStatement {public TryCatchStatement();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_01: newarray int3: astore_14: aload_15: iconst_46: iconst_57: iastore8: goto 1911: astore_112: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;15: ldc #3 // String Array index out of bounds!17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V20: returnException table:from to target type0 8 11 Class java/lang/ArrayIndexOutOfBoundsExceptionLineNumberTable:line 3: 0line 4: 4line 5: 11line 6: 12line 7: 20StackMapTable: number_of_entries = 2frame_type = 34 /* same */frame_type = 1 /* same_locals_1_stack_item */stack = [ class java/lang/ArrayIndexOutOfBoundsException ]
}
可以看到,try-catch语句的底层实现是通过异常表来实现的。在本例中,当数组下标越界时,会执行第12行的输出语句;否则,会跳转到第20行,继续执行。
6. i++ 和++i的字节码
i++
和 ++i
在语义上有些许不同,在字节码层面也有所体现。下面是它们的字节码层面的解释:
假设 i
是局部变量表的索引为1的变量。
i++
的伪字节码:
iload_1 // 从局部变量表中加载变量 i 到操作数栈顶
iinc 1 by 1 // 将局部变量表中的变量 i 增加1
++i
的伪字节码:
iinc 1 by 1 // 将局部变量表中的变量 i 增加1
iload_1 // 从局部变量表中加载变量 i 到操作数栈顶
可以看到,i++
与 ++i
的主要区别在于加载和增加操作的顺序不同。i++
是先将 i
加载到操作数栈顶,然后再增加 i
的值;而 ++i
是先增加 i
的值,然后再将 i
加载到操作数栈顶。这就解释了 i++
和 ++i
在语义上的不同:i++
是先取值后加1,++i
是先加1后取值。
7. try-catch-finally
在 Java 字节码中,try-catch-finally 结构主要通过异常表(Exception Table)来实现。Java 字节码并没有专门的指令来表示 try、catch 或者 finally 块。相反,它通过在异常表中记录 try 块的开始和结束位置、catch 块的开始位置和要捕获的异常类型,以实现异常处理的流程。
下面是一个简单的 try-catch-finally 代码例子:
void test() {try {System.out.println("try block");throw new Exception();} catch (Exception e) {System.out.println("catch block");} finally {System.out.println("finally block");}
}
对应的字节码指令
0: getstatic #2 // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
3: ldc #3 // 将常量池中的 "try block" 字符串压入栈顶
5: invokevirtual #4 // 调用 PrintStream 类的 println 方法输出字符串
8: new #5 // 创建一个 java/lang/Exception 类的对象
11: dup // 复制栈顶的元素,此时栈顶有两个相同的异常对象引用
12: invokespecial #6 // 调用 Exception 类的构造函数初始化对象
15: athrow // 抛出栈顶的异常对象
16: astore_1 // 捕获异常并存入局部变量表的第1个位置
17: getstatic #2 // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
20: ldc #7 // 将常量池中的 "catch block" 字符串压入栈顶
22: invokevirtual #4 // 调用 PrintStream 类的 println 方法输出字符串
25: jsr 26 // 无条件跳转到指令26(finally块的起始位置)
28: goto 34 // 执行完finally块后,跳过 catch 块剩下的代码,进入下一个处理流程
31: astore_2 // 捕获从finally块抛出的异常并存入局部变量表的第2个位置
32: jsr 26 // 无条件跳转到指令26(finally块的起始位置)
35: aload_2 // 从局部变量表的第2个位置加载异常对象至栈顶
36: athrow // 再次抛出该异常对象
37: astore_3 // 捕获异常并存入局部变量表的第3个位置
38: getstatic #2 // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
41: ldc #8 // 将常量池中的 "finally block" 字符串压入栈顶
43: invokevirtual #4 // 调用 PrintStream 类的 println 方法输出字符串
46: ret 3 // 返回到 astore_3 指令之后的代码
这段字节码中使用了 jsr
和 ret
指令,这两个指令主要用于实现 finally
块的逻辑。jsr
指令会跳转到 finally
块的代码,然后 ret
指令用于返回到 finally
块之前的代码继续执行。
字节码的解释
-
行0-15:这部分对应 try 块的内容。在这个例子中,它首先通过 getstatic 指令获取 System.out 对象,然后通过 ldc 指令加载常量 “try block”,最后调用 println 方法输出这个字符串。然后,它创建一个 Exception 对象并抛出。
-
行16-25:这部分对应 catch 块的内容。当 try 块抛出异常时,执行流程会跳转到这部分。在这个例子中,它首先通过 astore 指令将异常对象存储到局部变量表,然后类似于 try 块的处理,输出 “catch block” 字符串。
-
行37-46:这部分对应 finally 块的内容。无论 try 块是否抛出异常,这部分代码总是会被执行。在这个例子中,它输出 “finally block” 字符串。
-
行25-32和行35-36:这部分是对异常处理的一些额外控制。jsr 和 ret 指令用于实现无条件的跳转,确保 finally 块总是会被执行。
8. 参考文档
- 张亚 《深入理解JVM字节码》
- https://www.jonesjalapat.com/2021/09/11/internal-working-of-java-virtual-machine/
相关文章:

字节码学习之常见java语句的底层原理
文章目录 前言1. if语句字节码的解析 2. for循环字节码的解析 3. while循环4. switch语句5. try-catch语句6. i 和i的字节码7. try-catch-finally8. 参考文档 前言 上一章我们聊了《JVM字节码指令详解》 。本章我们学以致用,聊一下我们常见的一些java语句的特性底层…...

Godot C#连接信号不能像GDScirpt一样自动添加代码
前言 我网上找了好久,发现Godot 对于C# 的支持还有待增强 使用c#脚本有办法像gds那样连接节点自带信号时自动生成信号吗? 百度贴吧 Godot C# How To, Episode 9. Signals With Parameters | Godot Mono 解决方案 把信号拉长,看他的属性 修…...

快速自动化处理JavaScript渲染页面
在进行网络数据抓取时,许多网站使用了JavaScript来动态加载内容,这给传统的网络爬虫带来了一定的挑战。本文将介绍如何使用Selenium和ChromeDriver来实现自动化处理JavaScript渲染页面,并实现有效的数据抓取。 1、Selenium和ChromeDriver简介…...

通过API接口进行商品价格监控,可以按照以下步骤进行操作
要实现通过API接口进行商品价格监控,可以按照以下步骤进行操作: 申请平台账号并选择API接口:根据需要的功能,选择相应的API接口,例如商品API接口、店铺API接口、订单API接口等,这一步骤通常需要我们在相应…...

(vue3)大事记管理系统 文章管理页
[element-plus进阶] 文章列表渲染(带搜索&到分页) 表单架设:当前el-form标签配置一个inline属性,里面的元素就会在一行显示了 中英国际化处理:App.vue中el-config-provider标签包裹组件,意味着整个组…...

springboot 使用RocketMQ客户端生产消费消息DEMO
创建springboot项目省略 项目依赖 注意:当前客户端版本是 5.1.3 ,安装的rocketmq服务的版本要与其对应 <properties><java.version>11</java.version><rocketmq-client-java-version>5.1.3</rocketmq-client-java-version&…...

第三章 内存管理 四、连续分配管理方式
目录 一、内存空间的分配与回收 1、连续分配管理方式 (1)、单一连续分配 优点: 缺点: (2)、固定分区分配 分区大小相等: 分区大小不等: (3)、动态分区…...

npm install报--4048错误和ERR_SOCKET_TIMEOUT问题解决方法之一
一、问题描述 学习vue数字大屏加载动漫效果时,在项目终端页面输入全局下载指令 npm install -g json-server 问题1、报--4048错误 会报如下错误 operation not permitted......errno: -4048code:EPERMsyscall: mkdir......The operation was reiected by your op…...
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组…...

自动泊车系统设计学习笔记
1 概述 1.1 自动泊车系统研究现状 目前对于自动泊车系统的研究方法通常有两种实现方式: 整个泊车操作可以分为四个阶段:第一阶段车辆向前行驶进行车位识别,第二阶段车辆行驶到准备泊车时的待泊车区域,第三阶段车辆按照规划好的…...

基于Java的家电销售网站管理系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域…...
设计模式~备忘录模式(memento)-22
目录 (1)优点: (2)缺点: (3)使用场景: (4)注意事项: (5)应用实例: 代码 备忘录模式(memento) 备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对…...

【Agora UID 踩坑记录 Java 数据类型】
目录 负数二进制表示Java中32位无符号数的取法项目踩坑记录Java 0xffffffff隐式类型转换的坑 负数二进制表示 由于计算机中数据都以二进制表示,而负数的二级制是根据正数二进制取补码(补码就是先取反码,然后加1)得到,…...
ESP8285 RTOS SDK OTA
一、官方资源说明 官方指南:空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 v4.3.6 文档,虽然是正对ESP32的,但是原理是一样的。 官方参考例程:esp-idf\ESP8266_RTOS_SDK\examples\system\ota\,其中包含两个例程&…...

Hadoop3教程(四):HDFS的读写流程及节点距离计算
文章目录 (55)HDFS 写数据流程(56) 节点距离计算(57)机架感知(副本存储节点选择)(58)HDFS 读数据流程参考文献 (55)HDFS 写数据流程 …...

[0xGameCTF 2023] web题解
文章目录 [Week 1]signinbaby_phphello_httprepo_leakping [Week 2]ez_sqli方法一(十六进制绕过)方法二(字符串拼接) ez_upload [Week 1] signin 打开题目,查看下js代码 在main.js里找到flag baby_php <?php /…...

Qt之submodule编译
工作中会遇到这样一种情况:qt应用程序在运行时提示找不到某个qt的动态库。我遇到的是缺少libQt5Websocket.so,因为应用程序是在x86平台银河麒麟v10上开发,能够正常编译运行,然后移植到rk3588(aarch64架构)上…...

Python实现带图形界面的计算器
Python实现带图形界面的计算器 在本文中,我们将使用Python编写一个带有图形用户界面的计算器程序。这个程序将允许用户通过点击按钮或键盘输入数字和操作符,并在显示屏上显示计算结果。 开发环境准备 要运行这个计算器程序,您需要安装Pyth…...

$ vue -Vbash: vue: command not found
$ vue -V bash: vue: command not found报这个错,我们需要找到vue安装路径,添加在环境变量的用户变量中: 1、vue安装路径 2、编辑环境变量 然后重新打开命令框,就可以了...

专业音视频领域中,Pro AV的崛起之路
编者按:在技术进步的加持下,AV行业发展得如何了?本文采访了两位深耕于广播电视行业的技术人,为我们介绍了专业音视频的进展:一位冉冉升起的新星:Pro AV以及FPGA在其中发挥的作用。 美国,拉斯维加…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...