昨天我们学了package、crate、module
相关的概念,也大致清楚了项目的结构。
今天往下学第八章了
common collection
)这里我个人就冒然叫collections
为集合了,如果有问题麻烦评论区里说一下,谢谢。。。
rust
标准库中有很多这种集合。
和rust
中其他大多数据结构(一个变量只允许一种特定类型的值)不同的是,集合允许包含多个不同类型的数据。
当然,元组也可以。但是和数组/元组这俩原始复合类型不同的是,集合的数据是存放到heap
也就是堆内存的。存放到heap
上也就是说它的内存是动态分配的,也就意味着数据大小是可变的。这点和数组元组是完全不同的。
你应该还记得我们在介绍数组的时候还介绍了另一个和数组长得非常像的:vector
。是的,它就是一个集合。
在这一章我们将学到三个集合这种数据结构的类型:
vector
hash map
,哈希map
,一种特殊的map
不过在开始学习前,我们需要注意,它们都是放在standard library
中的,并非核心代码。
如果还想了解其他标准库里的集合,可以访问这里
std::collections - Rustdoc.rust-lang.org/std/collections/index.html
vector
vector
实际上是vector
类型,是一个泛型。 前面说到过和数组一样,它允许存储多个数据,数据类型必须相同,以及在内存里是连续的。不同点在于内存存储方式(stack/heap
)不同。
我们先开看下例子
vector
的关键字是Vec
,new
是关联函数,前面说过了,会返回一个Vec
实例。
注意,如果是::new
这种方式创建vector
,那么得声明数据的类型。
当然,也不是一定就要,比如后面再push
个数据进去
之前说到过了,rust
的编译器会自动推断类型,所以当你往里面存放一个或者一组类型相同的数据时就会自动推断出这个v
变量的类型。
如果你还没想好准备放什么进去,这么写是最好的。但是如果当你已经知道要放啥进去了,这时候这么写就未免有些麻烦。rust
提供了Vec
的macro
,也就是宏,可以理解为语法糖。
关键字是vec!
,这里你应该已经知道了怎么知道是不是一个macro
,带个!
结尾的就是~。
这样你也没必要去声明类型了。
现在我们知道如何创建Vec
实例了,接下来该学如何更新这个vector
了。
vector
上面那个push
实际上就是在更新v
这个Vec
实例。
和别的语言数组一样,都可以用push
往list
尾部添加相同类型数据。
当然,这个vector
必须是mut
可变的。
类似数组的pop
和insert
等方法也是有的,这个到时候用到再说。 我们先来学如何读取vector
里的数据。
vector
元素既然长得像十分像数组,那自然也是可以通过下标读取的,不过它又是一个集合collection
,那自然又可以用集合的方法get
来读取元素
不过需要注意的一点是,get
返回的是元素的引用。那么同样的,通过下标读取也尽量用引用方式。 因为vector
里可以存放多种类型,如果是标量类型等存放到stack
内存里的还好,但如果是需要存放到heap
上的,很容易就会出问题。
好在rust
防住了这种问题
来看个例子
完整的错误如下
这个时候改成引用就没问题了
我们再来看下get
的使用
返回的是一个Option
的枚举,并且可以看到T
是&String
,是引用类型。
既然是Option
,是一个枚举,那当然得配上match
,就像下雨天要配德芙一样,这样才优雅丝滑~
用下标还是get
取决于你自己,如果你能确定你所传入的下标值是一定有效的,那么你就没必要去选择get
,毕竟还得写一个match
匹配。
为什么这么说呢?我们来看个例子
我们声明了first
和third
两个变量,一个用下标获取第100
个,一个用get
获取第100
个。
还没开始run
的时候其实是没报错的,为什么呢?因为vector
是放在heap
上的,是动态的,runtime
的时候才会知道具体是多少。
我们来run
一下。
可以看到通过下标的方式获取直接崩溃了。
我们再来看下通过get
方法获取
可以看到程序并没有崩溃,而是走了match
为None
的arm
,告知你这是一个无效的值。
所以什么时候用下标方式访问取决于你是否确定这个下标一定不会越界。
另外还有个点我们需要注意:获取引用元素之后并且未失效之前不允许改动原vector
之前学引用的时候我们学到过“一个scoped
中不能同时存在不可变引用和可变引用”。
可以改成下面的样子
回到我们的例子,来看下相同问题
为啥我们改的是v
却得到相同的报错?
因为push
方法,我们看下push
这个method
的源码
是对v
自己的可变引用,所以这里就出现了一个可变和不可变声明。
我们可以调整下代码
我们在声明前调用push
就没问题了。
为什么会有这个问题呢????这里应该已经满头问号了。
其实这个和heap
也就是堆内存分配的方式有关。
在runtime
时,在你push
之前,内存分配器会根据v
的大小去找一块闲置的大于当前v
数据需要的空间存放这个v
的数据,但是你现在又push
了,之前的空间可能不满足需求了,所以这个时候内存分配器又得重新去找一块内存来存现在的v
。而原来的空间就闲置了。这个时候如果你在push
之前声明了一个不可变引用,它指向的地方还是之前的指针的位置,这个时候就会有问题了。
你可能又要问了,为啥这个可变引用没有失效?因为还没出作用域,只要在作用域内,它就是有效的。
所以说不允许同时有一个可变引用和不可变引用。
ok
,我们来继续往下学
vector
既然长得像数组,那自然会被用到遍历,我们来看下例子
和遍历array
没啥大差别,你应该注意到了*i += 10
这条语句的*
符号,这个表示解引用(dereference
),这个以后会说到,这里就暂时不说。
enum
前面说过vector
是一个list
,存放的是连续相同类型的数据。那么这些数据自然就是有关联的,那么有关联我们第一印象就是strcut
,但是enum
枚举类型也是可以的,比如下面这个例子
一个单元格可以存放三种类型的内容:数字、文本和小数。
然后又存在一组单元格,那自然就可以用vector
将他们组装到一起。
rust
需要在编译的时候就确定好vector
的泛型是啥类型,因为这样才能知道如何分配内存给这个vector
,如果类型都不清楚,那自然就不知道如何分配了。
声明好类型可以让编译器在编译的时候就知道要分配多少内存,这是一种优化。
另外,vector
出了作用域就会失效了,包括它里面的所有元素。
今天我们学了vector
的相关知识。
本来打算今天写完整个第八章的,但怎奈内容有些多,尤其是String
。所以还是老规矩拆分成三天或者四天内容。
最后如果觉得这篇文章对你有帮助的话,请务必点个赞,谢谢~
发布于 2022-12-10 13:24・IP 属地广东