马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
【Rust】004-Rust 所有权
rust 语言很严谨!符合我严谨认真的性格!
一、准备知识
1、堆和栈
堆和栈都可以在程序运行时给程序提供内存空间,但是他们是有区别的。
想象一下,栈就像一叠盘子。先来的盘子在底部,新盘子则放在顶部。取用时,总是先取最上面的盘子,就像在餐馆里洗完的盘子先用先拿。这就是先辈后出的原则。但是,盘子的大小必须是确定的,否则无法存放在这个“栈”里。
而堆则像一个大杂货堆栈。你可以随时放入各种大小和外形的物品。为了方便找到它们,你必要贴上标签,这些标签就像指针,告诉你物品的位置。固然这样存放物品灵活多变,但要找到合适的空间并读取它们就不如“栈”中的盘子方便了。
在性能的竞走中,栈就像速度迅猛的跑车,堆则更像一辆载重卡车。栈的读写速度快,由于一切都井井有条,就像把盘子放在最上面那样简单。而堆要先找到合适的存放空间,读取时还要通过标签(指针)找到物品,就像在杂货堆栈里寻找一个小小的零件一样,可能必要多花点时间。
2、String 类型
在 Rust 编程的天下中,字符串有点像我们现实生活中的名片(印好之后,不再改变)和便签(可以继续往上面写东西)。有两种类型让我们来认识它们:String和&str。
&str
存储逻辑
在 Rust 中,字符串字面量(如 "hello")的现实值通常存储在程序的只读数据段中。这个数据段是编译时确定的,并且在程序运行时是不可变的。这意味着字符串字面量在程序的生命周期内是常驻内存的,不会被修改或释放。
当你在代码中使用字符串字面量时,例如:
这个 &str 类型的变量 s 是一个胖指针,包罗了指向 "hello" 的内存地址和字符串的长度。这些信息自己存储在栈上,而 "hello" 的现实字符串数据则在程序的只读数据段中。
想象&str是一种名片,它被称为字符串字面值,就像是工厂里生产出来的名片,印好了,就再也不能改了。你可以这样给自己制造一张名片:
- fn main() {
- // 使用字符串字面量创建静态字符串
- let hello = "hello world";
- }
复制代码 这张名片是固定的,不可变的,由于它是硬编码到程序中的,就像是直接印在墙上的字。
String
存储逻辑
- 堆上存储数据
- String 的现实字符串数据存储在堆上。这意味着 String 可以在程序运行时动态调解其大小,而不受栈大小的限定。
- 栈上存储元数据:
- String 类型在栈上存储了一些元数据,包括一个指向堆上数据的指针、字符串的长度以及当前的容量(即分配的堆内存大小)。这些元数据使得 String 可以管理堆上的内存。
- 容量与长度:
- 长度:表示当前存储在 String 中的字符数。
- 容量:表示已经分配的内存空间,允许在不重新分配的环境下扩展字符串。String 可能会预先分配比现实必要更多的内存,以优化性能,淘汰频繁的内存分配和复制操作。
- 增长计谋:
- 当 String 的内容增加到超出其当前容量时,String 会主动分配更大的内存块,并将现有数据复制到新分配的内存中。这通常是通过倍增计谋来实现的,以平衡内存使用和性能。
- 所有权与内存管理:
- String 拥有其堆上数据的所有权,这意味着当 String 被抛弃时,它会主动释放其占用的堆内存。这是 Rust 所有权模型和借用检查器的一个紧张特性,确保了内存的安全管理。
- 可变性:
- String 是可变的,可以通过方法如 push 或 push_str 来追加数据,或者通过 truncate 来淘汰长度。
但生活中不是所有的信息都是确定的,偶然候我们必要一些便签。这就是String的脚色了,好比你想要记录下用户的一些临时想法或命令。String的数据存储在堆上,就像前面提到的杂货堆栈,可以随时变更。你还记得堆和栈的故事吗?
创建一个String就像是抓一张空白的便签纸,你可以从字符串字面值开始书写:
- fn main() {
-
- // 创建一个可变字符串
- let mut hi = String::new();
- // 写入文字
- hi.push_str("hello");
- hi.push_str(" world");
- }
复制代码
二、所有权规则
1、所有权系统的三条规则
- Rust 中每个值都有一个所有者;
- 一个值同时只能有一个所有者;
- 当所有者离开作用域范围,这个值将被抛弃。
2、代码示例
- fn main() {
- // 规则 1:Rust中每个值都有一个所有者
- let s1 = String::from("hello"); // s1 是值 "hello" 的所有者
- {
- // 规则 2:一个值同时只能有一个所有者
- let s2 = s1; // 所有权从 s1 转移到 s2(s1 不再有效)
- // println!("{}", s1); // 这会导致编译时错误,因为 s1 不再有效
- println!("{}", s2); // 这是允许的,因为 s2 现在拥有该值
- } // s2在此处超出范围
- // 规则 3:当所有者离开作用域范围,这个值将被丢弃
- // 由于 s2 超出范围,因此为值 "hello" 分配的内存在此处自动释放。
- }`
复制代码
3、所有权转移
简单示例
i32这样的简单类型,赋值的时候 Rust 会主动举行拷贝(Copy)。
这段代码中,起首将 5 绑定到 x,接着再将 x 的值拷贝给 y。这两行实验完, x 和 y 都是 5,且都可以正常使用。稍稍改变一下这个例子。
而对于 String 这样的分配到堆上的复杂类型,发生的却是所有权的转移,而不是拷贝。
- let s1 = String::from("hello");
- let s2 = s1;
复制代码 简单类型:主动拷贝(简单类型的现实数据内容存储在栈上的);
复杂类型:所有权转移(拷贝的是内存地址,现实数据内容在堆上,但会导致一个数据被两个变量同时拥有,离开作用域会出现双重释放的环境,进而导致安全题目,因此Rust计划了所欲全转移机制!)。
复杂类型的拷贝
这种拷贝不存在所有权的转移,他们是相互独立的!
- let s1 = String::from("hello");
- let s2 = s1.clone();
- println!("s1 = {}, s2 = {}", s1, s2); // s1 = hello, s2 = hello
复制代码
4、函数的传值与返回
将值传给函数跟上面讲的赋值类似,都会进行转移或者拷贝的过程。**函数返回一个值的时候,也会经历所有权转移的过程。**我们用下面的例子来说明:
- fn takes_ownership(s: String) {
- println!("Received string: {}", s);
- } // s 离开作用域,被丢弃
- fn gives_ownership() -> String {
- String::from("hello")
- } // 返回了String的所有权
- fn main() {
- // s 拿到了"hello"的所有权
- let s = String::from("hello");
- // 所有权转移给了 takes_ownership 函数的参数:s
- takes_ownership(s); // s转移到了函数内,不再可用
- // s 不再可用
- // 此处还可以声明一个 s ,是因为上面的 s 已经被回收了!
- let s = gives_ownership(); // s 获得了返回值的所有权
- }
复制代码
三、引用与借用
1、借用
只使用变量,而不拿走所有权,叫“借用”!
2、不可变引用(只读)
- fn main() {
- // s1 拿到"hello"的所有权
- let s1 = String::from("hello");
- // 使用 &s1 而不是 s1,借出去,只是借出去,并不允许值被改变
- // 使用 len 接收返回值
- let len = calculate_length(&s1);
- // s1 仍然具有"hello"的所有权
- // len 是借用出去后所得到 len() 的返回值
- println!("The length of '{}' is {}.", s1, len);
- }
- // 使用 &String 表示借用,是 String 类型的引用
- fn calculate_length(s: &String) -> usize {
- // 从借来的 s 取得 len() 的值,并返回
- s.len()
- }
复制代码
3、可变引用(可读可写)
- fn main() {
- // s 拿到 "hello" 的所有权,s 本身是可修改的
- let mut s = String::from("hello");
- // 将 s 借出去,并允许被修改
- change(&mut s);
- // s 的值被修改了
- println!("The updated string is: {}", s);
- }
- // 这里使用 &mut String 来接收,表示要求可被修改
- fn change(s: &mut String) {
- // 修改借来的数据
- s.push_str(", world!");
- }
复制代码
4、紧张规则
对于一个变量,同时只能存在一个可变引用或者多个不可变引用。
- fn main() {
- let mut s = String::from("hello");
- // 多个不可变引用是允许的
- let r1 = &s;
- let r2 = &s;
- println!("r1: {}, r2: {}", r1, r2);
- // 在这里,多个不可变引用是允许的,因此打印不会引发错误。
- // 一个可变引用
- let r3 = &mut s;
- println!("r3: {}", r3);
- // 在这里,只有一个可变引用,因此打印不会引发错误。
- // 不允许同时存在可变引用和不可变引用
- // let r4 = &s; // 这会导致编译时错误
- // 如果这里只打印 r4 是不会报错的,因为 r3 在上面已经释放,但此处打印了 r3 ,r3 就不会在此前被释放了!
- // println!("r3: {}, r4: {}", r3, r4);
- // 如果取消注释上面两行,同时存在可变引用和不可变引用将导致编译时错误。
- }
复制代码
5、NLL
在老版本的 Rust 编译器中(1.31之前),确实上述的r1,r2和r3是会报错的。但是这样实在会带来很多麻烦,导致代码很难写。于是 Rust 编译器做了一项优化:引用的作用域结束的位置不再是花括号的位置,而是末了一次使用的位置。因此,在现在 Rust 的版本中,上面的例子并不会报错。
6、悬垂引用
悬垂引用指的是指针指向的是内存中一个已经被释放的地址**,这在其他的一些有指针的语言中是很常见的错误。而 Rust 则可以在编译阶段就包管不会产生悬垂引用。也就是说,如果有一个引用指向某个数据,编译器能包管在引用离开作用域之前,被指向的数据不会被释放。
错误代码
- fn main() {
- let reference_to_nothing = dangle();
- }
- fn dangle() -> &String {
- let s = String::from("hello");
- // 这里会报错,因为 s 已经被释放了!返回的地址是一个无效的地址!
- // this function's return type contains a borrowed value, but there is no value for it to be borrowed from
- // 该函数返回了一个借用的值,但是没有可以借用的来源
- // 引用必须是有效的
- &s
- }
复制代码
四、切片
1、概念
切片可以让我们引用集合中的一段连续空间。切片也是一种引用,因此没有所有权。
2、字符串切片
基本写法
- fn main() {
- let s = String::from("hello world");
- let hello = &s[0..5];
- let world = &s[6..11];
- println!("{}", s);
- println!("{}", hello);
- println!("{}", world);
- }
复制代码
简化写法
- let s = String::from("hello");
- let len = s.len();
- // 以0开始时,0可以省略
- let slice = &s[0..2];
- let slice = &s[..2];
- // 以最后一位结束时,len可以省略
- let slice = &s[3..len];
- let slice = &s[3..];
- // 同时满足上述两条,那么两头都可以省略
- let slice = &s[0..len];
- let slice = &s[..];
复制代码
3、其他切片
String 自己就是数组
- #[derive(PartialEq, PartialOrd, Eq, Ord)]
- #[stable(feature = "rust1", since = "1.0.0")]
- #[cfg_attr(not(test), lang = "String")]
- pub struct String {
- vec: Vec<u8>,
- }
复制代码 除了 String,数组类型也有切片。例如:
- let a = [1, 2, 3, 4, 5];
- let slice = &a[1..3];
- assert_eq!(slice, &[2, 3]);
复制代码
五、总结
本节内容较多,主要包罗了三部门的知识:所有权,借用和切片。所有权这套系统是 Rust 内存安全的紧张保障。有了这套系统,我们既可以享受不必要手动释放内存的便利,又可以对内存使用有足够的控制,包管内存安全。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |