佛山手机网站设计制作,营销方案论文,电子商务网站设计步骤,北京企业网站搭建Rust 数据结构#xff1a;String Rust 数据结构#xff1a;String什么是字符串#xff1f;创建新字符串更新字符串将 push_str 和 push 附加到 String 对象后使用 运算符和 format! 宏 索引到字符串字符串在内存中的表示字节、标量值和字形簇 分割字符串遍历字符串的方法 R… Rust 数据结构String Rust 数据结构String什么是字符串创建新字符串更新字符串将 push_str 和 push 附加到 String 对象后使用 运算符和 format! 宏 索引到字符串字符串在内存中的表示字节、标量值和字形簇 分割字符串遍历字符串的方法 Rust 数据结构String
在本文中我们将讨论每种集合类型都具有的 String 操作例如创建、更新和读取。我们还将讨论 String 与其他集合的不同之处即由于人和计算机解释 String 数据的方式不同对 String 进行索引变得复杂。
什么是字符串
Rust 在核心语言中只有一种字符串类型它是字符串切片 str通常以它的借用形式 str 出现。字符串切片是对存储在其他地方的一些 UTF-8 编码字符串数据的引用。例如字符串字面值存储在程序的二进制文件中因此是字符串切片。
String 类型是由 Rust 的标准库提供的而不是编码到核心语言中它是一个可增长的、可变的、拥有的、UTF-8 编码的字符串类型。 当在 Rust 使用“字符串”时它们可能指的是 String 或 String slice str 类型而不仅仅是其中一种类型。虽然本文主要是关于 String 的但这两种类型在 Rust 的标准库中都大量使用并且 String 和 String 切片都是 UTF-8 编码的。 创建新字符串
String 实际上是作为字节向量的包装器实现的带有一些额外的保证、限制和功能所以在使用上很多和 vector 类似。
let mut s String::new();这一行创建了一个新的空字符串 s然后我们可以将数据加载到其中。
通常我们会有一些初始数据我们想用这些数据开始字符串。为此我们使用 to_string 方法该方法可用于任何实现 Display trait 的类型就像字符串字面量一样。 let data initial contents;let s data.to_string();// The method also works on a literal directly:let s initial contents.to_string();还可以使用 String::from 函数从字符串字面值创建 String。
let s String::from(initial contents);更新字符串
String 的大小可以增长其内容可以改变。
将 push_str 和 push 附加到 String 对象后
push_str 方法接受一个字符串切片并且不获取参数的所有权。 let mut s String::from(foo);s.push_str(bar);push 方法接受单个字符作为参数并将其添加到 String 中。 let mut s String::from(lo);s.push(l);使用 运算符和 format! 宏 操作符可以组合两个现有字符串。 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 useds1 在相加之后不再有效的原因以及我们使用 s2 引用的原因与使用 操作符时调用的方法的签名有关。 操作符使用 add 方法其签名看起来像这样
fn add(self, s: str) - String {首先s2 有一个 这意味着我们将第二个字符串的引用添加到第一个字符串。
我们能够在 add 调用中使用s2String 类型的原因是编译器可以将 String 实参强制转换为 str。当我们调用 add 方法时Rust 使用了一个强制转换它将 s2 转换为 s2[…]。因为 add 没有获得 s 形参的所有权所以在这个操作之后 s2 仍然是一个有效的 String。
其次我们可以在签名中看到 add 取得了 self 的所有权因为 self 没有 。这意味着 s1 将被移动到 add 调用中并且在此之后将不再有效。
综上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;对于以更复杂的方式组合字符串我们可以使用 format! 宏 let s1 String::from(tic);let s2 String::from(tac);let s3 String::from(toe);let s format!({s1}-{s2}-{s3});format! 宏返回一个包含内容的 String。format! 宏使用引用因此此调用不会获得其任何参数的所有权。
索引到字符串
在许多其他编程语言中通过索引引用字符串中的单个字符是一种有效且常见的操作。但是如果尝试在 Rust 中使用索引语法访问 String 的某些部分则会得到一个错误。 let s1 String::from(hi);let h s1[0];报错error[E0277]: the type str cannot be indexed by {integer}
这个要从 Rust 如何在内存中 存储字符串开始讲起。
字符串在内存中的表示
String是Vecu8的包装器。
考虑以下两个字符串
let s1 String::from(Hola);
let s2 String::from(Здравствуйте);s1 的长度是 4 字节。当用 UTF-8 编码时这些字母中的每个都占 1 字节。然而s2 的长度不是 12 字节而是 24 字节。因为该字符串中的每个 Unicode 标量值需要 2 字节的存储空间。
来看一下错误代码
let hello Здравствуйте;
let answer hello[0];当用 UTF-8 编码时З 的第一个字节是 208第二个字节是 151所以看起来答案实际上应该是 208但是 208 本身并不是一个有效的字符。为了避免返回意外值并导致可能无法立即发现的错误Rust 根本不编译此代码。
字节、标量值和字形簇
关于 UTF-8 的另一点是从 Rust 的角度来看实际上有三种相关的方式来看待字符串字节、标量值和字形簇最接近我们称之为字母的东西。
如果我们看看写在 Devanagari 脚本中的印地语单词 “नमस्ते”它被存储为 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 必须从头到尾遍历内容以确定有多少个有效字符。
分割字符串
对字符串进行索引通常不是一个好主意与其用 [] 索引单个数字不如用 [] 索引一个范围来创建包含特定字节的字符串切片。
let hello Здравствуйте;let s hello[0..4];这里s 将是一个 str它包含字符串的前 4 个字节。前面我们提到每个字符都是两个字节这意味着 s 将是 “Зд”。
如果我们尝试用 hello[0…1] Rust 会在运行时报错就像在 vector 中访问无效索引一样。
遍历字符串的方法
对字符串片段进行操作的最佳方法是明确说明是需要字符还是字节。对于单个 Unicode 标量值使用 chars 方法。在 “Зд” 上调用 chars分离并返回两个 char 类型的值再遍历。
for c in Зд.chars() {println!({c});
}程序输出
З
д或者bytes 方法返回每个原始字节。
for b in Зд.bytes() {println!({b});
}程序输出
208
151
208
180一定要记住有效的 Unicode 标量值可能由多个字节组成。