update chapter 3.7.1 contents

This commit is contained in:
Joe Chen
2023-06-14 23:28:50 +08:00
parent b8a2dde386
commit 62e75ee888

View File

@@ -8,7 +8,7 @@ Rust所有权的规则如下
- Rust中的每个值都有一个被称为其所有者的变量即值的所有者是某个变量
- 值在任何时刻有且仅有一个所有者;
- 当所有者离开作用域后,这个值将丢弃。
- 当所有者离开作用域后,这个值将丢弃。
```rust
fn main() {
@@ -18,9 +18,9 @@ fn main() {
}
```
上面的代码中a就是`8`的所有者,b`String::from("hello")`的所有者,c则是`vec![1, 2, 3]`的所有者。
上面的代码中a就是`8`的所有者,`b``String::from("hello")`的所有者,`c`则是`vec![1, 2, 3]`的所有者。
注意:b`String::from("hello")`的所有者,但是b不是字符串`"hello"`的所有者。同理,c`vec![1, 2, 3]`的所有者,但不是`[1, 2, 3]`的所有者。至于为什么后续内容String类型部分会进行讲解。
注意:`b``String::from("hello")`的所有者,但是`b`不是字符串`"hello"`的所有者。同理,`c``vec![1, 2, 3]`的所有者,但不是`[1, 2, 3]`的所有者。至于为什么后续内容String类型部分会进行讲解。
## 2. 变量的作用域
@@ -30,21 +30,21 @@ fn main() {
```rust
fn f() {
let b = 1u32; // --------------------------------|
let c = 2u32; //-----------| |
// | |
// | |---b的作用域范围
println!("b = {:?}", b);// |--c的作用范围 |
println!("c = {:?}", c);// | |
//-----------| -------------------|
let b = 1u32; // ---------------------------------|
let c = 2u32; // -----------| |
// | |
// | |--- b的作用域范围
println!("b = {:?}", b); // |-- c的作用范围 |
println!("c = {:?}", c); // | |
// -----------|---------------------|
}
fn main() {
let a: u32 = 8; // ----------------------------|
let a: u32 = 8; // ------------------------------|
println!("a = {:?}", a); // |
// | ---- a 的作用域范围
// |---- a的作用域范围
f(); // |
//---------------------------------------------------|
// --------------------------------------------------|
}
```
@@ -55,8 +55,8 @@ fn main() {
let a = 8u32; // --------------------------|
{ // |
let b = 5u32; // -------| |
println!("a = {:?}", a); // |--b的作用域范围 |
println!("b = {:?}", b); // | |-----a的作用域范围
println!("a = {:?}", a); // |-- b的作用域范围 |
println!("b = {:?}", b); // | |---- a的作用域范围
// -------| |
} // |
println!("a = {:?}", a); // |
@@ -66,7 +66,7 @@ fn main() {
## 3. String类型
### 3.1 String类型的创建有下面三种方式
### 1String类型的创建有下面三种方式
- `String::from`
- `to_string`
@@ -74,10 +74,10 @@ fn main() {
```rust
fn main() {
let s1 = String::from("Hello"); // 方法一
let s2 = "Hello".to_string(); // 方法二
let s1 = String::from("Hello"); // 方法一
let s2 = "Hello".to_string(); // 方法二
let mut s3 = String::new(); // 方法三
let mut s3 = String::new(); // 方法三
s3.push('H');
s3.push('e');
s3.push('l');
@@ -103,36 +103,36 @@ pub struct String {
Vec类型的定义如下
```Rust
```rust
pub struct Vec<T> {
buf: RawVec<T>,
len: usize, // 长度
len: usize, // 长度
}
```
RawVec定义则类似于如下为了更好说明String类型下面的定义用简化的代码
RawVec定义则类似于如下为了更好说明String类型下面的定义用简化的代码
```Rust
```rust
struct RawVec<T> {
ptr: NonNull<T>, // 指针
cap: usize, // 容量
ptr: NonNull<T>, // 指针
cap: usize, // 容量
}
```
那对于整个String类型可以用伪代码表示如下
```Rust
```rust
struct String {
v: struct Vec<u8> {
raw_vec: RawVec{ptr: NonNull<u8>, cap: usize},
len: usize,
}
raw_vec: RawVec{ptr: NonNull<u8>, cap: usize},
len: usize,
}
}
```
更进一步简化可以得到String类型本质如下
更进一步简化可以得到String类型本质如下
```Rust
```rust
struct String {
ptrNonNull<u8>,
cap: usize
@@ -142,7 +142,7 @@ struct String {
所以String类型本质是三个字段一个指针一个容量大小一个长度大小。
### 3.3 内存分配
### 3内存分配
在Rust中编译时大小确定的数据放在栈上编译时大小不能确定的数据放在堆上。考虑如下代码
@@ -152,21 +152,21 @@ fn main() {
s.push('A');
s.push('B');
println!("{s}"); // 打印AB
println!("{s}"); // 打印AB
}
```
在第2行定义String类型时并不能确定最终字符串的大小所以字符串内容本身应该存储在堆上。结合什么String类型的本质的内容可以得到String类型的存储如下
在第2行定义String类型时并不能确定最终字符串的大小所以字符串内容本身应该存储在堆上。结合String类型的本质的内容可以得到String类型的存储如下
![注释](.././assets/5.png)
![注释](../assets/5.png)
String类型本身是三个字段指针、长度、容量在编译时是已知的大小存储在栈上String类型绑定的字符串在上面代码中是“AB”在编译时大小未知是运行时在堆上分配内存分配后的内存地址保存在String类型的指针字段中内存大小保存在cap字段中内存上存储的字符串长度保存在len字段中。
String类型本身是三个字段指针、长度、容量在编译时是已知的大小存储在栈上String类型绑定的字符串在上面代码中是“AB”在编译时大小未知是运行时在堆上分配内存分配后的内存地址保存在String类型的指针字段中内存大小保存在`cap`字段中,内存上存储的字符串长度保存在`len`字段中。
## 4. move语义
Rust所有权规则第二条在任意时刻值有且仅有一个所有者。那么当一个变量赋给另外一个变量时发生了什么
### 4.1 完全存储在栈上的类型
### 1完全存储在栈上的类型
考虑如下代码:
@@ -178,9 +178,9 @@ fn main() {
}
```
x和y都是u32类型在编译时知道大小都存储在栈上。代码第2行是将5绑定到变量`x`第3行则是通过自动拷贝的方式将5绑定到`y`上(先拷贝`x`的值5,然后将拷贝后得到的5绑定到y上)。所以,当`let y = x`发生后,这段代码里面最后有两个值5,分别绑定到了`x``y`上。
`x``y`都是`u32`类型在编译时知道大小都存储在栈上。代码第2行是将`5`绑定到变量`x`第3行则是通过自动拷贝的方式将`5`绑定到`y`上(先拷贝`x`的值`5`,然后将拷贝后得到的`5`绑定到`y`上)。所以,当`let y = x`发生后,这段代码里面最后有两个值`5`,分别绑定到了`x``y`上。
### 4.2 涉及到堆存储的类型
### 2涉及到堆存储的类型
再考虑如下代码:
@@ -188,35 +188,37 @@ x和y都是u32类型在编译时知道大小都存储在栈上。代码第
fn main() {
let s = "Hello world!".to_string();
let s1 = s;
// println!("s: {:?}", s); // 此行打开编译将报错
// println!("s: {:?}", s); // 此行打开编译将报错
println!("s1: {:?}", s1);
}
```
s`String`类型,字符串`"Hello world"`是存储在堆内存上的,其内存布局如下:
`s``String`类型,字符串`"Hello world"`是存储在堆内存上的,其内存布局如下:
![注释](.././assets/6.png)
![注释](../assets/6.png)
当执行let s1 = s后内存布局如下
当执行`let s1 = s`后,内存布局如下:
![注释](.././assets/7.png)
![注释](../assets/7.png)
`let s1 = s`执行后就发生了所有权的转移String类型值的所有权从`s`转移到了`s1`。此时Rust认为原来的`s`不再有效。因此上面代码第4行打开编译将会出错。
## 5. 浅拷贝与深拷贝
### 5.1 浅拷贝
### 1浅拷贝
只拷贝栈上的内容,就叫做浅拷贝。
对于上面的String类型执行`let s1 = s`后,只把`s``ptr``len``cap`中的值拷贝给`s1``ptr``len``cap`的值,这种就叫做浅拷贝。浅拷贝发生后,`s``ptr``s1``ptr`都指向同样的堆内存。内存布局如下:
![注释](.././assets/8.png)
![注释](../assets/8.png)
### 5.2 深拷贝
### 2深拷贝
除了拷贝栈上的内容外,还拷贝堆内存中的内容,就叫做深拷贝。
对于上面的String类型执行`let s1 = s`后,除了把`s``len``cap`中的值拷贝给`s1``len``cap`还在堆上重新分配一块内存将s的ptr指向的堆内存的内容拷贝到这块内存然后`s1``ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s``ptr``s1``ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下:
![注释](.././assets/9.png)
对于上面的String类型执行`let s1 = s`后,除了把`s``len``cap`中的值拷贝给`s1``len``cap`外,还在堆上重新分配一块内存,将`s``ptr`指向的堆内存的内容拷贝到这块内存,然后`s1``ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s``ptr``s1``ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下:
![注释](../assets/9.png)
显然,**Rust中变量赋值Rust中叫所有权转移使用的是浅拷贝**。
@@ -232,11 +234,12 @@ fn main() {
println!("s1: {:?}", s1);
}
```
不过不是所有的类型都能使用`clone`方法进行深拷贝,只有实现了`Clone trait`的类型才能调用该方法。
## 7. Copy
按照Rust所有权规则第二条**在任意时刻,值有且仅有一个所有者**。所以当`let b = a`发生时,就将变量`b`拥有的值移到了`a`上,此时`a`应该回到未初始状态,但实际情况并不一定。不一定的原因是,部分类型实现了`Copy trait`,在值移动时会对值进行自动拷贝,能让变量a仍拥有原来的值。
按照Rust所有权规则第二条**在任意时刻,值有且仅有一个所有者**。所以当`let b = a`发生时,就将变量`a`拥有的值移到了`b`上,此时`a`应该回到未初始状态,但实际情况并不一定。不一定的原因是,部分类型实现了`Copy trait`,在值移动时会对值进行自动拷贝,能让变量`a`仍拥有原来的值。
Rust中默认实现了`Copy trait`的类型有:
@@ -250,18 +253,19 @@ Rust中默认实现了`Copy trait`的类型有:
## 8. 所有权和函数
### 8.1 将值传给函数
### 1将值传给函数
在将值传递给函数时,和变量赋值一样会发生值的移动(或复制),如下:
```rust
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("s: {:?}", s);//打开编译会报错因为s的所有权在上一行已经转移到take_ownership函数中了
// println!("s: {:?}", s); // 打开编译会报错因为s的所有权在上一行已经转移到take_ownership函数中了
let x = 5;
makes_copy(x);
println!("x: {:?}", x);//不会报错因为上一行将x传到makes_copy函数时会自动拷贝x的值到函数中
println!("x: {:?}", x); // 不会报错因为上一行将x传到makes_copy函数时会自动拷贝x的值到函数中
}
fn takes_ownership(some_string: String) {
@@ -273,19 +277,19 @@ fn makes_copy(some_integer: i32) {
}
```
### 8.2 返回值和作用域
### 2返回值和作用域
函数的返回值也可以转移所有权,如下:
```rust
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值转移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到takes_and_gives_back 中,
// 它也将返回值移给 s3
} //这里s3移出作用域并被丢弃。s2也移出作用域但已被移走所以什么也不会发生。s1离开作用域并被丢弃
let s1 = gives_ownership(); // gives_ownership 将返回值转移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到 takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里s3 移出作用域并被丢弃。s2 也移出作用域但已被移走所以什么也不会发生。s1 离开作用域并被丢弃
fn gives_ownership() -> String {// gives_ownership 会将返回值移动给调用它的函数
fn gives_ownership() -> String { // gives_ownership 会将返回值移动给调用它的函数
let some_string = String::from("yours"); // some_string 进入作用域。
some_string // 返回 some_string 并移出给调用的函数
}
@@ -296,4 +300,4 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用
}
```
**关于所有权的总结:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop函数后续讲解清理掉,除非数据被移动为另一个变量所有。**
**关于所有权的总结:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将被`drop`函数(后续讲解)清理掉,除非数据被移动为另一个变量所有。**