diff --git a/src/chapter_2.md b/src/chapter_2.md index 27907b7..8174d0d 100644 --- a/src/chapter_2.md +++ b/src/chapter_2.md @@ -157,7 +157,7 @@ cargo new hello_world 创建后,可以看到整个目录的结构如下: -![目录结构](../assets/62.png) +![目录结构](./assets/62.png) 其中Cargo.toml是项目的配置文件,src为源代码目录,main.rs为主程序文件。 @@ -184,7 +184,7 @@ cargo run 运行结果如下: -![运行结果](../assets/63.png) +![运行结果](./assets/63.png) ### 2.3.4 理解Rust源码的基本结构 diff --git a/src/chapter_3/chapter_3_10_2.md b/src/chapter_3/chapter_3_10_2.md index 3d36473..5f88ff1 100644 --- a/src/chapter_3/chapter_3_10_2.md +++ b/src/chapter_3/chapter_3_10_2.md @@ -116,7 +116,7 @@ fn main() { 在上面的代码中,vehicle1和vehicle1都是Vehicle trait对象的引用;对于vehicle1来说,trait对象的具体类型是`Car`;对于vehicle2来说,trait对象的具体类型是`Truck`。上面代码对应的内存布局如下: - ![注释](../../assets/14.png) + ![注释](.././assets/14.png) 变量car和变量truck分别是Car类型和Truck类型,存储在栈上;vehicle1和vehicle2是Vehicle trait对象的引用,具有两个指针,其中指针ptr指向具体类型的实例,vptr指向虚函数表;虚函数表存储在只读数据区。 diff --git a/src/chapter_3/chapter_3_11.md b/src/chapter_3/chapter_3_11.md index 91f2079..2619fc0 100644 --- a/src/chapter_3/chapter_3_11.md +++ b/src/chapter_3/chapter_3_11.md @@ -21,7 +21,7 @@ fn main() { 上面代码中,x的有效作用域是从第4行到第6行花括号结束前,即从第6行的花括号后开始,x变为无效,r为x的应用,此时将指向的是一块无效的内存。其内存示意图如下: - ![注释](../../assets/15.png) + ![注释](.././assets/15.png) ## 3.11.2 借用检查 diff --git a/src/chapter_3/chapter_3_12.md b/src/chapter_3/chapter_3_12.md index b3e6106..f08d9c9 100644 --- a/src/chapter_3/chapter_3_12.md +++ b/src/chapter_3/chapter_3_12.md @@ -15,11 +15,11 @@ fn main() { 运行该程序会打印如下错误: - ![注释](../../assets/16.png) + ![注释](.././assets/16.png) 运行时添加```RUST_BACKTRACE=1```,可以打印完整的堆栈,上面的代码运行时加```RUST_BACKTRACE=1```的结果如下: -![注释](../../assets/17.png) +![注释](.././assets/17.png) ## 3.12.2 用Result处理可恢复错误 diff --git a/src/chapter_3/chapter_3_16_1.md b/src/chapter_3/chapter_3_16_1.md index 23ccfd0..4f68f32 100644 --- a/src/chapter_3/chapter_3_16_1.md +++ b/src/chapter_3/chapter_3_16_1.md @@ -2,13 +2,13 @@ 指针是一个包含了内存地址的变量,该内存地址引用或者指向了另外的数据,其在内存中的示意图如下: -![注释](../../assets/18.png) +![注释](.././assets/18.png) 智能指针是一类数据结构,其表现类似于指针,但是相对于指针来说,还拥有额外的元数据。最明显的是它们拥有一个引用计数。引用计数记录了智能指针总共有多少个所有者,并且当没有任何所有者时清除数据。普通引用和智能指针的另一个**非常重要的区别**就是:**引用只是只借用数据的指针,而智能指针则是拥有它们指向的数据**。 **智能指针是一个胖指针,但是胖指针不一定是智能指针**。前面章节介绍过的```String```类型就是一个智能指针,而它对应的切片引用```&str```则只仅仅是一个胖指针,区别就在于```String```类型拥有对数据的所有权,而```&str```没有。两者在内存中的示意图如下: -![注释](../../assets/19.png) +![注释](.././assets/19.png) 同样的,**Vec类型也是一个智能指针**。 diff --git a/src/chapter_3/chapter_3_16_2.md b/src/chapter_3/chapter_3_16_2.md index 508a208..a4d3dae 100644 --- a/src/chapter_3/chapter_3_16_2.md +++ b/src/chapter_3/chapter_3_16_2.md @@ -16,7 +16,7 @@ fn main() { 前面示例中使用```Box```定义了变量```b```,其内存布局方式如下: -![注释](../../assets/20.png) +![注释](.././assets/20.png) ## 3. Box适合使用的场景 @@ -45,7 +45,7 @@ fn main() { ``` 但是上面的代码无法编译通过,因为```Cons```类型在编译时无法确定其具体大小。其内存示意图如下: -![注释](../../assets/22.png) +![注释](.././assets/22.png) 此时就需要使用Box,其代码如下: ```Rust @@ -65,7 +65,7 @@ fn main() { ``` 使用Box后,上面的Cons的内存表示如下: -![注释](../../assets/23.png) +![注释](.././assets/23.png) 每个Box的大小是固定的,所以编译不会有问题。 diff --git a/src/chapter_3/chapter_3_16_4.md b/src/chapter_3/chapter_3_16_4.md index 9b650c7..6a2fb1e 100644 --- a/src/chapter_3/chapter_3_16_4.md +++ b/src/chapter_3/chapter_3_16_4.md @@ -24,7 +24,7 @@ fn main() { 运行该代码,会有如下结果: -![注释](../../assets/24.png) +![注释](.././assets/24.png) 在上面示例代码中并没有打印语句,但是在drop方法中实现了打印,可以看到,当_a和_b离开作用域时,自动调用了drop方法。 @@ -53,6 +53,6 @@ fn main() { 代码运行结果如下: -![注释](../../assets/25.png) +![注释](.././assets/25.png) 第一个“Dog leave”打印是第14行调用释放_a产生,最后一个“Dog leave”打印则是_b离开作用域时调用drop方法产生。 diff --git a/src/chapter_3/chapter_3_16_5.md b/src/chapter_3/chapter_3_16_5.md index 073504f..4a2fe88 100644 --- a/src/chapter_3/chapter_3_16_5.md +++ b/src/chapter_3/chapter_3_16_5.md @@ -4,7 +4,7 @@ 假定有这样一个需求,希望创建两个共享第三个列表所有权的列表,其概念类似于如下图: -![注释](../../assets/26.png) +![注释](.././assets/26.png) 根据前面的知识,可能写出来的代码如下: @@ -39,7 +39,7 @@ fn main() { 上面代码中,a、b、c就共享数据5。当创建b时,不会获取a的所有权,会克隆a所包含的Rc(5u32),这会使引用计数从1增加到2并允许a和b共享Rc(5u32)的所有权。 创建c时也会克隆 a,引用计数从2增加为3。每次调用`Rc::clone`,Rc(5u32)的引用计数都会增加,直到有零个引用之前其数据都不会被清理。其在内存中的表示如下: -![注释](../../assets/27.png) +![注释](.././assets/27.png) 前面共享列表的需求则可以使用Rc实现如下: ```Rust @@ -58,7 +58,7 @@ fn main() { 对应的内存示意图如下: -![注释](../../assets/28.png) +![注释](.././assets/28.png) diff --git a/src/chapter_3/chapter_3_16_7.md b/src/chapter_3/chapter_3_16_7.md index b7f69c4..c6cd581 100644 --- a/src/chapter_3/chapter_3_16_7.md +++ b/src/chapter_3/chapter_3_16_7.md @@ -3,7 +3,7 @@ ## 1. 引用循环和内存泄露 下图是一个循环链表: -![注释](../../assets/29.png) +![注释](.././assets/29.png) 下面的代码试图用之前学到的智能指针相关知识实现上面的链表: @@ -55,23 +55,23 @@ fn main() { - 当执行完第26行后,内存布局如下: -![注释](../../assets/30.png) +![注释](.././assets/30.png) - 在执行完第33行后,b对应的Rc引用计数变成2,内存布局如下: -![注释](../../assets/31.png) +![注释](.././assets/31.png) 此时如果第40行代码执行将会panic,因为已经成了一个循环链表,Rust无法匹配到a的tail,最终会造成栈溢出。 - 在最后离开作用域时,Rust将会对b和a调用drop方法,对b调用drop方法后,内存布局如下: -![注释](../../assets/32.png) +![注释](.././assets/32.png) 此时b的Rc实例引用计数减去1,但是仍然不为0(因为第33行让a也引用了b的Rc实例),所以b所指向的内存并不会被释放。 - 然后Rust尝试drop a,其对应的Rc示例引用计数减去1,但仍然不为0,所以调用a的drop后,内存布局为: -![注释](../../assets/33.png) +![注释](.././assets/33.png) 至此,造成内存泄露。 @@ -162,7 +162,7 @@ fn main() { 下图为上面代码的内存布局示意图: -![注释](../../assets/34.png) +![注释](.././assets/34.png) 下面再总结一下Weak的特点: diff --git a/src/chapter_3/chapter_3_17_1.md b/src/chapter_3/chapter_3_17_1.md index dd56c23..45b1a4a 100644 --- a/src/chapter_3/chapter_3_17_1.md +++ b/src/chapter_3/chapter_3_17_1.md @@ -18,11 +18,11 @@ cargo new main 进入main文件夹,其目录层级如下: -![注释](../../assets/35.png) +![注释](.././assets/35.png) 其中Cargo.toml的内容如下: -![注释](../../assets/36.png) +![注释](.././assets/36.png) 可以看到这个crate的名字为main。 @@ -50,6 +50,6 @@ members = [ 最后的目录层级关系如下: -![注释](../../assets/37.png) +![注释](.././assets/37.png) 上面的步骤就创建了一个包my-pack,这个包包含两个crate,分别是my-lib和main。 diff --git a/src/chapter_3/chapter_3_17_2.md b/src/chapter_3/chapter_3_17_2.md index a76a717..94a4fde 100644 --- a/src/chapter_3/chapter_3_17_2.md +++ b/src/chapter_3/chapter_3_17_2.md @@ -160,4 +160,4 @@ fn main() { 整个工程的目录结构如下: -![注释](../../assets/38.png) +![注释](.././assets/38.png) diff --git a/src/chapter_3/chapter_3_17_3.md b/src/chapter_3/chapter_3_17_3.md index 2b90bf9..8e7bec1 100644 --- a/src/chapter_3/chapter_3_17_3.md +++ b/src/chapter_3/chapter_3_17_3.md @@ -28,7 +28,7 @@ cd main 打开main目录下的Cargo.toml文件,在[dependencies]下添加对rand库的依赖(即添加语句rand = "0.8.5"),添加后整个文件的内容如下: -![注释](../../assets/39.png) +![注释](.././assets/39.png) ### (3)在代码中使用rand库 编写src/main.rs如下: diff --git a/src/chapter_3/chapter_3_17_4.md b/src/chapter_3/chapter_3_17_4.md index cd1c350..f43ba43 100644 --- a/src/chapter_3/chapter_3_17_4.md +++ b/src/chapter_3/chapter_3_17_4.md @@ -68,6 +68,6 @@ fn main() { 在上面的示例中,通过my-project/Cargo.toml文件管理了main和add两个crate,上面所有步骤完成后,整个项目构成如下: -![注释](../../assets/40.png) +![注释](.././assets/40.png) 在my-project目录下或者my-project/main目录下运行cargo run可以编译执行整个项目。 diff --git a/src/chapter_3/chapter_3_18_1.md b/src/chapter_3/chapter_3_18_1.md index 4992d40..0d605fc 100644 --- a/src/chapter_3/chapter_3_18_1.md +++ b/src/chapter_3/chapter_3_18_1.md @@ -41,7 +41,7 @@ cargo test ``` 运行后可以看到对应的测试结果: -![注释](../../assets/58.png) +![注释](.././assets/58.png) ## 2. 使用断言 在前面的示例中,测试代码中使用了assert_eq!断言。除了assert_eq!以外,下面几个也是比较常用的断言: diff --git a/src/chapter_3/chapter_3_18_2.md b/src/chapter_3/chapter_3_18_2.md index 603031b..84c52d6 100644 --- a/src/chapter_3/chapter_3_18_2.md +++ b/src/chapter_3/chapter_3_18_2.md @@ -73,7 +73,7 @@ cargo test add_two_and_two 执行结果如下图: -![注释](../../assets/59.png) +![注释](.././assets/59.png) ## 4. 过滤运行测试 @@ -84,7 +84,7 @@ cargo test add 执行结果如下图: -![注释](../../assets/60.png) +![注释](.././assets/60.png) ## 5. 忽略某个测试 有时候运行cargo test时想忽略其中的某个测试,此时可以通过使用ignore属性来标记该测试来排除它。例如有如下测试代码: @@ -116,4 +116,4 @@ mod tests { ``` 上面的代码中将add_two_and_two用#[ignore]忽略,运行cargo test将不会执行该函数,执行结果如下: -![注释](../../assets/61.png) +![注释](.././assets/61.png) diff --git a/src/chapter_3/chapter_3_19.md b/src/chapter_3/chapter_3_19.md index 67bdde7..254f6c0 100644 --- a/src/chapter_3/chapter_3_19.md +++ b/src/chapter_3/chapter_3_19.md @@ -13,7 +13,7 @@ pub mod sub; 运行```cargo doc --open```出现如下界面: -![注释](../../assets/41.png) +![注释](.././assets/41.png) 而下面的示例因为没有将所有的包注释放在文件最上面,所以会报错: ```Rust @@ -74,13 +74,13 @@ pub fn add2(left: u32, right: u32) -> Option { 运行cargo test,可以看到运行了用例add::add2,如下: -![注释](../../assets/44.png) +![注释](.././assets/44.png) 运行cargo doc --open后,打开add和add2的文档,分别如下: -![注释](../../assets/46.png) +![注释](.././assets/46.png) -![注释](../../assets/45.png) +![注释](.././assets/45.png) 可以看到add2对应的测试用例的内容在文档中被隐藏了。 @@ -98,7 +98,7 @@ pub fn sub(left: u32, right: u32) -> Option { ``` 运行cargo doc --open后,出现如下: -![注释](../../assets/42.png) +![注释](.././assets/42.png) 从上图中,点击红色划线部分将分别跳转到标准库的Option和crate::add的位置。 @@ -112,4 +112,4 @@ pub struct A; 运行cargo doc --open后,出现如下: -![注释](../../assets/43.png) +![注释](.././assets/43.png) diff --git a/src/chapter_3/chapter_3_22_1.md b/src/chapter_3/chapter_3_22_1.md index 892efac..303832c 100644 --- a/src/chapter_3/chapter_3_22_1.md +++ b/src/chapter_3/chapter_3_22_1.md @@ -27,15 +27,15 @@ fn main() { 运行前整个项目的目录结构如下: -![注释](../../assets/49.png) +![注释](.././assets/49.png) 运行cargo run执行程序结果如下: -![注释](../../assets/50.png) +![注释](.././assets/50.png) 运行后整个项目的目录结构如下: -![注释](../../assets/51.png) +![注释](.././assets/51.png) - 构建脚本的生命周期 @@ -84,7 +84,7 @@ cc = "1.0.46" # 可以在build.rs中使用cc相关的功能 下面的示例展示如何在Rust中使用c代码,整个项目的目录结构如下: -![注释](../../assets/52.png) +![注释](.././assets/52.png) c目录中的pass.c为c代码,源码如下: ```Rust diff --git a/src/chapter_3/chapter_3_22_2.md b/src/chapter_3/chapter_3_22_2.md index d9913f8..0443e48 100644 --- a/src/chapter_3/chapter_3_22_2.md +++ b/src/chapter_3/chapter_3_22_2.md @@ -8,7 +8,7 @@ 下面示例展示在c中调用Rust,其目录结构如下: -![注释](../../assets/53.png) +![注释](.././assets/53.png) 目录中的foo是Rust代码,用cargo new foo --lib创建,其中src/lib.rs的代码如下: @@ -53,4 +53,4 @@ int main() 在main.c同级目录运行命令```gcc -o main main.c ./foo/target/debug/libfoo.a -lpthread -ldl```会生成执行文件main,如下图: -![注释](../../assets/54.png) +![注释](.././assets/54.png) diff --git a/src/chapter_3/chapter_3_23_2.md b/src/chapter_3/chapter_3_23_2.md index d5ac4fb..132cd66 100644 --- a/src/chapter_3/chapter_3_23_2.md +++ b/src/chapter_3/chapter_3_23_2.md @@ -24,7 +24,7 @@ struct A { 下面给出一个完整的自定义derive宏的示例,整个项目的目录如下: -![注释](../../assets/55.png) +![注释](.././assets/55.png) 整个项目中有三个crate,功能分别如下: @@ -138,7 +138,7 @@ fn main() { ## 2. 类函数宏 类函数宏是类似于函数那样的过程宏,下面的工程演示类函数宏,其目录结构如下: -![注释](../../assets/56.png) +![注释](.././assets/56.png) ### (1)定义工作空间 编写最外层的Cargo.toml文件,内容如下: @@ -195,7 +195,7 @@ fn main() { ## 3. 类属性宏 属性宏和自定义derive宏类似,不同的是derive宏生成代码,而类属性宏可以创建新的属性。自定义derive宏只能用于结构体和枚举,属性宏则还可以用于其它的项,如函数。下面的工程演示类属性宏,其目录结构如下: -![注释](../../assets/57.png) +![注释](.././assets/57.png) ### (1)定义工作空间 编写最外层的Cargo.toml文件,内容如下: diff --git a/src/chapter_3/chapter_3_4.md b/src/chapter_3/chapter_3_4.md index 1a6a3bd..98e12c4 100644 --- a/src/chapter_3/chapter_3_4.md +++ b/src/chapter_3/chapter_3_4.md @@ -99,4 +99,4 @@ cargo doc --open 将打开上面代码里面文档注释生成的文档,如下图: -![注释](../assets/1.png) +![注释](./assets/1.png) diff --git a/src/chapter_3/chapter_3_6.md b/src/chapter_3/chapter_3_6.md index dbacc07..c44b2e2 100644 --- a/src/chapter_3/chapter_3_6.md +++ b/src/chapter_3/chapter_3_6.md @@ -2,7 +2,7 @@ ## 3.6.1 Rust程序内存布局 -![注释](../../assets/2.png) +![注释](.././assets/2.png) 上图是一张linux系统上Rust程序的内存布局图。在linux操作系统中,会划分固定的区域给内核使用,即上图中的内核空间;应用程序使用的是用户空间。 @@ -44,7 +44,7 @@ fn main() { 对于上面的代码,在执行第17行和第18行的栈帧示意图如下: -![注释](../../assets/3.png) +![注释](.././assets/3.png) 这里需要注意的是两个帧对应同样的内存地址,这是因为在调用完f1函数后,其对应的栈帧释放(释放的实际意义就是这段内存可以被重新分配了),然后调用f2函数为其分配栈帧时从同样的地址进行分配。 @@ -64,4 +64,4 @@ Rust没有GC,但通过其独特的机制管理内存,程序员不用手动 当数据需要存放到堆上时,内存分配器则是根据数据的大小,在堆内存找到合适大小的空区域存放,把它标记为已使用,并返回一个表示该位置地址的指针。该指针存储在栈上,当需要访问具体的数据时,必须先访问指针,然后通过指针找到堆上的位置,从而访问数据。这个过程可以用下图表示: -![注释](../../assets/4.png) +![注释](.././assets/4.png) diff --git a/src/chapter_3/chapter_3_7_1.md b/src/chapter_3/chapter_3_7_1.md index 3eb5816..fc3d79f 100644 --- a/src/chapter_3/chapter_3_7_1.md +++ b/src/chapter_3/chapter_3_7_1.md @@ -158,7 +158,7 @@ fn main() { 在第2行定义String类型时,并不能确定最终字符串的大小,所以字符串内容本身应该存储在堆上。结合什么String类型的本质的内容,可以得到String类型的存储如下: -![注释](../../assets/5.png) +![注释](.././assets/5.png) String类型本身是三个字段(指针、长度、容量),在编译时是已知的大小,存储在栈上;String类型绑定的字符串(在上面代码中是“AB”)在编译时大小未知,是运行时在堆上分配内存,分配后的内存地址保存在String类型的指针字段中,内存大小保存在cap字段中,内存上存储的字符串长度保存在len字段中。 @@ -193,11 +193,11 @@ fn main() { ``` s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其内存布局如下: -![注释](../../assets/6.png) +![注释](.././assets/6.png) 当执行let s1 = s后,内存布局如下: -![注释](../../assets/7.png) +![注释](.././assets/7.png) 当`let s1 = s`执行后,就发生了所有权的转移,String类型值的所有权从`s`转移到了`s1`。此时Rust认为原来的`s`不再有效。因此,上面代码第4行打开编译将会出错。 @@ -208,14 +208,14 @@ s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其 只拷贝栈上的内容,就叫做浅拷贝。 对于上面的String类型,执行`let s1 = s`后,只把`s`的`ptr`、`len`、`cap`中的值拷贝给`s1`的`ptr`、`len`、`cap`的值,这种就叫做浅拷贝。浅拷贝发生后,`s`的`ptr`和`s1`的`ptr`都指向同样的堆内存。内存布局如下: -![注释](../../assets/8.png) +![注释](.././assets/8.png) ### 5.2 深拷贝 除了拷贝栈上的内容外,还拷贝堆内存中的内容,就叫做深拷贝。 对于上面的String类型,执行`let s1 = s`后,除了把`s`的`len`、`cap`中的值拷贝给`s1`的`len`、`cap`外,还在堆上重新分配一块内存,将s的ptr指向的堆内存的内容拷贝到这块内存,然后`s1`的`ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s`的`ptr`和`s1`的`ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下: -![注释](../../assets/9.png) +![注释](.././assets/9.png) 显然,**Rust中变量赋值(Rust中叫所有权转移)使用的是浅拷贝**。 diff --git a/src/chapter_3/chapter_3_7_2.md b/src/chapter_3/chapter_3_7_2.md index a54ca4a..a62f288 100644 --- a/src/chapter_3/chapter_3_7_2.md +++ b/src/chapter_3/chapter_3_7_2.md @@ -48,7 +48,7 @@ fn print(s: String) -> String { 上面代码中,变量`a`、`b`、`c`、`d`的内存布局如下: -![注释](../../assets/10.png) +![注释](.././assets/10.png) **获取变量的引用,称之为借用** 。通过借用,允许使用被引用变量绑定的值,同时又没有移动该变量的所有权。前面的示例代码可以变成如下: ```Rust @@ -265,11 +265,11 @@ int main() 在执行第14行前,其内存布局为: -![注释](../../assets/11.png) +![注释](.././assets/11.png) 当执行第14行后,变成如下: -![注释](../../assets/12.png) +![注释](.././assets/12.png) 第14行执行后,`ptr`就变成了一个悬垂指针(或者交悬垂引用),然后在第16行继续使用`ptr`,则会发生错误。 diff --git a/src/chapter_3/chapter_3_7_3.md b/src/chapter_3/chapter_3_7_3.md index 43a2709..9da82fa 100644 --- a/src/chapter_3/chapter_3_7_3.md +++ b/src/chapter_3/chapter_3_7_3.md @@ -48,7 +48,7 @@ fn main() { `&str`和`&String`的内存布局如下: -![注释](../../assets/13.png) +![注释](.././assets/13.png) ## 3. 其它Slice