字符串-切片-数组-元组-hashpmap
字符串切片
字符串切片String 是定义在标准库中的类型,分配在堆上,可以动态的增长。它的底层存储是动态字节数组的方式( Vec<u8> ),但是与字节数组不同,String 是 UTF-8 编码。
&str 和 String 的关系类似于 &[T] 和 Vec<T> 。
let s = "Hello, world!";
实际上,s 的类型是 &str
let s: &str = "Hello, world!";
str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码。
除了 String 类型的字符串,Rust 的标准库还提供了其他类型的字符串,例如 OsString, OsStr, CsString 和CsStr 等,注意到这些名字都以 String 或者 Str 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
只能将 String 跟 &str 类型进行拼接,并且 String 的所有权在此过程中会被 move。
String 与 &str 的转换
可以使用 String::from 或 to_string 将 &str 转换成 String 类型。
从 &str 类型生成 String 类型
String::from("hello,world")"hello,world".to_string()
如何将 String 类型转为 &str 类型? 取引用即可
字符串索引
切片的索引是通过字节来进行,但是字符串又是 UTF-8 编码,因此你无法保证索引的字节刚好落在字符的边界上。
因此在通过索引区间来访问字符串时,需要格外的小心,一不注意,就会导致你程序的崩溃。
操作字符串
push
在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。
Insert
字符串变量必须由 mut 关键字修饰
Replace
replace:第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。
replacen:前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。
replace_range:第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。
Delete
pop():删除并返回字符串的最后一个字符。该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。
remove():删除并返回字符串中指定位置的字符。该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove(0)。按照字节来处理字符串的
truncate ():删除字符串中从指定位置开始到结尾的全部字符。直接操作原来的字符串。无返回值,truncate(3)。按照字节来处理字符串的
clear():清空字符串。直接操作原来的字符串,相当于 truncate() 方法参数为 0 的时候。clear()。
连接
使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型,调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法。+ 和 += 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰。
使用 format! 连接字符串,format! 的用法与 print! 的用法类似
let s = format!("{} {}!", s1, s2);
转义
如果字符串包含双引号,可以在开头和结尾加 #
UTF-8
以 Unicode 字符的方式遍历字符串,最好的办法是使用 chars 方法——字符
返回字符串的底层字节数组表现形式——字节
数组
数组的类型是 [T; Length],数组的长度是类型签名的一部分,因此数组的长度必须在编译期就已知。
数组中的所有元素必须是同一类型
数组的下标索引从 0 开始,越界索引会导致代码的
panic
第一种是速度很快但是长度固定的 array,第二种是可动态增长的但是有性能损耗的 Vector
长度固定
元素必须有相同的类型
依次线性排列
let v: Vec = Vec::new();
如果预先知道要存储的元素个数,可以使用
Vec::with_capacity(capacity)创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能。
切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
创建切片的代价非常小,因为切片只是针对底层数组的一个引用
切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,
&str字符串切片也同理
几个要注意的点:
数组类型容易跟数组切片混淆,[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述
[u8; 3]和[u8; 4]是不同的类型,数组的长度也是类型的一部分在实际开发中,使用最多的是数组切片[T],我们往往通过引用的方式去使用
&[T],因为后者有固定的类型大小
deref 隐式强制转换,具体我们会在 Deref 特征进行详细讲解。
&v[100] 的访问方式会导致程序无情报错退出,因为发生了数组越界访问。 但是 v.get 就不会,它在内部做了处理,有值的时候返回 Some(T),无值的时候返回 None,因此 v.get 的使用方式非常安全。
当你确保索引不会越界的时候,就用索引访问,否则用 .get。例如,访问第几个数组元素并不取决于我们,而是取决于用户的输入时,用 .get 会非常适合。
存储不同类型
枚举来处理
trait
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于特征对象非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。
元组
元组中的元素可以是不同的类型。元组的类型签名是 (T1, T2, ...), 这里 T1, T2 是相对应的元组成员的类型。
可以使用索引来获取元组的成员
过长的元组无法被打印输出
元组可以用于函数的参数和返回值
hashmap
使用 HashMap 需要手动通过use std::collections::HashMap;
HashMap 也是内聚性的,即所有的 K 必须拥有同样的类型,V 也是如此。
跟
Vec一样,如果预先知道要存储的KV对个数,可以使用HashMap::with_capacity(capacity)创建指定大小的HashMap,避免频繁的内存分配和拷贝,提升性能
HashMap 的所有权规则与其它 Rust 类型没有区别:
若类型实现
Copy特征,该类型会被复制进HashMap,因此无所谓所有权若没实现
Copy特征,所有权将被转移给HashMap中
如果你使用引用类型放入 HashMap 中,请确保该引用的生命周期至少跟 HashMap 活得一样久。
f32 和 f64 浮点数,没有实现 std::cmp::Eq 特征,因此不可以用作 HashMap 的 Key。
目前,
HashMap使用的哈希函数是SipHash,它的性能不是很高,但是安全性很高。SipHash在中等大小的Key上,性能相当不错,但是对于小型的Key(例如整数)或者大型Key(例如字符串)来说,性能还是不够好。若你需要极致性能,例如实现算法,可以考虑这个库:ahash
先将 Vec 转为迭代器,接着通过 collect 方法,将迭代器中的元素收集后,转成 HashMap:
into_iter 方法将列表转为迭代器,接着通过 collect 进行收集
代码参考
Last updated