====== 第六章 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有助于编写高效、安全的代码。