【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

文章目录

  • 系列文章目录
  • 前言
  • 一、所有权(Ownership)
    • 1.1.、所有权(Ownership)
    • 1.2、栈(Stack)和堆(Heap)
    • 1.3、所有权规则(Ownership Rules)
    • 1.4、变量作用域(Variable Scope)
    • 1.5、字符串类型(String Type)
      • 1.5.1、字符串切片引用(&str 类型)
      • 1.5.2、String类型
        • 1.5.2.1、 String 字符串介绍
        • 1.5.2.2、 创建 String 字符串
        • 1.5.2.2、 追加字符串
        • 1.5.2.3、 插入字符串
        • 1.5.2.4、字符串替换
        • 1.5.2.4、字符串删除
        • 1.5.2.5、字符串连接
        • 1.5.2.6、使用 format!连接字符串
        • 1.5.2.7、转义的方式 `\`
        • 1.5.2.7、字符串行连接
        • 1.5.2.7、原始字符串
        • 1.5.2.8、字符串带双引号问题
        • 1.5.2.9、字符数组
    • 1.6、内存(Memory)和分配(Allocation)
    • 1.7、变量与数据交互的方式
      • 1.7.1、移动(Move)
      • 1.7.3、拷贝(copy)
      • 1.7.2、克隆(clone)
    • 1.8、涉及函数的所有权机制
    • 1.9、函数返回值的所有权机制
  • 二、引用(Reference)和租借(Borrowing)
    • 2.1、引用(Reference)
    • 2.3、租借(Borrowing)
      • 2.3.1、租借
      • 2.3.2、Mutable References
    • 2.4、垂悬引用(Dangling Reference)
  • 三、切片(Slice Type)
    • 3.1、字符串切片(String Slice)
    • 3.2、数组切片
  • 总结


前言

本章节将讲解 Rust 独有的概念(所有权)。所有权是 Rust 最独特的特性,它使得 Rust 能够在不需要垃圾收集器的情况下保证内存安全。因此理解所有权是如何工作很重要,本章节将讲解所有权相关的特性:借用、切片以及 Rust 如何在内存中布局数据。

主要教材参考 《The Rust Programming Language》


一、所有权(Ownership)

所有权是 Rust 最独特的特性,它使得 Rust 能够在不需要垃圾收集器的情况下保证内存安全。

1.1.、所有权(Ownership)

在 Java 等编程语言中存在垃圾回收机制,在程序运行时定期查找不再使用的内存,在C/C++中,程序员必须显式地分配和释放内存。

在 Rust 之中使用了第三种方法:内存通过一个所有权系统进行管理,该系统拥有一组编译器检查的规则,如果违反了任何规则,程序将无法编译。所有权的任何特性都不会再程序运行时减慢它的速度。

1.2、栈(Stack)和堆(Heap)

许多语言不需要经常考虑堆和栈,但是在Rust 这样的系统编程语言中,值存在栈还是堆上都会影响语言的行为,以及你要做出什么样的处理。

堆栈都是都可以在运行时使用的内粗部分,但是它们的结构方式不同。栈按照获取值的顺序存储值,并按照相反的顺序删除值。这被称为后进先出。添加数据称为压栈(push),删除数据称为出栈(pop)。

存储在栈上的所有数据必须具有已知的固定大小。在编译时大小未知或大小可能改变的数据必须存储在堆中。

堆的组织较差:当你数据放在堆上,您请求一定数量的空间。内存分配器在堆中找到一个足够大的空间,将其标记为正在使用,并返回一个指针,该指针是该位置的地址,这个过程叫做在堆上分配,由于指向堆堆指针是已知的固定大小,因此可以将指针存储在栈上。

1.3、所有权规则(Ownership Rules)

所有权有如下三条规则

  • Rust 中的每个值都有一个变量,称为其所有者;
  • 一次只能有一个所有者;
  • 当所有者不再程序运行范围时,该值将会被删除;

这三条规则时所有权概念的基础;

1.4、变量作用域(Variable Scope)

通过理解下面的代码实例,可以理解变量作用范围。

fn main() {
    let s1 = "hello";
    {                        // s2 is not valid here, it’s not yet declared
        let s2 = "hello";    // s2 is valid from this point forward
        // do stuff with s2
    }                        // this scope is now over, and s is no longer valid
}

1.5、字符串类型(String Type)

1.5.1、字符串切片引用(&str 类型)

使用字符串字面初始化的字符串类型是 &str 类型的字符串。此种类型是已知长度,存储在可执行程序的只读内存段中(rodata)。通过 &str 可以引用过 rodata 中的字符串。

let s:&str = "hello";

如果想直接使用 str 类型 是不可以的,只能通过 Box<str> 来使用。

1.5.2、String类型

1.5.2.1、 String 字符串介绍

Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是上文提到的字符串切片引用。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。

Rust 中的字符串是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的。

字符串字面量值被硬编码到程序中,它们是不可变的。但是实际上并不是所有的字符串值都是已知的。如果我们想要获取用户输入并存储他,Rust 提供了第二种字符串类型。这种类型管理在堆上分配的数据。

fn main() {
    let s1:&str = "hello";
    let s2:String = s1.to_string();
    let s3:&String = &s2;
    let s4:&str = &s2[0..3];
    let s5:String = String::from(s1);
}

需要注意,rust 要求索引必须是 usize 类型。如果起始索引是0,可以简称 &s[..3] ,同样可以终止索引 是 String 的最后一个字节,那么可以简写为 &s[1..],如果要引用整个 String 可以简写为 &s[..]

字符串切片引用索引必须在字符之间的边界未知,但是由于 rust 的字符串是 utf-8 编码,因此必须小心。

1.5.2.2、 创建 String 字符串

let s = "Hello".to_string();
let s = String::from("world");
let s: String = "also this".into();

1.5.2.2、 追加字符串

fn main() {
    let mut s = String::from("Hello ");
    s.push('r');
    println!("追加字符 push() -> {}", s);

    s.push_str("ust!");
    println!("追加字符串 push_str() -> {}", s);
}

1.5.2.3、 插入字符串

fn main() {
    let mut s = String::from("Hello rust!");
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);
    s.insert_str(6, " I like");
    println!("插入字符串 insert_str() -> {}", s);
}

1.5.2.4、字符串替换

1、replace 方法使用 两种类型的 字符串;

fn main() {
    let string_replace = String::from("I like rust. Learning rust is my favorite!");
    let new_string_replace = string_replace.replace("rust", "RUST");
    dbg!(new_string_replace); // 调试使用宏
    let s = "12345";
    let new_s = s.replace("3", "t");
    dbg!(new_s); 
}

2、replacen 方法使用 两种类型的 字符串;

fn main() {
    let string_replace = "I like rust. Learning rust is my favorite!";
    let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
    dbg!(new_string_replacen);
}

3、replace_range 只使用 String 类型

fn main() {
    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");
    dbg!(string_replace_range);
}

1.5.2.4、字符串删除

1、pop

fn main() {
    let mut string_pop = String::from("rust pop 中文!");
    let p1 = string_pop.pop();
    let p2 = string_pop.pop();
    dbg!(p1);
    dbg!(p2);
    dbg!(string_pop);
}

2、remove

fn main() {
    let mut string_remove = String::from("测试remove方法");
    println!(
        "string_remove 占 {} 个字节",
        std::mem::size_of_val(string_remove.as_str())
    );
    // 删除第一个汉字
    string_remove.remove(0);
    // 下面代码会发生错误
    // string_remove.remove(1);
    // 直接删除第二个汉字
    // string_remove.remove(3);
    dbg!(string_remove);
}

3、truncate

fn main() {
    let mut string_truncate = String::from("测试truncate");
    string_truncate.truncate(3);
    dbg!(string_truncate);
}

4、clear

fn main() {
    let mut string_clear = String::from("string clear");
    string_clear.clear(); // 相当于string_clear.truncate(0)
    dbg!(string_clear);
}

1.5.2.5、字符串连接

字符串连接 使用 + 或 += 操作符,要求右边的参数必须是字符串的切片引。使用 + 相当于使用 std::string 标准库中的 add 方法

fn main() {
    let string_append = String::from("hello ");
    let string_rust = String::from("rust");
    // // &string_rust会自动解引用为&str,这是因为deref coercing特性。这个特性能够允许把传进来的&String,在API执行之前转成&str。
    let result = string_append + &string_rust;
    let mut result = result + "!";
    result += "!!!";

    println!("连接字符串 + -> {}", result);
}

1.5.2.6、使用 format!连接字符串

这种方式适用于 String 和 &str,和C/C++提供的sprintf函数类似

fn main() {
    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);
}

1.5.2.7、转义的方式 \

fn main() {
    // 通过 \ + 字符的十六进制表示,转义输出一个字符
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // \u 可以输出一个 unicode 字符
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!(
        "Unicode character {} (U+211D) is called {}",
        unicode_codepoint, character_name
    );
}

1.5.2.7、字符串行连接

fn main() {
    let long_string = "String literals
                    can span multiple lines.
                    The linebreak and indentation here \	
                    can be escaped too!";
    println!("{}", long_string);
}

1.5.2.7、原始字符串

使用 r 开头的字符串不会被转义

fn main() {
    println!("{}", "hello \x52\x75\x73\x74");           // 输出hello Rust
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";    // 原始字符串
    println!("{}", raw_str);        // 输出Escapes don't work here: \x3F \u{211D}
}

1.5.2.8、字符串带双引号问题

rust 提供来 r# 方式来避免引号嵌套的问题。

fn main() {
    // 如果字符串包含双引号,可以在开头和结尾加 #
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 如果还是有歧义,可以继续增加#,没有限制
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

1.5.2.9、字符数组

由于 rust 的字符串是 utf-8 编码的,而String 类型不允许以字符为单位进行索引。所以 String 提供了 chars() 遍历字符和 bytes() 方法遍历字节。

但是需要从 String 中获取子串是比较困难的,标准库中没有提供相关的方法。

1.6、内存(Memory)和分配(Allocation)

字符出字面量快速高效,是因为硬编码到最终可执行文件之中。这些字符出是不可变的。但是我们不能为每个在编译时大小未知且运行程序时可能改变的文本放入二进制文件的内存块中。

String 类型为了支持可变、可增长的文本片段,我们需要在堆上分配一定数量的内存来保存内容,这就意味着内存必须在运行时从内存分配器中请求,我们需要一种方法,在使用完 String 后将这些内存返回给分配器。

第一部分:当调用String::from时,它的实现请求它所需的内存。
第二部分:在带有垃圾回收器(GC)的语言, GC 会清理不在使用的内存,我们不需要考虑他。在没有GC的语言中我们有责任识别内存不再使用,并且调用代码显式释放它。

Rust 采用不同的方式:一旦拥有的内存的变量超出作用域,内存就会自动返回(Rust 会为我们调用一个特殊的函数叫做 drop)。
在c++中 这种项目生命周期结束时释放资源的模式有时候被称为 资源获取即初始化(RALL)。

1.7、变量与数据交互的方式

1.7.1、移动(Move)

1、赋值

将一个变量赋值给另一个变量会将所有权转移。

    let s1 = String::from("hello");
    let s2 = s1;

2、参数传递或函数返回

赋值并不是唯一涉及移动的操作,值在作为参数传递或从函数返回时也会被移动。

3、赋给结构体或 enum

1.7.3、拷贝(copy)

在编译是已知大小的整数类型完全存在栈中,因此可以快速直接复制实际值。

    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);

在 Rust 中有一个特殊的注解 叫做 Copy trait ,我们可以把它放在存储在栈上的类型,就像整数一样,如果一个类型实现了 Copy 特性,那么它的变量就不会移动。

如果类型或其任何部分实现了Drop trait,Rust将不允许我们用Copy注释类型。

实现了 Copy trait的类型

  • 所有的整型类型

  • bool 类型: true、false

  • 浮点类型;f32、f64

  • 字符类型:char

  • 元组(只包含了实现了 Copy trait),例如 (i32,i32) 就是 copy,而( i32,String) 是move;

1.7.2、克隆(clone)

    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);

1.8、涉及函数的所有权机制

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    println!("s {}", s);
    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

1.9、函数返回值的所有权机制

fn main() {
  let s1 = gives_ownership();         // gives_ownership moves its return
                                      // value into s1

  let s2 = String::from("hello");     // s2 comes into scope

  let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                      // takes_and_gives_back, which also
                                      // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                           // return value into the function
                                           // that calls it

  let some_string = String::from("yours"); // some_string comes into scope

  some_string                              // some_string is returned and
                                           // moves out to the calling
                                           // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                    // scope

  a_string  // a_string is returned and moves out to the calling function
}

二、引用(Reference)和租借(Borrowing)

2.1、引用(Reference)

引用(Reference) 是 C++ 开发者较为熟悉的概念,如果你熟悉指针的概念,你可以把它当作一种指针。实质上,引用是变量的间接访问方式。

我们使用引用就可以避免所有权移动导致原先的变量不能使用的问题。

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

引用的原理图
因为 s 是 String的引用,它没有所有权,当函数结束的时候,并不会drop。

2.3、租借(Borrowing)

2.3.1、租借

引用不会获得值的所有权,引用只能租借(Borrow)值的所有权。引用本身也是一种类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所有值的所有权。

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:3
  |
8 |   some_string.push_str(", world");
  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn change(some_string: &mut String) {
  |                        ~~~~~~~~~~~

For more information about this error, try `rustc --explain E0596`.
error: could not compile `hello` (bin "hello") due to previous error

从上述错误提示可以知道 reference 是租借引用,只能读不能进行写操作,我们可以使用 &mut 来进行 可变引用。

2.3.2、Mutable References

使用 Mutable References 可以修改引用的内容。

fn main() {
  let mut s = String::from("hello");

  change(&mut s);
}

fn change(some_string: &mut String) {
  some_string.push_str(", world");
}

一个变量只能有一个 Mutable References。

对同一个值有不可变引用的时候,不能有可变引用。

2.4、垂悬引用(Dangling Reference)

垂悬引用 好像也叫做 野指针。

如果在有指针概念的编程语言,它指的是那种没有实际指向一个真正能访问的数据和指针(注意,不一定是空指针,还有可能是已经释放的资源),它们就像失去悬挂物体的绳子,所以叫做垂悬引用。

垂悬引用 在 Rust 语言里面不允许出现,如果有,编译器会发现它

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

很显然,伴随着 dangle 函数的结束,其局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回,这个引用所指向的值已经不能确定的存在,故不允许其出现。

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                 +++++++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `hello` (bin "hello") due to previous error

三、切片(Slice Type)

切片(Slice) 是对数据值的部分引用。

3.1、字符串切片(String Slice)

最简单、最常用的数据切片类型是字符串切片(String Slice)。

3.2、数组切片

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);

总结

以上就是今天要讲的内容

  • 本文介绍 rust的所有权、切片、字符出类型、租借、可变引用,本章节的内容比较难以理解;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】不就是子查询

前言 今天我们来学习多表查询的下一个模块——子查询&#xff0c;子查询包括了标量子查询、列子查询、行子查询、表子查询&#xff0c;话不多说我们开始学习。 目录 前言 目录 一、子查询 1. 子查询的概念 2. 子查询语法格式 2.1 根据子查询结果不同可以分为&#xff1a;…

flutter聊天界面-Text富文本表情emoji、url、号码展示

flutter聊天界面-Text富文本表情emoji、url、号码展示 Text富文本表情emoji展示&#xff0c;主要通过实现Text.rich展示文本、emoji、自定义表情、URL等 一、Text及TextSpan Text用于显示简单样式文本 TextSpan它代表文本的一个“片段”&#xff0c;不同“片段”可按照不同的…

Matlab画等构造图

clc;clear;close all; data xlsread(TOPBRENT等T0构造.xlsx); x data(:,1) xmax max(x); xmin min(x); y data(:,2) ymax max(y); ymin min(y); z data(:,3); N 45; …

IDEA使用插件绘制UML类图+PlantUML语法讲解

安装 IDEA安装插件 安装完插件记得重启一下IDEA 安装Graphviz&#xff08;亲测win11可以使用&#xff09; 安装完插件之后&#xff0c;还需要安装Graphviz才可以渲染图形。 Graphviz安装包下载地址 安装过程很简单&#xff0c;直接双击或者管理员身份运行即可&#xff0c;注…

Docker中部署Redis集群与部署微服务项目的详细过程

目录 一、使用Docker部署的好处二、Docker 与 Kubernetes 对比三、Redis集群部署实战四、Spring Boot项目 打包镜像?小结 一、使用Docker部署的好处 Docker的好处在于&#xff1a;在不同实例上运行相同的容器 Docker的五大优点&#xff1a; 持续部署与测试、多云服务平台支…

微信小程序开发与应用——字体样式设置

要求&#xff1a;设置字体样式。 1、打开微信开发者工具&#xff0c;创建一个小程序&#xff0c;如下&#xff1a; 2、设置小程序的项目名称和路径&#xff0c;并选择开发语言为JavaScript&#xff0c;如下&#xff1a; 3、小程序的主体部分由三个文件组成&#xff0c;且都要…

VS2019+Qt5.15 在线显示百度地图

1.Qt5.15编译程序需要选择mscv2019 Release版本 2.需要到百度地图开发平台注册并获取到开发者key 3.显示地图是JS与Qt的交互过程&#xff0c;显示地图的html文件&#xff1a; <!DOCTYPE html> <html><head> <meta name"viewport" content&q…

基于ubuntu的驱动开发

一般的linux驱动开发都是基于交叉编译来进行的&#xff0c;本文尝试着从另一个角度&#xff1a;基于ubuntu的本地驱动开发来学习一下驱动的开发 一、驱动的开发与编译 1.1、编写驱动文件 #include <linux/init.h> #include <linux/module.h> static int hello_i…

深入理解Linux网络——内核是如何接收到网络包的

文章目录 一、相关实际问题二、数据是如何从网卡到协议栈的1、Linux网络收包总览2、Linux启动1&#xff09;创建ksotfirqd内核线程2&#xff09;网络子系统初始化3&#xff09;协议栈注册4&#xff09;网卡驱动初始化5&#xff09;网卡启动 3、迎接数据的到来1&#xff09;硬中…

Gradio库中的Model3D模块:实时上传和展示3D模型

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

结合具体场景举例说明chatgpt预训练模型中Tokenization的原理

假设我们有一个场景&#xff0c;Alice想向Chatbot询问一部电影的推荐。她发送了一条消息&#xff1a;“你好&#xff0c;能给我推荐一部好看的电影吗&#xff1f;” 在这个场景中&#xff0c;Chatbot使用了ChatGPT预训练模型。首先&#xff0c;Chatbot需要对Alice的消息进行Tok…

OpenCV读取一张8位无符号三通道图像并显示

#include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/opencv.hpp> #include

c++编写网络爬虫

c爬虫项目 实现图形化界面UI 安装easyX&#xff08;需要用的graphisc.h&#xff09; 我之前的文章详细写到过如何安装。是这篇文章提到的&#xff1a;传送门 easyx官网 创建图形化界面 #define WINDOW_WIDTH 482 #define WINDOW_HEIGHT 300 void initUI() {initgraph(WINDO…

SwiftUI的优缺点

2019年WWDC大会上&#xff0c;苹果在压轴环节向大众宣布了基于Swift语言构建的全新UI框架——SwiftUI&#xff0c;开发者可通过它快速为所有的Apple平台创建美观、动态的应用程序。推荐大量使用struct代替类。 SwiftUI 就是⼀种声明式的构建界面的用户接口工具包。 SwiftUI使用…

机器学习与深度学习——自定义函数进行线性回归模型

机器学习与深度学习——自定义函数进行线性回归模型 目的与要求 1、通过自定义函数进行线性回归模型对boston数据集前两个维度的数据进行模型训练并画出SSE和Epoch曲线图&#xff0c;画出真实值和预测值的散点图&#xff0c;最后进行二维和三维度可视化展示数据区域。 2、通过…

SpringBoot + Vue前后端分离项目实战 || 五:用户管理功能后续

系列文章&#xff1a; SpringBoot Vue前后端分离项目实战 || 一&#xff1a;Vue前端设计 SpringBoot Vue前后端分离项目实战 || 二&#xff1a;Spring Boot后端与数据库连接 SpringBoot Vue前后端分离项目实战 || 三&#xff1a;Spring Boot后端与Vue前端连接 SpringBoot V…

企业内部安全:利用 ADAudit Plus 管理与加强安全审计

在现代数字化时代&#xff0c;企业面临着日益复杂和不断变化的安全威胁。为了保护敏感数据、遵守合规要求以及防范内部威胁&#xff0c;企业需要有效的安全审计解决方案。ADAudit Plus 是一款强大而全面的安全审计工具&#xff0c;可以帮助企业管理和加强内部安全。 ADAudit Pl…

Jenkins持续集成,在Linux中安装最新版Jenkins(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 去年从6月28日发布…

git学习1

打标签 与其他版本控制系统&#xff08;VCS&#xff09;一样&#xff0c;Git 可以给仓库历史中的某一个提交打上标签&#xff0c;以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点&#xff08; v1.0 、 v2.0 等等&#xff09;。 列出标签 在 Git 中列出已有的…

SpringCloud:微服务技术

一、认识微服务&#xff1a; 首先&#xff0c;微服务架构不等于SpringCloud&#xff0c;微服务架构是一种经过良好架构设计的分布式架构方案&#xff0c; &#xff0c;它将应用构建成一系列按业务领域划分模块的&#xff0c;小的自治服务&#xff0c;并解决服务拆分所产生的各种…