Rust 学习(8)-切片类型

文章目录

返回首页 | 上篇 | 下篇


掌握 Rust 中的切片 ------ 字符串切片、数组切片与切片作为参数


一、回顾与导入

前面两篇我们学习了 Rust 的所有权系统:

  • 所有权规则、移动语义、Clone 与 Copy
  • 引用与借用、可变引用、借用规则

今天我们来学习另一种不持有所有权 的数据类型 ------ 切片(Slice)

💡 切片允许你引用集合中一段连续的元素序列,而不需要引用整个集合。


二、为什么需要切片?

来看一个需求:写一个函数,接收一个字符串,返回它第一个单词的长度。

rust 复制代码
// 没有切片时,返回第一个单词的长度
fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

fn main() {
    let mut s = String::from("hello world");
    let word_len = first_word(&s);
    println!("第一个单词长度: {}", word_len);

    s.clear();  // 清空字符串
    // word_len 仍然是 5,但 s 已经空了!这是 bug!
    println!("清空后第一个单词长度: {}", word_len);
}

问题:word_lens 的状态不同步。切片可以解决这个问题。


三、字符串切片(String Slice)

字符串切片是 &str 类型,它是对 String 或字符串字面量中一部分的引用。

3.1 创建字符串切片

rust 复制代码
fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5];   // 取索引 0-4,不包含5 -> "hello"
    let world = &s[6..11];  // 取索引 6-10 -> "world"

    println!("{} {}", hello, world);
}

[start..end] 语法

  • start:起始索引(包含)
  • end:结束索引(不包含)
  • 索引必须落在 UTF-8 字符边界上

3.2 Range 语法变体

rust 复制代码
fn main() {
    let s = String::from("hello");

    // 各种切片写法
    let slice1 = &s[0..2];   // [0,2) -> "he"
    let slice2 = &s[..2];    // 从开头到索引2 -> "he"
    let slice3 = &s[2..];    // 从索引2到末尾 -> "llo"
    let slice4 = &s[..];     // 整个字符串 -> "hello"

    println!("{} {} {} {}", slice1, slice2, slice3, slice4);
}

3.3 字符串字面量本身就是切片

rust 复制代码
fn main() {
    let s: &str = "Hello, world!";  // s 的类型是 &str
    // 字符串字面量直接存储在二进制中,类型就是 &str
}

3.4 用切片重写 first_word 函数

rust 复制代码
fn first_word(s: &str) -> &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 mut s = String::from("hello world");
    let word = first_word(&s);  // word 是切片,持有对 s 的引用

    println!("第一个单词: {}", word);  // hello

    // s.clear();  // ❌ 如果取消注释会编译错误!
    // 因为 word 还在借用 s,不能修改 s

    println!("word 仍然有效: {}", word);
}

关键:切片引用了原字符串的一部分,借用检查器会确保切片有效期间原字符串不能被修改。


四、字符串字面量与 String 的切片操作对比

操作 String &str(字符串字面量/切片)
创建 String::from("hello") "hello"
可变性 可变的(需 mut 不可变
切片 &s[0..3]&str &s[0..3]&str
内存位置 二进制文件(静态存储区)
所有权 拥有数据 借用数据
rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = "world";  // &str

    let slice1 = &s1[1..3];  // "el"
    let slice2 = &s2[1..3];  // "or"

    println!("{} {}", slice1, slice2);  // el or
}

五、数组切片

切片不仅适用于字符串,也适用于数组。

rust 复制代码
fn main() {
    let a = [1, 2, 3, 4, 5];

    let slice = &a[1..4];  // 切片类型 &[i32],包含 [2, 3, 4]

    println!("切片: {:?}", slice);
    println!("第一个元素: {}", slice[0]);
    println!("长度: {}", slice.len());
}

数组切片的类型&[T](T 是元素类型)

实用函数:使用数组切片

rust 复制代码
// 计算切片中元素的和
fn sum_slice(slice: &[i32]) -> i32 {
    slice.iter().sum()
}

// 找到切片中最大的元素
fn max_in_slice(slice: &[i32]) -> Option<&i32> {
    slice.iter().max()
}

fn main() {
    let numbers = [10, 20, 30, 40, 50];

    let s1 = &numbers[..3];   // [10, 20, 30]
    let s2 = &numbers[2..];   // [30, 40, 50]

    println!("s1 的和: {}", sum_slice(s1));      // 60
    println!("s2 的和: {}", sum_slice(s2));      // 120

    if let Some(max) = max_in_slice(s1) {
        println!("s1 的最大值: {}", max);        // 30
    }
}

六、切片作为函数参数的最佳实践

6.1 使用 &str 而不是 &String

rust 复制代码
// ❌ 不够通用,只能接收 &String
fn bad(s: &String) -> &str {
    &s[0..1]
}

// ✅ 更好,可以接收 &String 或 &str
fn good(s: &str) -> &str {
    &s[0..1]
}

fn main() {
    let s1 = String::from("hello");
    let s2 = "world";

    // bad(&s1);    // 可以
    // bad(s2);     // ❌ 类型不匹配

    good(&s1);  // ✅
    good(s2);   // ✅
}

6.2 使用 &[T] 而不是 &Vec<T>

同理,对于数组/向量,使用 &[T] 更通用:

rust 复制代码
// 更通用的函数签名
fn print_first_three(slice: &[i32]) {
    for &item in slice.iter().take(3) {
        println!("{}", item);
    }
}

fn main() {
    let arr = [1, 2, 3, 4, 5];
    let vec = vec![1, 2, 3, 4, 5];

    print_first_three(&arr);   // ✅ 可以传数组引用
    print_first_three(&vec);   // ✅ 可以传 Vec 引用
}

七、切片边界与 UTF-8

重要:字符串切片必须落在 UTF-8 字符边界上,否则会 panic。

rust 复制代码
fn main() {
    let s = "你好,世界!";  // 中文字符每个占 3 字节

    println!("字符串长度: {} bytes", s.len());  // 18

    let slice1 = &s[0..3];   // ✅ "你"
    println!("slice1: {}", slice1);

    // let slice2 = &s[0..2];  // ❌ panic! 字节边界不对
    // 因为'你'占3字节,0..2 不是一个完整的字符边界
}

安全做法 :使用 chars()char_indices() 处理 UTF-8:

rust 复制代码
fn first_word_utf8(s: &str) -> &str {
    for (i, c) in s.char_indices() {
        if c == ' ' {
            return &s[0..i];
        }
    }
    s
}

fn main() {
    let s = "Rust 编程语言";
    let word = first_word_utf8(s);
    println!("第一个单词: {}", word);  // Rust
}

八、完整代码示例

rust 复制代码
fn main() {
    println!("=== 1. 字符串切片基础 ===");
    let s = String::from("hello world");
    let hello = &s[0..5];
    let world = &s[6..11];
    println!("hello = {}, world = {}", hello, world);

    println!("\n=== 2. Range 语法简写 ===");
    let slice = &s[..5];
    println!("&s[..5] = {}", slice);
    let slice = &s[6..];
    println!("&s[6..] = {}", slice);
    let slice = &s[..];
    println!("&s[..] = {}", slice);

    println!("\n=== 3. first_word 函数演示 ===");
    let s = String::from("hello world");
    let word = first_word(&s);
    println!("第一个单词: {}", word);
    // s.clear();  // 取消注释会编译错误

    println!("\n=== 4. 数组切片 ===");
    let arr = [10, 20, 30, 40, 50];
    let arr_slice = &arr[1..4];
    println!("数组切片: {:?}", arr_slice);
    println!("切片和: {}", sum_slice(arr_slice));

    println!("\n=== 5. 字符串字面量 ===");
    let literal: &str = "Hello, Rust!";
    let word = first_word(literal);
    println!("字符串字面量的第一个单词: {}", word);

    println!("\n=== 6. UTF-8 边界示例 ===");
    let chinese = "你好世界";
    println!("字符串: {}", chinese);
    let first_char = &chinese[0..3];
    println!("第一个字符: {}", first_char);
}

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 sum_slice(slice: &[i32]) -> i32 {
    let mut sum = 0;
    for num in slice {
        sum += num;
    }
    sum
}

九、总结与速查

概念 语法 类型 说明
字符串切片 &s[start..end] &str 对字符串部分的引用
数组切片 &a[start..end] &[T] 对数组部分的引用
全范围切片 &s[..] &str/&[T] 整个集合的引用
字符串字面量 "hello" &str 本身就是切片

核心要点

  • ✅ 切片是一种不持有所有权的引用
  • ✅ 字符串切片类型是 &str,数组切片类型是 &[T]
  • ✅ 函数参数优先使用 &str 而非 &String,使用 &[T] 而非 &Vec<T>
  • ✅ 切片让借用检查器能确保数据一致性
  • ✅ UTF-8 字符串切片必须在字符边界上

十、思考题

  1. 以下代码会输出什么?为什么?
rust 复制代码
fn main() {
    let s = String::from("Rust");
    let slice = &s[1..3];
    println!("{}", slice);
}
  1. 为什么下面的代码不能编译?
rust 复制代码
fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear();
    println!("{}", word);
}
  1. &String&str 有什么区别?什么时候应该用哪一个?

参考链接

返回首页 | 上篇 | 下篇

相关推荐
自信不摆烂美女2 小时前
从0开始学习3个月进入网络安全行业
学习
程序员爱钓鱼5 小时前
Rust 函数与返回值详解:参数、表达式与返回类型
后端·rust
AOwhisky11 小时前
Python 学习笔记(第一期与第二期)——基础语法——核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发
花褪残红青杏小11 小时前
Rust图像处理第17节-Julia 朱利亚分形:拖动常数 c 看图实时变化
rust·webassembly·图形学
STLearner11 小时前
ICML 2026 | LLM×Graph论文总结[1]【图基础模型,文本属性图,多模态属性图,图对齐,图提示学习,关系深度学习
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
习明然13 小时前
我的本地化AI项目(三)
人工智能·python·electron·c#·avalonia
什巳13 小时前
JAVA练习278- 和为 K 的子数组
java·学习·算法·leetcode
碎光拾影14 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
An_s15 小时前
rust开发wasm与浏览器(js)交互
javascript·rust·wasm