堆内存区
静态内存、栈内存、堆内存都能用来存放数据,静态内存主要用来存全局变量那种,栈内存用来存函数变量那种,因为函数的调用很适合用栈来表示,所以处理函数变量相当方便,变量的地址也一般都是相邻的,而堆一般用来存放动态的变量,存放的地址也一般是离散的
在 rust 中,内存分配机制主要是使用栈来存储数据,只要是编译时可以确定大小的变量都使用栈来存储,而那些没法在编译时确认大小的变量,都必须用堆内存来存储,想要堆的话需要向操作系统申请
比如说 Vec 的数据存储,都是使用堆来存放里面的元素的,当然因为 Vec 不是需要用户自己实现,所以我们也不需要去关心它的底层,我们自己写的时候也会遇到无法确定大小的情况,比如说,自己调用自己的结构体
struct Link {
data: i32,
next: Option<Link>
}
这个为了实现链表的结构体调用了自己,而不是引用的形式,当我们编译的时候,编译器就会教我们做人,说这个结构体拥有无限的 size,告诉我们需要使用 box 来包裹 next 变量
想象一下,如果一个结构体能不停的容纳自己相同类型的结构体,里面的结构体也能具有相同的的能力,那么就真的无法预测它需要多大的内存空间了,所以这时候,编译器就要求我们不要那栈来存这个东西,要用堆,而把变量存进堆里,就需要它说的这个 box
Box 类型
按照编译器提示,我们应该写成:
struct Link {
data: i32,
next: Option<Box<Link>>
}
Box 类能把目标变量存放在堆内存里,具体的使用会是这样子
fn main() {
let link = Some(Box::new(Link {
data: 1,
next: Some(Box::new(Link {
data: 2,
next: Some(Box::new(Link {
data: 3,
next: None
}))
}))
}));
let mut lnk = &link;
loop {
if let Some(b) = lnk {
println!("{}", b.data);
lnk = &b.next;
}
else {
break;
}
}
}
Box::new
方法用来创建一个堆内存区中的数据实例,会向系统申请一块与传入数据大小相等的堆内存区来放这个数据
Box 实现了解引用特性 Deref,也就是 Box<SomeType>
相当于 &SomeType
,当然和一般实现了 Deref 的结构体还是有区别的,Box 的解引用是自动的,不需要使用显式 *
Box 其实只是一个指向堆区的指针,不是像盒子一样的东西
在 rust 中堆区的数据不存在直接的所有权,他的所有权归 Box 指针所有,当 Box 变量超过生命周期时,就会释放掉在堆中的内存空间
dyn 关键字
实现了相同特性的,变量可以被统一处理
但是实现相同特性的不同类型,往往他的大小是不同的,这里编译时就无法确定大小,需要 box 的帮忙
dyn 关键字能让 box 明确下面这个是个特性而不是一个具体类型
高级引用
前面介绍的 Box 就是一种高级引用,除了 Box 外还有其他高级引用,都是能实现普通引用难以实现的重要功能
Rc
rc 是什么?rc 是浪漫取消
Rc 是 Reference Counting,实现了 Copy 特性的泛型类,作用让不易于实现克隆特性的变量能够被轻易得使用 clone 方法,它会把数据放在堆上,当调用它的 clone 方法时,它会将引用计数加1,当 Rc 检测到计数为 0 时,Rc 才会释放堆上的数据
use std::rc::Rc;
struct A {
value: i32
}
fn main() {
let x = Rc::new(A {value: 46});
let y = x.clone();
println!("{}", x.value);
}
使用 Rc 只要知道两个函数就好,一个是 new
用来创建一个 Rc, 一个是 clone
方法用来克隆
可以看出 Rc 实现了自动解引用的特性,但是它没有实现 DerefMut 特性,所以不能当作可变引用来使用,也就是如果单靠 Rc 只能当作不可变引用来使用
Mutex
Mutex 是互斥锁,用来保障数据对象不能被同时超过一个方面访问或使用,就是锁的概念
它没有实现自动解引用,所以需要使用显式 *
来解引用
use std::sync::Mutex;
fn main() {
let mutex = Mutex::new(1000);
let mut locked_1 = mutex.lock().unwrap();
*locked_1 += 2;
println!("{}", locked_1);
let locked_2 = mutex.lock().unwrap();
println!("locked_2 locked");
}
上面这个程序会卡在第八行,因为 mutex 已经被 locked_1 上了锁,在 locked_1 生命周期结束被释放前,locked_2 都会一直等待,所以这个程序是有问题的,如果把 locked_1 写进代码块里就行了
use std::sync::Mutex;
fn main() {
let mutex = Mutex::new(1000);
{
let mut locked_1 = mutex.lock().unwrap();
*locked_1 += 2;
println!("{}", locked_1);
}
let locked_2 = mutex.lock();
}
Mutex 可以和 Rc 相结合,间接的能够改变 Rc 的值
use std::sync::Mutex;
use std::rc::Rc;
fn main() {
let mut_rc = Rc::new(Mutex::new(10000));
{
let copy = mut_rc.clone();
let mut lock_1 = copy.lock().unwrap();
*lock_1 += 1;
}
let lock_2 = mut_rc.lock().unwrap();
println!("{}", lock_2);
}
我们也可以发现 Rc 其实只是对内存多了一个引用,并没有对数据本身进行复制
运算符方法
也叫重载运算符(?
Rust 的重载不用像 cpp 里那样一堆奇怪的符号关键字记忆,只需要实现特性即可
比如说我想要实现一个向量结构体的加法,那么我就可以对它实现一个 std::ops::Add 特性
use std::ops::Add;
struct Vectors (i32, i32);
impl Add for Vectors {
type Output = Self;
fn add(self, other: Self) -> Self {
Self (self.0 + other.0, self.1 + other.1)
}
}
fn main() {
let x = Vectors (2, 4);
let y = Vectors (5, 5);
let z = x + y;
println!("z = ({}, {})", z.0, z.1);
}
在上面这个代码里,我们在 z = x + y
之后,x 和 y 的所有权就发生了转移,不能再使用了,但有些情况下我们不想让这种情况发生,我们就需要使用引用符号,但是由于在 rust 里引用也是一种类型,所以我们并没有实现引用的加法
我们还需要对引用实现 Add 特性
impl Add for &Vectors {
type Output = Vectors;
fn add(self, other: Self) -> Vectors {
Vectors (self.0 + other.0, self.1 + other.1)
}
}
这样使用就能避免失去所有权了
fn main() {
let x = Vectors (2, 4);
let y = Vectors (5, 5);
let z = &x + &y;
println!("z = ({}, {})", z.0, z.1);
println!("x = ({}, {})", x.0, x.1);
}
但是有一点要注意,运算符不能创造新的符号了,所以我们只能对现有的运算符进行重构,但可以看出,符号可以简单得看作是对调用函数或方法的语法糖,我们实现符号是一方面为了方便编写,另一方面也是为了提高代码的可读性
rust 中可以重载的运算符都在 std::ops
里,可以去标准库的官方文档查看
另外还有几个比较重要的特殊重载运算符,一个是解引用 Deref 和 DerefMut, 还有一个是 Drop 特性
具体的使用场景就不放了,就讲讲对应什么运算符号吧
{
let x = &mut a;
println!("{}", *x); // Deref
*x += 1; // DerefMut
}// x 的生命周期结束,调用 Drop,相当于是析构函数
2023/04/24在看完知识点后信心满满的开始写代码,想要不利用 Box 实现一个根据数组来构建链表的程序,然而被编译器干沉默了,什么生命周期,所有权机制,悬垂指针的概念疯狂对我输出,然后知道自己的修炼还不够,还需要多写 rust, 所以花了这么长的时间才把这篇东西搞定,累死 (:з」∠)