网站做链接算侵权吗,学院网站建设策划书,贵州网站建设营销公司哪家好,wordpress开发ideRust 标准库中包含一系列被称为 集合#xff08;collections#xff09;的非常有用的数据结构。大部分其他数据类型都代表一个特定的值#xff0c;不过集合可以包含多个值。不同于内建的数组和元组类型#xff0c;这些集合指向的数据是储存在堆上的#xff0c;这意味着数据…Rust 标准库中包含一系列被称为 集合collections的非常有用的数据结构。大部分其他数据类型都代表一个特定的值不过集合可以包含多个值。不同于内建的数组和元组类型这些集合指向的数据是储存在堆上的这意味着数据的数量不必在编译时就已知并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本而根据当前情况选择合适的集合这是一项应当逐渐掌握的技能。在这一章里我们将详细的了解三个在 Rust 程序中被广泛使用的集合 • vector 允许我们一个挨着一个地储存一系列数量可变的值 • 字符串string是字符的集合。我们之前见过 String 类型不过在本章我们将深入了解。 • 哈希 maphash map允许我们将值与一个特定的键key相关联。这是一个叫做 map 的更通用的数据结构的特定实现。 Sequences: Vec, VecDeque, LinkedList Maps: HashMap, BTreeMap Sets: HashSet, BTreeSet Misc: BinaryHeap 一、vector
vector 允许我们在一个单独的数据结构中储存多于一个的值它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。类似java中的ArrayList结构。
1.1 创建 let v:Veci32 Vec::new();vector 是用泛型实现的Vec 是一个由标准库提供的类型它可以存放任何类型而当 Vec 存放某个特定类型时那个类型位于尖括号中。 通常我们会用初始值来创建一个 Vec 而 Rust 会推断出储存值的类型所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏这个宏会根据我们提供的值来创建一个新的vector。
let v vec![1,2,3];1.2 更新
使用push方法
//如果想要能够改变它的值必须使用 mut 关键字使其可变。
let mut v Vec::new();
v.push(1);
v.push(2);1.3 读取
有两种方法引用 vector 中储存的值通过索引或使用 get 方法。
fn main() {let v vec![1,2,3,4,5];let third v[2];println!(third is {},third);let third v.get(2);match third {Some(third) println!(The third element is {third}),None println!(There is no third element.),}
}这里有几个细节需要注意。我们使用索引值 2 来获取第三个元素因为索引是从数字 0 开始的。使用 和 [] 会得到一个索引位置元素的引用。当使用索引作为参数调用 get 方法时会得到一个可以用于 match 的 OptionT 。 使用[] 访问vector时当引用一个不存在的元素时 Rust 会造成panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况这时应该使程序崩溃。 当 get 方法被传递了一个数组外的索引时它不会造成panic而会返回None。当偶尔出现超过vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理Some(element) 或 None 的逻辑。 一旦程序获取了一个有效的引用借用检查器将会执行所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候如果尝试在函数的后面引用这个元素是行不通的
fn main() {let mut v vec![1, 2, 3, 4, 5];let first v[0];v.push(6);println!(The first element is: {first});
}error[E0502]: cannot borrow v as mutable because it is also borrowed as immutable-- src\main.rs:232:5|
231 | let first v[0];| - immutable borrow occurs here
232 | v.push(6);| ^^^^^^^^^ mutable borrow occurs here
233 | println!(The first element is: {first});| ------- immutable borrow later used here
看起来应该能够运行为什么第一个元素的引用会关心 vector 结尾的变化不能这么做的原因是由于 vector 的工作方式在 vector 的结尾增加新元素时在没有足够空间将所有元素依次相邻存放的情况下可能会要求分配新内存并将老的元素拷贝到新的空间中。这时第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
1.4 遍历 vector 中的元素
如果想要依次访问 vector 中的每一个元素我们可以遍历其所有的元素而无需通过索引一次一个的访问。
fn main() {let mut v vec![1, 2, 3, 4, 5];for i in v {println!({i})}
}我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变它们。
fn main() {let mut v vec![1, 2, 3, 4, 5];for i in mut v {*i 10;}
}1.5 使用枚举来储存多种类型
vector 只能储存相同类型的值。这是很不方便的绝对会有需要储存一系列不同类型的值的用例。幸运的是枚举的成员都被定义为相同的枚举类型所以当需要在 vector 中储存不同类型值时我们可以定义并使用一个枚举
enum SpreadsheetCell {Int(i32),Float(f64),Text(String),
}
let row vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from(blue)),SpreadsheetCell::Float(10.12),];Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许vector 存放任意类型那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成 错误。 如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型枚举技术就行不通了。相反你可以使用 trait 对象这个后面再说 vector文档
1.6 丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 struct vector 在其离开作用域时会被释放当 vector 被丢弃时所有其内容也会被丢弃这意味着这里它包含的数据将被清理。借用检查器确保了任何 vector 中内容的引用仅在 vector 本身有效时才可用。
二、string
字符串就是作为字节的集合外加一些方法实现的当这些字节被解释为文本时这些方法提供了实用的功能。在这一部分我们会讲到 String 中那些任何集合类型都有的操作比如创建、更新和读取。索引 String 是很复杂的由于人和计算机理解 String 数据方式的不同。
2.1 什么是字符串
Rust 的核心语言中只有一种字符串类型字符串 slice str 它通常以被借用的形式出现str 。字符串 slices它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说由于字符串字面值被储存在程序的二进制输出中因此字符串字面值也是字符串 slices。 字符串String 类型由 Rust 标准库提供而不是编入核心语言它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。而且 String 和 字符串 slices 都是UTF-8 编码的。
2.2 新建
很多 Vec 可用的操作在 String 中同样可用事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。
let mut s String::new();
let str hello.to_string();
let str String::from(hello world);因为字符串应用广泛这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余不过都有其用武之地在上面例子中String::from 和 .to_string 最终做了完全相同的工作所以如何选择就是代码风格与可读性的问题了。
2.3 更新
String 的大小可以增加其内容也可以改变就像可以放入更多数据来改变 Vec 的内容一样。另外可以方便的使用 运算符或 format! 宏来拼接 String 值。
使用 push_str 和 push 附加字符串 可以通过 push_str 方法来附加字符串 slice从而使 String 变长。 push 方法被定义为获取一个单独的字符作为参数并附加到 String 中。 let mut str hello.to_string();str.push_str( world);使用拼接字符串其实使用的add函数 fn add(self, s: str) - String { let s1 String::from(Hello, );let s2 String::from(world!);let s3 s1 s2; // 注意 s1 被移动了不能继续使用s2 使用了 意味着我们使用第二个字符串的 引用 与第一个字符串相加。这是因为add 函数的 s 参数只能将 str 和 String 相加不能将两个 String 值相加。之所以能够在 add 调用中使用 s2 是因为 String 可以被 强转coerced成 str 。当add 函数被调用时Rust 使用了一个被称为 Deref 强制转换deref coercion的技术你可以将其理解为它把 s2 变成了 s2[…] 。 其次可以发现签名中 add 获取了 self 的所有权因为 self 没有 使用 。这意味着 s1 的所有权将被移动到 add 调用中之后就不再有效。所以虽然 let s3 s1 s2; 看起来就像它会复制两个字符串并创建一个新的字符串而实际上这个语句会获取 s1 的所有权附加上从 s2 中拷贝的内容并返回结果的所有权。换句话说它看起来好像生成了很多拷贝不过实际上并没有这个实现比拷贝要更高效。
如果想要级联多个字符串 的行为就显得笨重了,对于更为复杂的字符串链接可以使用 format! 宏 let s1 String::from(aaaaaa);let s2 String::from(bbbbbb);let s3 String::from(cccccc);let s format!({s1}-{s2}-{s3});format! 与 println! 的工作原理相同不过不同于将输出打印到屏幕上它返回一个带有结果内容的 String 。这个版本就好理解的多宏 format! 生成的代码使用引用所以不会获取任何参数的所有权。
2.4 索引字符串
在很多语言中通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中如果你尝试使用索引语法访问 String 的一部分会出现一个错误。
fn main() {let s1 String::from(hello);let h s1[0];
}error[E0277]: the type String cannot be indexed by {integer}-- src\main.rs:232:13|
232 | let h s1[0];| ^^^^^ String cannot be indexed by {integer}| help: the trait Index{integer} is not implemented for String help: the following other types implement trait IndexIdx:String as IndexRangeFullString as Indexstd::ops::RangeusizeString as IndexRangeFromusizeString as IndexRangeTousizeString as IndexRangeInclusiveusizeString as IndexRangeToInclusiveusize从上错误所知Rust 的字符串不支持索引。 那为什么呢 String 是一个 Vecu8 的封装。如果对于这个let hello String::from(Hola);在这里len 的值是 4这意味着储存字符串 “Hola” 的 Vec 的长度是四个字节这里每一个字母的 UTF-8 编码都占用一个字节。如果有其它语言的编码那么它的长度可能是不能确认的。比如对于let hello String::from(Здравствуйте);这样的在rust里面不是12个字节而是24个。这是使用 UTF-8编码 “Здравствуйте” 所需要的字节数这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。
let hello String::from(Здравствуйте);
let s hello[0];当使用 UTF-8 编码时西里尔字母的 ZeЗ 的第一个字节是 208 第二个是 151 所以 s 实际上应该是 208 不过 208 自身并不是一个有效的字母。返回 208 可不是一个请求字符串第一个字母的人所希望看到的不过它是Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母如果 “hello”[0] 是返回字节值的有效代码它也会返回 104 而不是 h 。为了避免返回意外的值并造成不能立刻发现的 bugRust 根本不会编译这些代码并在开发过程中及早杜绝了误会的发生。
Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据这样程序就可以选择它需要的表现方式而无所谓是何种人类语言。 最后一个 Rust 不允许使用索引获取 String 字符的原因是索引操作预期总是需要常数时间O(1)。但是对于 String 不可能保证这样的性能因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。
2.5 字符串 slice
索引字符串不是一个好方案因为字符串索引应该返回的类型是不明确的字节值、字符、字形簇或者字符串 slice。因此如果你真的希望使用索引创建字符串 slice 时Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice相比使用 [] 和单个值的索引可以使用 [] 和一个 range来创建含特定字节的字符串 slice
let hello Здравствуйте;
let s hello[0..4];这里s 会是一个 str 它包含字符串的头四个字节。这些字母都是两个字节长的所以这意味着 s 将会是 “Зд”。 如果获取 hello[0…1] 会发生什么呢答案是Rust 在运行时会 panic就跟访问 vector中的无效索引时一样
2.6 遍历字符串
操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 chars 方法。对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值接着就可以遍历其结果来访问每一个元素了
for c in Зд.chars() {println!({c});
}另外 bytes 方法返回每一个原始字节
for b in Зд.bytes() {println!({b});
}Rust 选择了以准确的方式处理 String 数据作为所有 Rust 程序的默认行为这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。
三、hashmap
哈希 maphash map。HashMapK, V 类型储存了一个键类型K 对应一个值类型 V 的映射。它通过一个 哈希函数hashingfunction来实现映射决定如何将键和值放入内存中。
3.1 新建
可以使用 new 创建一个空的 HashMap 并使用 insert 增加元素。
fn main() {use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10);scores.insert(String::from(Yellow), 50);
}3.2 访问
使用 get 方法进行访问。 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10);scores.insert(String::from(Yellow), 50);let color_name String::from(Blue);let score scores.get(color_name).copied().unwrap_or(0);get 方法返回 OptionV 如果某个键在哈希 map 中没有对应的值get 会返回 None 。程序中通过调用 copied 方法来获取一个 Option 而不是 Optioni32 接着调用 unwrap_or 在 scores 中没有该键所对应的项时将其设置为零。可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10);scores.insert(String::from(Yellow), 50);for (k,v) in scores {println!({k}:{v})}3.3 哈希 map 和所有权
对于像 i32 这样的实现了 Copy trait 的类型其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值其值将被移动而哈希 map 会成为这些值的所有者 use std::collections::HashMap;let field_name String::from(Favorite);let field_value String::from(Java);let mut map HashMap::new();map.insert(field_name, field_value);// 这里 field_name 和 field_value 不有效// 尝试使用它们看看会出现什么编译错误当 insert 调用将 field_name 和 field_value 移动到哈希 map 中后将不能使用这两个绑定。
3.4 更新哈希 map
尽管键值对的数量是可以增长的每个唯一的键只能同时关联一个值
覆盖直接insert相同key的数据进去就行只在没有键的时候插入使用api map.entry(String::from(Favorite)).or_insert(Rust); 根据旧值更新一个值 use std::collections::HashMap;let text hello world wonderful world;let mut map HashMap::new();for word in text.split_whitespace() {let count map.entry(word).or_insert(0);*count 1;}println!({:?}, map);3.5 删除键
在Rust中可以使用remove方法从HashMap中删除一个键值对。
use std::collections::HashMap;fn main() {let mut map: HashMapString, i32 HashMap::new();map.insert(one.to_string(), 1);map.insert(two.to_string(), 2);map.insert(three.to_string(), 3);println!(Before removing: {:?}, map);map.remove(two);println!(After removing: {:?}, map);
}3.6 哈希函数
HashMap 默认使用一种叫做 SipHash 的哈希函数它可以抵御涉及哈希表hash table 的拒绝服务Denial of Service, DoS攻击。然而这并不是可用的最快的算法不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢以致于你无法接受你可以指定一个不同的 hasher 来切换为其它函数。hasher 是一个实现了 BuildHasher trait的类型。crates.io也提供了一些好用的第三方库。 要使用自定义哈希函数需要实现std::hash::Hasher trait。
use std::collections::HashMap;
use std::hash::{BuildHasher, Hasher};struct MyHasher {hash: u64,
}impl Hasher for MyHasher {fn write(mut self, bytes: [u8]) {for byte in bytes {self.hash self.hash.wrapping_mul(31).wrapping_add(*byte as u64);}}fn finish(self) - u64 {self.hash}
}fn main() {let mut map: HashMapString, i32, BuildHasherDefaultMyHasher HashMap::default();map.insert(one.to_string(), 1);map.insert(two.to_string(), 2);map.insert(three.to_string(), 3);println!(Before removing: {:?}, map);map.remove(two);println!(After removing: {:?}, map);
}