【Rust】集合的使用——Rust语言基础16

news2025/3/22 21:23:20

文章目录

  • 1. 前言
  • 2. Vector
    • 2.1. 构建一个 vector
    • 2.2. 获取 vector 中的元素
    • 2.3. 遍历 vector
    • 2.4. 使用枚举来储存多种类型
  • 3. String
    • 3.1. 新建字符串
    • 3.2. 更新字符串
    • 3.3. 字符串的内部结构
      • 3.3.1. 字符串如何访问内部元素?
      • 3.3.2. 字节、标量值和字形簇
    • 3.4. 字符串 slice
    • 3.5. 字符串遍历
  • 4. Hash Map
    • 4.1. 新建一个 Hash Map
    • 4.2. 获取 map 中值
    • 4.4. 哈希 map 和所有权
    • 4.5. 覆盖关键字的值
    • 4.6. 当关键字不存在时才插入键值对
    • 4.7. 根据旧值更新一个值

1. 前言

看过上一篇博客的长篇大论一定很疲惫,不过笔者保证这一篇我们将会很轻松的度过~

在其它编程语言中也会有集合这样的类似概念,例如在 C++ 中将这种数据结构称为容器,而在 Rust 中则将这些数据结构称为集合collections)。集合是一种非常有用的数据结构,当有一类具有相同属性或者共同点的东西归类或统计时,集合就会派上大用处。

Rust 中常见的几个集合:

  • vector :存储一系列同类型数量可变的值;
  • 字符串(string):这时字符的集合,本文将会着重介绍它;
  • 哈希 map(hash map):每个成员是由一个唯一存在的关键字(key)和其所一一对应的值(value) 所组成。

接下来让我们一个个的认识并掌握!

2. Vector

Vec<T>,也被称为 vectorvector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

正如所看到的一样这里的 <> 表示这是一个泛型实现的类型,到目前为止可以不用过于深入理解泛型,只要大概明白它的概念即可,这里的 T 可以被任何类型替换,表示该向量将会且只能存储这种类型的值。

2.1. 构建一个 vector

创建一个新的空 vector,可以调用 Vec::new 函数:

    let v: Vec<i32> = Vec::new();

可以看到这里创建的 vector 并没有初始化,因此我们在声明的时候需要指明其具体类型。

当有初始值时,创建的 vector 允许不显示声明其类型:

    let v = vec![1, 2, 3];

这里虽然没有显示的注明 vector 的类型,但是编译器通过其初始值可以推断出 v 的类型是 Vec<i32>

当我们新建完一个空的 vector 后可以用 push 方法向其添加元素:

	// 注意,此时则需要创建可变类型
    let mut v = Vec::new();

	// 向最后插入一个新的元素
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);

	// 删除末尾的一个元素
	v.pop();

[注]:Rust 编译器也可以通过后面所添加的值来推断 vector 类型。

2.2. 获取 vector 中的元素

有两种方式可以获取 vector 中的元素:

  • 通过索引的方式访问获取;
  • 使用 get 方法获取。
    let v = vec![1, 2, 3, 4, 5];
	// 通过索引的方式访问
    let third: &i32 = &v[2];
    println!("The third element is {third}");

	// 通过 vector 的 get 方法访问
    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }

这里需要注意的一个点是 get 方法返回的是 Option<&T> 类型并非元素值本身,因此我们需要通过 match 提取具体的值。

越界问题

访问越界这是一个很常见的问题,我们来看看 Rust 是如何处理的:

	let v = vec![32, 26, 48];

    let unexist = &v[10];		// 索引
    let unexist = v.get(10);	// get

首先这段代码是可以编译通过的,因为语法没有任何问题,但是在运行时将会使得程序崩溃,由于 Rust 发现试图去访问一个超过 vector 范围的内存,直接会终止程序运行。

那么当我们注释掉 “索引” 那行代码后,这段程序时可以正常运行的,这是何原因呢?

由于 get 返回的是 Option<&T> 类型,也就是当有值的时候会返回 Some(i),没有值时候返回 None,这两个都不会导致程序崩溃。这样设计的原因在于 Rust 考虑到这种越界特殊情况的处理工作也可以交给开发者自己处理,而不是一味的终止代码运行,这是很不好的体验。

再看另外一种情况:

    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0];

    v.push(6);

    println!("The first element is: {first}");

在编译这段代码时会报错:

$ cargo run
   Compiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:5
  |
4 |     let first = &v[0];
  |                  - immutable borrow occurs here
5 |
6 |     v.push(6);
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("The first element is: {first}");
  |                                     ------- immutable borrow later used here

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

编译器告诉我们,因为 first 这里用的是 v 的不可变引用,而下面尝试调用 push 方法时的 v 应该是可变变量,而在其后又希望再继续使用 first 这样的操作是不被允许的,其实当我们注释掉最后一行的打印这段代码也是没问题的。

那么为什么呢?这个看起来是之前我们学习引用那块学习到的一个规则 “不能在相同作用域中同时存在可变和不可变引用”

这里就涉及到 vector 的工作原理,vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存

2.3. 遍历 vector

Rust 提供了非常便利的遍历方式,无需我们自己通过索引一个个的访问:

    let v = vec![32, 26, 48];
    for i in &v {
        println!("{i}");
    }

也可以遍历过程中使用元素的值:

    let mut v = vec![32, 26, 48];
    for i in &mut v {
        *i += 50;
    }

修改可变引用所指向的值,需要在使用 += 运算符之前必须使用解引用运算符(*)获取 i 中的值。

2.4. 使用枚举来储存多种类型

上文提到了 vector 只能够存储同一类型的值,不过这一点概念是相对的。还记得之前我们学习过一种叫做枚举的类型吗?

没错!你知道我想说什么了,是的,枚举变量中可以包含各种各样的类型,而对外这一个整体只会被认作同一个类型那就是枚举本身。

    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

这样一来 row 的类型就是 Vec<enum SpreadsheetCell>,再配合 match,我们就可以利用同一个 vector 存储不同类型的值:

	enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row  = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

    for i in &row {
        match i {
            SpreadsheetCell::Int(i) => println!("{}", i),
            SpreadsheetCell::Float(f) => println!("{}", f),
            SpreadsheetCell::Text(s) => println!("{}", s),
        }
    }

同样的当 vector 变量离开自己所属的作用域时将会被丢弃,所有的内容将会清空,同时变量本身也会清理变得无效。

3. String

这一小节是本文的重头戏,请各位同学打起精神仔细阅读本小节。

其实之前我们已经使用过 String 类型,因此它对于我们并不算陌生,而 String 类型在初学者一路学习过来很容易与另一个类型 str 搞混,这个类型值之前有解释过,strString 中的一部分值的引用,这是两种类型。

RustString 类型实际上是对 Vec<u8> 类型的封装实现的。且字符串是 UTF-8 编码的。

3.1. 新建字符串

使用 new 方法创建一个空的字符串。

    let mut s = String::new();

有两种方式可以让我们为空的字符串填充数据:

  • to_string() 方法;
  • String::from() 函数。
    let data = "initial contents";

    let s = data.to_string();
    // 效果与上行代码相同
    //let s = String::from("initial contents");


    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();

正因为 String 类型是 UTF-8 编码的,因此可以使用任何符合 UTF-8 编码的数据:

    let hello = String::from("السلام عليكم");
    let hello = String::from("Dobrý den");
    let hello = String::from("Hello");
    let hello = String::from("שלום");
    let hello = String::from("नमस्ते");
    let hello = String::from("こんにちは");
    let hello = String::from("안녕하세요");
    let hello = String::from("你好");
    let hello = String::from("Olá");
    let hello = String::from("Здравствуйте");
    let hello = String::from("Hola");
    let hello = String::from("🌟");

3.2. 更新字符串

String 的大小可以增加,其内容也可以改变。可以方便的使用 + 运算符或 format! 宏来拼接 String 值。
[注]:还记得吧,带有 ! 表示一个宏。忘了的同学罚款3000w。

可以通过 push_str 函数为 String 类型追加 str 类型而使得 String 变长。

    let mut s = String::from("hello,");
    s.push_str("world!");

执行这两行代码之后,s 将会包含 hello,world!push_str 方法采用字符串 slice,因为该方法并不需要获取参数的所有权。

    let mut s1 = String::from("hello,");
    let s2 = "world!";
    s1.push_str(s2);
    println!("s2 is {s2}");

例如,这里希望在将 s2 的内容附加到 s1 之后还能使用它。是的,由于 push_str 使用的字符串 slice,还记得吧,str 只是 String 内容的一部分的引用,是的它是引用,因此不会获取所有权,因此这里在后面是可以继续用 s2

还可以通过使用 push 方法为末尾添加一个字符。

    let mut s = String::from("Ma");
    s.push('x');

通过使用 + 组合两个字符串。

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

执行完这些代码之后,字符串 s3 将会包含 Hello, world!s1 在相加后不再有效的原因,和使用 s2 的引用的原因,与使用 + 运算符时调用的函数签名有关。+ 运算符使用了 add 函数,这个函数签名看起来像这样:

fn add(self, s: &str) -> String {

在标准库中你会发现,add 的定义使用了泛型和关联类型。在这里我们替换为了具体类型,这也正是当使用 String 值调用这个方法会发生的。这个签名提供了理解 + 运算那微妙部分的线索。

首先,s2 使用了 &,意味着我们使用第二个字符串的 引用 与第一个字符串相加。这是因为 add 函数的 s 参数:只能将 String&str 相加,不能将两个 String 值相加。&s2 的类型是 &String, 而不是 add 第二个参数所指定的 &str。但为什么编译器没有报错呢?

是的,你想的没错,这里的确发生了隐式的强制类型转换,将 &String 转为 &str,这是 Rust 替我们完成的工作。而由于 s2 这里用的是引用,因此在 add 之后还是可以正常使用的。而这里的 s1 可就惨了,被 add 获取的所有权,在 add 之后就不能在使用了。

再来看 format! 这个宏的使用:

    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");

这些代码也会将 s 设置为 “tic-tac-toe”format!println! 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 String

3.3. 字符串的内部结构

3.3.1. 字符串如何访问内部元素?

在大多数编程语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问 String 的一部分,会出现一个错误。

    let s1 = String::from("hello");
    let h = s1[0];

这样的访问编译器将会报错:

$ cargo run
   Compiling collections v0.1.0 (file:///projects/collections)
error[E0277]: the type `str` cannot be indexed by `{integer}`
 --> src/main.rs:3:16
  |
3 |     let h = s1[0];
  |                ^ string indices are ranges of `usize`
  |
  = help: the trait `SliceIndex<str>` is not implemented for `{integer}`, which is required by `String: Index<_>`
  = note: you can use `.chars().nth()` or `.bytes().nth()`
          for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
  = help: the trait `SliceIndex<[_]>` is implemented for `usize`
  = help: for that trait implementation, expected `[_]`, found `str`
  = note: required for `String` to implement `Index<{integer}>`

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

错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。

上文提到了 String 其实是一个 Vec<u8> 的封装,让我们来看一个例子:

    let hello = String::from("Hello");

在这里,len 的值是 5,这意味着储存字符串 “Hello” 的 Vec 的长度是五个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。)

    let hello = String::from("Здравствуйте");

当问及这个字符是多长的时候有人可能会说是 12。然而,Rust 的回答是 24。这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。

let hello = "Здравствуйте";
let answer = &hello[0];

[注]:这段代码无法编译通过,在这里仅为说明问题。

我们已经知道 answer 不是第一个字符 3。当使用 UTF-8 编码时,(西里尔字母的 ZeЗ 的第一个字节是 208,第二个是 151,所以 answer 实际上应该是 208,不过 208 自身并不是一个有效的字母。返回 208 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果 &"hello"[0] 是返回字节值的有效代码,它也会返回 104 而不是 h

为了避免返回意外的值并造成不能立刻发现的 bugRust 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。

3.3.2. 字节、标量值和字形簇

这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节标量值字形簇(最接近人们眼中字符的概念)。
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 u8 值看起来像这样:

[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rustchar 类型那样,这些字节看起来像这样:

['न', 'म', 'स', '्', 'त', 'े']

这里有六个 char,不过第四个和第六个都不是字母,它们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:

["न", "म", "स्", "ते"]

Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。

最后一个 Rust 不允许使用索引获取 String 字符的原因是,索引操作预期总是需要常数时间(O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。

3.4. 字符串 slice

索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice

let hello = "Здравствуйте";

let s = &hello[0..4];

这里,s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 s 将会是 “Зд”

如果获取 &hello[0..1] 会发生什么呢?答案是,在运行中 Rust 将会报错,不允许将一个编码的字符拆开输出,因为没有与之对应的字符。

3.5. 字符串遍历

操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 chars 方法。对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值,接着就可以遍历其结果来访问每一个元素了:

for c in "Зд".chars() {
    println!("{c}");
}

运行结果:

З
д

另外 bytes 方法返回每一个原始字节,这可能会适合你的使用场景:

for b in "Зд".bytes() {
    println!("{b}");
}

这些代码会打印出组成 String4 个字节:

208
151
208
180

总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 String 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。

好消息是标准库提供了很多围绕 String&str 构建的功能,来帮助我们正确处理这些复杂场景。请务必查看这些使用方法的文档,例如 contains 来搜索一个字符串,和 replace 将字符串的一部分替换为另一个字符串。

好了,本文最繁杂的部分已经结束了,接下来将会非常轻松的进行。

4. Hash Map

哈希 map(hash map)HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。

在很多变成语言都支持 map 这个数据结构,因此也不算陌生。

4.1. 新建一个 Hash Map

使用 new 创建一个空的 HashMap,并使用 insert 增加元素。这里我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

注意必须首先 use 标准库中集合部分的 HashMap。像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 HashMap 的键类型是 String 而值类型是 i32

4.2. 获取 map 中值

通过 get 方法根据 关键字 可以获取对应的

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");
    let score = scores.get(&team_name).copied().unwrap_or(0);

这里,score 是与蓝队分数相关的值,应为 10get 方法返回 Option<&V>,如果某个键在哈希 map 中没有对应的值,get 会返回 None

程序中通过调用 copied 方法来获取一个 Option<i32> 而不是 Option<&i32>,接着调用 unwrap_orscores 中没有该键所对应的项时将其设置为零。

可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是 for 循环:

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{key}: {value}");
    }

这会以任意顺序打印出每一个键值对:

Yellow: 50
Blue: 10

4.4. 哈希 map 和所有权

对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者:

    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 这里 field_name 和 field_value 不再有效,
    // 尝试使用它们看看会出现什么编译错误!

insert 调用将 field_namefield_value 移动到哈希 map 中后,将不能使用这两个绑定。

如果将值的引用插入哈希 map,这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。

4.5. 覆盖关键字的值

当插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25);

    println!("{scores:?}");

这会打印出 {"Blue": 25}。原始的值 10 则被覆盖了。

4.6. 当关键字不存在时才插入键值对

我们经常会检查某个特定的键是否已经存在于哈希 map 中并进行如下操作:如果哈希 map 中键已经存在则不做任何操作。如果不存在则连同值一块插入。

    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);

    println!("{scores:?}");

Entryor_insert 方法在键对应的值存在时就返回这个值的可变引用如果不存在则将参数作为新值插入并返回新值的可变引用。这比编写自己的逻辑要简明的多,另外也与借用检查器结合得更好。

运行结果会打印出 {"Yellow": 50, "Blue": 10}。第一个 entry 调用会插入黄队的键和值 50,因为黄队并没有一个值。第二个 entry 调用不会改变哈希 map 因为蓝队已经有了值 10

4.7. 根据旧值更新一个值

另一个常见的哈希 map 的应用场景是找到一个键对应的值并根据旧的值更新它。计数一些文本中每一个单词分别出现了多少次:

    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{map:?}");

这会打印出 {"world": 2, "hello": 1, "wonderful": 1}。你可能会看到相同的键值对以不同的顺序打印:回忆一下“获取 map 中的值”部分中提到遍历哈希 map 会以任意顺序进行。

其中 split_whitespace 方法返回一个由空格分隔 text 值子 slice 的迭代器。or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。


下一篇《Rust语言基础17》


觉得这篇文章对你有帮助的话,就留下一个赞吧v*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用知识共享署名-非商业性-相同方式共享 4.0 国际许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/146397162

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

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

相关文章

centos 7 部署ftp 基于匿名用户

在 CentOS 7 上搭建基于匿名用户的 FTP 服务&#xff0c;可按以下步骤进行&#xff1a; 1. 安装 vsftpd 服务 vsftpd 是一款常用的 FTP 服务器软件&#xff0c;可使用以下命令进行安装&#xff1a; bash sudo yum install -y vsftpd2. 启动并设置开机自启 vsftpd 服务 bash …

Apache SeaTunnel脚本升级及参数调优实战

最近作者针对实时数仓的Apache SeaTunnel同步链路&#xff0c;完成了双引擎架构升级与全链路参数深度调优&#xff0c;希望本文能够给大家有所启发&#xff0c;欢迎批评指正&#xff01; Apache SeaTunnel 版本 &#xff1a;2.3.9 Doris版本&#xff1a;2.0.6 MySQL JDBC Conne…

学习记录-cssjs-综合复习案例(二)

目录 商城复合案例功能实现&#xff08;二&#xff09;商城首页实现步骤1.准备工作2. 搭建html框架3. 编写js代码 完整实例代码完整项目心得 商城复合案例功能实现&#xff08;二&#xff09; 使用html&#xff0c;css&#xff0c;基于bootstrap框架以及媒体查询搭建响应式布局…

图解AUTOSAR_CP_EEPROM_Abstraction

AUTOSAR EEPROM抽象模块详细说明 基于AUTOSAR标准的EEPROM抽象层技术解析 目录 1. 概述 1.1 核心功能1.2 模块地位2. 架构概览 2.1 架构层次2.2 模块交互3. 配置结构 3.1 主要配置容器3.2 关键配置参数4. 状态管理 4.1 基本状态4.2 状态转换5. 接口设计 5.1 主要接口分类5.2 接…

汇川EASY系列之以太网通讯(MODBUS_TCP做从站)

汇川easy系列PLC做MODBUS_TCP从站,不需要任何操作,但是有一些需要知道的东西。具体如下: 1、汇川easy系列PLC做MODBUS_TCP从站,,ModbusTCP服务器默认开启,无需设置通信协议(即不需要配置),端口号为“502”。ModbusTCP从站最多支持31个ModbusTCP客户端(ModbusTCP主站…

QT 图表(拆线图,栏状图,饼状图 ,动态图表)

效果 折线图 // 创建折线数据系列// 创建折线系列QLineSeries *series new QLineSeries;// series->append(0, 6);// series->append(2, 4);// series->append(3, 8);// 创建图表并添加系列QChart *chart new QChart;chart->addSeries(series);chart->setTit…

基于vue框架的在线影院系统a079l(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,电影,电影类别,电影库 开题报告内容 基于Vue框架的在线影院系统开题报告 一、研究背景与意义 随着文化娱乐产业的蓬勃发展&#xff0c;电影院作为人们休闲消遣的重要场所&#xff0c;其管理效率和服务质量直接影响着顾客的观影体…

OpenCV图像拼接(1)概述

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 此图说明了在Stitcher类中实现的拼接模块流程。使用该类&#xff0c;可以配置/移除某些步骤&#xff0c;即根据特定需求调整拼接流程。流程中的所…

基于ssm学科竞赛小程序的设计及实现(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着信息时代的来临&#xff0c;过去的学科竞赛管理方式的缺点逐渐暴露&#xff0c;本次对过去的学科竞赛管理方式的缺点进行分析&#xff0c;采取计算机方式构建学科竞赛小程序。本文通过阅读相关文献&#xff0c;研究国内外相关技术&#xff0c;提出了一种关于竞赛信息…

[特殊字符][特殊字符][特殊字符][特殊字符][特殊字符][特殊字符]壁紙 流光染墨,碎影入梦

#Cosplay #&#x1f9da;‍♀️Bangni邦尼&#x1f430;. #&#x1f4f7; 穹妹 Set.01 #后期圈小程序 琼枝低垂&#xff0c;霜花浸透夜色&#xff0c;风起时&#xff0c;微光轻拂檐角&#xff0c;洒落一地星辉。远山隐于烟岚&#xff0c;唯余一抹青黛&#xff0c;勾勒出天光水…

虚拟机的三种 Linux 网络配置原理图解读

前言 虚拟机的网络连接方式主要有 三种模式&#xff1a;桥接模式&#xff08;Bridged&#xff09;、NAT 模式&#xff08;Network Address Translation&#xff09;、仅主机模式&#xff08;Host-Only&#xff09;。每种模式都有不同的使用场景和网络适应性&#xff0c;具体解释…

AI Agent系列(七) -思维链(Chain of Thought,CoT)

AI Agent系列【七】 前言一、CoT技术详解1.1 CoT组成1.2 CoT的特点 二、CoT的作用三、CoT的好处四、CoT适用场景五、CoT的推理结构 前言 思维链(Chain of Thought,CoT)&#xff0c;思维链就是一系列中间的推理步骤(a series of intermediate reasoning steps)&#xff0c;通过…

SpringBoot实现异步调用的方法

在Java中使用Spring Boot实现异步请求和异步调用是一个常见的需求&#xff0c;可以提高应用程序的性能和响应能力。以下是实现这两种异步操作的基本方法&#xff1a; 一、异步请求&#xff08;Asynchronous Request&#xff09; 异步请求允许客户端发送请求后立即返回&#x…

PurpleLlama大模型安全全套检测方案

1. 引入 PurpleLlama是Meta的大模型安全整体解决方案&#xff08;参考1&#xff09;&#xff0c;它包括了 &#xff08;1&#xff09;安全评估 CyberSecEval是一个用于评估大型语言模型&#xff08;LLMs&#xff09;安全风险的基准套件&#xff0c;其目标是解决随着 LLMs 的广…

vue el-table 设置selection选中状态

toggleRowSelection 方法 vue el-table 设置selection选中状态 关键代码 multipleTableRef.value!.toggleRowSelection(item, true);<el-table:data"data":border"setBorder"v-bind"$attrs"row-key"id"stripestyle"width: 1…

STM32学习笔记之常用总线(原理篇)

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

【数据结构】栈(Stack)、队列(Queue)、双端队列(Deque) —— 有码有图有真相

目录 栈和队列 1. 栈&#xff08;Stack&#xff09; 1.1 概念 1.2 栈的使用&#xff08;原始方法&#xff09; 1.3 栈的模拟实现 【小结】 2. 栈的应用场景 1、改变元素的序列 2、将递归转化为循环 3、逆波兰表达式求值 4、括号匹配 5、出栈入栈次序匹配 6、最小栈…

OpenCV中的矩阵操作

OpenCV中的矩阵操作主要围绕Mat类展开&#xff0c;涵盖创建、访问、运算及变换等。 1. 创建矩阵 ‌零矩阵/单位矩阵‌&#xff1a; Mat zeros Mat::zeros(3, 3, CV_32F); // 3x3浮点零矩阵 Mat eye Mat::eye(3, 3, CV_32F); // 3x3单位矩阵 自定义初始化‌&#xff1a…

OAK相机入门(一):深度测距原理

文章目录 1. 测距参数介绍2. 测距原理3. 总结 官方文档 Configuring Stereo Depth 1. 测距参数介绍 理论范围&#xff1a;0.2-35m 推荐范文&#xff1a;不低于0.5m 存储类型&#xff1a;uint16&#xff0c;0代表没有数据&#xff0c;或者测不到 2. 测距原理 通过视差进行测距…

Powershell WSL .wslconfig 实现与宿主机的网络互通

前言.wslconfig .wslconfig 用于在 WSL 2 上运行的所有已安装发行版中配置全局设置 wsl 2 网络模式介绍 Bridged (外部): 桥接模式将虚拟机的网络连接直接桥接到物理网络适配器上Mirrored (镜像): 镜像模式并不是一个标准的 Hyper-V 网络类型,但它通常指的是在网络适配器级…