Rust 之五 所有权、.. 和 _ 语法、引用和切片、Vec<T>、HashMap<K, V>
概述
Rust 的基本语法对于从事底层 C/C++ 开发的人来说多少有些难以理解,虽然官方有详细的文档来介绍,不过内容是相当的多,看起来也费劲。本文通过将每个知识点简化为 一个 DEMO + 每种特性各用一句话描述的形式来简化学习过程,提高学习效率。
所有 DEMO 可以从 https://gitee.com/itexp 获取
所有权
所有权(ownership)是 Rust 用于如何管理内存的一组规则。它决定了数据的生命周期、如何管理内存,并且确保数据在使用过程中不发生数据竞争。所有权系统可以有效地避免内存泄漏和悬挂指针(dangling pointers)等问题,从而保证程序的安全性和高效性。
-
Rust 中的每一个值都有一个 所有者(owner)
- 每个值(数据)都有一个与之相关联的变量,称为所有者(owner)。
- 每当值被创建时,它有一个明确的所有者。
-
值在任一时刻有且只有一个所有者
- 变量的所有者是唯一的,这意味着当一个值的所有者发生转移时,原所有者将不再拥有对该值的访问权限。
-
当所有者(变量)离开作用域,这个值将被丢弃
- 当一个变量超出其作用域时,Rust 会自动释放该变量占用的内存。这个过程被称为“销毁”或“释放”(drop)。
-
编译器在编译时会根据一系列的规则进行检查,如果违反了任何这些规则,程序都不能编译。
转移(Move)
当将一个变量赋值给另一个变量、作为函数参数传递或作为返回值时,如果类型未实现 Copy trait,则 Rust 会执行转移操作。此时,原变量失去所有权,无法再被使用。
fn main() {let s1 = String::from("Hello"); // s1 是 String 类型的所有者let s2 = s1; // s1 的所有权被转移给 s2// println!("{}", s1); // 错误!s1 的所有权已转移,不能再使用 s1println!("{}", s2); // 正确!s2 是所有者
}
克隆(Clone)
如果需要在转移所有权时保留原始值,可以通过 克隆(clone)来创建一个值的副本,这样两个变量可以各自拥有一份数据。新的数据与原数据是没有任何关系两个独立值,他们的生命周期、作用域等都可以不同。
fn main() {let s1 = String::from("Hello");let s2 = s1.clone(); // 克隆 s1,s2 拥有一份新副本println!("{}", s1); // 正常工作!s1 仍然有效println!("{}", s2); // 正常工作!s2 是 s1 的副本
}
..
语法
在 Rust 中,..
语法主要用于表示范围或忽略某些内容。
表示范围
..
可以作为范围操作符。基本格式有 start_index..end_index
是前闭后开区间;start_index..=end_index
是闭区间;start_index..
是从 start_index
开始的所有内容;..end_index
是表示到 end_index
结束的所有内容;只写 ..
则表示全范围。
其中,start_index
和 end_index
可以是任意整数类型(i32、u32、i64 等),并且可以省略其一或全部。如果要用负数,则前提是我们的范围本身支持使用负数索引!
-
如果将
start_index
和end_index
指定为浮点数,则编译会报错 -
当
start_index >= end_index
时,则表示为空范围 -
start_index..end_index
是std::ops::Range<Idx>
类型;start_index..=end_index
是std::ops::RangeInclusive<Idx>
类型
定义范围变量
..
可以作为范围操作符用来定义范围变量。
fn main() {/* ========================= 闭区间 ============================ */let range = -5..=5; // 从 -5 到 5(包括 5)for i in range {println!("{}", i); // 输出 -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}/* ========================= 前闭后开区间 ============================ */let range = 1..5; // 包含 1 到 4 的数字,不含 5for i in range {println!("{}", i); // 输出 1, 2, 3, 4}/* ========================= 只有开始区间 ============================ */let range = 3..; // 从 3 开始,直到无限大for i in range.take(5) { // take(5) 限制最多打印 5 个数字println!("{}", i); // 输出 3, 4, 5, 6, 7}/* ========================= 只有结束区间 ============================ */// let range = ..5; // 包含 0 到 4 的数字// for i in range {// println!("{}", i);// }
}
定义切片
..
可以作为范围操作符可以用来定义切片。
let arr = [0, 1, 2, 3, 4];let slice = &arr[1..4]; // [1, 2, 3]println!("Slice: {:?}", slice);
模式匹配
..
可以用在模式匹配中以匹配某个范围。
let num = 42;
match num {1..=10 => println!("1到10"),11..50 => println!("11到49"), // 不包含50_ => println!("其他"),
}
忽略某些内容
_
忽略单个值,..
忽略剩余所有值。
解构操作
在解构结构体或者元组时,..
语法用于表示忽略某些字段。它是解构模式的一部分,可以让你在解构时不关心结构体的某些字段,只关心你需要的字段。
let point = (3, 5, 10);
// 只取第一个值,忽略后续所有值
let (x, ..) = point;
println!("x = {}", x); // x = 3struct Config {width: u32,height: u32,debug: bool,
}
fn init_config(Config { width, height, .. }: Config) {println!("初始化尺寸: {}x{}", width, height);
}
结构体更新语法
在结构体更新语法中,可以使用 ..结构体变量
的形式来用旧的结构体变量初始化新的结构体,而无需显示指明所有结构体成员挨个赋值。
#[derive(Debug)]struct Point { x: i32, y: i32 }let p1 = Point { x: 10, y: 20 };let p2 = Point { x: 30, ..p1 }; // 其中的 ..p1 表示自动使用 p1 的其他值println!("p2: {:?}", p2); // Point { x: 30, y: 20 }
..
必须放在结构体字面量的最后,且只能使用一次
模式匹配
在匹配匹配中,可以使用 ..
格式来忽略其他字段。
match point {(0, ..) => println!("x 为 0"),(.., 0) => println!("z 为 0"),_ => println!("其他情况"),
}
_
语法
在 Rust 中,_
通常用于表示忽略个元素或由编译器推断类型的占位符。
忽略某个元素
_
忽略单个值,..
忽略剩余所有值。
忽略未使用的变量
let _ = 42; // 明确忽略值,避免编译器警告
let s = String::from("hello");
let _ = s; // s 立即被 Drop
// let _x = s; // s 会在 _x 的作用域结束时 Drop
忽略函数参数和返回值
fn foo(_: i32) {println!("忽略参数");
}
foo(10); // 调用时传入的参数被忽略let _ = setup_test_env(); // 忽略初始化返回值
忽略模式匹配的部分值
match Some(42) {Some(_) => println!("有值,但忽略具体内容"),None => println!("无值"),_ => println!("其他"),
}
忽略结构体字段
struct Point { x: i32, y: i32 }
let p = Point { x: 10, y: 20 };
let Point { x: _, y } = p; // 只绑定 y,忽略 x
忽略元组值
let (x, _) = (1, 2); // 只绑定元组的第一个元素,忽略第二个
类型推断占位符
泛型类型推断
let numbers: Vec<_> = vec![1, 2, 3].into_iter().collect();
// 编译器推断 `Vec<i32>`,等价于 `Vec<i32>`
use std::collections::HashMap;
let map: HashMap<_, _> = vec![("a", 1), ("b", 2)].into_iter().collect();
// 编译器推断为 `HashMap<&str, i32>`
闭包参数类型推断
let square = |x: _| x * x; // 错误:不能直接用于闭参
let square = |x| x * x; // 正确:编译器自动推断为 `i32`(根据上下文)
生命周期占位符
作为生命周期占位符时,需要符合生命周期标准的格式 '_
。关于声明周期,我们将在 Rust 之六 语句和表达式、作用域、生命周期、变量与常量、控制流 中详细学习!
struct Parser<'a> {data: &'a str,
}impl Parser<'_> { // 等价于 impl<'a> Parser<'a>fn parse(&self) -> &str {self.data}
}
数字字面量分隔符
let million = 1_000_000; // 提高可读性,等价于 1000000
let hex = 0xdead_beef; // 十六进制分隔
集合类型
集合类型(Collections)这种数据结构是用于存储和管理多个数据的容器,Rust 标准库中提供了 Vec<T>
、HashMap<K, V>
、HashSet<T>
、BTreeMap<K, V>
、BTreeSet<T>
、LinkedList<T>
和 VecDeque<T>
等集合类型。
不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的
Vec<T>
Vec<T>
(动态数组)类型通常被称为 Vector(向量)类型,它允许我们用一个单独的数据结构中在堆内存中彼此相邻地排列的储存多于一个相同类型的值。它是一个动态数组,可以根据需要自动增长和缩小。
- Vec 会根据需要自动调整其容量。当 Vec 的容量不够时,它会自动重新分配更多内存。
结构
Vec<T>
内部由一个指向堆内存中数据的起始地址(*mut T
)的指针、 指示当前存储的元素数量的长度(len)以及表示分配的内存可容纳的元素总数(len ≤ capacity)的容量(capacity) 这三个部分组成。
pub struct Vec<T> {ptr: NonNull<T>, // 堆内存指针len: usize, // 当前元素数量cap: usize, // 分配的总容量
}
定义
-
Rust 为 Vector 提供了很多预定义的接口,可以调用
Vec::new()
函数来创建一个新的空 Vector。let v: Vec<i32> = Vec::new(); // 显示的将 `Vec<T>` 中的 `T` 指定为了 `i32` 类型。 println!("{:?}", v); // {:?} 是调试格式化输出,输出 Vec 的内容 let mut v: Vec<i32> = Vec::new(); v.push(1); // 向向量中添加元素 v.push(2); println!("{:?}", v);
-
最常用的方法是以
let 变量名 = vec![元素 1, 元素 2, 元素 n];
这样的格式用初始值来创建一个Vec<T>
而让 Rust 推断出储存值的类型。let v = vec![1, 2, 3]; // vec! 宏创建一个 Vec,类型由编译器推断 println!("{:?}", v); let mut v = vec![1, 2, 3]; v[0] = 4; // 修改第一个元素 v.push(5); // 在末尾添加元素 println!("{:?}", v);
其中,
vec!
是一个宏,这个宏会根据我们提供的值来创建一个新的 Vector,Rust 可以推断出变量v
的类型是Vec<i32>
-
还可以用 Rust 为 Vector 提供预接口
with_capacity()
来预分配容量,然后后续补充值let mut v = Vec::with_capacity(10); // 初始容量为 10,避免频繁扩容 v.push(1); v.push(2); println!("{:?}", v);
-
Vec 在 Rust 中是拥有所有权的,这意味着当 Vec 被移动到另一个变量时,原来的变量就不再可用。
let v1 = vec![1, 2, 3]; let v2 = v1; // v1 的所有权转移给 v2 // println!("{:?}", v1); // 错误,v1 不再有效 println!("{:?}", v2); // 输出 [1, 2, 3]
访问元素
无论 Vector 是否可变,都是可以使用 变量名[从 0 开始的索引]
来访问其中的元素。此外,还可以使用 Rust 还为 Vector 提供的一些常用的方法来访问。
let mut v = vec![1, 2, 3];println!("v[0]: {}", v[0]); // 访问第一个元素println!("v[1]: {}", v[1]); // 访问第二个元素println!("v[2]: {}", v[2]); // 访问第三个元素println!("v.len(): {}", v.len()); // 获取向量长度println!("v.is_empty(): {}", v.is_empty()); // 判断向量是否为空println!("v.capacity(): {}", v.capacity()); // 获取向量容量println!("v.get(0): {:?}", v.get(0)); // 获取第一个元素,返回 Option<&i32>println!("v.get(3): {:?}", v.get(3)); // 获取第四个元素,返回 Noneprintln!("v.pop(): {:?}", v.pop()); // 弹出最后一个元素,返回 Option<i32>println!("v: {:?}", v); // 弹出后向量的内容println!("v.remove(0): {:?}", v.remove(0)); // 删除第一个元素,返回 i32println!("v: {:?}", v); // 删除后向量的内容println!("v.clear()"); // 清空向量v.clear(); // 清空向量println!("v: {:?}", v); // 清空后向量的内容println!("v.is_empty(): {}", v.is_empty()); // 判断向量是否为空println!("v.capacity(): {}", v.capacity()); // 获取向量容量println!("v.push(1)"); // 向向量中添加元素v.push(1); // 向向量中添加元素println!("v: {:?}", v); // 添加后向量的内容println!("v.len(): {}", v.len()); // 获取向量长度
迭代遍历
let v = vec![1, 2, 3];for num in &v { // 不可变迭代println!("{}", num); // 1, 2, 3}let mut v = vec![1, 2, 3];for num in &mut v { // 可变迭代*num *= 2; // 修改元素值}println!("vec: {:?}", v);
HashMap<K, V>
HashMap<K, V>
是标准库提供的哈希表的实现,是一个存储键值对(key-value pair)的集合类型,。可以非常高效地进行查找、插入和删除操作。
- K 为键类型,V 为值类型
- 键 (K) 必须实现 Eq 和 Hash trait(如 String、i32 等),值 (V) 无特殊要求。
- HashMap 是基于哈希表实现的,所以查找操作的时间复杂度通常是 O(1),但在最坏情况下会退化为 O(n),具体取决于哈希冲突的情况。
定义
-
Rust 为 HashMap 提供了很多预定义的接口,可以调用
HashMap::new()
函数来创建一个新的空 HashMaplet mut map = HashMap::new(); // 空 HashMap map.insert("key1".to_string(), 42); // 插入键值对 println!("{:?}", map); // {"key1": 42}
-
使用
HashMap::from()
或迭代器构建。let mut map = HashMap::from([("apple".to_string(), 3),("banana".to_string(), 5), ]); map.insert("orange".to_string(), 2); // 插入键值对 println!("{:?}", map); // {"apple": 3, "banana": 5, "orange": 2}let teams = vec![("Alice", 100), ("Bob", 90)]; let scores: HashMap<_, _> = teams.into_iter().collect(); println!("{:?}", scores); // {"Alice": 100, "Bob": 90}
-
还可以用 Rust 为 HashMap 提供接口
HashMap::with_capacity()
来预分配容量,然后后续补充值let mut map = HashMap::with_capacity(100);
-
HashMap 在 Rust 中是拥有所有权的,这意味着当 HashMap 被移动到另一个变量时,原来的变量就不再可用。
let mut map = HashMap::new(); // 空 HashMap map.insert("key1".to_string(), 42); // 插入键值对 let map1 = map; // map 的所有权转移给 map1 // println!("{:?}", map); // 错误:使用了已转移所有权的变量 println!("{:?}", map1); // {"key1": 42}
访问元素
无论 HashMap 是否可变,都是可以使用 变量名[KEY]
来访问其中的元素。此外,此外,还可以使用 Rust 还为 HashMap 提供的一些常用的方法来访问。
let mut map = HashMap::new(); // 空 HashMapmap.insert("key1".to_string(), 42); // 插入键值对map.insert("key2".to_string(), 43); // 插入键值对map.insert("key3".to_string(), 44); // 插入键值对let value = map["key2"]; // 如果键存在,否则会 panicprintln!("Value: {}", value); // 输出 Value: 43println!("{:?}", map.get("key1")); // Some(42)println!("{:?}", map.get("key4")); // Noneprintln!("{:?}", map.get("key1").unwrap()); // 42let mut map = HashMap::new(); // 空 HashMapmap.insert("key1".to_string(), 42); // 插入键值对map.insert("key2".to_string(), 43); // 插入键值对map.insert("key3".to_string(), 44); // 插入键值对println!("{:?}", map); // {"key1": 42, "key2": 43, "key3": 44}map.remove("key1"); // 删除键为 key1 的元素println!("{:?}", map); // {"key2": 43, "key3": 44}map.clear(); // 清空哈希表println!("{:?}", map); // {}map.insert("key1".to_string(), 42); // 插入键值对println!("{:?}", map.len()); // 1println!("{:?}", map.is_empty()); // falsemap.remove("key1"); // 删除键为 key1 的元素println!("{:?}", map.is_empty()); // true
迭代遍历
let mut map = HashMap::new(); // 空 HashMapmap.insert("key1".to_string(), 42); // 插入键值对map.insert("key2".to_string(), 43); // 插入键值对map.insert("key3".to_string(), 44); // 插入键值对for (key, value) in &map { // 不可变遍历哈希表println!("{}: {}", key, value); // key1: 42}for (_, value) in &mut map { // 可变遍历哈希表*value *= 2;}for key in map.keys() { // 遍历键println!("{}", key); // key1}for value in map.values() { // 遍历值println!("{}", value); // 42}for (key, value) in map.iter() { // 遍历键值对println!("{}: {}", key, value); // key1: 42}
引用(借用)
在 Rust 中,引用(reference) 是对某个值的借用,它允许你在不获取值所有权的情况下访问该值。引用帮助我们管理内存,避免不必要的内存拷贝,同时保证内存安全。
引用(Reference)像一个指针,因为它代表了一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用可以确保指向某个特定类型的有效值。
Rust 中的引用分为用 &数据类型
表示的不可变引用和用 &mut 数据类型
表示的可变引用两种类型。其中的 数据类型
可以是标量类型、复合类型、结构体等类型,详见之前博文 Rust 之四 运算符、标量、元组、数组、字符串、结构体、枚举 中的介绍。
-
由于标量类型实现了 Copy trait,因此,不会进行所有权的转移。
-
数组的引用比较特殊,其格式为
&[T; N]
-
一个数据在同一时刻只能有一个可变引用,或者多个不可变引用,但不能同时拥有可变和不可变引用
不可变引用
不可变引用(Immutable Reference)允许你在不修改原始数据的情况下访问它。Rust 中使用 let 引用名: &数据类型 = &变量或值;
来创建不可变引用。允许同时创建同一值的多个不可变引用,因为它们不会改变数据。
/* ======================= 不可变引用作为函数的参数 =========================== */
fn calculate_length(s: &String, a: &i32) -> usize {println!("s: {}, a: {}", s, a);s.len()
}fn main() {/* ======================== 标量类型不可变引用 =========================== */let x = 5;let y1: &i32 = &x; // 显示指定类型。let y2 = &x; // 自动推导类型。可以同时拥有多个不可变引用println!("x: {}, y1: {}, y2: {}", x, y1, y2);/* ========================= 字符串不可变引用 ============================ */let s = String::from("Hello, world!"); // 创建一个 Stringlet r1: &String = &s; // 显示指定类型。let r2 = &s; // 自动推导类型。可以同时拥有多个不可变引用println!("r1: {}, r2: {}", r1, r2); // 可以同时拥有多个不可变引用/* ========================= 数组不可变引用 ============================ */let x = [1, 2, 3, 4, 5];let r1: &[i32; 5] = &x; // 显示指定类型。let r2 = &x; // 自动推导类型。可以同时拥有多个不可变引用println!("r1: {:?}, r2: {:?}", r1, r2); // 可以同时拥有多个不可变引用/* ====================== 不可变引用作为函数参数 ========================== */let a = 10;let s = String::from("Hello, world!");let len = calculate_length(&s, &a);println!("The length of '{s}' is {len}");
}
- 不可变引用可以作为函数的参数。我们将在 Rust 之六 语句和表达式、作用域、生命周期、函数 详细学习!
可变引用
可变引用(Mutable Reference)允许修改所引用的原始数据的值。Rust 中使用 let 引用名: &mut 数据类型 = &mut 变量或值;
来创建可变引用。在同一作用域内,只能有一个可变引用可以存在,也不能同时拥有可变引用和不可变引用,也不能同时拥有可变引用和对该变量的直接使用。
// 可变引用作为函数的参数
fn change(s: &mut String, a: &mut i32) {s.push_str(", world!");*a += 1;
}fn main() {/* ======================== 标量类型可变引用 =========================== */let mut x = 5;let y1: &mut i32 = &mut x; // 指定类型的不可变引用 y1let y1 = &mut x; // 自动推导类型let y2 = &mut x; // 【错误】同一时刻不能有多个可变引用。let y3 = &x; // 【错误】不能同时拥有可变引用和不可变引用println!("x: {}, y1: {}", x, y1); // 【错误】不能同时拥有可变引用和对该变量的直接使用let x = 6;let y1: &mut i32 = &mut x; // 【错误】x 本身是不可变的,无法创建可变引用println!("y1 = {y1}");/* ================== 不可变引用和可变引用不能同时存在 ===================== */let mut x = String::from("Hello");let y1: &mut String = &mut x; // 指定类型的不可变引用 y1let y1 = &mut x; // 自动推导类型let y2 = &mut x; // 【错误】同一时刻不能有多个可变引用。let y3 = &x; // 【错误】不能同时拥有可变引用和不可变引用println!("x: {}, y1: {}", x, y1); // 【错误】不能同时拥有可变引用和对该变量的直接使用let x = String::from("Hello");let y1: &mut String = &mut x; // 【错误】x 本身是不可变的,无法创建可变引用println!("y1 = {y1}");/* ====================== 可变引用作为函数参数 ========================== */let mut s = String::from("Hello");let mut a = 1;change(&mut s, &mut a);println!("s = {s}, a = {a}");
}
-
可变引用对应的原数据也必须是可变的。
-
可变引用可以作为函数的参数。我们将在 Rust 之六 语句和表达式、作用域、生命周期、函数 详细学习!
-
注意
let r3 = &mut s;
中r3
是一个不可变的可变引用,也就意味着,我们不能更改 r3,例如修改r3 = &mut s1
是不允许的 -
可变性必须从最外层开始显式声明
悬垂引用
悬垂引用(Dangling Reference)是指一个引用指向一个已经被销毁(例如,离开作用域)的内存位置。Rust 编译器会通过严格的所有权和生命周期机制,从根本上杜绝了悬垂引用的出现。悬垂引用主要有以下两种情况,Rust 编译器会直接报错,不允许编译。
- 引用一个已经被销毁的对象:例如,当一个变量在某个作用域结束后被销毁,但仍然有引用指向它时,就会出现悬垂引用。
let r; {let mut x = 5;r = &mut x; // r 的生命周期绑定到 x } // x 在此处被释放,r 成为悬垂引用 // println!("{}", r); // 错误:使用悬垂引用
- 在栈上引用局部变量并返回该引用:例如,返回指向局部变量的引用,局部变量在函数结束时会被销毁。
fn dangle() -> &String { // 错误:缺失生命周期参数let s = String::from("hello");&s // s 在这里被销毁,返回的引用指向无效内存! }
解引用
- 显式解引用:使用
*
操作符访问引用指向的值let x = 5; let r = &x; assert_eq!(*r, 5); // 显式解引用
- 隐式解引用:方法调用和部分场景(如
println!
)自动解引用let s = String::from("hello"); println!("Length: {}", s.len()); // 自动解引用 &s 为 &str
- Deref 强制转换:允许智能指针(如
String
)自动转换为底层引用(如&str
)fn print_str(s: &str) {println!("{}", s); } let s = String::from("hello"); print_str(&s); // &String 自动转换为 &str
切片类型
在 Rust 中,切片(Slice) 是一种引用类型,用于表示对数组、向量、字符串这三种类型中的一部分数据的引用。它允许我们操作其中的一部分数据,而无需复制整个数据,从而高效且安全地处理动态长度的数据。
-
切片本身不拥有数据(没有所有权),只是对现有数据的借用(引用)。
-
切片是一个指向数据起始位置的胖指针(fat pointer),其中包含了指向数据的指针(指向原数组或 Vec 的某个元素)和切片的长度(元素的个数)。
字符串切片
字符串切片(String slice)允许我们访问字符串中的部分数据,而无需复制数据。使用 &str
和 &mut str
分别表示不可变字符串切片类型和可变字符串切片类型。
-
字符串字面值默认的类型就是
&str
类型。 -
某些情况下,Rust 可以自动推到类型,因此定义时的
: &str
和: &mut str
可以省略 -
字符串切片类型
&str
和&mut str
可以作为函数的形参或者返回值,实参则是字符串的引用&字符串名
或&mut 字符串名
。我们将在 Rust 之六 语句和表达式、作用域、生命周期、函数 详细学习!
定义
使用 let 切片名: &str = &字符串名[..语法]
和 let 切片名: &str = &"字符串字面值";
分别从字符串变量和字符串字面值创建不可变字符串切片;使用 let 切片名: &mut str = &mut 字符串名[..语法]
从字符串变量创建可变字符串切片(字符串字面值是不可变的,因此不能字符串字面值创建可变字符串切片)。
/* ========================= 不可变字符串切片 ============================ */let s = String::from("Hello World!");let str_slice: &str = &s[0..5]; // 显示指定类型。"Hello"println!("str_slice: {str_slice}");let str_slice = &s[0..5]; // 自动推导类型。"Hello"println!("str_slice: {str_slice}");let literal_slice = &"hello"; // 从字面值直接创建println!("literal_slice: {literal_slice}");/* ========================= 可变字符串切片 ============================== */let mut s = String::from("Hello World!");let str_slice: &mut str = &mut s[0..5]; // 显示指定类型。"Hello"str_slice.make_ascii_uppercase();println!("str_slice: {str_slice}");let str_slice = &mut s[0..5]; // 自动推导类型。"Hello"str_slice.make_ascii_lowercase();println!("str_slice: {str_slice}");
由于字符串字面值默认的类型就是 &str
类型,因此,从字符串字面值创建的字符串切片可以是 let literal_slice: &str = "hello";
这个各种格式,也可以是 let literal_slice = "hello";
这种格式
UTF-8 安全约束
Rust 的字符串是基于 UTF-8 编码的,因此切片的索引是以字节为单位的,而不是字符。因此,切片的操作需要注意字符边界(切片范围必须落在 UTF-8 字符边界),否则会导致 panic。
let s = "你好,世界!";let slice = &s[1..3]; // 运行出错,因为切片会从字符中间截断(一个汉字是2个字节)println!("slice: {slice}");
访问操作
无论字符串切片是否可变,都不能使用索引来直接访问元素。我们需要可以按字节索引访问或者转换为字符迭代器。此外,还可以使用 Rust 还为切片提供了一些常用的方法。
let s = "hello";let first_byte = s.as_bytes()[0]; // 按字节索引访问(需确保是 UTF-8 边界)104('h' 的 ASCII 码)println!("first_byte: {}", first_byte);for c in s.chars() { // 转换为字符迭代器println!("{}", c); // h, e, l, l, o}let s = "hello, world";let (hello, world) = s.split_at(7);println!("s.split_at: left: {} right: {}", hello, world);
与字节切片的转换
字符串切片可安全转换为字节切片(&[u8]
),但反向转换需确保 UTF-8 有效性
let bytes: &[u8] = "hello".as_bytes();// &str → &[u8]let raw = &[0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" 的 ASCII 码let s = std::str::from_utf8(raw).unwrap(); // &[u8] → &str(需 UTF-8 校验) "Hello"
数组切片
数组切片(Array Slice)是对固定大小数组([T; N
])中连续元素的引用,它提供了对数组部分或全部元素的安全访问,而无需复制数据。分别使用 &[数组类型]
和 &mut [数组类型]
来表示不可变数组切片类型和可变数组切片类型。
-
某些情况下,Rust 可以自动推到类型,因此定义时的
: &[数组类型]
或: &mut [数组类型]
可以省略 -
数组切片类型
&[数组类型]
和&mut [数组类型]
可以作为函数的形参或者返回值,实参则是数组的引用&数组名
或&mut 数组名
。我们将在 Rust 之六 语句和表达式、作用域、生命周期、函数 详细学习! -
对字节数组(
u8
类型的数组)的引用也叫 字节切片。字节切片是数组切片 的一种特殊形式
定义
使用 let 切片名: &[数组类型] = &数组名[..语法]
和 let 切片名: &[数组类型] = &[元素 1, 元素 1, 元素 n];
分别从数组变量和数组字面值创建不可变数组切片;使用 let 切片名: &mut [数组类型] = &mut 数组名[..语法]
和 let 切片名: &mut [数组类型] = &mut [元素 1, 元素 1, 元素 n];
分别从数组变量和数组字面值创建可变数组切片。
/* =========================== 创建不可变数组切片 ========================= */let arr = [1, 2, 3, 4, 5];let slice: &[i32] = &arr[1..3]; // 显式指定类型。包含数组的第 1 个到第 3 个元素(不包括第 3 个元素)println!("slice: {:?}", slice); // 输出: [1, 2, 3]let slice = &arr[..3]; // 自动推导类型。包含数组的第 0 个到第 3 个元素(不包括第 3 个元素)println!("slice: {:?}", slice); // 输出: [1, 2, 3]let slice = &[1, 2, 3, 4, 5]; // 直接从数组字面值创建println!("slice: {:?}", slice); // 输出: [1, 2, 3, 4, 5]/* =========================== 创建可变数组切片 ========================= */let mut arr = [1, 2, 3, 4, 5];let slice: &mut [i32] = &mut arr[..3]; // 显式指定 &mut [i32] 类型,包含数组的第 0 个到第 3 个元素(不包括第 3 个元素)slice[0] = 9; // 修改数组的第 0 个元素println!("slice: {:?}", slice); // 输出: [0, 2, 3]let slice = &mut arr[..3]; // 自动推导类型,包含数组的第 0 个到第 3 个元素(不包括第 3 个元素)slice[0] = 8; // 修改数组的第 0 个元素println!("slice: {:?}", slice); // 输出: [0, 2, 3]let slice = &mut [1, 2, 3, 4, 5]; // 直接从数组字面值创建slice[0] = 8;println!("slice: {:?}", slice); // 输出: [1, 2, 3, 4, 5]
访问操作
无论数组切片是否可变,都是可以使用 切片名[从 0 开始的索引]
来访问其中的元素。此外,还可以使用 Rust 还为切片提供了一些常用的方法。
let arr = [1, 2, 3, 4, 5];let slice = &arr[..3]; // 自动推导类型,包含数组的第 0 个到第 3 个元素(不包括第 3 个元素)println!("{:?}", slice); // 输出: [1, 2, 3]println!("{:?}", slice.len()); // 输出: 3println!("{:?}", slice.is_empty()); // 输出: falseprintln!("{:?}", slice.first()); // 输出: Some(1)println!("{:?}", slice.last()); // 输出: Some(3)println!("{:?}", slice.get(1)); // 输出: Some(2)println!("{:?}", slice.get(3)); // 输出: None
迭代遍历
let arr = [10, 20, 30];let slice = &arr[..];for num in slice { // 不可变迭代println!("{}", num); // 10, 20, 30}let mut arr = [1, 2, 3];for num in &mut arr[..] { // 可变迭代*num *= 2; // 修改元素值}println!("arr: {:?}", arr);
空数组切片
在 Rust 中,空数组切片(Empty Slice)是一个长度为 0 的切片,它不包含任何实际数据元素,但依然遵循 Rust 的借用和生命周期规则。
let b: &[i32] = &[]; // 直接从字面值创建空切片println!("{:?}", b); // 输出: []let arr = [1, 2, 3];let empty_from_range = &arr[0..0]; // 从范围 0..0 生成空切片println!("empty_from_range: {:?}", empty_from_range); // 输出: []let empty_arr_slice: &[i32] = &[][..]; // 显式范围语法println!("empty_arr_slice: {:?}", empty_arr_slice); // 输出: []let mut vec_empty = Vec::<i32>::new();let empty_slice_mut: &mut [i32] = &mut vec_empty[..];println!("empty_slice_mut: {:?}", empty_slice_mut); // 输出: []
与数组引用的互转
- 数组引用 ➜ 数组切片(隐式转换):数组引用可以自动转换为切片(零成本抽象)
let arr_ref: &[i32; 5] = &[1, 2, 3, 4, 5];let slice: &[i32] = arr_ref; // 隐式转换为整个数组的切片let sub_slice = &arr_ref[1..4]; // 显式生成子切片 [2, 3, 4]println!("slice: {:?} sub_slice: {:?}", slice, sub_slice);
- 数组切片 ➜ 数组引用(需明确长度)
let slice = &[1, 2, 3, 4, 5][..];use std::convert::TryInto;// 方法 1: 使用 try_into(需导入 std::convert::TryInto)let arr_ref: Option<&[i32; 5]> = slice.try_into().ok();println!("arr_ref: {:?}", arr_ref);if let [a, b, c, d, e] = slice { // 方法 2: 模式匹配// 在此作用域内,a~e 对应各元素println!("{a} {b} {c} {d} {e}");}
Vec<T>
切片
在 Rust 中,Vec<T>
的切片是一种引用类型,它允许我们以引用形式安全地访问 Vec 中连续的元素序列,而无需复制数据。使用 &[数组类型]
和 &mut [数组类型]
分别表示不可变 Vec<T>
切片类型和可变 Vec<T>
切片类型,与普通数组切片类型一致!
定义
使用 let 切片名: &[数组类型] = &数组名[..语法]
和 let 切片名: &[数组类型] = &vec![元素 1, 元素 1, 元素 n];
分别从数组变量和 Vec<T>
字面值创建不可变 Vec<T>
切片;使用 let 切片名: &mut [数组类型] = &mut 数组名[..语法]
和 let 切片名: &mut [数组类型] = &mut vec![元素 1, 元素 1, 元素 n];
分别从数组变量和 Vec<T>
字面值创建可变 Vec<T>
切片。
/* ============================== 不可变切片 ============================= */let vec = vec![1, 2, 3, 4, 5];let slice_vec: &[i32] = &vec[1..4]; // 显示指定类型。索引 1 到 3(左闭右开)println!("slice_vec: {:?}", slice_vec);println!("vec: {:?}", vec);let slice_vec = &vec[1..4]; // 自动推导类型。索引 1 到 3(左闭右开)println!("slice_vec: {:?}", slice_vec);println!("vec: {:?}", vec);let slice_vec = &vec![1, 2, 3]; // 直接从字面值创建println!("slice_vec: {:?}", slice_vec);/* =============================== 可变切片 ============================== */let mut vec = vec![1, 2, 3, 4, 5];let slice_vec: &mut [i32] = &mut vec[1..4]; // 显示指定类型。索引 1 到 3(左闭右开)slice_vec[0] = 6;println!("slice_vec: {:?}", slice_vec);println!("vec: {:?}", vec);let slice_vec = &mut vec[1..4]; // 自动推导类型。索引 1 到 3(左闭右开)slice_vec[0] = 7;println!("slice_vec: {:?}", slice_vec);println!("vec: {:?}", vec);let slice_vec: &mut [i32] = &mut vec![1, 2, 3];// 直接从字面值创建println!("slice_vec: {:?}", slice_vec);
访问操作
无论 Vec<T>
切片是否可变,都是可以使用 切片名[从 0 开始的索引]
来访问其中的元素。此外,还可以使用 Rust 还为切片提供了一些常用的方法。
let slice = &vec![10, 20, 30];println!("slice[0] = {} slice[2] = {}", slice[0], slice[2]);println!("slice.len() = {}", slice.len());println!("slice.is_empty() = {}", slice.is_empty());let (left, right) = slice.split_at(1);println!("slice.split_at: slice_left: {:?} - slice_right: {:?}", left, right);if let Some((first, rest)) = slice.split_first() {println!("First: {}, Rest: {:?}", first, rest); // 10, [20, 30]}
迭代遍历
let mut vec = vec![1, 2, 3, 4, 5];for num in &vec[1..3] {// 不可变迭代println!("{}", num); // 2, 3}for num in &mut vec[1..3] {// 可变迭代*num *= 2; // 修改元素}println!("vec: {:?}", vec);
与数组切片互转
- 普通数组切片 ➜
Vec<T>
切片let slice = &[1, 2, 3];let new_vec = slice.to_vec(); // 普通数组切片转 Vec<T> 切片,克隆数据生成新 Vecprintln!("new_vec: {:?}", new_vec);
Vec<T>
切片 ➜ 普通数组切片let vec = vec![4, 5, 6];let slice: &[i32] = &vec; // Vec<T> 切片转普通数组切片,零成本println!("slice: {:?}", slice);
与 Vec<T>
引用互转
Vec<T>
引用 ➜Vec<T>
切片let vec = vec![1, 2, 3, 4]; // 自动解引用转换(Deref Coercion) let slice: &[i32] = &vec; // 隐式转换为整个 Vec 的切片 let partial_slice = &vec[1..3]; // 显式获取子切片 [2, 3] // 等价于显式调用方法 let slice = vec.as_slice(); // 整个切片
Vec<T>
切片 ➜Vec<T>
引用let slice = &[1, 2, 3]; let new_vec = slice.to_vec(); // 拷贝数据创建新 Vec let new_vec: Vec<_> = slice.into(); // 同样创建新 Vec
参考
- https://kaisery.github.io/trpl-zh-cn/
相关文章:
Rust 之五 所有权、.. 和 _ 语法、引用和切片、Vec<T>、HashMap<K, V>
概述 Rust 的基本语法对于从事底层 C/C 开发的人来说多少有些难以理解,虽然官方有详细的文档来介绍,不过内容是相当的多,看起来也费劲。本文通过将每个知识点简化为 一个 DEMO 每种特性各用一句话描述的形式来简化学习过程,提高学…...
如何运行Vue 3 + Tauri + Rust 前端项目
Vue 3 Tauri Rust 前端项目运行需要安装以下工具和依赖: 1. 基本开发工具 Node.js (建议 LTS 版本) - 用于运行前端构建工具 包含 npm 或 yarn 包管理器下载地址 Rust 工具链 - Tauri 基于 Rust 构建 通过 rustup 安装安装命令: curl --proto https --tlsv1.2 -…...
Nature图形复现—Origin绘制顶刊水准的多组柱状图
多组柱状图(也称分组柱状图或簇状柱状图)是一种数据可视化图形,用于同时展示多个组别在不同子类别下的数据对比。其核心特点是通过并列的柱子将不同组别的数据排列在同一子类别下,便于直观比较。 本期教程以2022年发表于Nature的文…...
空格键会提交表单吗?HTML与JavaScript中的行为解析
在网页开发中,理解用户交互细节对于提供流畅的用户体验至关重要。一个常见的问题是:空格键是否会触发表单提交?本文将通过一个简单的示例解释这一行为,并探讨如何使用HTML和JavaScript来定制这种交互。 示例概览 考虑以下HTML代…...
详解@JsonFormat和@DateTimeFormat注解:处理日期格式化的利器
在Java开发中,尤其是Spring和Spring Boot项目中,日期时间类型(如Date、LocalDateTime等)的格式化问题经常困扰开发者。例如,前端传递的日期字符串如何转换为后端对象?后端返回的日期对象如何按指定格式序列化?这时候,@JsonFormat和@DateTimeFormat两个注解可以轻松解决…...
python-各种文件(txt,xls,csv,sql,二进制文件)读写操作、文件类型转换、数据分析代码讲解
1.文件txt读写标准用法 1.1写入文件 要读取文件,首先得使用 open() 函数打开文件。 file open(file_path, moder, encodingNone) file_path:文件的路径,可以是绝对路径或者相对路径。mode:文件打开模式,r 代表以…...
Python(16)Python文件操作终极指南:安全读写与高效处理实践
目录 背景介绍一、文件操作基础架构1. 文件打开模式详解 二、文件读取全攻略1. 基础读取方法2. 大文件处理方案3. 定位与截断 三、文件写入进阶技巧1. 基础写入操作2. 缓冲控制与实时写入 四、with上下文原理剖析1. 上下文管理协议2. 多文件同时操作 五、综合实战案例1. 加密文…...
Maven相关名词及相关配置
1、相关名词 1 Project: 任何你想build的事物,maven都可以认为他们是工程,这些工程被定义为工程对象模型(POM:Project Object Model)一个工程可以依赖其他的工程,一个工程也可以有多个子工程构成。 2 POM: 就是xml文件…...
【自动化测试】如何获取cookie,跳过登录的简单操作
前言 🌟🌟本期讲解关于自动化测试函数相关知识介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…...
登录校验:保障系统安全访问的关键技术解析
摘要:本文围绕Tlias智能学习辅助系统的登录校验功能展开,深入剖析了实现登录校验的必要性,介绍了会话技术和统一拦截技术等关键实现思路,并对Cookie、Session和令牌技术三种会话跟踪方案进行了详细对比,旨在为系统的安…...
数据库案例1--视图和索引
以下是一个关于数据库视图和索引的高级使用教程,结合实际案例进行讲解。我们将使用一个电商系统的数据库作为示例,展示如何创建和优化视图,以及如何通过索引提高查询性能。 案例背景 假设我们有一个电商系统,包含以下表…...
load_summarize_chain ,load_qa_chain 是什么
load_summarize_chain ,load_qa_chain 是什么 目录 load_summarize_chain ,load_qa_chain 是什么`load_summarize_chain``load_qa_chain`其他构建链的方式SequentialChain, TransformChainload_summarize_chain 和 load_qa_chain 都是 LangChain 库中的实用工具函数,用于快速构…...
SQL2API 核心理念:如何重构数据服务交付范式
在企业数据治理的漫长链条中,"数据服务交付" 始终是决定数据价值转化效率的关键一环。传统数据接口开发需经历需求沟通、SQL 编写、后端编码、接口测试、权限配置等多个环节,平均开发周期长达 7-10 天,且技术门槛高,导致…...
Proteus 仿真51单片机-串口收发小窥
51单片机 51单片机是一种经典的8位微控制器,广泛应用于各种嵌入式系统开发中。它具有结构简单、可靠性高、价格低廉等特点,深受工程师和电子爱好者的喜爱。 51单片机的核心是一个8位的CPU,能够执行多种指令,完成数据处理和逻辑运…...
信号与系统期中复习(第一章)
1、连续信号叠加的周期 2、离散信号的周期判定 离散信号只有当周期为整数的时候,才是周期信号 3、信号的时间变换 4、取样性质相关计算 5、系统的描述 6、线性系统的性质 7、信号的时间变换 8、基本概念 判断时不变系统的方法: 若f(t&#…...
bash的特性-常用的通配符
在Linux或Unix系统中,Bash作为最常用的命令行解释器之一,提供了多种通配符(wildcards)来帮助用户更高效地进行文件操作。这些通配符可以用来匹配多个文件名或路径名,极大地简化了批量处理任务。本文将详细介绍Bash中常…...
Android12 自定义系统服务
在Android中可以通过两种方式创建系统服务: 由SystemServer启动的系统服务,使用SystemServer线程资源,适合轻量级的服务,比如各种XMS服务;占用独立进程,在系统启动时,由init进程拉起,比如SurfaceFlinger;本文采用的是第一种方式。 自定义AssistantManagerService 参…...
安防监控视频管理平台EasyCVR助力建筑工地施工4G/5G远程视频监管方案
一、项目背景 随着城市建设的快速发展,房地产建筑工地的数量、规模与施工复杂性都在增加,高空作业、机械操作频繁,人员流动大,交叉作业多,安全风险剧增。施工企业和政府管理部门在施工现场管理上都面临难题。政府部门…...
如何针对游戏、金融行业定制CC攻击防护规则?
针对游戏和金融行业的高并发、实时交互及高价值特性,CC攻击防护需结合业务场景定制规则。以下是分行业的定制化防护方案: 一、游戏行业CC攻击防护规则 1. 核心防护目标 保障低延迟(毫秒级响应)防止作弊工具伪造…...
【redis】初识redis
初识redis Redis 是一种基于键值对(key-value) 的 NoSQL 的数据库,它与很多键值数据库不同, Redis 中的值可以是 string(字符串) 、hash(哈希)、list(链表)、…...
MJ PDF汉化版:功能强大,阅读无忧
在数字化时代,PDF 文件已成为我们日常生活和工作中不可或缺的一部分。无论是学习资料、工作报告还是电子书,PDF 格式因其兼容性和稳定性而被广泛使用。然而,对于许多中文用户来说,使用英文界面的 PDF 阅读器可能会带来不便。 今天…...
线代第二章矩阵第二课:矩阵的加法、减法、数乘
一、矩阵的加法、减法 加法 减法 二、矩阵的数乘 规律: (1)K(AB)KAKB (2)(KL)AKALA (3)k(LA)(kL)AL(kA) (4)1*A A ; (-1)*A -A 观看笔记来源: 《线性代数…...
Python实例题:Python自动获取海量IP工具
目录 Python实例题 题目 实现思路 代码实现 代码解释 get_proxy_list 函数: check_proxy_validity 函数: save_valid_ips 函数: 主程序: 运行思路 注意事项 Python实例题 题目 Python自动获取海量IP工具 实现思路 …...
Vue el-from的el-form-item v-for循环表单如何校验rules(一)
实际业务需求场景: 新增或编辑页面(基础信息表单,一个数据列表的表单),数据列表里面的表单数是动态添加的。数据可新增、可删除,在表单保存前,常常需要做表单必填项的校验,校验通过以…...
AI 边缘计算盒子:开启智能物联新时代
一、什么是 AI 边缘计算盒子 AI 边缘计算盒子是一种集成了高性能芯片、AI 算法和数据处理能力的硬件设备。它部署在数据源的边缘侧,如工厂、商场、交通路口等,能够在本地进行数据采集、预处理、分析和决策,而无需将所有数据上传到云端。这种…...
【AGI】MCP生态的“飞轮效应”
【AGI】MCP生态的“飞轮效应” (一)打通AI代理外部交互的“最后一公里”1. AI代理的核心挑战:可靠的外部服务交互2. MCP的解决方案:结构化交互协议3. 案例对比:Figma设计修改任务4. 行业影响:从实验性技术到…...
【无标题】win7和win11双系统共存
一、背景: .本人自有戴尔E6440型老款笔记本电脑一台。500g固态硬盘,12g内存,硬盘共分了两个分区。由于本人想实现Win11系统和win七双系统共存,和可以分别加载,今晚上折腾了几个小时终于搞定了!现把经验总结…...
国内开源医疗模型研究报告
引言 随着人工智能技术的快速发展,医疗AI领域正经历前所未有的变革。开源医疗模型作为这一领域的核心技术基础设施,不仅推动了医疗智能化进程,也为医疗工作者提供了强大的辅助工具。本报告将深入探讨国内优秀的开源医疗模型,分析…...
【工具变量】1907年大清邮政舆图数据集(高清图+PDF)
大清邮政舆图是清朝时期为邮政管理而制作的一部详细地图,主要用于邮政线路的规划与管理。该舆图不仅标示了全国各地的邮政线路,还包括了各地的地理位置、行政区划和重要的交通枢纽。大清邮政舆图在中国历史上具有重要的地理和邮政历史价值,是…...
leetcode 121. Best Time to Buy and Sell Stock
题目描述 本题属于动态规划类问题。 dp数组的含义 dp[i][0]表示从第0天到第i天为止,处于持有股票的状态下,账户里的最大金额。 dp[i][1]表示从第0天到第i天为止,处于不持有股票的状态下,账户里的最大金额。 按照这个定义dp[n-…...
UWB定位技术面临的主要挑战
UWB定位技术面临的主要挑战(品铂科技视角) 一、复杂工业场景下的信号稳定性挑战 品铂科技QM35825芯片虽通过4天线射频架构和接收分集技术将金属密集环境下的多径误差降低至传统方案的1/8,但在多层混凝土厂房或动态金属设备场景…...
获取 arm-none-eabi-ld 默认使用的链接脚本
使用如下命令 ./arm-none-eabi-ld --verbose > "arm-none-eabi-ld-default.ld"将输出重定向到一个 .ld 文件中。得到的文件内容如下 GNU ld (Arm GNU Toolchain 14.2.Rel1 (Build arm-14.52)) 2.43.1.20241119Supported emulations:armelf using internal linke…...
无人机设备遥控器之多控一机技术篇
无人设备遥控器的多控一机技术,是指通过多个遥控器或控制端协同实现对同一台无人设备的精准控制。该技术融合了通信技术、信号处理与协同控制算法,其核心在于多源信号的融合处理与动态控制权分配。 一、技术原理 多源信号融合 通过接收多个遥控器发送的…...
python自动化浏览器标签页的切换
#获取全部标签页的句柄返回句柄的列表 handleswebdriver.window_handles#获取全部标签页的句柄返回句柄的列表 print(len(handles)) 切换标签页 handleswebdriver.window_handles webdriver.switch_to.window(handles[index])#切换到第几个标签页就写几 关闭标签页 关闭标…...
一个异步架构设计:批量消费RabbitMQ,批量写入Elasticsearch(golang实现)
在数仓团队,异步任务设计非常常见,主要原因就是数据量太大,不适合做成同步,在自动驾驶这个业务上,数据大到什么程度呢,单模块每天标签的上报数量就能达到5000W,如果算上车端挖掘、云端挖掘、标注…...
加固笔记本:无人机领域智能作业的可靠算力中枢
在无人机技术快速发展的今天,行业应用场景日益复杂化——从高空电力巡检到极地环境监测,从军事侦察到农业植保,无人机的智能化作业对计算设备的稳定性、环境适应性和数据处理能力提出了更高要求。鲁成伟业深耕工业计算领域十余年,…...
非参数 Spearman 相关在多组学分析中的力量及AI拓展
在大数据生物学时代,多组学分析为理解复杂的生物系统提供了前所未有的洞察力。然而,解读这些数据集内部错综复杂的关系需要强大且通用的分析工具。其中,非参数 Spearman 相关性作为一种揭示隐藏关联的有力方法脱颖而出。 AI拓展 多组学数据…...
大模型面经 | 请你介绍一下ReAct(Reasoning and Acting)?
大家好,我是皮先生!! 今天给大家分享一些关于大模型面试常见的面试题,希望对大家的面试有所帮助。 往期回顾: 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题一) 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题二) 大模型面经 | 春招、秋招算法…...
如何高效使用 Text to SQL 提升数据分析效率?四个关键应用场景解析
数据分析师和业务人员常常面临这样的困境:有大量数据等待分析,但 SQL 编写却成为效率瓶颈。即使对于经验丰富的数据分析师来说,编写复杂 SQL 查询也需要耗费大量时间;而对于不具备 SQL 专业知识的业务人员,数据分析则更…...
Python对ppt进行文本替换、插入图片、生成表格
目录 1. 安装pptx2. 文本替换和插入图片3. 生成表格 1. 安装pptx pip install python-pptx2. 文本替换和插入图片 文本通过占位符例如{{$xxx}}进行标记,然后进行替换;图片通过ppt中的图形和图片中的占位符进行标记ppt如下 具体实现 from pptx import …...
OpenResty与Nginx的功能对比分析
OpenResty 和 Nginx 都是高性能的 Web 服务器和反向代理服务器,但它们在设计目标和功能上有显著区别。以下是它们的主要差异: 1. 核心定位 Nginx 是一个轻量级、高性能的 HTTP 服务器和反向代理,专注于静态内容服务、负载均衡和 HTTP 请求处…...
React 在组件间共享状态
在组件间共享状态 有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关 state 从这两个组件上移除,并把 state 放到它们的公共父级,再通过 props 将 state 传递给这两个组件。这被称为“状态提升”,这…...
1.Framer Motion 中 motion/react 和 motion/react-client 的用法和区别
背景知识:服务器端渲染 (SSR) 和客户端渲染 (CSR) 在理解这两个模块的区别之前,我们需要了解 React 应用的两种主要渲染方式: 服务器端渲染 (SSR): React 组件在服务器上被渲染成 HTML 字符串,然后发送给浏览器。浏览器接收到的…...
在 Redis 中存储对象类型的数据时,选择hash还是string?
在 Redis 中存储对象类型的数据时,选择 String(存储 JSON)还是其他数据结构(如 Hash),需根据业务场景、性能需求和数据操作模式综合权衡。以下是详细分析: 1. 使用 String 类型存储 JSON 适用场景 整体读写为主:频繁存取整个对象,如用户信息、配置信息。跨语言兼容性…...
使用Python+xml+shutil修改目标检测图片和对应xml标注文件
使用Pythonxmlshutil修改目标检测图片文件名和对应xml标注文件: import os import glob import xml.etree.ElementTree as et import shutildef change_labels(source_dir):name_id 18001file_list glob.glob(os.path.join(source_dir, "*.xml"))print…...
基于STM32、HAL库的PCA9557 I/O扩展器驱动程序设计
一、简介: PCA9557是NXP公司生产的一款8位IC总线I/O扩展芯片,主要特性包括: 工作电压:2.3V至5.5V 低待机电流消耗 8个通用输入/输出引脚 极性反转寄存器 内部上电复位 3个硬件地址引脚,最多可连接8个器件 400kHz快速IC总线 中断输出引脚 二、硬件接口: PCA9557 <-&g…...
【Qt】Qt 信号与槽机制全解析
🍑个人主页:Jupiter. 🚀 所属专栏:QT 欢迎大家点赞收藏评论😊 目录 一.信号和槽概述信号的本质槽的本质 信号和槽的使⽤连接信号和槽查看内置信号和槽通过 Qt Creator ⽣成信号槽代码⾃定义信号和槽带参数的信号和槽信…...
【QT】 QT定时器的使用
QT定时器的使用 1. QTimer介绍(1)QTimer的使用方法步骤示例代码1:定时器的启动和关闭现象:示例代码2:定时器每隔1s在标签上切换图片现象: (2)实际开发的作用 2.日期 QDate(1)主要方法 3.时间 QTime(1)主要方…...
golang使用stdio与子进程进行通信
在使用Cline调用本地MCP Server的时候,使用的是STDIO模式,也就是Cline启动一个子进程来运行MCP Server,然后通过STDIO来进行通信。这种方式即高效又安全。 import ("bufio""fmt""io""os/exec""…...
无服务器架构(Serverless)在Web开发与云原生中的应用研究
无服务器架构(Serverless)在Web开发与云原生中的应用研究 摘要 无服务器架构(Serverless Architecture)作为一种新兴的云计算范式,通过抽象化服务器管理,使开发者能够专注于业务逻辑的实现,而无需关注底层基础设施的运维。本文从研究学者的角度,探讨了无服务器架构的核…...