Rust day02 字符串-所有权-引用-切片

25/9/15 10:00 - 2025/9/15 22:00

参考资料 章节4

Rust 程序设计语言 - Rust 程序设计语言 中文版

所有权系统

所有权规则

  • 所有权三原则
    1. 每个值都有一个唯一的所有者
    2. 同一时间只能有一个所有者
    3. 当所有者离开作用域,值将被自动释放

String类型内存布局

let s = String::from("hello");
  • 栈上:存储指针(pointer)、长度(len)和容量(capacity)
  • 堆上:存储实际字符串数据"hello"

变量与数据交互

  • 移动(Move):赋值时转移所有权,原变量失效
let s1 = String::from("hello");
let s2 = s1; // s1移动到s2,s1不再可用
  • 克隆(Clone):深拷贝堆上数据
let s1 = String::from("hello");
let s2 = s1.clone(); // s1和s2都有效
  • Copy trait:实现Copy的类型在赋值时自动拷贝(如整数、布尔、字符等)

引用与借用

不可变引用

fn calculate_length(s: &String) -> usize {
    s.len()
}

let s = String::from("hello");
let len = calculate_length(&s); // &s创建不可变引用

可变引用

fn change(s: &mut String) {
    s.push_str(", world");
}

let mut s = String::from("hello");
change(&mut s); // &mut s创建可变引用

引用规则

  1. 同一作用域内,只能有一个可变引用 多个不可变引用
  2. 引用必须始终有效(无悬垂引用)

切片类型

字符串切片

let s = String::from("hello world");
let hello = &s[0..5]; // 从索引0到4(不包含5)
let world = &s[6..11];

切片范围简写

let s = String::from("hello");
let slice1 = &s[0..2]; // 从0开始可简写为&s[..2]
let slice2 = &s[3..5]; // 到结尾可简写为&s[3..]
let slice3 = &s[..]; // 整个字符串切片

其他类型切片

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 数组切片,类型为&[i32]

字符串详解

Rust中的字符串类型

  • &str:字符串切片,不可变,通常作为引用使用
  • String:可变、可增长、拥有所有权的字符串类型

创建字符串

let s1 = String::new(); // 创建空字符串
let s2 = "initial contents".to_string(); // 从字面量创建
let s3 = String::from("initial contents"); // 直接构造

更新字符串

let mut s = String::from("hello");
s.push_str(" world"); // 追加字符串切片
s.push('!'); // 追加字符

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = s1 + "-" + &s2 + "-" + &s3; // 字符串拼接

字符串索引的问题

字符串不支持直接索引访问(如s[0]),因为:

  1. UTF-8编码中字符长度可变(1-4字节)
  2. 索引操作预期O(1)时间复杂度,而字符串字符访问是O(n)

安全访问字符串内容

let s = String::from("hello中国");
for c in s.chars() { // 迭代Unicode字符
    println!("{}", c);
}

for b in s.bytes() { // 迭代字节
    println!("{}", b);
}

字符串切片的安全边界

let s = "中国";
let slice = &s[0..3]; // 正确,"中"占3字节
// let slice = &s[0..2]; // 错误,会导致运行时panic

实际应用示例

查找字符串中的第一个单词

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[..]
}

let my_string = String::from("hello world");
let word = first_word(&my_string[..]);

结合所有权与字符串处理

fn main() {
    let mut s = String::from("hello world");
    
    let word = first_word(&s);
    
    s.clear(); // 这里不会导致word悬垂引用,因为word的生命周期在s.clear()之前
}

完整代码

// Rust 所有权系统与字符串处理详解

// 示例1:所有权三原则的展示
fn ownership_principles() {
    println!("=== 所有权三原则 ===");
    
    // 原则a: 每个值都有一个唯一的所有者
    let s1 = String::from("hello");
    println!("s1 = {}", s1);
    
    // 原则b: 同一时间只能有一个所有者
    let s2 = s1; // s1的所有权移动到s2,s1不再可用
    println!("s2 = {}", s2);
    // println!("s1 = {}", s1); // 编译错误:s1已不再可用
    
    // 原则c: 当所有者离开作用域,值将被自动释放
    {
        let s3 = String::from("临时字符串");
        println!("s3 = {}", s3);
    } // s3离开作用域,内存被自动释放
    
    // println!("s3 = {}", s3); // 编译错误:s3不在作用域内
}

// 示例2:移动(Move)与克隆(Clone)
fn move_and_clone() {
    println!("\n=== 移动(Move)与克隆(Clone) ===");
    
    // 移动(Move):赋值时转移所有权,原变量失效
    let s1 = String::from("hello");
    println!("s1创建后: {}", s1);
    
    let s2 = s1; // s1移动到s2,s1不再可用
    println!("s1移动到s2后,s2 = {}", s2);
    // println!("s1 = {}", s1); // 编译错误
    
    // 克隆(Clone):深拷贝堆上数据
    let s3 = String::from("world");
    let s4 = s3.clone(); // s3和s4都有效
    println!("s3克隆到s4后,s3 = {}, s4 = {}", s3, s4);
    
    // 实现Copy的类型在赋值时自动拷贝
    let x = 5; // i32实现了Copy
    let y = x; // x被复制,x仍然可用
    println!("x复制到y后,x = {}, y = {}", x, y);
}

// 示例3:引用与借用
fn references_and_borrowing() {
    println!("\n=== 引用与借用 ===");
    
    // 不可变引用
    let s = String::from("hello");
    let len = calculate_length(&s); // &s创建不可变引用
    println!("字符串 '{}' 的长度是 {}", s, len);
    
    // 可变引用
    let mut s_mut = String::from("hello");
    change(&mut s_mut); // &mut s创建可变引用
    println!("修改后的字符串: '{}'", s_mut);
    
    // 引用规则1: 同一作用域内,只能有一个可变引用 或 多个不可变引用
    // let r1 = &mut s_mut;
    // let r2 = &mut s_mut; // 编译错误:同一作用域内不能有多个可变引用
    
    // 正确的做法:在不同作用域使用可变引用
    {
        let r1 = &mut s_mut;
        r1.push_str(", Rust");
    }
    let r2 = &mut s_mut;
    r2.push_str(", World");
    println!("多次修改后的字符串: '{}'", s_mut);
    
    // 可变引用和不可变引用不能同时存在
    // let r1 = &s_mut; // 不可变引用
    // let r2 = &mut s_mut; // 编译错误:不能同时有不可变和可变引用
    
    // 引用规则2: 引用必须始终有效(无悬垂引用)
    // Rust编译器会确保引用不会超出被引用值的生命周期
}

// 辅助函数:计算字符串长度(不可变引用参数)
fn calculate_length(s: &String) -> usize {
    s.len() // 可以读取s,但不能修改
}

// 辅助函数:修改字符串(可变引用参数)
fn change(s: &mut String) {
    s.push_str(", world"); // 可以修改s
}

// 示例4:切片类型
fn slice_types() {
    println!("\n=== 切片类型 ===");
    
    // 字符串切片
    let s = String::from("hello world");
    let hello = &s[0..5]; // 从索引0到4(不包含5)
    let world = &s[6..11];
    println!("hello = '{}', world = '{}'", hello, world);
    
    // 切片范围简写
    let slice1 = &s[0..2]; // 从0开始可简写为&s[..2]
    let slice2 = &s[3..5]; // 到结尾可简写为&s[3..]
    let slice3 = &s[..];   // 整个字符串切片
    println!("切片简写 - slice1 = '{}', slice2 = '{}', slice3 = '{}'", slice1, slice2, slice3);
    
    // 其他类型切片
    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3]; // 数组切片,类型为&[i32]
    println!("数组切片: {:?}", slice);
    
    // 实际应用:查找字符串中的第一个单词
    let my_string = String::from("hello world");
    let word = first_word(&my_string[..]);
    println!("第一个单词: '{}'", word);
    
    // 字符串字面量就是切片
    let literal = "hello rust";
    let word2 = first_word(literal); // literal的类型是&str
    println!("字符串字面量的第一个单词: '{}'", word2);
}

// 辅助函数:查找字符串中的第一个单词
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[..] // 如果没有空格,返回整个字符串切片
}

// 示例5:字符串详解
fn string_operations() {
    println!("\n=== 字符串详解 ===");
    
    // 创建字符串
    let s1 = String::new(); // 创建空字符串
    let s2 = "initial contents".to_string(); // 从字面量创建
    let s3 = String::from("initial contents"); // 直接构造
    println!("创建的字符串 - s1: '{}', s2: '{}', s3: '{}'", s1, s2, s3);
    
    // 更新字符串
    let mut s = String::from("hello");
    s.push_str(" world"); // 追加字符串切片
    s.push('!'); // 追加字符
    println!("更新后的字符串: '{}'", s);
    
    // 字符串拼接
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");
    let s = s1 + "-" + &s2 + "-" + &s3; // 注意s1的所有权被转移
    println!("拼接后的字符串: '{}'", s);
    // println!("s1 = {}", s1); // 编译错误:s1已不再可用
    
    // 字符串索引的问题
    // let first_char = s[0]; // 编译错误:字符串不支持直接索引访问
    
    // 安全访问字符串内容
    let s_unicode = String::from("hello中国");
    println!("Unicode字符串的字符迭代:");
    for c in s_unicode.chars() {
        print!("{}", c);
    }
    println!();
    
    println!("Unicode字符串的字节迭代:");
    for b in s_unicode.bytes() {
        print!("{}", b);
    }
    println!();
    
    // 字符串切片的安全边界
    let chinese = "中国";
    let slice = &chinese[0..3]; // 正确,"中"占3字节
    println!("安全切片: '{}'", slice);
    // let invalid_slice = &chinese[0..2]; // 错误,会导致运行时panic
}

// 示例6:结合所有权与字符串处理
fn ownership_and_strings() {
    println!("\n=== 结合所有权与字符串处理 ===");
    
    // 引用的生命周期
    let mut s = String::from("hello world");
    let word = first_word(&s);
    println!("第一个单词: '{}'", word);
    
    // 这里s.clear()不会导致word悬垂引用
    // 因为Rust编译器会确保word的生命周期在s.clear()之前
    // 但如果word的生命周期包含s.clear(),则会编译错误
    
    // 以下代码如果取消注释会导致编译错误
    // println!("第一个单词: '{}'", word);
    // s.clear(); // 尝试修改已被引用的内容
    
    // 正确的做法:先清理,再获取引用
    s.clear();
    s.push_str("new string");
    let new_word = first_word(&s);
    println!("新字符串的第一个单词: '{}'", new_word);
}

fn main() {
    println!("Rust 所有权系统与字符串处理详解\n");
    
    // 运行各个示例
    ownership_principles();
    move_and_clone();
    references_and_borrowing();
    slice_types();
    string_operations();
    ownership_and_strings();
}

posted on 2025-09-15 21:45  依只  阅读(12)  评论(0)    收藏  举报

导航