字节码进阶之ASM字节码操作类库详解

news2024/11/16 1:55:46

文章目录

  • 0.前言
    • 1. 引言
    • 2. ASM简介
    • 3. 字节码基础知识回顾
  • 4. ASM的核心概念
  • 5. ASM的基本用法
    • 5.1. 读取和分析字节码
    • 5.2. 修改和生成字节码
  • 6. ASM的高级用法
    • 6.1. 字节码增强技术
    • 6.2. 自定义类加载器和类定义
  • 7. 实例演示:使用ASM实现简单的字节码增强

在这里插入图片描述
字节码进阶之ASM字节码操作类库详解

0.前言

1. 引言

  1. 引言

Java 字节码是 Java 语言的一种中间表现形式,它是在 Java 源代码被编译后生成的。Java 字节码可以通过 JVM(Java Virtual Machine)在任何平台上执行,这也是 “Write Once, Run Anywhere” 口号的实现基础。

字节码操作即对这些编译后的字节码文件进行编辑、修改或生成新的字节码。字节码操作的主要用处包括但不限于:

  • 性能优化:比如方法内联、消除冗余操作等。
  • 实现一些高级特性:比如动态代理、AOP(面向切面编程)等。
  • 混淆和反混淆:为了防止代码被轻易反编译,可以对字节码进行混淆处理。

ASM 是一款功能强大且易用的 Java 字节码操作和分析框架。ASM 提供了一些简单的 API,可以通过它来直接生成、转换和处理 Java 字节码。ASM 是性能最好的字节码框架之一,被广泛用于许多开源项目中,如 Apache Groovy, Clojure, Spring Boot 等。

Java 字节码操作类库的需求来源于字节码操作的复杂性。原始的字节码由一串数字组成,对人类来说难以理解和操作。而字节码操作类库,如 ASM,提供了友好的接口和丰富的工具,极大地简化了字节码操作。这对于需要对字节码进行操作的开发者来说,无疑是一大福音。

2. ASM简介

  • 什么是ASM

ASM (Abstract State Machine) 是一种用于计算机科学和数学的模型,用于抽象化和模拟计算机系统的行为。在编程领域,ASM可能是指用于操作和创建Java字节码的库。

  • ASM的特点和优势
  1. 动态性: ASM提供了动态生成或修改类的字节码的功能。
  2. 高效性: ASM生成的字节码与由Java编译器生成的字节码效率基本相同。
  3. 灵活性: 由于ASM直接操作字节码,所以它提供了Java语言本身不可能实现的功能。
  4. 可扩展性: ASM有能力操作复杂的数据结构,从而实现各种高级功能。
  • ASM的应用领域
  1. 动态代码生成: 在运行时动态地生成或修改字节码,可以创建具有特殊行为的类。
  2. 性能优化: 通过修改字节码来进行微观的性能优化,比如运行时的计算消除,无用代码的移除。
  3. 程序分析和测试: 利用ASM修改字节码以收集执行信息,从而进行程序的分析和测试。
  4. 安全检查: 通过对字节码的检查和修改,可以实现一些安全相关的检查和控制。

3. 字节码基础知识回顾

  • 什么是字节码

字节码,也称为p-code(portable code),是一种在特定软件解释器环境下执行的计算机指令集。这种指令集通常被设计用于通过堆栈机或寄存器机执行。Java字节码是Java虚拟机(JVM)的指令集,其是Java语言编译后的中间表示形式,它降低了系统间的依赖性,为Java的跨平台特性提供支持。

  • 字节码的结构和格式

Java字节码文件主要由四个部分组成:魔数与版本信息,常量池,类信息,类的方法和属性。魔数与版本信息是文件的头部,用于标识这是一个可以被JVM读取的字节码文件。常量池存储了Java程序中所有的文字和符号引用,类信息包括这个类的名字,父类,接口等信息,类的方法和属性是Java类的主体部分,里面包括了方法的声明和实现。

  • 字节码指令集

Java字节码指令集是一套完整的操作集,包括了操作数栈,局部变量,类的字段和方法,对象创建和操作,控制转移,异常处理等指令。这些指令分别对应了Java中的各种语言特性,比如算术运算,逻辑运算,循环,条件跳转,方法调用,对象操作等。

4. ASM的核心概念

  • ClassVisitor和MethodVisitor

在ASM框架中,ClassVisitor和MethodVisitor是两个核心的接口,它们为访问和处理字节码提供了方法。

ClassVisitor提供了用于访问Java类的方法,它在访问到类的头部,字段,方法和接口等信息时被调用。在实践中,我们通常通过继承ClassVisitor并覆盖需要的方法来实现我们自己的类访问逻辑。

MethodVisitor则提供了用于访问Java方法的字节码的方法。我们可以通过继承MethodVisitor并覆盖其中的方法来处理方法中的各种字节码指令。

  • ClassReader和ClassWriter

ClassReader是ASM提供的一个用于读取字节码的工具,它能够解析Java类文件,并将解析的结果传递给ClassVisitor。

ClassWriter则是ASM提供的一个用于生成新的字节码或者修改已有字节码的工具。它是一个ClassVisitor,会将接收到的所有访问操作转换为字节码。

  • 字节码增强和修改的原理

在ASM中,字节码的增强和修改主要通过ClassVisitor和MethodVisitor接口进行。首先使用ClassReader读入一个类的字节码,然后通过传入ClassVisitor(通常是一个ClassWriter或者其子类)来访问这个类的字节码。在访问的过程中,通过覆盖ClassVisitor和MethodVisitor的方法,可以在相应的地方插入新的字节码指令,或者修改已有的指令,从而实现字节码的增强和修改。

最后,通过ClassWriter的toByteArray()方法可以得到修改后的字节码,这个字节码可以直接被JVM加载并执行,也可以保存到文件中,形成一个新的Java类文件。

5. ASM的基本用法

5.1. 读取和分析字节码

  • 使用ClassReader解析字节码

    ClassReader是一个可以解析Java类的组件,它接受一个类的字节码作为输入,分析类中的所有元素(包括类名,父类,接口,构造函数,方法,字段等)。

    使用ClassReader很简单,只需要将字节码(以byte[]的形式)传递给ClassReader的构造函数,然后调用其accept方法,将一个ClassVisitor对象作为参数传入,ClassReader就会按照类的结构,按照顺序访问到类的每一个元素,并调用相应的ClassVisitor的方法。

    示例如下:

 byte[] bytecode = ...;
 ClassReader cr = new ClassReader(bytecode);
 ClassVisitor cv = new MyClassVisitor();
 cr.accept(cv, 0);
  • 使用ClassVisitor访问和处理类的结构

    ClassVisitor是ASM中的一个接口,定义了一系列的方法供我们在访问类的结构时使用。我们可以创建一个ClassVisitor的子类,覆盖其中的方法,来处理类的各个部分。

    例如,如果我们想在访问到类的方法时做一些处理,可以覆盖visitMethod方法:

    class MyClassVisitor extends ClassVisitor {
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            System.out.println("Visiting method: " + name);
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    }
    
  • 使用MethodVisitor访问和处理方法的指令

    类似于ClassVisitor,MethodVisitor也提供了一系列的方法,供我们在访问方法的字节码时使用。我们可以创建一个MethodVisitor的子类,覆盖其中的方法,来处理方法的字节码。

    例如,如果我们想在访问到方法的每一个指令时打印出指令的信息,可以覆盖visitInsn方法:

    class MyMethodVisitor extends MethodVisitor {
        @Override
        public void visitInsn(int opcode) {
            System.out.println("Visiting instruction: " + opcode);
            super.visitInsn(opcode);
        }
    }
    

5.2. 修改和生成字节码

  • 使用ClassWriter生成新的字节码

    ClassWriter是ASM提供的一个强大的组件,它可以用来生成新的字节码。ClassWriter继承自ClassVisitor,可以作为ClassReader的visitor来生成一个和已有类结构完全相同的新类,也可以独立使用,来生成完全新的类。

    例如,以下是一个简单的ClassWriter使用例子,生成了一个新的类"Example",该类有一个无参构造函数和一个方法hello:

    ClassWriter cw = new ClassWriter(0);
    cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Example", null, "java/lang/Object", null);
    
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
    mv.visitInsn(Opcodes.RETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    
    mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "hello", "()V", null, null);
    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("Hello, world!");
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    mv.visitInsn(Opcodes.RETURN);
    mv.visitMaxs(2, 1);
    mv.visitEnd();
    
    cw.visitEnd();
    
    byte[] bytecode = cw.toByteArray();
    
  • 使用ClassVisitor修改已有的字节码

    我们可以通过覆盖ClassVisitor的方法来修改已有的字节码。例如,我们可以覆盖visitMethod方法,对每个方法的名字进行修改:

    class MyClassVisitor extends ClassVisitor {
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            return super.visitMethod(access, "prefix_" + name, desc, signature, exceptions);
        }
    }
    
  • 使用MethodVisitor生成和修改方法的指令

    我们也可以通过覆盖MethodVisitor的方法来生成新的方法字节码,或者修改已有的字节码。例如,我们可以在方法的开始和结束时添加一些指令:

    class MyMethodVisitor extends MethodVisitor {
        @Override
        public void visitCode() {
            super.visitCode();
            // Add instructions at the beginning of the method
        }
    
        @Override
        public void visitInsn(int opcode) {
            if (opcode == Opcodes.RETURN) {
                // Add instructions before return
            }
            super.visitInsn(opcode);
        }
    }
    

6. ASM的高级用法

6.1. 字节码增强技术

字节码增强技术通常被用于监控、性能优化、动态代理、热替换等场景。ASM提供了丰富的接口和工具来实现这些功能。

  • 方法注入

    方法注入是在某个方法的字节码中添加新的字节码,从而改变该方法的行为。这种技术可以用于添加日志、捕获异常、检查参数等操作。ASM的方法注入主要通过MethodVisitor实现。

    class AddTimerMethodAdapter extends MethodVisitor {
        private String owner;
    
        public AddTimerMethodAdapter(MethodVisitor mv, String owner) {
            super(ASM5, mv);
            this.owner = owner;
        }
    
        @Override
        public void visitCode() {
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitInsn(LSUB);
            mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
        }
    
        @Override
        public void visitInsn(int opcode) {
            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                mv.visitInsn(LADD);
                mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
            }
            mv.visitInsn(opcode);
        }
    }
    
  • 字节码插入和替换

    字节码插入是在现有的字节码中插入新的字节码,而字节码替换是将现有的字节码替换为新的字节码。这两种操作可以通过ASM的ClassVisitor和MethodVisitor实现。

    字节码插入示例:

    class MethodInsertAdapter extends MethodVisitor {
        public MethodInsertAdapter(MethodVisitor mv) {
            super(ASM5, mv);
        }
    
        @Override
        public void visitInsn(int opcode) {
            if (opcode == RETURN) {
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("Exit method");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            mv.visitInsn(opcode);
        }
    }
    

- 字节码删除和重排

  字节码删除就是移除某些不需要的字节码,而字节码重排则是改变字节码的顺序。这两种操作也可以通过ASM的ClassVisitor和MethodVisitor实现。

  字节码删除示例:
```java
  class MethodRemoveAdapter extends MethodVisitor {
      public MethodRemoveAdapter(MethodVisitor mv) {
          super(ASM5, mv);
      }

      @Override
      public void visitInsn(int opcode) {
          // Remove the RETURN instruction
          if (opcode == RETURN) {
              return;
          }
          mv.visitInsn(opcode);
      }
  }

6.2. 自定义类加载器和类定义

ASM提供了丰富的接口和工具类来生成和定义新的字节码,同时我们还可以使用自定义的类加载器来加载和使用这些生成的字节码。

  • 通过ASM生成和定义新的类

    我们可以使用ASM的ClassWriter类来生成新的字节码,然后通过ClassVisitorMethodVisitor来定义类的结构和方法。

    下面的代码定义了一个名为Example的类,包含一个无参数构造方法和一个hello方法。

    ClassWriter cw = new ClassWriter(0);
    ClassVisitor cv = new ClassVisitor(ASM5, cw) {};
    
    String className = "Example";
    String classDesc = "L" + className + ";";
    
    cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null);
    
    // define constructor
    MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitCode();
    mv.visitVarInsn(ALOAD, 0);
    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
    mv.visitInsn(RETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    
    // define hello method
    mv = cv.visitMethod(ACC_PUBLIC, "hello", "()V", null, null);
    mv.visitCode();
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("Hello, world!");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    mv.visitInsn(RETURN);
    mv.visitMaxs(2, 1);
    mv.visitEnd();
    
    cv.visitEnd();
    
    byte[] b = cw.toByteArray();
    
  • 自定义类加载器加载和使用生成的类

    我们可以定义一个自定义的类加载器,通过该类加载器加载字节码生成的类。

    class MyClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }
    
    MyClassLoader cl = new MyClassLoader();
    Class<?> c = cl.defineClass("Example", b);
    
    Object obj = c.newInstance();
    Method m = c.getMethod("hello");
    m.invoke(obj);
    

    上述代码将会打印出Hello, world!

7. 实例演示:使用ASM实现简单的字节码增强

一种常见的使用ASM的方式是字节码增强,也就是在运行时动态修改类的行为。下面的实例展示了如何使用ASM对一个简单的方法进行字节码增强。

  • 初始代码:

    我们有一个简单的类,其中有一个方法print,它接受一个String参数并打印它。

    public class HelloWorld {
        public void print(String message) {
            System.out.println(message);
        }
    }
    
  • 分步演示:

    1. 读取字节码

    ASM提供了ClassReader类来读取现有的字节码。

    ClassReader cr = new ClassReader("HelloWorld");
    

    2. 修改字节码

    我们可以通过ClassVisitorMethodVisitor来修改字节码。这里我们想在print方法内部添加一段代码以打印额外的消息。

    ClassWriter cw = new ClassWriter(cr, 0);
    ClassVisitor cv = new ClassVisitor(ASM5, cw) {
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            if (name.equals("print")) {
                return new MethodVisitor(ASM5, mv) {
                    @Override
                    public void visitCode() {
                        mv.visitCode();
                        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("About to print a message.");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                    }
                };
            }
            return mv;
        }
    };
    
    cr.accept(cv, 0);
    

    3. 生成字节码

    使用ClassWritertoByteArray方法将修改后的字节码转化为字节数组,然后可以将其保存到文件或者在运行时直接加载进JVM。

    byte[] b = cw.toByteArray();
    
    // 保存到文件
    try (FileOutputStream fos = new FileOutputStream("HelloWorld.class")) {
        fos.write(b);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    // 或者在运行时直接加载
    ClassLoader cl = new ClassLoader() {
        public Class<?> defineClass(String name, byte[] b) {
            return super.defineClass(name, b, 0, b.length);
        }
    };
    
    Class<?> c = cl.defineClass("HelloWorld", b);
    

在这个例子中,我们增强了HelloWorld类的print方法,每次调用print方法之前,都会先打印"About to print a message."这句话。这就是一个简单的使用ASM进行字节码增强的例子。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营第二十八天丨 回溯算法part04

491.递增子序列 思路 这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。 在90.子集II (opens new window)中是通过排序&#xff0c;再加一个标记数组来达到去重的目的。 而本题求自增子序列&#xff0c;是不能对原数组进行排序的&#xff0c;排…

前端工作方式要换了?HTMX简介:无需JavaScript的动态HTML

HTMX允许你使用扩展的HTML语法代替 JavaScript 来实现交互性。HTMX 在标记中直接为你提供HTTP 交互&#xff0c;并支持许多其他交互需求&#xff0c;无需求助于 JavaScript。这是一个有趣的想法&#xff0c;可能最终会影响到web前端的工作方式。让我们看看如何使用HTMX以及它的…

pytorch 入门 (三)案例一:mnist手写数字识别

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】mnist手写数字识别&#x1f356; 原作者&#xff1a;K同学啊 目录 一、 前期准备1. 设置GPU2. 导入…

聊聊分布式架构09——分布式中的一致性协议

目录 01从集中式到分布式 系统特点 集中式特点 分布式特点 事务处理差异 02一致性协议与Paxos算法 2PC&#xff08;Two-Phase Commit&#xff09; 阶段一&#xff1a;提交事务请求 阶段二&#xff1a;执行事务提交 优缺点 3PC&#xff08;Three-Phase Commit&#x…

kubeadm初始化搭建cri-dockerd记录 containerd.io

07.尚硅谷_搭建K8s集群&#xff08;kubeadm方式&#xff09;-部署master节点_哔哩哔哩_bilibili 视频里的版本只有1.17而现在&#xff08;2023.10.20&#xff09;kubernetes最新版本是1.28&#xff0c;需要搭载cri-dockerd&#xff0c; 先去网站下载了对应的rpm包cri-dockerd…

计算机算法分析与设计(14)---贪心算法(会场安排问题和最优服务次序问题)

文章目录 一、会场安排问题1.1 问题描述1.2 思路分析1.3 例题分析1.4 代码编写 二、最优服务次序问题2.1 问题描述2.2 思路分析2.3 代码编写 一、会场安排问题 1.1 问题描述 假设在足够多的会场里安排一批活动&#xff0c;并希望使用尽可能少的会场。设计一个有效的贪心算法进…

kettle 导出Excel 日期信息为空bug

今天做个需求&#xff0c;跨库联表查询数据。导出为Excel &#xff0c;但是日期数据除了问题。日期yyyy/mm/dd hh:mm:ss 竟然是空的 。 解决办法&#xff1a; 一、&#xff08;网上给出最多的解决方案&#xff0c;但本人不实用。需要安装MySQL监听&#xff09; to_char(日期,…

Mojo——会燃的 AI 编程语言

点击链接了解详情 导语&#xff1a;本文简介 Mojo 的背景与特点&#xff0c;并分享如何通过腾讯云 Cloudstudio 的 WebIDE 和分享社区快速学习和上手 Mojo。 &#x1f525;&#x1f525;&#x1f525; 腾讯云 Cloud Studio 已开放 Mojo 应用模版。 什么是 Mojo Mojo 是基于 P…

CodeFormer和GFPGAN的本地部署与效果对比

CodeFormer和GFPGAN是两个图片人脸修复的开源程序&#xff0c;两个程序不相伯仲&#xff0c;效果都非常棒&#xff0c;在stable diffusion中这两个插件都有集成进去&#xff01;我们今天就将这两个程序的本地独立安装和使用方法记录一下&#xff01; CodeFormer github主页地址…

【前端】使用tesseract插件识别提取图片中的文字

前言 有时候项目需要识别证照信息&#xff0c;或者拍照搜索内容等。图片处理一般是后端处理比较好&#xff0c;不过前端也有相关插件处理&#xff0c;tesseract.js就是一种前端处理方案。 使用tesseract tesseract更多的语言模型&#xff1a;language配置 安装 Tesseract.…

react中JSX基础与useState的基本使用 + 评论显示删除需求案例

参考视频&#xff1a;https://www.bilibili.com/video/BV1ZB4y1Z7o8/?p3&spm_id_frompageDriver&vd_source5c584bd3b474d579d0bbbffdf0437c70 如果没有安装create-react-app需要先全局安装 命令&#xff1a;npm i -g create-react-app1.快速搭建开发环境 create-re…

国内有哪些做得好的企业协同办公软件

在当今信息化时代&#xff0c;企业协同办公软件成为了提升企业效率和推动协作的重要工具。国内市场涌现出许多优秀的企业协同办公软件&#xff0c;为企业提供了高效、便捷的协同办公解决方案。在本文中&#xff0c;我们将向大家介绍3款在国内好评如潮的企业协同办公软件&#x…

驱动day2 字符设备驱动 LED亮灯

可参考arm点灯C语言 cortex-A7核 点LED灯 &#xff08;附 汇编实现、使用C语言 循环实现、使用C语言 封装函数实现【重要、常用】&#xff09;-CSDN博客 1 应用程序 test.c #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <…

Adobe 推出 Photoshop Elements 2024 新版

&#x1f989; AI新闻 &#x1f680; Adobe 推出 Photoshop Elements 2024 新版 摘要:Adobe 最新发布 Photoshop Elements 2024 版本,新增引入 AI 功能,提供匹配颜色、创建照片卷、一键选择照片天空或背景等新功能,界面也进行了优化更新。本次发布重点加强了 AI 支持,简化复杂…

软考系列(系统架构师)- 2019年系统架构师软考案例分析考点

试题一 软件架构&#xff08;架构风格、质量属性&#xff09; 【问题1】&#xff08;13分&#xff09; 针对用户级别与折扣规则管理功能的架构设计问题&#xff0c;李工建议采用面向对象的架构风格&#xff0c;而王工则建议采用基于规则的架构风格。请指出该系统更适合采用哪种…

ant design vue Message 用法以及内容为 html片段情况

ant design vue 的 Message 用法 全局展示操作反馈信息 何时使用 # 可提供成功、警告和错误等反馈信息。顶部居中显示并自动消失&#xff0c;是一种不打断用户操作的轻量级提示方式。 全局配置&#xff1a; // main.ts// 进行全局配置 message.config({top: 0.7rem,//高度…

Qt作业九

1、思维导图 2、作业 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimer> #include <QTime> #include <QTimerEvent> #include <QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAME…

面向对象设计原则,详细介绍及分析

一、介绍&#xff1a; 面向对象设计原则是面向对象设计的基石&#xff0c;是面向对象设计的质量、保障、思想。 一共有七个设计原则&#xff0c;设计模式就是面向对象设计原则的经典应用 单一职责原则* 强调&#xff1a;高内聚低耦合&#xff0c;每一种类型的业务区分 开闭原则…

计算各数位的和是否相等(桶排)

// 分别计算左右两边各数的和&#xff0c;不同值时的数量 eg.四位数&#xff0c;左边数的有10~99&#xff0c;右边数有00~99 和有1~18 和有0~18 桶排&#xff0c;和相同放一个桶&#xff0c;分别计算左右两边不同和的数量 while (len < 8){int left[50] …

2023区块链国赛有黑幕

2023全国职业院校技能大赛区块链技术应用赛项 有黑幕&#xff01;&#xff01;河北软件职业技术学院举行的全国职业院校技能大赛区块链技术应用赛项违反比赛公平原则&#xff1a; 1、在评分阶段居然允许企业人员进入裁判所在区域&#xff0c;偏向性的引导裁判评分&#xff0c…