0


【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

将会包含

foobar

push_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 字符的错误。


本文转载自: https://blog.csdn.net/shsjssnn/article/details/136237892
版权归原作者 Y小夜 所有, 如有侵权,请联系我们删除。

“【Rust】——String集合”的评论:

还没有评论