====== 第六章 Slice类型 ======
===== 6.1 什么是Slice =====
Slice(切片)允许你引用集合中一段连续的元素序列,而不用引用整个集合。Slice是一种**动态大小类型(DST, Dynamically Sized Type)**。
==== Slice的特点 ====
* 没有所有权
* 是对原数据的引用
* 长度在运行时确定
* 语法:[T] 或 &[T]
===== 6.2 字符串Slice =====
==== 创建字符串Slice ====
字符串Slice(&str)是String的一部分值的引用:
let s = String::from("hello world");
let hello = &s[0..5]; // 从索引0到5(不包括5)
let world = &s[6..11]; // 从索引6到11(不包括11)
println!("{} {}", hello, world); // "hello world"
**图示:**
s: String
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| h | e | l | l | o | | w | o | r | l | d | \0 |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
0 1 2 3 4 5 6 7 8 9 10 11
hello = &s[0..5]
+--------+--------+--------+--------+--------+
| h | e | l | l | o |
+--------+--------+--------+--------+--------+
world = &s[6..11]
+--------+--------+--------+--------+--------+
| w | o | r | l | d |
+--------+--------+--------+--------+--------+
==== Slice语法 ====
let s = String::from("hello");
let slice = &s[0..2]; // 从开始到2(不包括2)
let slice = &s[..2]; // 同上,省略开始索引
let slice = &s[3..len]; // 从3到结尾
let slice = &s[3..]; // 同上,省略结束索引
let slice = &s[0..len]; // 整个字符串
let slice = &s[..]; // 同上,两者都省略
==== 字符串Slice的边界 ====
**必须在有效的UTF-8字符边界:**
let s = "中国人"; // 每个汉字3字节
let a = &s[0..3]; // "中" - 正确
// let b = &s[0..2]; // 错误!不是有效的UTF-8边界
运行时的越界会导致panic:
let s = String::from("hello world");
// let slice = &s[0..100]; // panic!越界
===== 6.3 字符串字面量就是Slice =====
字符串字面量的类型是&str,它是一个指向二进制程序特定位置的slice:
let s = "Hello, world!"; // s的类型是&str
这也是为什么字符串字面量是不可变的:&str是不可变引用。
==== &str vs String ====
| 特性 | String | &str |
|------|--------|------|
| 所有权 | 有 | 无(借用) |
| 存储位置 | 堆 | 任意(堆/栈/静态数据区) |
| 可变性 | 可变(mut) | 总是不可变 |
| 大小 | 运行时确定 | 编译时确定指针+长度 |
===== 6.4 将String转为&str =====
==== 使用&操作符 ====
let s = String::from("hello");
let slice: &str = &s;
==== 使用as_str() ====
let s = String::from("hello");
let slice = s.as_str();
==== 使用Deref自动转换 ====
fn takes_slice(s: &str) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
takes_slice(&s); // 自动转换
}
===== 6.5 字符串Slice方法 =====
==== 常用方法 ====
let s = "hello world";
// 长度(字节数)
println!("长度:{}", s.len());
// 是否为空
println!("是否为空:{}", s.is_empty());
// 获取字符
for c in s.chars() {
println!("{}", c);
}
// 分割
let parts: Vec<&str> = s.split(' ').collect();
println!("{:?}", parts);
// 包含检查
println!("包含'world':{}", s.contains("world"));
// 替换
let new_s = s.replace("world", "Rust");
println!("{}", new_s);
// 去除空白
let s2 = " hello ".trim();
println!("'{}'", s2);
===== 6.6 数组Slice =====
==== 创建数组Slice ====
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // &[i32] 包含 [2, 3]
assert_eq!(slice, &[2, 3]);
==== 数组Slice的方法 ====
let a = [1, 2, 3, 4, 5];
let slice = &a[1..4];
// 长度
println!("长度:{}", slice.len());
// 是否为空
println!("是否为空:{}", slice.is_empty());
// 获取元素
println!("第一个元素:{}", slice[0]);
println!("最后一个元素:{}", slice.last().unwrap());
// 遍历
for item in slice.iter() {
println!("{}", item);
}
// 查找
if let Some(pos) = slice.iter().position(|&x| x == 3) {
println!("找到3在索引{}", pos);
}
===== 6.7 Slice的内存布局 =====
Slice由两个部分组成:
* 指向数据的指针
* 长度(usize)
在64位系统上,&str和&[T]都是16字节(两个usize)。
use std::mem;
fn main() {
println!("&str大小:{}", mem::size_of::<&str>());
println!("&[i32]大小:{}", mem::size_of::<&[i32]>());
// 64位系统输出:16
}
===== 6.8 使用Slice编写函数 =====
==== 第一个单词提取 ====
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
let word = first_word(&my_string);
println!("第一个单词是:{}", word);
let my_string_literal = "hello world";
let word = first_word(my_string_literal);
println!("第一个单词是:{}", word);
}
**使用&str作为参数的优势:**
* 可以接受String的引用
* 也可以接受字符串字面量
* 更灵活、更通用
==== 泛型Slice函数 ====
fn largest(slice: &[T]) -> Option<&T> {
if slice.is_empty() {
return None;
}
let mut largest = &slice[0];
for item in slice.iter() {
if item > largest {
largest = item;
}
}
Some(largest)
}
fn main() {
let numbers = [34, 50, 25, 100, 65];
match largest(&numbers) {
Some(max) => println!("最大值:{}", max),
None => println!("数组为空"),
}
}
===== 6.9 其他类型的Slice =====
==== Vec的Slice ====
let v = vec![1, 2, 3, 4, 5];
let slice = &v[1..3];
println!("{:?}", slice); // [2, 3]
==== 可变Slice ====
fn clear_slice(slice: &mut [i32]) {
for item in slice.iter_mut() {
*item = 0;
}
}
fn main() {
let mut arr = [1, 2, 3, 4, 5];
clear_slice(&mut arr[1..4]);
println!("{:?}", arr); // [1, 0, 0, 0, 5]
}
===== 6.10 Slice与迭代器 =====
==== iter()方法 ====
let arr = [1, 2, 3, 4, 5];
// 不可变迭代
for item in arr.iter() {
println!("{}", item);
}
// 使用iter()
for item in arr[..].iter() {
println!("{}", item);
}
==== windows()和chunks() ====
let arr = [1, 2, 3, 4, 5];
// 滑动窗口
for window in arr.windows(3) {
println!("{:?}", window);
}
// 输出:[1, 2, 3], [2, 3, 4], [3, 4, 5]
// 分块
for chunk in arr.chunks(2) {
println!("{:?}", chunk);
}
// 输出:[1, 2], [3, 4], [5]
===== 练习题 =====
==== 练习题6.1:字符串反转 ====
fn reverse_words(s: &str) -> String {
s.split_whitespace()
.rev()
.collect::>()
.join(" ")
}
fn main() {
let s = "hello world from Rust";
println!("原字符串:{}", s);
println!("反转单词:{}", reverse_words(s));
}
==== 练习题6.2:查找子串 ====
fn find_substring(s: &str, sub: &str) -> Option {
s.find(sub)
}
fn main() {
let s = "hello world";
match find_substring(s, "world") {
Some(index) => println!("找到子串在索引{}", index),
None => println!("未找到子串"),
}
}
==== 练习题6.3:旋转数组 ====
fn rotate_left(slice: &mut [T], k: usize) {
slice.rotate_left(k);
}
fn main() {
let mut arr = [1, 2, 3, 4, 5];
println!("原数组:{:?}", arr);
rotate_left(&mut arr, 2);
println!("左移2位后:{:?}", arr); // [3, 4, 5, 1, 2]
}
==== 练习题6.4:字符串分析 ====
fn analyze_string(s: &str) {
println!("字符串:{}", s);
println!("字符数(Unicode):{}", s.chars().count());
println!("字节数:{}", s.len());
println!("单词数:{}", s.split_whitespace().count());
println!("行数:{}", s.lines().count());
}
fn main() {
let text = "Hello, Rust!\n这是第二行。\n这是第三行。";
analyze_string(text);
}
==== 练习题6.5:实现trim功能 ====
fn my_trim(s: &str) -> &str {
let bytes = s.as_bytes();
// 找到第一个非空白字符
let start = bytes.iter()
.position(|&b| !b.is_ascii_whitespace())
.unwrap_or(bytes.len());
// 找到最后一个非空白字符
let end = bytes.iter()
.rposition(|&b| !b.is_ascii_whitespace())
.map(|i| i + 1)
.unwrap_or(0);
&s[start..end]
}
fn main() {
let s = " hello world ";
println!("'{}'", my_trim(s));
}
===== 本章小结 =====
本章详细介绍了Rust的Slice类型:
* **字符串Slice(&str)**:String的一部分或字符串字面量
* **数组Slice(&[T])**:数组或向量的一部分
* **Slice语法**:[start..end],可以省略start或end
* **UTF-8边界**:字符串Slice必须在有效的UTF-8字符边界
* **内存布局**:指针+长度,16字节(64位系统)
* **函数参数**:使用&str比&String更灵活
Slice是Rust中处理序列数据的重要工具,理解Slice有助于编写高效、安全的代码。