Rust day02 字符串-所有权-引用-切片
25/9/15 10:00 - 2025/9/15 22:00
参考资料 章节4
所有权系统
所有权规则
- 所有权三原则:
- 每个值都有一个唯一的所有者
- 同一时间只能有一个所有者
- 当所有者离开作用域,值将被自动释放
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创建可变引用
引用规则
- 同一作用域内,只能有一个可变引用 或 多个不可变引用
- 引用必须始终有效(无悬垂引用)
切片类型
字符串切片
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]),因为:
- UTF-8编码中字符长度可变(1-4字节)
- 索引操作预期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();
}
浙公网安备 33010602011771号