切片基础
> 理解切片(&[T] 和 &str)的概念与语法,掌握范围操作符的各种写法。
为什么需要切片?
切片语法
概念名称: 切片是对集合中一段连续元素的引用。
语法结构:
┌──────────────────────────────────────┐
│ &集合[start..end] │
│ ↑ ↑ ↑ │
│ 引用 开始 结束(不含) │
│ │
│ &s[0..5] → 索引 0-4 │
│ &s[..5] → 开头到 4 │
│ &s[5..] → 5 到末尾 │
│ &s[..] → 整个集合 │
│ &s[..=5] → 0-5(包含结束) │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
println!("{} {}", hello, world);
}问题场景
rust
▶ Run// 问题:只想使用字符串的一部分
fn main() {
let s = String::from("hello world");
// 方案 1:创建新 String(低效)
let hello = String::from("hello"); // 复制数据
// 方案 2:使用切片(高效)
let hello = &s[0..5]; // 只是引用,不复制
}切片的优势
┌─────────────────────────────────────────────────────┐
│ 切片 vs 复制 │
├─────────────────────────────────────────────────────┤
│ │
│ 复制数据 │
│ ┌─────────────────┐ │
│ │ 原始数据 (10MB) │ │
│ │ ↓ 复制 │ ← 耗时、耗内存 │
│ │ 新数据 (10MB) │ │
│ └─────────────────┘ │
│ │
│ 使用切片 │
│ ┌─────────────────┐ │
│ │ 原始数据 (10MB) │ │
│ │ ↑ 引用 │ ← 瞬间、零内存开销 │
│ │ 切片 (16 字节) │ (指针 + 长度) │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘什么是切片?
切片(Slice)= 对集合中一段连续元素的引用
┌────────────────────────────────────────┐
│ 切片数据结构 │
├────────────────────────────────────────┤
│ • 不拥有数据,只是引用 │
│ • 可以指向数组、Vec、String 的一部分 │
│ • 大小在运行时确定(胖指针) │
│ • 类型表示:&[T] 或 &str │
└────────────────────────────────────────┘字符串切片(&str)
基本语法
rust
▶ Runfn main() {
let s = String::from("hello world");
// 创建字符串切片
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
println!("{} {}", hello, world);
// 验证类型
println!("hello 类型:{:?}", std::any::type_name_of_val(&hello));
// 输出:&str
}范围语法详解
范围表示法:start..end
┌─────────────────────────────────────────────────────┐
│ 语法 │ 含义 │ 示例 (s="hello") │
├─────────────────────────────────────────────────────┤
│ start..end │ start 到 end-1 │ s[0..3] = "hel" │
│ start.. │ start 到末尾 │ s[2..] = "llo" │
│ ..end │ 开头到 end-1 │ s[..3] = "hel" │
│ .. │ 整个字符串 │ s[..] = "hello" │
│ ..=end │ 开头到 end │ s[..=2] = "hel" │
└─────────────────────────────────────────────────────┘
重要:范围不包含 end 值(Rust 的 range 都是左闭右开)语法糖(简写)
rust
▶ Runfn main() {
let s = String::from("hello world");
// 以下写法等价
// 完整写法
let hello1 = &s[0..5];
// 省略 0(从开头开始)
let hello2 = &s[..5];
// 验证相等
assert_eq!(hello1, hello2);
// 更多简写
let world = &s[6..]; // 从 6 到末尾
let all = &s[..]; // 整个字符串
let empty = &s[5..5]; // 空字符串
}内存布局详解
String 和切片的内存布局:
String s = "hello world"
┌─────────────────────────────────────────────────────┐
│ 栈(Stack) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ String s │ │
│ │ ┌───────┬───────┬───────┐ │ │
│ │ │ ptr │ len │ cap │ │ │
│ │ │ ●───┼───11──┼───15──┤ │ │
│ │ └───┼───┴───────┴───────┘ │ │
│ │ │ │ │
│ │ ┌───┴─────────────────────────────┐ │ │
│ │ │ &str hello │ │ │
│ │ │ ┌───────┬───────┐ │ │ │
│ │ │ │ ptr │ len │ │ │ │
│ │ │ │ ●───┼───5───┤ │ │ │
│ │ │ └───┼───┴───────┘ │ │ │
│ │ │ │ │ │ │
│ │ └──────┼───────────────────────────┘ │ │
│ │ │ │ │
│ └─────────┼─────────────────────────────────┘ │
│ │ │
└────────────┼──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 堆(Heap) │
│ │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ h │ e │ l │ l │ o │ │ w │ o │ r │ l │ d │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ 0 1 2 3 4 5 6 7 8 9 10 (索引) │
│ ↑ ↑ │
│ │ │ │
│ └─ s.ptr └─ s.ptr + 6 │
│ └─ hello.ptr │
│ │
└─────────────────────────────────────────────────────┘
关键点:
• String = (ptr, len, cap) = 24 字节
• &str = (ptr, len) = 16 字节
• 切片不拥有数据,只是指向现有数据的视图字符串字面量的类型
&str 的本质
rust
▶ Runfn main() {
// 字符串字面量的类型是 &str
let s: &str = "Hello, world!";
// 更准确的类型是 &'static str
// 'static 表示整个程序生命周期
println!("类型:{:?}", std::any::type_name_of_val(&s));
// 输出:&str
}&'static str 详解
字符串字面量的存储:
┌─────────────────────────────────────────────────────┐
│ 静态区(.rodata) │
│ │
│ 程序编译时,字符串字面量被放入静态区 │
│ 整个程序运行期间都存在 │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ "Hello, world!\0" │ │
│ │ "Rust\0" │ │
│ │ "你好\0" │ │
│ │ ... │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 当写 let s = "Hello"; 时: │
│ • s 是一个 &str │
│ • 指向静态区的 "Hello\0" │
│ • 生命周期是 'static │
│ │
└─────────────────────────────────────────────────────┘&str vs String 对比
rust
▶ Runfn main() {
// &str - 字符串切片
let s1: &str = "hello";
// • 不可变
// • 通常指向静态区
// • 大小:16 字节(指针 + 长度)
// • 不能修改内容
// String - 可增长字符串
let mut s2: String = String::from("hello");
// • 可变
// • 数据在堆上
// • 大小:24 字节(指针 + 长度 + 容量)
// • 可以修改内容
s2.push_str(" world");
println!("{}", s2);
}对比表格
┌─────────────────────────────────────────────────────┐
│ &str vs String 对比 │
├──────────────┬──────────────────┬───────────────────┤
│ 特性 │ &str │ String │
├──────────────┼──────────────────┼───────────────────┤
│ 所有权 │ 借用(引用) │ 拥有 │
├──────────────┼──────────────────┼───────────────────┤
│ 可变性 │ 不可变 │ 可变 │
├──────────────┼──────────────────┼───────────────────┤
│ 数据存储 │ 引用其他数据 │ 堆上分配 │
├──────────────┼──────────────────┼───────────────────┤
│ 大小 │ 16 字节 │ 24 字节 │
├──────────────┼──────────────────┼───────────────────┤
│ 字面量 │ "hello" │ String::from("hello") │
├──────────────┼──────────────────┼───────────────────┤
│ 修改内容 │ ❌ 不支持 │ ✅ push_str 等 │
├──────────────┼──────────────────┼───────────────────┤
│ 生命周期 │ 有生命周期参数 │ 拥有数据 │
└──────────────┴──────────────────┴───────────────────┘小结
- 切片是对集合中一段连续元素的引用,不拥有数据
&str是字符串切片,&[T]是数组切片- 范围语法:
start..end(不含 end)、..、start..、..end、..=end - 切片是"胖指针":包含指针和长度,零拷贝高效
练习题
详见:练习题