当前位置: 首页 > news >正文

沈阳网站建设策划方案做一款微信小程序多少钱

沈阳网站建设策划方案,做一款微信小程序多少钱,h5免费制作平台易企秀官方,设计师可以在哪些网站接单上一篇#xff1a; 07-使用Package、Crates、Modules管理项目 Rust 的标准库包含许多非常有用的数据结构#xff0c;称为集合。大多数其他数据类型表示一个特定值#xff0c;但集合可以包含多个值。与内置的数组和元组类型不同#xff0c;这些集合指向的数据存储在堆上 07-使用Package、Crates、Modules管理项目 Rust 的标准库包含许多非常有用的数据结构称为集合。大多数其他数据类型表示一个特定值但集合可以包含多个值。与内置的数组和元组类型不同这些集合指向的数据存储在堆上这意味着数据量不需要在编译时就知道可以随着程序的运行而增减。每种集合都有不同的功能和代价根据当前情况选择合适的集合是一项需要长期积累的技能。在本章中我们将讨论 Rust 程序中经常使用的三种集合 ①. 矢量允许你将数量可变的数值相邻存储 ②. 字符串是字符的集合。我们之前提到过 String 类型本章我们将深入讨论它 ③. Hash Map允许你将一个值与一个特定的键关联起来。它是更通用的数据结构Map的一种特殊实现 要了解标准库提供的其他类型的集合请参阅文档。 1. 用Vector存储值列表 我们要了解的第一种集合类型是 VecT 也称为vector。vector允许你在一个数据结构中存储多个值并将所有值放在内存中的相邻位置。vector只能存储相同类型的值。当你有一个项目列表时比如文件中的文本行或购物车中的项目价格vector就非常有用。 1.1 创建vector 要创建一个新的空向量我们需要调用 Vec::new 函数, 如下代码所示 let v: Veci32 Vec::new();请注意我们在这里添加了一个类型注解。因为我们没有在这个vector中插入任何值所以 Rust 不知道我们打算存储什么样的元素。这一点很重要。vector是使用泛型实现的我们将在后面的章节介绍如何在自己的类型中使用泛型。现在我们要知道标准库提供的 VecT 类型可以容纳任何类型。当我们创建一个存放特定类型的向量时我们可以在角括号中指定该类型。上面代码我们告诉 Rust 变量v 中的 VecT 将保存 i32 类型的元素。 更常见的情况是你会创建一个带有初始值的 VecT Rust 会推断出你想要存储的值的类型所以你很少需要做这种类型注解。Rust 方便地提供了 vec! 宏它将创建一个新的vector来保存你给它的值。下面示例代码创建了一个新的 Veci32 其中包含 1 、 2 和 3 。整数类型是 i32 因为这是默认的整数类型 let v vec![1, 2, 3]; 因为我们已经给出了 i32 的初始值所以 Rust 可以推断 v 的类型是 Veci32 而不需要类型注解。接下来我们来看看如何修改向量。 1.2 更新Vector 要创建vector并向其中添加元素我们可以使用 push 方法如下代码所示 let mut v Vec::new();v.push(5); v.push(6); v.push(7); v.push(8);与任何变量一样如果我们想改变它的值就需要使用 mut 关键字使其可变。我们放在里面的数字都是 i32 类型的Rust 从数据中推断出了这一点所以我们不需要 Veci32 注解。 1.3 获取vector中的元素 有两种方法可以引用存储在向量中的值通过索引或使用 get 方法。在下面的示例中我们注释了这些函数返回值的类型以便更加清晰。 下列代码展示了访问向量中值的两种方法索引语法和 get 方法。 let v vec![1, 2, 3, 4, 5];let third: i32 v[2]; println!(The third element is {third});let third: Optioni32 v.get(2); match third {Some(third) println!(The third element is {third}),None println!(There is no third element.), }请注意这里的一些细节。我们使用 2 的索引值来获取第三个元素因为矢量是按数字索引的从 0 开始。使用 和 [] 可以得到索引值处元素的引用。当我们使用 get 方法并将索引作为参数传递时我们会得到一个 OptionT 可以与 match 一起使用。 Rust 提供这两种引用元素的方法是为了让你在尝试使用现有元素范围之外的索引值时可以选择程序的行为方式。举例来说当我们有一个包含五个元素的向量并尝试使用每种技术访问索引值为 100 的元素时会发生什么情况如下代码所示 let v vec![1, 2, 3, 4, 5];let does_not_exist v[100]; let does_not_exist v.get(100);当我们运行这段代码时第一个 [] 方法会导致程序崩溃因为它引用了一个不存在的元素。如果你想让程序在试图访问超过向量末尾的元素时崩溃最好使用这种方法。 当 get 方法传入一个超出向量范围的索引时它会返回 None 而不会引起恐慌。如果在正常情况下偶尔会出现访问超出向量范围的元素的情况则可以使用该方法。然后你的代码将包含处理 Some(element) 或 None 的逻辑。例如索引可能来自输入数字的人。如果用户不小心输入了一个过大的数字导致程序得到一个 None 的值你可以告诉用户当前向量中有多少个项目然后再给他们一次机会输入一个有效值。这比因为输入错误而导致程序崩溃更方便用户使用 当程序有了一个有效的引用时借用检查程序就会执行所有权和借用规则以确保这个引用和其他对向量内容的引用保持有效。请回想一下不能在同一作用域中使用可变引用和不可变引用的规则该规则适用于下列示例代码。在该代码中我们持有对向量中第一个元素的不可变引用并尝试在末尾添加一个元素。如果我们在函数的稍后部分也尝试引用该元素那么该程序将无法运行 let mut v vec![1, 2, 3, 4, 5];let first v[0];v.push(6);println!(The first element is: {first});编译此代码将导致此错误 PS E:\rustProj\collections cargo.exe buildCompiling collections v0.1.0 (E:\rustProj\collections) error[E0502]: cannot borrow v as mutable because it is also borrowed as immutable-- src\main.rs:6:5| 4 | let first v[0];| - immutable borrow occurs here 5 | 6 | v.push(6);| ^^^^^^^^^ mutable borrow occurs here 7 | 8 | println!(The first element is: {first});| ------- immutable borrow later used hereFor more information about this error, try rustc --explain E0502. error: could not compile collections (bin collections) due to previous error 上述代码中看起来似乎应该有效为什么第一个元素的引用要关心vector末尾的变化呢这个错误是由于vector的工作方式造成的由于vector将值放在内存中的相邻位置如果没有足够的空间将所有元素放在vector当前存储位置的相邻位置那么在vector末尾添加新元素可能需要分配新内存并将旧元素复制到新空间。在这种情况下对第一个元素的引用将指向已取消分配的内存。借用规则可以防止程序出现这种情况。 1.4 遍历vector中的值 要依次访问vector中的每个元素我们需要遍历所有元素而不是使用索引一次访问一个元素。下列代码展示了如何使用 for 循环获取 i32 值向量中每个元素的不可变引用并打印出来。 let v vec![100, 32, 57]; for i in v {println!({i}); }我们还可以遍历可变vector中每个元素的可变引用以便对所有元素进行更改。下列代码中的 for 循环将为每个元素添加 50 。 let mut v vec![100, 32, 57]; for i in mut v {*i 50; }要更改可变引用所指向的值我们必须先使用 * 解除引用操作符获取 i 中的值然后才能使用 操作符。我们将在后面章节中详细介绍解除引用操作符。 对vector进行迭代无论是不变迭代还是可变迭代都是安全的因为有借用检查器的规则。如果我们试图在上述代码中的 for 循环体中插入或删除项我们将收到类似的编译器错误。 for 循环对vector的引用可以防止同时修改整个vector。 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允许包含哪些类型。如果 Rust 允许vector容纳任何类型那么在对vector元素进行操作时其中一种或多种类型就有可能导致错误。使用枚举和 match 表达式意味着 Rust 将在编译时确保处理每一种可能的情况。 如果你不知道程序运行时会获得哪些详尽的类型来存储在vector中那么枚举技术就不会起作用。相反你可以使用特质对象我们将在后面章介绍。 现在我们已经讨论了使用vector的一些最常见方法请务必查看 API 文档了解标准库在 VecT 上定义的所有实用方法。例如除了 push 之外还有一个 pop 方法可以删除并返回最后一个元素。 1.6 丢弃vector会丢弃其元素 与任何其他 struct 一样当vector退出作用域时它将被释放如下代码所示 {let v vec![1, 2, 3, 4];// do stuff with v } // - v goes out of scope and is freed here当vector被丢弃时它的所有内容也会被丢弃这意味着它所保存的整数将被清理。借用检查器确保只有在vector本身有效时才会使用对vector内容的引用。 2. 用String存储 UTF-8 编码文本 我们在第 4 章中讨论过字符串现在我们将对其进行更深入的研究。 新的 Rustaceans 通常会被字符串卡住这有三个原因Rust 有暴露可能错误的倾向字符串是一种比许多程序员认为的更复杂的数据结构以及 UTF-8。当你来自其他编程语言时这些因素结合在一起就会显得很困难。 我们在集合的上下文中讨论String是因为String是作为字节集合实现的另外还有一些方法在这些字节被解释为文本时提供有用的功能。在本节中我们将讨论每种集合类型都具有的对 String 的操作如创建、更新和读取。我们还将讨论 String 与其他集合的不同之处即由于人们和计算机对 String 数据的解释方式不同在 String 中建立索引会变得复杂。 2.1 什么是String 我们首先定义一下字符串的含义。Rust 的核心语言中只有一种字符串类型那就是字符串片 str 它的借用形式通常是 str 。在第 4 章中我们谈到了字符串片它是对存储在其他地方的UTF-8 编码字符串数据的引用。例如字符串字面量存储在程序的二进制文件中因此是字符串片。 String 类型是由 Rust 标准库提供的而不是编入核心语言的它是一种可增长、可变、自有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 字符串 时他们可能指的是 String 或字符串片 str 类型而不仅仅是其中一种类型。虽然本节主要讨论的是 String 但这两种类型在 Rust 的标准库中都被大量使用而且 String 和字符串切片(str)都是 UTF-8 编码的。 2.2 创建新字符串 与 VecT 相同的许多操作在 String 中也可以使用因为 String 实际上是作为字节Vector的包装器来实现的并带有一些额外的保证、限制和功能。与 VecT 和 String 运作方式相同的函数示例是用于创建实例的 new 函数如清单 8-11 所示。 let mut s String::new();清单 8-11创建一个新的空 String 这一行将创建一个名为 s 的新空字符串然后我们就可以向其中加载数据了。通常情况下我们会有一些初始数据希望以此作为字符串的开头。为此我们使用 to_string 方法该方法适用于任何实现 Display 特性的类型字符串字面量也是如此。清单 8-12 展示了两个示例。 let data initial contents;let s data.to_string();// the method also works on a literal directly: let s initial contents.to_string();(清单 8-12使用 to_string 方法从字符串字面量创建 String) 这段代码将创建一个包含 initial contents 的字符串。 我们还可以使用函数 String::from 从字符串字面量创建 String 。清单 8-13 中的代码等同于清单 8-12 中使用 to_string 的代码。 let s String::from(initial contents);(清单 8-13使用 String::from 函数从字符串字面量创建 String) 由于字符串的用途非常广泛我们可以使用许多不同的字符串通用应用程序接口从而为我们提供了大量选择。其中有些看起来是多余的但它们都有自己的用武之地在本例中 String::from 和 to_string 做的是同一件事所以选择哪一个只是风格和可读性的问题。 请记住字符串是 UTF-8 编码的因此我们可以在其中包含任何正确编码的数据如清单 8-14 所示。 let hello String::from(السلام عليكم); let hello String::from(Dobrý den); let hello String::from(Hello); let hello String::from(שָׁלוֹם); let hello String::from(नमस्ते); let hello String::from(こんにちは); let hello String::from(안녕하세요); let hello String::from(你好); let hello String::from(Olá); let hello String::from(Здравствуйте); let hello String::from(Hola);(清单 8-14用字符串存储不同语言的问候语) 所有这些都是有效的 String 值。 2.2 更新字符串 String 的大小可以增大其内容也可以改变就像 VecT 的内容一样。此外您还可以方便地使用 运算符或 format! 宏来连接 String 的值。 2.2.1 使用 push_str 和 push向String追加数据 如清单 8-15 所示我们可以使用 push_str 方法追加字符串片段从而增长 String 。 let mut s String::from(foo); s.push_str(bar);(清单 8-15使用 push_str 方法向 String 追加字符串片段) 这两行之后 s 将包含 foobar 。 push_str 方法使用一个字符串片段因为我们并不一定要掌握参数的所有权。例如在清单 8-16 中的代码中我们希望在将 s2 的内容追加到 s1 之后能够使用 。 let mut s1 String::from(foo); let s2 bar; s1.push_str(s2); println!(s2 is {s2});(清单 8-16将字符串切片的内容追加到 String) 如果 push_str 方法拥有 s2 的所有权我们就无法在最后一行打印它的值。然而这段代码的工作原理正如我们所料 push 方法将单个字符作为参数并将其添加到 String 中。清单 8-17 使用 push 方法将字母 l 添加到 String 。 let mut s String::from(lo); s.push(l); (清单 8-17使用 String 值添加一个字符 push) 因此 s 将包含 lol 。 2.2.2 使用 运算符或 format! 宏进行连接 通常情况下您需要合并两个现有字符串。一种方法是使用 操作符如清单 8-18 所示。 let s1 String::from(Hello, ); let s2 String::from(world!); let s3 s1 s2; // note s1 has been moved here and can no longer be used (清单 8-18使用 运算符将两个 String 值合并为一个新的 String 值) 字符串 s3 将包含 Hello, world! 。 s1 在添加后不再有效而我们之所以使用 s2 的引用与我们使用 操作符时调用的方法的签名有关。 操作符使用 add 方法其签名如下 fn add(self, s: str) - String { 在标准库中您会看到使用泛型和关联类型定义的 add 。在这里我们代入了具体类型这就是使用 String 值调用此方法时的情况。我们将在第 10 章讨论泛型。这个签名为我们提供了理解 操作符的棘手之处所需的线索。 首先 s2 有一个 这意味着我们要将第二个字符串的引用添加到第一个字符串。这是因为 add 函数中的 s 参数我们只能将 str 添加到 String 中而不能将两个 String 值添加到一起。但是等等 s2 的类型是 String 而不是 add 的第二个参数中指定的 str 。那么为什么清单 8-18 可以编译呢 之所以能够在调用 add 时使用 s2 是因为编译器可以将 String 参数强转为 str 。当我们调用 add 方法时Rust 使用了 deref 强制在这里将 s2 变成了 s2[..] 。我们将在第 15 章更深入地讨论 deref 强制。由于 add 并不拥有 s 参数的所有权因此 s2 在执行此操作后仍将是一个有效的 String 。 其次我们可以从签名中看到 add 拥有 self 的所有权因为 self 没有 。这意味着清单 8-18 中的 s1 将被移到 add 调用中之后将不再有效。因此虽然 let s3 s1 s2; 看起来像是复制了两个字符串并创建了一个新字符串但实际上该语句获取了 s1 的所有权追加了 s2 内容的副本然后返回结果的所有权。换句话说这条语句看起来像是复制了很多内容但实际上并没有它的实现比复制更有效率。 如果我们需要连接多个字符串那么 操作符的行为就会变得非常复杂 let s1 String::from(tic); let s2 String::from(tac); let s3 String::from(toe);let s s1 - s2 - s3;此时 s 将变为 tic-tac-toe 。有了 和 这些字符就很难看出发生了什么。对于更复杂的字符串组合我们可以使用 format! 宏 let s1 String::from(tic); let s2 String::from(tac); let s3 String::from(toe);let s format!({s1}-{s2}-{s3});这段代码还将 s 设置为 tic-tac-toe 。 format! 宏的工作原理与 println! 类似但它不是将输出结果打印到屏幕上而是返回一个包含内容的 String 。使用 format! 的代码版本更易于阅读而且 format! 宏生成的代码使用了引用因此该调用不会占用任何参数的所有权。 2.3 索引字符串 在许多其他编程语言中通过索引来访问字符串中的单个字符是一种有效且常见的操作。但是如果在 Rust 中尝试使用索引语法访问 String 的部分内容则会出现错误。请看清单 8-19 中的无效代码。 let s1 String::from(hello); let h s1[0];(清单 8-19尝试对字符串使用索引语法) 该代码将导致以下错误 cargo buildCompiling string v0.1.0 (/home/rustProj/string) error[E0277]: the type String cannot be indexed by {integer}-- src/main.rs:3:13| 3 | 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 IndexRangeToInclusiveusizeFor more information about this error, try rustc --explain E0277. error: could not compile string (bin string) due to previous error 错误和注释说明了问题所在Rust 字符串不支持索引。但为什么不支持呢要回答这个问题我们需要讨论一下 Rust 如何在内存中存储字符串。 2.4 内部代表 tring 是对 Vecu8 的封装。让我们看看清单 8-14 中正确编码的 UTF-8 示例字符串。首先是这个 let hello String::from(Hola);在这种情况下 len 将是 4这意味着存储字符串 Hola 的向量长度为 4 个字节。用 UTF-8 编码时每个字母需要 1 个字节。不过下面一行可能会让你大吃一惊。(请注意该字符串以大写西里尔字母 Ze 开头而不是数字 3。 let hello String::from(Здравствуйте);如果问这个字符串有多长你可能会说是 12。事实上Rust 的答案是 24这是用 UTF-8 编码 Здравствуйте 所需的字节数因为该字符串中的每个 Unicode 标量值需要 2 个字节的存储空间。因此字符串字节索引并不总是与有效的 Unicode 标量值相关联。请看这段无效的 Rust 代码以作说明 let hello Здравствуйте; let answer hello[0]; 您已经知道 answer 不会是 З 即第一个字母。用 UTF-8 编码时 З 的第一个字节是 208 第二个字节是 151 因此 answer 似乎实际上应该是 208 但 208 本身并不是一个有效字符。如果用户要求得到这个字符串的第一个字母那么返回 208 很可能不是他们想要的结果然而这是 Rust 在字节索引 0 处拥有的唯一数据。用户通常不希望返回字节值即使字符串只包含拉丁字母如果 hello[0] 是返回字节值的有效代码它将返回 104 而不是 h 。 那么答案就是为了避免返回一个意外的值并导致可能无法立即发现的错误Rust 完全不编译这段代码并在开发过程的早期防止误解。 2.5 字节、标量值和字形集群!噢我的天! 关于 UTF-8 的另一点是从 Rust 的角度来看字符串实际上有三种相关的方式字节、标量值和字形集群最接近我们所说的字母。 如果我们看一下用梵文书写的印地语单词 नमस्ते它是以 u8 值的矢量形式存储的看起来像这样 [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]这就是 18 个字节也是计算机最终存储这些数据的方式。如果我们把它们看作 Unicode 标量值Rust 的 char 类型这些字节看起来就像这样 [न, म, स, ्, त, े]Rust 提供了不同的方式来解释计算机存储的原始字符串数据这样无论数据使用的是哪种人类语言每个程序都可以选择自己需要的解释方式。 Rust 不允许我们对 String 进行索引来获取字符的最后一个原因是索引操作预计总是需要恒定的时间O(1)。但 String 无法保证这样的性能因为 Rust 必须从头到尾走一遍索引内容才能确定有多少个有效字符。 2.6 分割字符串 向字符串建立索引通常不是个好主意因为不清楚字符串索引操作的返回类型应该是什么字节值、字符、字符串簇还是字符串片。因此如果你真的需要使用索引来创建字符串片段Rust 会要求你提供更具体的信息。 与使用 [] 和单个数字编制索引不同您可以使用 [] 和范围来创建包含特定字节的字符串片段 let hello Здравствуйте;let s hello[0..4];在这里 s 将是包含字符串前 4 个字节的 str 。前面我们提到每个字符都是 2 个字节这意味着 s 将成为 Зд 。 如果我们尝试使用 hello[0..1] 这样的方法只切分字符的部分字节Rust 就会在运行时发生恐慌就像在向量中访问无效索引一样 cargo buildCompiling string v0.1.0 (/home/rustProj/string) error: expected one of !, ., ::, ;, ?, {, }, or an operator, found hello-- src/main.rs:2:8| 2 | et hello Здравствуйте;| ^^^^^ expected one of 8 possible tokenserror: could not compile string (bin string) due to previous error 你应该谨慎使用范围来创建字符串切片因为这样做可能会导致程序崩溃。 2.7 对字符串进行迭代的方法 对字符串片段进行操作的最佳方法是明确说明您需要的是字符还是字节。对于单个 Unicode 标量值请使用 chars 方法。在 Зд 上调用 chars 会分离并返回两个 char 类型的值您可以遍历结果以访问每个元素 for c in Зд.chars() {println!({c}); }这段代码将打印如下内容 З д另外 bytes 方法会返回每个原始字节这可能适合您的领域 for b in Зд.bytes() {println!({b}); }这段代码将打印组成这个字符串的四个字节 208 151 208 180但请务必记住有效的 Unicode 标量值可能由一个以上的字节组成。 从字符串中获取字素簇如梵文脚本非常复杂因此标准库不提供此功能。如果您需要这种功能可在 crates.io 上下载 Crates。 2.8 String并不简单 总而言之字符串是复杂的。对于如何向程序员展示这种复杂性不同的编程语言有不同的选择。Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为这意味着程序员必须在处理 UTF-8 数据时花费更多心思。与其他编程语言相比这种权衡暴露了字符串的更多复杂性但却避免了在开发生命周期的后期处理涉及非 ASCII 字符的错误。 好消息是标准库提供了大量基于 String 和 str 类型的功能可帮助正确处理这些复杂情况。请务必查看文档了解有用的方法如 contains 用于在字符串中搜索 replace 用于用另一个字符串替换字符串的部分内容。 3. 在hasp map中存储带有相关值的键 最后一个常用的集合是散列映射。 HashMapK, V 类型使用散列函数将 K 类型的键映射到 V 类型的值该函数决定了如何将这些键和值放入内存。许多编程语言都支持这种数据结构但它们通常使用不同的名称例如散列、映射、对象、散列表、字典或关联数组等。 散列映射在查找数据时非常有用它不像矢量那样使用索引而是使用可以是任何类型的键。例如在一场比赛中你可以在哈希图中记录每支球队的得分其中每个键都是一支球队的名称而值则是每支球队的得分。给定一个队名就可以检索到它的得分。 我们将在本节中介绍散列映射的基本 API但标准库在 HashMapK, V 上定义的函数中还隐藏着更多精彩内容。如需了解更多信息请查看标准库文档。 3.1 创建新的哈希图 创建空散列映射的一种方法是使用 new 然后使用 insert 添加元素。在清单 8-20 中我们要记录蓝队和黄队这两支队伍的得分。蓝队从 10 分开始黄队从 50 分开始。 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10); scores.insert(String::from(Yellow), 50);(清单 8-20创建新的哈希映射并插入一些键和值) 请注意我们首先需要从标准库的集合部分 use HashMap 。在我们常用的三个集合中这个集合是最不常用的所以它并不包含在前奏中自动纳入范围的特性中。标准库对哈希映射的支持也较少例如没有内置宏来构造哈希映射。 与向量一样散列映射也将数据存储在堆上。 HashMap 的键类型为 String 值类型为 i32 。与向量一样散列表也是同质的所有键的类型必须相同所有值的类型也必须相同。 3.2 访问哈希映射表中的值 如清单 8-21 所示我们可以通过向 get 方法提供键值从散列映射中获取一个值。 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10); scores.insert(String::from(Yellow), 50);let team_name String::from(Blue); let score scores.get(team_name).copied().unwrap_or(0);(清单 8-21访问散列映射中存储的蓝队得分) 在这里 score 将具有与蓝队相关联的值结果将是 10 。 get 方法返回 OptionV 如果哈希图中没有该键的值 get 将返回 None 。本程序在处理 Option 时先调用 copied 获得 Optioni32 而不是 Optioni32 然后调用 unwrap_or 如果 scores 没有该键的条目则将 score 设置为零。 我们可以使用 for 循环以类似于处理向量的方式遍历哈希映射中的每个键/值对 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10); scores.insert(String::from(Yellow), 50);for (key, value) in scores {println!({key}: {value}); }这段代码将以任意顺序打印每个键值对 Yellow: 50 Blue: 103.3 Hash map和所有权 对于实现 Copy 特质的类型如 i32 值将被复制到哈希映射中。对于自有值如 String 值将被移动哈希映射将成为这些值的所有者如清单 8-22 所示。 use std::collections::HashMap;let field_name String::from(Favorite color); let field_value String::from(Blue);let mut map HashMap::new(); map.insert(field_name, field_value); // field_name and field_value are invalid at this point, try using them and // see what compiler error you get!(清单 8-22: 显示键和值插入后归哈希映射所有) 在调用 insert 将变量 field_name 和 field_value 移入散列映射后我们就无法使用它们了。 如果我们在哈希映射中插入对值的引用这些值就不会被移入哈希映射。引用指向的值必须至少在哈希映射有效期内有效。我们将在第 10 章 使用生命周期验证引用 一节中详细讨论这些问题。 3.4 更新哈希映射表 虽然键和值对的数量可以增长但每个唯一键一次只能有一个值与之关联反之亦然例如蓝队和黄队都可以在 scores 哈希图中存储值 10。 要更改哈希映射中的数据时必须决定如何处理键已赋值的情况。可以用新值替换旧值完全忽略旧值。可以保留旧值忽略新值只有在键还没有值的情况下才添加新值。或者也可以将旧值和新值合并。让我们分别看看如何操作 3.4.1 重写数值 如果我们在哈希映射中插入一个键和一个值然后再插入一个带有不同值的相同键那么与该键相关的值就会被替换。尽管清单 8-23 中的代码调用了两次 insert 但散列映射只包含一个键/值对因为我们两次插入的都是蓝队的键值。 use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10); scores.insert(String::from(Blue), 25);println!({:?}, scores);(清单 8-23替换以特定键存储的值) 这段代码将打印 {Blue: 25} 。 10 的原始值已被覆盖。 3.4.2 仅在键不存在时添加键和值 通常的做法是检查散列映射中是否已经存在某个键和某个值然后采取以下措施如果散列映射中确实存在该键则保持现有值不变。如果键不存在则插入该键及其值。 哈希映射有一个特殊的应用程序接口名为 entry 它把要检查的键作为参数。 entry 方法的返回值是一个名为 Entry 的枚举代表一个可能存在也可能不存在的值。比方说我们要检查 黄队 的关键字是否有相关的值。如果没有我们就插入值 50蓝队也一样。使用 entry API代码看起来像清单 8-24。         use std::collections::HashMap;let mut scores HashMap::new(); scores.insert(String::from(Blue), 10);scores.entry(String::from(Yellow)).or_insert(50); scores.entry(String::from(Blue)).or_insert(50);println!({:?}, scores);(清单 8-24使用 entry 方法仅在键还没有值时插入) Entry 上的 or_insert 方法是这样定义的如果相应的 Entry 关键字存在则返回该关键字值的可变引用如果不存在则插入参数作为该关键字的新值并返回新值的可变引用。这种技术比我们自己编写逻辑要简洁得多而且与借用检查器的配合也更加默契。 运行清单 8-24 中的代码将打印 {Yellow: 50, Blue: 10} 。第一次调用 entry 将插入值为 50 的黄队键因为黄队还没有值。第二次调用 entry 不会更改哈希图因为蓝队已经有了值 10。 3.4.3 根据旧值更新数值 散列映射的另一个常见用例是查找键值然后根据旧值更新键值。例如清单 8-25 显示了计算每个单词在某些文本中出现次数的代码。我们使用一个以单词为键的哈希映射并递增键值来记录我们看到该单词的次数。如果我们第一次看到一个单词我们会首先插入值 0。 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);(清单 8-25使用存储单词和计数的哈希映射计算单词的出现次数) 这段代码将打印 {world: 2, hello: 1, wonderful: 1} 。您可能会看到以不同顺序打印的相同键/值对请回顾 访问哈希映射中的值 部分对哈希映射的迭代是以任意顺序进行的。 split_whitespace 方法返回 text 中值的子片段用空白分隔的迭代器。 or_insert 方法返回指定键值的可变引用 mut V 。在这里我们将该可变引用存储在 count 变量中因此要对该值赋值我们必须首先使用星号 * 取消引用 count 。在 for 循环结束时该可变引用将退出作用域因此所有这些更改都是安全的也是借用规则所允许的。 3.5  散列函数 默认情况下 HashMap 使用名为 SipHash 的散列函数可以抵御涉及散列表的拒绝服务DoS攻击 。这不是目前最快的散列算法但性能下降带来的更好安全性是值得的。如果你在分析代码时发现默认散列函数的速度太慢可以指定不同的散列函数来切换到其他函数。散列是一种实现 BuildHasher 特质的类型。我们将在第 10 章讨论特质以及如何实现特质。你不一定要从头开始实现自己的散列器crates.io 提供了由其他 Rust 用户共享的库这些库提供了实现许多常见散列算法的散列器。 下一篇 09-错误处理
http://www.hkea.cn/news/14520541/

相关文章:

  • 手机号注册的网站好用的网站
  • 可以免费做商业网站的cms公司只有一个设计师
  • 手机网站建设品牌下载android版本下载安装
  • 新增网站网络推广培训学费几万
  • 网站建设后的注意问题做网站申请域名的流程
  • 互联网开网站怎么做广东网页空间租用平台
  • 南昌做网站费用设计师培训大概多少钱
  • 企业品牌文化建设学习网站免费奖励自己的网站
  • 怎么网站搜索排名优化欧米茄官方手表价格
  • 广西两学一做考试网站四川网站建设那家好
  • 网站建设行业资讯免费软件追剧
  • 做酒店网站所用到的算法手机网站建设 新闻
  • 保定网站建设培训班消费金融网站建设
  • 网站内容被攻击该怎么做网站排名优化首页
  • 租房子网站怎么做绣花图案设计网站
  • 特产网站开发的目的兰州企业 网站建设
  • 光谷做网站推广怎么样wordpress添加一级菜单
  • 会ps的如何做网站seo门户
  • 公司网站自己创建好男人视频在线观看免费直播
  • 简述网站的推广策略优化系统是什么意思
  • 寻找定制型网站建设上海做高端网站
  • oss静态网站托管电子商务网站建设与管理试题及答案
  • 手机网站建设价钱wordpress主题基础
  • 奢侈品手表网站团购网站 如何做推广
  • 网站备案收费吗施工企业甲乙资质
  • 荆州 商务 网站建设建设哪里有
  • 网站被跳转公告怎么写提升学历的方式
  • 最好的书籍设计网站wordpress自动识别手机
  • 专业网站推广服务咨询网站建设的主要工作
  • 苏州高级网站建设写软文一篇多少钱合适