文章目录
- 第三讲 所有权:移动与借用&
- 例1
- 例2
- 例3
- 错误处理(开头)
- 为什么空指针如此危险,我们能做什么以应对?— 引出Option
- is_none()函数
- unwrap_or()函数
- 常见用法
- 第四讲 代码实践:链表
- Box
- 节点和链表的定义
- 节点和链表的构造函数
- 判空 得到size
- 插:take
- Push Pop
第三讲 所有权:移动与借用&
为什么学习Rust?— 为了写出更安全的c/c++代码。Rust是人们对于c/c++缺点的回应,这个回应也有一些不足。
有些库会自己释放,给你指针,你不能用free,而是库提供的
一个包含了指向内存其他地方的指针的结构体的所有权传递问题在Rust和c/c++中都是有挑战的。Rust在编译时强迫你解决清除所有所有权的问题。
例1
// eg1.rs
fn main() {
let s = String::from("hello");
s.push_str(" world");
}
错,改为let **mut** s = String::from("hello");
例2
//eg2.rs
fn om_nom_nom(s: String) {
println!("{}", s);
// 释放s
}
fn main() {
let s = String::from("hello");
om_nom_nom(s);
// 到这里s的生命周期就结束了
om_nom_nom(s);
}
错。相比以下c代码,free有四种放置方式,都可以通过编译。但只有一种是正确的。其余都会在运行时出现漏洞。
Rust起初感觉上是反直觉的,但是不容易错的。
//eg2.c
void om_nom_nom(char* s) {
printf("%s\n", s);
}
int main() {
char* s = strup("hello");
om_nom_nom(s);
om_nom_nom(s);
free(s);
}
eg2.rs改法:
//eg2.rs
fn om_nom_nom(s: &String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
om_nom_nom(&s); // 传引用
om_nom_nom(&s);
// s在这里释放
}
例3
// eg3.rs
fn main() {
let s = String::from("hello");
let s1 = &s;
let s2 = &s;
println!("{} {} {}", s, s1, s2);
}
对,s1 s2都是不可更改的副本
// eg3.rs
fn main() {
let mut s = String::from("hello");
let s1 = &mut s;
let s2 = s;
println!("{} {} {}", s, s1, s2);
}
错,rust的引用模型就是只有一个可改的,或者多个不可改的
// eg3.rs
fn main() {
let mut s = String::from("hello");
let s1 = &mut s;
println!("{} {}", s, s1);
}
错, Rust 的借用规则不允许在同一作用域内同时存在可变引用和原始变量
// eg3.rs
fn main() {
let mut s = String::from("hello");
let s1 = &mut s;
println!("{} {}", s1);
println!("{} {}", s);
}
对,编译器看到了在使用s1时没有用过s,那就可以
// eg3.rs
fn main() {
let mut s = String::from("hello");
let s1 = &mut s;
println!("{} {}", s);
println!("{} {}", s1);
}
错
错误处理(开头)
Q:这段代码哪里有问题?
A:(本节课只讲第一点 —— 空指针)
为什么空指针如此危险,我们能做什么以应对?— 引出Option
None和值不会混在一起。
fn feeling_lucky() -> Option<String> {
if get_random_num() > 10 {
Some(String::from("lucky"))
} else {
None
}
}
is_none()函数
if feeling_lucky().is_none()
unwrap_or()函数
let message = feeling_lucky().unwrap_or(String::from("No lucky"));
常见用法
match feeling_lucky() {
Some(message) => {
println!("Got message: {}", message);
},
None => {
println!("No message returned.");
}
}
第四讲 代码实践:链表
Box
在堆上分配,类似于c++的unique_ptr,优点:指针超出作用域后销毁,那块内存自动释放。
fn main() {
let x: Box<u32> = Box::new(10);
println!("{}", *x); // *x改为x,输出同样是10,因为自动解引用
}
// 输出:10
节点和链表的定义
struct LinkedList {
head : Option<Box<Node>>, // ,
size : usize,
}
struct Node {
value : u32,
next : Option<Box<Node>>,
}
Q: next : Box<&Node>
行吗?
A:不行。我们在这里需要的是实际拥有下一个节点,不然考虑你从哪里借用。一个Box就是一个指针,指向heap上某处的拥有指针。只要Box存活(指在生命周期内),Node必须存活。
节点和链表的构造函数
impl Node {
pub fn new(value: u32, next: Option<Box<Node>>) -> Node {
Node {value: value, next: next}
}
}
impl LinkedList {
pub fn new() -> LinkedList {
LinkedList {head: None, size: 0}
}
}
判空 得到size
pub fn get_size(&self) -> usize {
self.size // (*self).size 也可。从习惯上讲,不需要的话不要显式解引用
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
插:take
let mut x : Option<u32> = Some(5);
let x_ref : &mut Option<u32> = &mut x;
println!("{:?}", x_ref.take());
println!("{:?}", x);
// Some(5)
// None
Push Pop
pub fn push(&mut self, value: u32) {
let tmp_node: Box<Node> = Box::new(Node::new(value, self.head.take()));
self.head = Some(tmp_node); // 此Some对应head的类型是Option
self.size += 1;
}
pub fn pop(&mut self) -> Option<u32> {
let tmp_node: Box<Node> = self.head.take()?; //问号
self.head = tmp_node.next;
Some(tmp_node.value) // 此Some对应返回值的Option
}
关于?符号
的解释:?
是错误传播操作符,它用于在遇到 Err
值时提前退出函数,并返回错误。当 ?
被用于 Option
类型时,如果 Option
是 None
,那么它会导致函数返回 None
,并且提前退出函数。如果 Option
是 Some(value)
,那么 ?
会提取出 value
并继续执行。