目录

第六章 Slice类型

6.1 什么是Slice

Slice(切片)允许你引用集合中一段连续的元素序列,而不用引用整个集合。Slice是一种动态大小类型(DST, Dynamically Sized Type)

Slice的特点

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由两个部分组成:

在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作为参数的优势:

泛型Slice函数

fn largest<T: PartialOrd>(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::<Vec<_>>()
        .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<usize> {
    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<T>(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是Rust中处理序列数据的重要工具,理解Slice有助于编写高效、安全的代码。