IT评测·应用市场-qidao123.com技术社区
标题:
【Rust】004-Rust 所有权
[打印本页]
作者:
千千梦丶琪
时间:
2024-8-26 06:58
标题:
【Rust】004-Rust 所有权
【Rust】004-Rust 所有权
rust 语言很严谨!符合我严谨认真的性格!
一、准备知识
1、堆和栈
堆和栈
都可以在
程序
运行时给
程序
提供
内存
空间,但是他们是有
区别
的。
想象一下,栈就像一叠
盘子
。先来的
盘子
在底部,
新盘子
则放在
顶部
。取用时,总是先取最上面的
盘子
,就像在
餐馆
里洗完的
盘子
先用
先拿。这就是先辈后出的
原则。但是,
盘子
的
大小
必须是确定的,否则无法存放在这个“栈”里。
而堆则像一个大
杂货堆栈
。你可以随时放入各种
大小
和
外形
的
物品
。为了方便找到它们,你必要贴上
标签
,这些
标签
就像
指针
,告诉你
物品
的
位置
。固然这样存放
物品
灵活多变,但要找到合适的
空间
并读取它们就不如“栈”中的
盘子
方便了。
在
性能
的
竞走
中,栈就像
速度
迅猛的
跑车
,堆则更像一辆载重
卡车
。栈的
读写速度
快,由于一切都
井井有条
,就像把
盘子
放在最上面那样简单。而堆要先找到合适的
存放空间
,读取时还要通过
标签
(
指针
)找到
物品
,就像在
杂货堆栈
里寻找一个小小的
零件
一样,可能必要多花点
时间
。
2、String 类型
在
Rust
编程
的
天下
中,
字符串
有点像我们
现实生活
中的
名片
(印好之后,不再改变)和
便签
(可以继续往上面写东西)。有两种
类型
让我们来认识它们:String和&
str
。
&str
存储逻辑
在 Rust 中,字符串字面量(如 "hello")的现实值通常存储在程序的只读数据段中。这个数据段是编译时确定的,并且在程序运行时是不可变的。这意味着字符串字面量在程序的生命周期内是常驻内存的,不会被修改或释放。
当你在代码中使用字符串字面量时,例如:
let s: &str = "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)。
let x = 5;
let y = x;
复制代码
这段代码中,起首将 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企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/)
Powered by Discuz! X3.4