重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
在Rust里寫一個鏈表可不是一件容易的事,涉及到很多的知識點(diǎn),需要熟練掌握之后才能寫出一個不錯的鏈表。這篇文章主要介紹了如何寫一個Rust鏈表,并且補(bǔ)充了涉及到的很多的額外知識點(diǎn),尤其是所有權(quán)問題。
首先,你需要明白,為什么Rust鏈表難寫,同樣的為什么C實(shí)現(xiàn)簡單一點(diǎn)呢?
只能有一個引用?。?!這是最關(guān)鍵的,然后就是Rust中是沒有NULL指針的,這就需要用到Option枚舉,在編譯階段必須知道類型的大小,這就需要使用Box智能指針。
在C語言實(shí)現(xiàn)一個簡單的鏈表,可以這樣寫:
Node* new_node = create_new_node(v);
new_node->next = head;
head = new_node;
這段代碼里面,head和new_node->next指向了同一個節(jié)點(diǎn),這個在C語言里面沒事,但是在Rust不允許,因為指針類型為Box,Box對象同一時刻只能有一個可變引用,而在上面的插入過程中,第2行,有兩個指針指向同一個頭結(jié)點(diǎn)。
1.最簡單的Option枚舉就是,里面有Some和None,對于枚舉大家一定要了解,他是等于其中一個的,并不是包含關(guān)系,這一點(diǎn)一定要理清楚,不然代碼會很難理解。None可以替代C語言中的NULL。
pub enum Option{Some(T),
None,
}
2.unwrap()方法:
在確認(rèn)Option不為None的情況下,可以用unwrap方法拆解出其中的值,并獲取值的所有權(quán),如果為None,就會引發(fā)panic。這里要強(qiáng)調(diào)的是,unwrap會消費(fèi)Option本身的值,后面就不能再用了。
let a = Some(Box::new(5));
let d = a.unwrap();
// println!("{:?}", a); // cannot use 'a' again.
println!("{:?}", d);
但這里有一個所有權(quán)的限制,因為涉及到其內(nèi)部值的所有權(quán)的轉(zhuǎn)移,所以只有Option原始變量本身可以調(diào)用unwrap方法,其引用(包括可變引用)均不可調(diào)用。這和unwrap的實(shí)現(xiàn)方法有關(guān)系,因為其消費(fèi)了原始變量。下方代碼不可編譯:
let mut a = Some(Box::new(5));
let b = &mut a;
let c = b.unwrap(); // Error! cannot move out of `*b` which is behind a mutable reference
下面是unwrap源碼:傳入self,會轉(zhuǎn)移所有權(quán)。
pub const fn unwrap(self) ->T {match self {Some(val) =>val,
None =>panic!("called `Option::unwrap()` on a `None` value"),
}
3.take()方法:
take方法可以從Option中取出值,為原來的Option變量留下None值,但原來的變量還有效(但實(shí)際上沒什么用處了,因為只剩下None了)。take方法的原始值或者引用必須為mut類型。強(qiáng)調(diào)一下,借用情況下可以調(diào)用take方法?;蛘哒f指向Option的可變引用(指針)可以調(diào)用take方法。
fn main() {let mut a = Some(Box::new(5));
let mut b = &mut a;
let c = &mut b;
let d = c.take();
println!("{:?}", c);//None
println!("{:?}", d);//Some(5)
println!("{:?}", a);//None
}
4.as_ref()方法:
但上面這兩個方法,都改變了Option變量本身。如果不改變或受到限制無法改變Option本身時,只想借用或可變借用其中unwrap的值應(yīng)該怎么辦呢?這也是在實(shí)現(xiàn)鏈表時遇到的讓人掉坑的問題。幸好Option提供了 as_ref 和 as_mut方法。
fn as_ref(&self) ->Option<&T>//接受一個不可變引用,不獲取所有權(quán),返回一個Option枚舉,其中的T為不可變引用類型,不獲取所有權(quán)。
該方法將對Option的引用變?yōu)閷ption所包含對象的不可變引用,并且返回一個新的Option。對這個新的Option進(jìn)行unwrap操作,可以獲得原Option所包含的對象的不可變引用。原始Option變量及其引用,均可調(diào)用as_ref方法,有點(diǎn)克隆的味道。例如:
let a = Some(Box::new(5));
let b = a.as_ref();
let c = b.unwrap();
println!("{:?}", a);//Some(5)
println!("{:?}", b);//Some(5)
println!("{:?}", c);//5
5.as_mut()方法
與as_ref類似,as_mut只是返回了Option包含的數(shù)據(jù)的可變引用,
fn main() {let mut a = Some(Point {x: 5, y: 5 });
// let b = a.as_mut();
let b = &mut a;
let c = b.as_mut(); // c is a new Option;
let d = c.unwrap();
d.x += 10;
let e = &mut d.y;
*e += 20;
println!("{:?}", d.x);//15
// println!("{:?}", c); // c is not available because of method call of "unwrap".
println!("{:?}", b);//Some(Point { x: 15, y: 25 })
println!("{:?}", a);//Some(Point { x: 15, y: 25 })
}
通過以上代碼可以看出,在未改變Option變量a的情況下,通過as_mut方法,改變了其包含的數(shù)據(jù)的值。這個能力對于編寫鏈表時,尤其是節(jié)點(diǎn)的插入、刪除時,可以靈活的操作指向下一個節(jié)點(diǎn)的指針。
說明一點(diǎn),調(diào)用as_mut方法時,Option變量本身或其引用,必須為可變的,即mut類型。
簡單類型的一個例子:這里注意原來的5已經(jīng)改變成15。
fn main() {let mut a = Some(5);
let b = a.as_mut().unwrap();
*b += 10;
println!("b = {:?}", b);//b = 15
println!("a = {:?}", a);//a=Some(15)
}
if let和while let還有?操作符if let Some(a_T) = a_option { //a_T生命周期僅在花括號內(nèi)生效。
//當(dāng)a_option不為None時滿足條件。
}
while let Some(a_T) = a_option { //當(dāng)a_option 為None時退出循環(huán)。
}
?操作符的作用就是簡化Result枚舉的,舉個例子:
let f = File::open("username.txt");
let mut f = match f {Ok(file) =>file,
Err(e) =>return Err(e),
};
//有了?運(yùn)算符,就可以改成下面一行話
let mut f = File::open("username.txt")?;
Rust方法和關(guān)聯(lián)函數(shù)具體可以看https://blog.csdn.net/cacique111/article/details/126311569,說的很詳細(xì)。
如何編寫一個鏈表 鏈表的定義鏈表比較繞圈子,回顧Rust內(nèi)存管理的基礎(chǔ)知識,Rust需要在編譯時知道一個類型占用多少空間,Node結(jié)構(gòu)體內(nèi)部嵌套了它自己,這樣在編譯時就無法確認(rèn)其占用空間大小了。 在Rust中當(dāng)有一個在編譯時未知大小的類型,而又想要在需要確切大小的上下文中使用這個類型值的時候,可以使用智能指針Box。
/// 單鏈表節(jié)點(diǎn)
#[derive(Debug)]
struct Node{val: T,
next: Option>>,
}
/// 單鏈表
#[derive(Debug)]
struct LinkedList{head: Option>>,
size: usize,
}
上面的代碼定義之后,在Rust里面經(jīng)常使用New函數(shù),所以下面寫上面結(jié)構(gòu)體的new方法,
implLinkedList{pub fn new() ->Self {SimpleLinkedList {head: None,
size: 0,
}
}
LinkedList的new函數(shù)用來創(chuàng)建一個空鏈表。
常用的鏈表操作pub fn is_empty(&self) ->bool {self.size == 0
}
pub fn len(&self) ->usize {self.size
}
//頭插法
pub fn push(&mut self, data: T)->&mut Self {let node = Box::new(Node::{val: data,
next: self.head.take(),//如果是空鏈表的話,直接為None,要不然就是Some(T)。為什么這里不用self.head?
//看上去一樣,但是如果不take,就會出現(xiàn)多個指針指向同一個節(jié)點(diǎn),不符合所有權(quán)規(guī)則,可變引用可以進(jìn)行take
//take之后就會釋放,變成None
});
self.head = Some(node);//將“頭指針”指向新插入的節(jié)點(diǎn)
self.size += 1;
self
}
//pop會從頭節(jié)點(diǎn)開始刪除節(jié)點(diǎn),返回例如Some(5),Option,T的所有權(quán)轉(zhuǎn)移,刪除轉(zhuǎn)移也沒啥。
pub fn pop(&mut self) ->Option{if self.is_empty() {return None;
}
let head = self.head.take().unwrap();//取出Some里面的值
self.head = head.next;//head指向下一個節(jié)點(diǎn)
self.size -= 1;
Some(head.val)
}
//peek返回頭節(jié)點(diǎn),是T的引用,不轉(zhuǎn)移所有權(quán)。
pub fn peek(&self) ->Option<&T>{if self.is_empty() {return None
}
return Some(&self.head.as_ref().unwrap().val)
//這里確實(shí),只是返回一個引用,不能對原來的鏈表造成任何影響。
}
pub fn peek_mut(&mut self) ->Option<&mut T>{if self.is_empty() {return None
}//這里就是as_mut,外部改變T的值,鏈表里面的值也會受到改變。
return Some(&mut self.head.as_mut().unwrap().val)
}
pub fn rev(mut self) ->SimpleLinkedList{let mut rev_list = SimpleLinkedList::::new();
while !self.is_empty() {rev_list.push(self.pop().unwrap());
}
rev_list
}
main函數(shù)部分,其中迭代器部分下期會有詳細(xì)的解答。
fn main(){let mut list=SimpleLinkedList::new();
list.push(3);
list.push(2);
list.push(1);
list.len();
// println!("{list:?}");
// for val in list.from_iter() {// println!("{}",val);
// }
// for val in list.iter_mut() {// *val += 1;
// println!("{}",val);
// }
for val in list.into_iter() {println!("{}",val);
}
//println!("{:?}",list.pop());
//list.pop();
//println!("{list:?}");
//println!("{}",list.len());
}
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧