跳至内容
张叶安的小站
用户工具
登录
站点工具
搜索
工具
显示页面
过去修订
反向链接
最近更改
媒体管理器
网站地图
登录
>
最近更改
媒体管理器
网站地图
您的足迹:
rust:第二章所有权系统
本页面只读。您可以查看源文件,但不能更改它。如果您觉得这是系统错误,请联系管理员。
====== 第二章 所有权系统 ====== ===== 2.1 什么是所有权 ===== 所有权(Ownership)是Rust最独特的特性,它使得Rust能够在没有垃圾回收器(GC)的情况下保证内存安全。理解所有权是理解Rust的关键。 ==== 栈与堆 ==== 在深入了解所有权之前,需要理解栈(Stack)和堆(Heap)的区别: **栈(Stack):** * 后进先出(LIFO)的数据结构 * 存储已知固定大小的数据 * 入栈和出栈速度快 * 数据必须拥有已知且固定的大小 **堆(Heap):** * 用于存储编译时大小未知或可能变化的数据 * 在堆上分配内存称为分配(allocating) * 访问堆数据比栈慢,需要指针寻址 * 灵活性更高,但管理更复杂 **所有权规则:** 1. Rust中的每个值都有一个所有者(owner) 2. 值在任一时刻有且只有一个所有者 3. 当所有者离开作用域,值将被丢弃 ===== 2.2 变量作用域 ===== 作用域(Scope)是程序中一个项在有效性的范围: <code rust> { // s 在这里无效,它尚未声明 let s = "hello"; // s 从这里开始有效 // 使用 s println!("{}", s); } // 作用域结束,s 不再有效 // println!("{}", s); // 错误!s已失效 </code> **关键点:** * 当变量进入作用域,它就是有效的 * 一直有效直到离开作用域 * 作用域由大括号{}界定 ===== 2.3 String类型与内存分配 ===== ==== 字符串字面量 vs String ==== <code rust> let s = "hello"; // 字符串字面量,硬编码进程序,不可变 let mut s = String::from("hello"); // String类型,可变的 s.push_str(", world!"); // 追加字符串 </code> **为什么String可变而字符串字面量不可变?** * 字符串字面量:编译时已知内容,直接硬编码到最终可执行文件,快速高效 * String:在堆上分配,编译时大小未知,可以存储在运行时修改的文本 ==== 内存分配方式 ==== **String的内存布局:** <code rust> let s = String::from("hello"); </code> 内存中的表示: * 指向堆上内容的指针 * 长度(当前内容使用的字节数) * 容量(从分配器获得的内存总量) ===== 2.4 所有权转移(Move) ===== ==== 变量与数据交互的方式 ==== **1. 移动(Move):** <code rust> let s1 = String::from("hello"); let s2 = s1; // s1的所有权移动到s2 // println!("{}", s1); // 错误!s1不再有效 println!("{}", s2); // 正确 </code> 当s1赋值给s2时,String的数据被复制了,包括: * 指向堆数据的指针 * 长度 * 容量 但堆上的数据**没有被复制**!为了避免双重释放(double free)错误,Rust让s1失效。 **图示:** 赋值前: <code> s1 +--------+ +---------+ | 指针 | ---> | hello | | 长度 5 | +---------+ | 容量 5 | +--------+ </code> 赋值后(s1失效): <code> s2 +--------+ +---------+ | 指针 | ---> | hello | | 长度 5 | +---------+ | 容量 5 | +--------+ </code> ==== 克隆(Clone)==== 如果需要深拷贝堆数据,使用clone方法: <code rust> let s1 = String::from("hello"); let s2 = s1.clone(); // 深拷贝 println!("s1 = {}, s2 = {}", s1, s2); // 都有效 </code> **注意**:clone操作可能很昂贵,Rust通过显式调用来表明这一点。 ==== 只在栈上的数据:Copy trait ==== 某些类型在赋值后仍然可用,因为它们实现了Copy trait: <code rust> let x = 5; let y = x; // Copy,不是Move println!("x = {}, y = {}", x, y); // 都有效 </code> **实现Copy trait的类型:** * 所有整数类型(i32, u64等) * 布尔类型(bool) * 浮点类型(f32, f64) * 字符类型(char) * 仅包含Copy类型的元组(如(i32, i32),但(i32, String)不行) ===== 2.5 所有权与函数 ===== ==== 将值传递给函数 ==== <code rust> fn main() { let s = String::from("hello"); // s进入作用域 takes_ownership(s); // s的值移动到函数里 // println!("{}", s); // 错误!s不再有效 let x = 5; // x进入作用域 makes_copy(x); // x被Copy到函数里 println!("x = {}", x); // 正确!x仍然有效 } fn takes_ownership(some_string: String) { println!("{}", some_string); } // some_string离开作用域,调用drop,内存释放 fn makes_copy(some_integer: i32) { println!("{}", some_integer); } // some_integer离开作用域,无需特殊处理 </code> ==== 返回值与作用域 ==== <code rust> fn main() { let s1 = gives_ownership(); // gives_ownership将返回值转移给s1 let s2 = String::from("hello"); // s2进入作用域 let s3 = takes_and_gives_back(s2); // s2被移动到函数,返回值给s3 println!("s1 = {}, s3 = {}", s1, s3); } // s3离开作用域被丢弃,s2已移动(不发生),s1被丢弃 fn gives_ownership() -> String { let some_string = String::from("yours"); some_string // 返回并转移所有权 } fn takes_and_gives_back(a_string: String) -> String { a_string // 返回并转移所有权 } </code> ===== 2.6 引用与借用 ===== ==== 什么是引用 ==== 为了避免所有权转移带来的不便,可以使用引用: <code rust> fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // 传递引用,不转移所有权 println!("'{}'的长度是{}", s1, len); // s1仍然有效 } fn calculate_length(s: &String) -> usize { s.len() } // s离开作用域,但它没有所有权,所以不释放内存 </code> **借用(Borrowing)**:将创建一个引用的行为称为借用。 **引用与指针的区别:** * 引用总是有效的(编译器保证) * 不能有空引用 * 不能有多重引用导致的问题 ==== 可变引用 ==== 默认情况下引用是不可变的,需要可变引用才能修改: <code rust> fn main() { let mut s = String::from("hello"); change(&mut s); println!("{}", s); // "hello, world" } fn change(some_string: &mut String) { some_string.push_str(", world"); } </code> **可变引用的限制:** <code rust> let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; // 错误!不能同时有两个可变引用 println!("{}, {}", r1, r2); </code> **好处:** 在编译期防止数据竞争(data race)。 **数据竞争条件:** 1. 两个或更多指针同时访问同一数据 2. 至少有一个用于写入 3. 没有同步访问机制 ==== 混合引用规则 ==== <code rust> let mut s = String::from("hello"); let r1 = &s; // 没问题 let r2 = &s; // 没问题 let r3 = &mut s; // 大问题! </code> **规则总结:** * 可以有任意数量的不可变引用 * 或者只有一个可变引用 * 不能同时存在可变和不可变引用 ===== 2.7 悬垂引用(Dangling References) ===== Rust编译器保证引用永远不会悬垂: <code rust> fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s // 错误!s将在函数结束时被释放 } // s离开作用域,内存被释放 </code> 编译器错误信息: <code> error[E0106]: missing lifetime specifier </code> **解决方案:返回所有权** <code rust> fn no_dangle() -> String { let s = String::from("hello"); s // 转移所有权 } </code> ===== 2.8 Slice类型 ===== ==== 字符串Slice ==== 字符串slice是String中一部分值的引用: <code rust> let s = String::from("hello world"); let hello = &s[0..5]; // 从0开始到5(不包括5) let world = &s[6..11]; // 从6开始到11(不包括11) </code> **语法:** * [开始索引..结束索引]:从开始到结束(不包括结束) * [..2]:从0开始到2 * [3..]:从3开始到结尾 * [..]:整个字符串 **注意:** slice的索引必须在有效的UTF-8字符边界。 ==== 字符串字面量就是slice ==== <code rust> let s = "Hello, world!"; // &str类型 </code> 字符串字面量被直接存储在二进制文件中,&str是指向该位置的slice。 ==== 数组Slice ==== <code rust> let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; // &[i32]类型,包含[2, 3] </code> ===== 2.9 生命周期(初步介绍) ===== 生命周期是Rust的另一个重要概念,将在第十四章深入讲解。这里简要介绍其必要性: <code rust> fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } </code> 这段代码会报错,因为编译器不知道返回的引用与哪个参数相关。需要使用生命周期标注: <code rust> fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } </code> ===== 练习题 ===== ==== 练习题2.1:理解所有权转移 ==== 预测以下代码的输出: <code rust> fn main() { let s = String::from("hello"); let mut s2 = s; s2.push_str(" world"); println!("{}", s2); // println!("{}", s); // 能编译吗? } </code> ==== 练习题2.2:借用练习 ==== 修复以下代码中的错误: <code rust> fn main() { let s = String::from("hello"); let r1 = &s; let r2 = &mut s; // 错误! println!("{}, {}", r1, r2); } </code> **修复方案:** <code rust> fn main() { let mut s = String::from("hello"); { let r1 = &s; println!("{}", r1); } // r1在这里离开作用域 let r2 = &mut s; // 现在可以了 println!("{}", r2); } </code> ==== 练习题2.3:实现第一个单词提取 ==== 编写函数返回字符串中的第一个单词: <code rust> 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); } </code> ==== 练习题2.4:计算字符串字节长度 ==== <code rust> fn string_length(s: &String) -> usize { s.len() } fn main() { let s = String::from("hello"); let len = string_length(&s); println!("长度:{}", len); } </code> ===== 本章小结 ===== 本章深入讲解了Rust的所有权系统: * **所有权规则**:每个值有且只有一个所有者,所有者离开作用域值被丢弃 * **Move语义**:赋值时所有权转移,原变量失效 * **Copy trait**:基本类型实现Copy,赋值时复制而非移动 * **借用**:通过引用访问数据而不获取所有权 * **可变引用**:同一作用域只能有一个可变引用 * **生命周期**:编译器通过生命周期确保引用总是有效 所有权系统是Rust内存管理的核心,也是Rust能够提供内存安全保证的基础。掌握所有权系统是成为Rust程序员的关键一步。 在下一章中,我们将详细学习Rust的基本数据类型。
rust/第二章所有权系统.txt
· 最后更改:
2026/02/03 19:45
由
127.0.0.1
页面工具
显示页面
过去修订
反向链接
回到顶部