·
Rust基础学习–(五) 十一、动态数组 Vector 时隔三个月,rust base learning又被捡起来了嘻嘻()实则舍友开始学rust,被卷到了所以捡起来了…回归正题。
vector实际上在cpp里经常见到了,这么好用的功能居然在这么恶心的语言里有,良心了。
1、创建动态数组 vec::new 1 let v : Vec <i32 > = Vec ::new ();
最经典的创建方式,如果不显式标注数据类型,编译器并不能得知其中的数据类型,我们可以通过输入一共数据的方式【即v.push(1);】
(如果预先知道要存储的元素个数,可以使用 Vec::with_capacity(capacity) 创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能)
vec![]
2、更新vector 使用push方法即可,同样需要声明为mut
1 2 let mut v = Vec ::new (); v.push (1 );
注:作用域和结构体一样,超过{}即会自动删除
3、读取元素 下标索引&get方法 1 2 3 4 5 6 7 8 9 let v = vec! [1 , 2 , 3 , 4 , 5 ];let third : &i32 = &v[2 ];println! ("第三个元素是 {}" , third);match v.get (2 ) { Some (third) => println! ("第三个元素是 {third}" ), None => println! ("nop!" ), }
二者均可,下标越界即造成报错,而get()方法不会报错,会返回none
显然,get()更安全,对应下标更高效
同时借用多个元素 1 2 3 4 let mut v = vec! [1 , 2 , 3 , 4 , 5 ];let first = &v[0 ]; v.push (6 );println! ("The first element is: {first}" );
因为let first = &v[0];是个不可变借用,而v.push(6);是个可变借用,所以可以编译,但是由于在使用了可变借用之后又调用了first,这是因为数组的大小是可变的,当旧数组的大小不够用时,Rust 会重新分配一块更大的内存空间,然后把旧数组拷贝过来。这种情况下,之前的引用显然会指向一块无效的内存
迭代遍历元素 允许这样迭代元素,同时也可以进行修改元素的操作
1 2 3 4 5 let v = vec! [1 , 2 , 3 ];for i in &v { println! ("{i}" ); *i += 10 }
4、存储不同类型元素 枚举类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #[derive(Debug)] enum IpAddr { V4 (String ), V6 (String ) }fn main () { let v = vec! [ IpAddr::V4 ("127.0.0.1" .to_string ()), IpAddr::V6 ("::1" .to_string ()) ]; for ip in v { show_addr (ip) } }fn show_addr (ip: IpAddr) { println! ("{:?}" ,ip); }
特征对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 trait IpAddr { fn display (&self ); }struct V4 (String );impl IpAddr for V4 { fn display (&self ) { println! ("ipv4: {:?}" ,self .0 ) } }struct V6 (String );impl IpAddr for V6 { fn display (&self ) { println! ("ipv6: {:?}" ,self .0 ) } }fn main () { let v : Vec <Box <dyn IpAddr>> = vec! [ Box ::new (V4 ("127.0.0.1" .to_string ())), Box ::new (V6 ("::1" .to_string ())), ]; for ip in v { ip.display (); } }
特征对象数组远多于枚举数组,这是因为特征对象数组可以动态增加类型
常用方法 1 2 3 4 5 6 7 8 9 10 11 fn main () { let mut v = Vec ::with_capacity (10 ); v.extend ([1 , 2 , 3 ]); println! ("Vector 长度是: {}, 容量是: {}" , v.len (), v.capacity ()); v.reserve (100 ); println! ("Vector(reserve) 长度是: {}, 容量是: {}" , v.len (), v.capacity ()); v.shrink_to_fit (); println! ("Vector(shrink_to_fit) 长度是: {}, 容量是: {}" , v.len (), v.capacity ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let mut v = vec! [1 , 2 ];assert! (!v.is_empty ()); v.insert (2 , 3 ); let slice = &v[1 ..=2 ];assert_eq! (slice, &[2 , 3 ]);assert_eq! (v.remove (1 ), 2 ); assert_eq! (v.pop (), Some (3 )); assert_eq! (v.pop (), Some (1 )); assert_eq! (v.pop (), None ); v.clear (); let mut v1 = [11 , 22 ].to_vec (); v.append (&mut v1); v.truncate (1 ); v.retain (|x| *x > 10 ); let mut v = vec! [11 , 22 , 33 , 44 , 55 ];let mut m : Vec <_> = v.drain (1 ..=3 ).collect (); let v2 = m.split_off (1 );
排序:
分为稳定的排序 sort 和 sort_by,以及非稳定排序 sort_unstable 和 sort_unstable_by。总体而言,非稳定排序的算法的速度会优于 稳定排序算法,同时,稳定排序还会额外分配原数组一半的空间。
但是,对于浮点数的排序并不一定可以实现,报错可能来自浮点数的nan值,无法与其他浮点数对比,浮点数的ord全序可比较性rust都没做到,只能做到PartialOrd偏序可比较性!
1 2 3 4 5 fn main () { let mut vec = vec! [1.0 , 5.6 , 10.3 , 2.0 , 15f32 ]; vec.sort_unstable (); assert_eq! (vec, vec! [1.0 , 2.0 , 5.6 , 10.3 , 15f32 ]); }
【我查了一下,其他语言基本都有制定规则,像Java :Double.compare/Double::compareTo 明确规定了全序 :
视 NaN 为等于自身,且大于 所有其他 double(含 +∞);
规定 +0.0 > -0.0。
其实,rust不规定全序反而更加安全合理,可以避免一些极端问题出现,当然,我们可以通过其他方式(见下代码)使其强制可比较
1 2 3 4 5 fn main () { let mut vec = vec! [1.0 , 5.6 , 10.3 , 2.0 , 15f32 ]; vec.sort_unstable_by (|a, b| a.partial_cmp (b).unwrap ()); assert_eq! (vec, vec! [1.0 , 2.0 , 5.6 , 10.3 , 15f32 ]); }
】
结构体同样可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #[derive(Debug)] struct Person { name: String , age: u32 , }impl Person { fn new (name: String , age: u32 ) -> Person { Person { name, age } } }fn main () { let mut people = vec! [ Person::new ("Zoe" .to_string (), 25 ), Person::new ("Al" .to_string (), 60 ), Person::new ("John" .to_string (), 1 ), ]; people.sort_unstable_by (|a, b| b.age.cmp (&a.age)); println! ("{:?}" , people); }
自然,我们可以drive Ord、Eq、PartialEq、PartialOrd 这些属性,使很多原本PartialOrd的数据类型实现ord。
这样我们就可以比较结构体,这时候需要确保你的结构体中所有的属性均实现了 Ord 相关特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] struct Person { name: String , age: u32 , }impl Person { fn new (name: String , age: u32 ) -> Person { Person { name, age } } }fn main () { let mut people = vec! [ Person::new ("Zoe" .to_string (), 25 ), Person::new ("Al" .to_string (), 60 ), Person::new ("Al" .to_string (), 30 ), Person::new ("John" .to_string (), 1 ), Person::new ("John" .to_string (), 25 ), ]; people.sort_unstable (); println! ("{:?}" , people); }
derive 的默认实现会依据属性的顺序依次进行比较,如上述例子中,当 Person 的 name 值相同,则会使用 age 进行比较。
十二、KV 存储 HashMap 与vector的区别在于:存储的是一一映射的KV键值对,并且提供了平均复杂度为 O(1) 的查询方法,也就是可以通过一个key去快速查询值。
1、创建 new 同样可以使用new方法创建,通过insert方法插入
1 2 3 4 5 6 7 8 9 use std::collections::HashMap;let mut my_gems = HashMap::new (); my_gems.insert ("红宝石" , 1 ); my_gems.insert ("蓝宝石" , 2 ); my_gems.insert ("河边捡的误以为是宝石的破石头" , 18 );
熟悉的感觉,区别就是需要 use std::collections::HashMap;将其引入作用域,这是因为hashmap并没有和string和vector一样被自动引入到作用域。
使用迭代器和collect创建 1 2 3 4 5 6 7 8 9 10 11 12 13 fn main () { use std::collections::HashMap; let teams_list = vec! [ ("中国队" .to_string (), 100 ), ("美国队" .to_string (), 10 ), ("日本队" .to_string (), 50 ), ]; let teams_map : HashMap<_,_> = teams_list.into_iter ().collect (); println! ("{:?}" ,teams_map) }
从另一个数据结构中获取数据并且存储时,可以通过这种方式,极大简化过程
所有权转移 所有权规则:
若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权
若没实现 Copy 特征,所有权将被转移给 HashMap 中
另外,值得新人吐槽的是,使用引用类型放入hashmap时,需要保证该引用的生命周期和hashmap一样长,否则报错;同时又不能直接再次使用该引用类型,否则同样报错…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 fn main () { use std::collections::HashMap; let name = String ::from ("h2q" ); let age = 18 ; let mut handsome_boys = HashMap::new (); handsome_boys.insert (name, age); println! ("想什么呢,{}xnn怎么能是handsomeboy呢" , name); println! ("还有,他的真实年龄远远不止{}岁" , age); std::mem::drop (name); println! ("自然,{:?}已经被从handsomeboy中除名" , handsome_boys); }
是的,这两个都会报错,悲
查询