【Rust】——String集合

🎃个人专栏:

🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客

🐳Java基础:Java基础_IT闫的博客-CSDN博客

🐋c语言:c语言_IT闫的博客-CSDN博客

🐟MySQL:数据结构_IT闫的博客-CSDN博客

🐠数据结构:​​​​​​数据结构_IT闫的博客-CSDN博客

💎C++:C++_IT闫的博客-CSDN博客

🥽C51单片机:C51单片机(STC89C516)_IT闫的博客-CSDN博客

💻基于HTML5的网页设计及应用:基于HTML5的网页设计及应用_IT闫的博客-CSDN博客​​​​​​

🥏python:python_IT闫的博客-CSDN博客

🐠离散数学:离散数学_IT闫的博客-CSDN博客

​​​​​​🥽Linux:​​​​Linux_Y小夜的博客-CSDN博客

🚝Rust:Rust_Y小夜的博客-CSDN博客

欢迎收看,希望对大家有用!

目录


🎯Rust开发者经常被字符串困扰的原因

  • 倾向于确保暴露出可能的错误。
  • 字符串是比很多程序员所想象的要更为复杂的数据结构。
  • UTF-8。

🎯字符串是什么

        Rust 的核心语言中只有一种字符串类型:字符串 slice str,它通常以被借用的形式出现,&str

        字符串(String)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 “字符串 “时,他们可能指的是 String 或 string slice &str 类型,而不仅仅是其中一种类型。

🎯创建字符串

        很多 Vec 可用的操作在 String 中同样可用,事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 Vec<T> 和 String 函数的例子是用来新建一个实例的 new 函数

    let mut s = String::new();

        这新建了一个叫做 s 的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 to_string 方法,它能用于任何实现了 Display trait 的类型,比如字符串字面值。

    let data = "initial contents";

    let s = data.to_string();

    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();

        因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,String::from 和 .to_string 最终做了完全相同的工作,所以如何选择就是代码风格与可读性的问题了。

    let s = String::from("initial contents");

记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据

    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");

🎯更新String

  String 的大小可以增加,其内容也可以改变,就像可以放入更多数据来改变 Vec 的内容一样。另外,可以方便的使用 + 运算符或 format! 宏来拼接 String 值。

✨使用push_str和push附加字符串

可以通过 push_str 方法来附加字符串 slice,从而使 String 变长:

    let mut s = String::from("foo");
    s.push_str("bar");

执行这两行代码之后,s 将会包含 foobarpush_str 方法采用字符串 slice,因为我们并不需要获取参数的所有权。

    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {s2}");

push 方法被定义为获取一个单独的字符作为参数,并附加到 String 中。

    let mut s = String::from("lo");
    s.push('l');

✨如何拼接字符串

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

        执行完这些代码之后,字符串 s3 将会包含 Hello, world!s1 在相加后不再有效的原因,和使用 s2 的引用的原因,与使用 + 运算符时调用的函数签名有关。+ 运算符使用了 add 函数,这个函数签名看起来像这样:

fn add(self, s: &str) -> String {

解释:s2 使用了 &,意味着我们使用第二个字符串的 引用 与第一个字符串相加。这是因为 add 函数的 s 参数:只能将 &str 和 String 相加,不能将两个 String 值相加。不过等一下 —— &s2 的类型是 &String, 而不是 add 第二个参数所指定的 &str

        如果想要级联多个字符串,+ 的行为就显得笨重了:

    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}");

🎯索引字符串

        在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问 String 的一部分,会出现一个错误。

    let s1 = String::from("hello");
    let h = s1[0];

        错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。

✨内部表示

String 是一个 Vec<u8> 的封装。

    let hello = String::from("Hola");

        在这里,len 的值是 4,这意味着储存字符串 “Hola” 的 Vec 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。

    let hello = String::from("Здравствуйте");

        我们已经知道 answer 不是第一个字符 3。当使用 UTF-8 编码时,(西里尔字母的 Ze)З 的第一个字节是 208,第二个是 151,所以 answer 实际上应该是 208,不过 208 自身并不是一个有效的字母。返回 208 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果 &"hello"[0] 是返回字节值的有效代码,它也会返回 104 而不是 h

✨字节、标量值、字形簇

比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 u8 值看起来像这样:

[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rust 的 char 类型那样,这些字节看起来像这样:

['न', 'म', 'स', '्', 'त', 'े']

这里有六个 char,不过第四个和第六个都不是字母,它们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:

["न", "म", "स्", "ते"]

        Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。

        最后一个 Rust 不允许使用索引获取 String 字符的原因是,索引操作预期总是需要常数时间(O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符

🎯字符串切割slice

        索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice:

let hello = "Здравствуйте";

let s = &hello[0..4];

        这里,s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 s 将会是 “Зд”。

        如果获取 &hello[0..1] 会发生什么呢?答案是:Rust 在运行时会 panic,就跟访问 vector 中的无效索引时一样。你应该小心谨慎地使用这个操作,因为这么做可能会使你的程序崩溃。

🎯遍历string

        操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 chars 方法。对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值,接着就可以遍历其结果来访问每一个元素了:

for c in "Зд".chars() {
    println!("{c}");
}

另外 bytes 方法返回每一个原始字节,这可能会适合你的使用场景:

for b in "Зд".bytes() {
    println!("{b}");
}

不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。

🎯字符串不简单

        总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 String 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。

版权声明:本文为博主作者:Y小夜原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/shsjssnn/article/details/136237892

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2024年4月16日
下一篇 2024年4月16日

相关推荐