【Rust】Rust学习

文档:Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (bootcss.com)

墙裂推荐这个文档

第一章入门

入门指南 - Rust 程序设计语言 简体中文版

第二章猜猜看游戏

猜猜看游戏教程 - Rust 程序设计语言 简体中文版 (bootcss.com)

// 导入库
use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");
    
    // 生成一个随机数,范围在[1, 100],,需要在配置文toml中加依赖
    let secret_number = rand::thread_rng().gen_range(1, 101);

    // loop循环
    loop {
        println!("Please input your guess.");

        // 定义一个可变变量,加mut是可变的,不加mut是不可变的
        let mut guess = String::new();

        // 读一行数据到guess中,返回值是Result(枚举,成员是Ok和Err)
        // Result 拥有expect方法
        // 如果 io::Result 实例的值是 Err,expect 会导致程序崩溃,并显示当做参数传递给 expect 的信息。
        // 如果 read_line 方法返回 Err,则可能是来源于底层操作系统错误的结果。如果 io::Result 实例的值是 Ok,expect 会获取 Ok 中的值并原样返回。
        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");

        // Rust 允许用一个新值来 隐藏 (shadow) guess 之前的值
        // String 实例的 trim 方法会去除字符串开头和结尾的空白字符。
        // 字符串的 parse 方法 将字符串解析成数字。
        // parse也是有返回值,成功的话就是返回num,失败则继续输入
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        // 用match来匹配
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),       // guess < secret_number
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");                       // 匹配,跳出循环
                break;
            }
        }
    }
}

代码量不大,学了不少语法

测试

第三章常见编程概念

3.1 变量与可变性

变量不可改

fn main() {
    println!("Hello, world!");
    let i = 3;
    println!("i = {}", i);
    i = 23;                 // 变量不可以更改
    println!("i = {}", i);
}

结果

 可变变量,可以修改

fn main() {
    println!("Hello, world!");
    let mut i = 3;
    println!("i = {}", i);
    i = 23;
    println!("i = {}", i);
}

变量与常量的区别

(1)不允许对常量使用mut;

(2)声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型;

(3)常量可以在任何作用域中声明;

(4)常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。

const MAX_POINTS : u32 = 100_100;

隐藏(Shadowing)

fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

    println!("The value of x is: {}", x);
}

隐藏与将变量标记为 mut 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。

mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字。

3.2 数据类型

标量类型 标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型

整数

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

整形字面值

数字字面值例子
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'

浮点型

Rust 也有两个原生的 浮点数floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。

Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。

fn main() {
    // 加法
    let _sum = 5 + 10;

    // 减法
    let _difference = 95.5 - 4.3;

    // 乘法
    let _product = 4 * 30;

    // 除法
    let _quotient = 56.7 / 32.2;

    // 取余
    let _remainder = 43 % 5;
}

布尔型

正如其他大部分编程语言一样,Rust 中的布尔类型有两个可能的值:true 和 false。Rust 中的布尔类型使用 bool 表示。

字符类型

在 Rust 中,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

复合类型

复合类型Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)数组(array)

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。

使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);


    let x: (i32, f64, u8) = (500, 6.4, 1);

    let _five_hundred = x.0;          // 解构
    let _six_point_four = x.1;        // 解构
    let _one = x.2;                   // 解构

}

另一个包含多个值的方式是 数组array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。

Rust 中,数组中的值位于中括号内的逗号分隔的列表中。

fn main() {
    let a = [1, 2, 3, 4, 5];

    
    let months = ["January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"];

    

    let a: [i32; 5] = [1, 2, 3, 4, 5];
    let first = a[0];
    let second = a[1];

    let index = 3;

    let element = a[index];

    println!("The value of element is: {}", element);
}

3.3 函数如何工作

和C++大同小异,这里不同的是具有返回值的函数

fn five() -> i32 {
    // 没有return
    // 没有分号
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {}", x);
}

3.4 注释

不赘述

3.5 控制流

if-else

fn main() {
    let number = 6;
    // 注意没有括号
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

在let语句中使用if

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

if 的每个分支的可能的返回值都必须是相同类型

循环语句

Rust 有三种循环:loopwhile 和 for

loop

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}

while

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}

for

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }


    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

第四章认识所有权

所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。

4.1 所有权

所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。

所有权规则

  1. Rust 中的每一个值都有一个被称为其 所有者owner)的变量。
  2. 值有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

字符串字面值不可变,这里以String为例

fn main() {
    println!("Hello, world!");
    // String在堆上
    // from 函数基于字符串字面值来创建 String
    let mut str = String::from("hello");
    str.push_str(", world");              // push_str() 在字符串后追加字面值
    println!("{}", str);
}

结果

对于 String 类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:

  • 必须在运行时向操作系统请求内存。
  • 需要一个当我们处理完 String 时将内存返回给操作系统的方法。

第一部分由我们完成:当调用 String::from 时,它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。

然而,第二部分实现起来就各有区别了。在有 垃圾回收garbage collectorGC)的语言中, GC 记录并清除不再使用的内存,而我们并不需要关心它。没有 GC 的话,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 allocate 配对一个 free

Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。

fn main() {
    {
        let s = String::from("hello"); // 从此处起,s 是有效的

        // 使用 s
    }                                  // 此作用域已结束,
                                       // s 不再有效
}

注意:在 C++ 中,这种 item 在生命周期结束时释放资源的模式有时被称作 资源获取即初始化Resource Acquisition Is Initialization (RAII))。如果你使用过 RAII 模式的话应该对 Rust 的 drop 函数并不陌生。

变量与数据交互的方式(一):移动

fn main() {
    let x = 5;
    let y = x;
}

基本变量赋值,x和y都在栈中,且值为5。

那么String类型呢?

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
}

String 由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。

长度表示 String 的内容当前使用了多少字节的内存。容量是 String 从操作系统总共获取了多少字节的内存。长度与容量的区别是很重要的,不过在当前上下文中并不重要,所以现在可以忽略容量。

当将 s1 赋值给 s2String 的数据被复制了,这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上数据。

 像C++中的浅拷贝

如果 Rust 也拷贝了堆上的数据,那么内存看起来就是这样的。如果 Rust 这么做了,那么操作 s2 = s1 在堆上数据比较大的时候会对运行时性能造成非常大的影响。

浅拷贝)这就有了一个问题:当 s2 和 s1 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存,Rust 则认为 s1 不再有效,因此 Rust 不需要在 s1 离开作用域后清理任何东西。看看在 s2 被创建之后尝试使用 s1 会发生什么;这段代码不能运行:

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

结果:

如果你在其他语言中听说过术语 浅拷贝shallow copy)和 深拷贝deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是浅拷贝。上面的例子可以解读为 s1 被 移动 到了 s2 中。那么具体发生了什么,如图

这样就解决了之前的问题!因为只有 s2 是有效的,当其离开作用域,它就释放自己的内存。

另外,这里还隐含了一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 自动 的复制可以被认为对运行时性能影响较小。

变量与数据交互的方式(二):克隆

如果 确实 需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    
    println!("{}, world!", s1);
    println!("{}, world!", s2);
}

这段代码的实际结果就是如下图

只在栈上的数据:拷贝

但这段代码似乎与我们刚刚学到的内容相矛盾:没有调用 clone,不过 x 依然有效且没有被移动到 y 中。

fn main() {
    let x = 5;
    let y = x;

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

原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 clone 并不会与通常的浅拷贝有什么不同,可以不用管它。

所有权与函数

将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。

简单的案例,见注释

fn main() {
    let s = String::from("hello");           // s 进入作用域

    takes_ownership(s);                  // s 的值移动到函数里 ...
                                                     // ... 所以到这里不再有效
    // println!("{}", s);                               // 报错
                

    let x = 5;                                  // x 进入作用域

    makes_copy(x);                      // x 应该移动函数里,
                                                      // 但 i32 是 Copy 的,所以在后面可继续使用 x

} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  // 所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

复杂一点的

fn main() {
    let s = String::from("hello");           // s 进入作用域

    takes_ownership(s);                  // s 的值移动到函数里 ...
                                                     // ... 所以到这里不再有效
    // println!("{}", s);                               // 报错
} 
fn takes_ownership(some_string: String) { // some_string 进入作用域
    let str = String::from(some_string);

    println!("{}", some_string);                      // 报错         
}

返回值与作用域

返回值也可以转移所有权

见注释

fn main() {
    let s1 = gives_ownership();         // gives_ownership 将返回值
                                                // 移给 s1

    let s2 = String::from("hello");     // s2 进入作用域

    let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                        // takes_and_gives_back 中, 
                                        // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {             // gives_ownership 将返回值移动给
                                             // 调用它的函数

    let some_string = String::from("hello"); // some_string 进入作用域.

    some_string                                      // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域

    a_string  // 返回 a_string 并移出给调用的函数
}

变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果想要函数使用一个值但不获取所有权该怎么办呢?如果还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,也可能想返回函数体中产生的一些数据。可以使用元组来返回多个值

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

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() 返回字符串的长度

    (s, length)
}

返回参数的所有权

见引用部分

4.4 引用与借用

下面是如何定义并使用一个calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:

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()
}

& 符号就是 引用,它允许你使用值但不获取其所有权。

注意:与使用 & 引用相反的操作是 解引用dereferencing),它使用解引用运算符,*

变量 s 有效的作用域与函数参数的作用域一样,不过当引用离开作用域后并不丢弃它指向的数据,因为我们没有所有权。

将获取引用作为函数参数称为 借用(borrowing)。

正如变量默认是不可变的,引用也一样(默认)不允许修改引用的值。

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

    change(&s);
}

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

结果

可变引用 

fn main() {
    // s也必须是mut的
    let mut s = String::from("hello");

    change(&mut s);
}

// 注意形参的形式
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。

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

    let r1 = &mut s;
    let r2 = &mut s;             // 错误
    
    println!("{}, {}", r1, r2);
    
}

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

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

相关文章

2023.08.01 驱动开发day8

驱动层 #include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/gpio.h> #include <linux/of_gpio.h>#…

NVM保姆级安装配置

nvm安装配置 1、NVM简介2、NVM安装三、NVM使用四、NVM常用命令 1、NVM简介 在项目开发过程中&#xff0c;使用到vue框架技术&#xff0c;需要安装node下载项目依赖&#xff0c;但经常会遇到node版本不匹配而导致无法正常下载&#xff0c;重新安装node却又很麻烦。为解决以上问…

Docker 网络模型使用详解 (1)Dockers网络基础

目录 环境准备 Dockers 网络基础 1.端口映射 查看随机映射端口范围 -p可以指定映射到本地端口 映射指定地址和指定端口 映射指定地址 宿主机端口随机分配 指定传输协议 端口暴露 容器互联 自定义网络 现在把container7加入到demo_net中 在启动一个容器加入到demo_net…

Linux进程(二)

文章目录 进程&#xff08;二&#xff09;Linux的进程状态R &#xff08;running&#xff09;运行态S &#xff08;sleeping&#xff09;阻塞状态D &#xff08;disk sleep&#xff09;深度睡眠T&#xff08;stopped&#xff09;状态X&#xff08;dead&#xff09;状态Z&#x…

数据结构 二叉树(C语言实现)

绪论 雄关漫道真如铁&#xff0c;而今迈步从头越。 本章将开始学习二叉树&#xff08;全文共一万两千字&#xff09;&#xff0c;二叉树相较于前面的数据结构来说难度会有许多的攀升&#xff0c;但只要跟着本篇博客深入的学习也可以基本的掌握基础二叉树。 话不多说安全带系好&…

关于 Ubuntu 长按 shift 无效, 按 Esc 直接进入 grub 改密码的解决方法

本次长按shift没有反应&#xff0c;直接进入了系统界面&#xff0c;所以改用长按Esc键&#xff0c;步骤如下&#xff1a; 1. 长按esc&#xff0c;进入grub>提示 2.输入grub>normal &#xff0c;回车 3.上一步回车后&#xff0c;继续敲击Esc &#xff0c;出现grub界面 …

HCIP---OSPF的优化

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.汇总&#xff1a; 目的&#xff1a;减少骨干区域的LSA的更新量 作用&#xff1a;OSPF的…

可缝合神经网络

文章目录 Stitchable Neural Networks摘要本文方法实验结果 Stitchable Neural Networks 摘要 包含大量强大的预训练模型族(如ResNet/DeiT)的model zoo已经达到了前所未有的范围&#xff0c;这对深度学习的成功有重要贡献。由于每个模型族都由具有不同尺度的预训练模型(例如&…

Linux 匿名页的生命周期

目录 匿名页的生成 匿名页生成时的状态 do_anonymous_page缺页中断源码 从匿名页加入Inactive lru引出 一个非常重要内核patch 匿名页何时回收 本文以Linux5.9源码讲述 匿名页的生成 用户空间malloc/mmap(非映射文件时&#xff09;来分配内存&#xff0c;在内核空间发生…

Docker实战-关于Docker镜像的相关操作(二)

导语   之前的分享中&#xff0c;我们介绍了关于Docker镜像的查询操作相关的内容&#xff0c;下面我们继续来介绍删除清理、导入导出、创建镜像等操作。 如何删除和清理镜像&#xff1f; 使用标签删除镜像 可以使用docker rmi 或者是 docker image rm 命令来删除镜像&#x…

Spring源码之XML文件中Bean标签的解析1

读取XML文件&#xff0c;创建对象 xml文件里包含Bean的信息&#xff0c;为了避免多次IO&#xff0c;需要一次性读取xml文件中所有bean信息&#xff0c;加入到Spring工厂。 读取配置文件 new ClassPathResource("applicationContext.xml")ClassPathResource是Sprin…

微信小程序nodejs+vue+uniapp高校食堂线上预约点餐系统

本次设计任务是要设计一个食堂线上预约点餐系统&#xff0c;通过这个系统能够满足管理员及学生的食堂线上预约点餐分享功能。系统的主要包括首页、个人中心、学生管理、菜品分类管理、菜品管理、关于我们管理、意见反馈、系统管理、订单管理等功能。 开发语言 node.js 框架&am…

Flink之RedisSink

在Flink开发中经常会有将数据写入到redis的需求,但是Flink官方并没有对应的扩展包,这个时候需要我们自己编译对应的jar资源,这个时候就用到了bahir,barhir是apahce的开源项目,是专门给spark和flink提供扩展包使用的,bahir官网,这篇文章就介绍下如何自己编译RedisSink扩展包. 下…

Prometheus服务器、Prometheus被监控端、Grafana、Prometheus服务器、Prometheus被监控端、Grafana

day03 day03Prometheus概述部署Prometheus服务器环境说明&#xff1a;配置时间安装Prometheus服务器添加被监控端部署通用的监控exporterGrafana概述部署Grafana展示node1的监控信息监控MySQL数据库配置MySQL配置mysql exporter配置mysql exporter配置prometheus监控mysql自动…

spring security + oauth2 使用RedisTokenStore 以json格式存储

1.项目架构 2.自己对 TokenStore 的 redis实现 package com.enterprise.auth.config;import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis…

Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本

Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本 引言 在 Java 生态系统中&#xff0c;Spring Boot、Spring Cloud 和 Spring Alibaba 是非常流行的框架&#xff0c;它们提供了丰富的功能和优雅的解决方案。然而&#xff0c;随着不断的发展和更新&…

如何建立含有逻辑删除字段的唯一索引

业务场景 在实际工作当中&#xff0c;遇到一个场景&#xff0c;就是在用户注册时&#xff0c;名字要全局唯一&#xff0c;当然&#xff0c;我们是可以对用户进行删除的&#xff0c;你会怎么去做&#xff1f; 分析 一般来说&#xff0c;我们可以在用户注册请求时&#xff0c…

基于时空RBF神经网络的混沌时间序列预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【阵列信号处理】空间匹配滤波器、锥形/非锥形最佳波束成形器、样本矩阵反演 (SMI) 研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

chrome扩展在popup、background、content之间通信解决传输文件问题

文章目录 背景介绍案例介绍代码示例popup页面&#xff0c;上传文件页面popup页面&#xff0c;js上传代码&#xff0c;file文件转base64background监听消息&#xff0c;base64转file文件&#xff0c;axios上传 附-转base64后直接下载 背景介绍 示例扩展API版本MV2。 以弹…