【Rust 基础入门】(07) | 字符串

  • 0xE
  • 更新于 5小时前
  • 阅读 74

本文介绍了 Rust 中字符串的基本操作,包括字符串切片、动态字符串操作(如追加、插入、替换和删除)以及字节、字符和字符串的区别,帮助读者深入理解 Rust 中字符串的内存管理和常用操作方法。

1. 动态字符串切片

在 Rust 的世界里,字符串切片(String Slice)就像是一种“书签”,它允许你只关注字符串中你感兴趣的部分,而不需要拷贝整个字符串。

字符串切片是引用类型,类型为 &str,它通过索引或范围来指定字符串的一部分,提供了对字符串的引用,而不引入额外的内存开销。切片并不拥有字符串的内容,因此不会消耗额外的内存。

代码示例:

fn main() {
    let s = String::from("Rust is awesome!");

    // 获取 "Rust"
    let rust: &str = &s[0..4];

    // 获取 "awesome"
    let awesome: &str = &s[8..15];

    println!("{}", rust);      // 输出: Rust
    println!("{}", awesome);   // 输出: awesome
}

通过 [开始索引..终止索引] 的方式,你可以轻松获取字符串的一部分。需要注意的是,索引范围是前闭后开的,即包含开始位置,但不包含结束位置。

2. 创建切片的几种方式

Rust 提供了多种方式来创建字符串切片,以下是几种常见的用法:

代码示例:

fn main() {
    let s: String = String::from("Rust is powerful!");

    // 从索引 0 开始,获取前 4 个字节
    let slice1: &str = &s[0..4];

    // 默认从索引 0 开始,获取前 4 个字节
    let slice2: &str = &s[..4];

    let len: usize = s.len();

    // 从索引 5 开始,获取到字符串末尾
    let slice3: &str = &s[5..len];

    // 默认从索引 5 开始,获取到字符串末尾
    let slice4: &str = &s[5..];

    // 获取整个字符串的切片
    let slice5: &str = &s[0..len];

    // 同上,获取整个字符串的切片
    let slice6: &str = &s[..];

    // 输出结果
    println!("slice1: {}", slice1);  // 输出: Rust
    println!("slice2: {}", slice2);  // 输出: Rust
    println!("slice3: {}", slice3);  // 输出: is powerful!
    println!("slice4: {}", slice4);  // 输出: is powerful!
    println!("slice5: {}", slice5);  // 输出: Rust is powerful!
    println!("slice6: {}", slice6);  // 输出: Rust is powerful!
}

说明:

  • slice1slice2 返回从字符串开头的前四个字符。
  • slice3slice4 返回从索引 5 开始的子字符串,直到字符串末尾。
  • slice5slice6 返回整个字符串的切片,slice5 使用了明确的 0..len 范围,而 slice6 使用了简化语法 [..]

3. 处理多字节字符

在 Rust 中,字符串采用 UTF-8 编码,这意味着某些字符(如汉字、表情符号等)可能由多个字节组成。如果你在切片时没有正确处理字符的字节边界,可能会导致无效的切片。Rust 会确保切片操作不跨越 UTF-8 字符的边界,否则会在运行时 panic。

代码示例:

fn main() {
    let chinese_string = "编程语言";

    // 错误示例:试图获取 "编" 的前两个字节,但 "编" 占用 3 个字节
    // let wrong_slice = &chinese_string[0..2]; // 编译通过,但运行时会导致 panic

    // 正确示例:获取 "编" 的完整字节范围
    let correct_slice = &chinese_string[0..3];
    println!("正确的切片: {}", correct_slice); // 输出 "编"
}

解释:

  • 上述的错误示例会导致运行时 panic,因为 "编" 这个字符占用了 3 个字节,而我们尝试在不完整的字节边界上进行切片。
  • 正确的切片方式是确保切片时字符不会被拆分。

4. 字符串字面量

字符串字面量是在代码中直接写死的字符串,比如 "Rust is awesome!"。它们在程序编译时就已经确定并固定下来,类型为 &str。与动态字符串不同,字符串字面量是不可变的,并且它们的生命周期与整个程序的生命周期一致。

代码示例:

fn main() {
    // 字符串字面量
    let slogan: &str = "Rust is the future!";
    println!("{}", slogan); // 输出 "Rust is the future!"
}

说明:

字符串字面量在编译时已知大小,因此它的生命周期与整个程序的运行期一致。这使得它们在内存中更加高效,而无需像动态字符串那样进行内存分配。

5. 字符串字面量与动态字符串的转换

Rust 提供了简单的方式在字符串字面量和动态字符串(String)之间进行转换,让字符串的使用更加灵活。

代码示例:

fn main() {
    // 字符串字面量转动态字符串
    let s1: String = "Rust is cool".to_string();
    let s2: String = String::from("Rust is powerful");

    // 动态字符串转字符串字面量
    let s3: &str = s1.as_str();

    println!("s1: {}", s1);  // 输出: Rust is cool
    println!("s2: {}", s2);  // 输出: Rust is powerful
    println!("s3: {}", s3);  // 输出: Rust is cool
}

说明:

  • to_string() 将字符串字面量转换为动态字符串 String
  • String::from() 是另一种将字面量转换为动态字符串的方式。
  • as_str() 方法可以将动态字符串 String 转回切片 &str

6. 动态字符串操作

代码示例:

以下是一些常见的字符串操作:追加(push)、插入(insert)、替换(replace)、删除(pop)等。

fn main() {
    let mut s = String::from("Hello");

    // 追加字符串,修改原来的字符串
    s.push_str(", world");
    println!("追加字符串 push_str() -> {}", s);

    // 追加单个字符
    s.push('!');
    println!("追加字符 push() -> {}", s);

    // 在指定位置插入字符,修改原来的字符串
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);

    // 在指定位置插入字符串
    s.insert_str(6, " how are you?");
    println!("插入字符串 insert_str() -> {}", s);

    // 替换字符串中的某个子串,返回新字符串
    let str_old = String::from("I like rust, rust is great!");
    let str_new = str_old.replace("rust", "Rust");
    println!("原字符串:{}, 新字符串:{}", str_old, str_new);

    // pop 删除操作,修改原来的字符串
    let mut string_pop = String::from("删除操作,rust 中文!");
    // 删除末尾字符
    let p1 = string_pop.pop();
    println!("删除字符 pop() -> {:?}", p1); // 输出:Some('!')
    println!("剩余字符串: {}", string_pop);

    // 删除末尾字符,再次 pop
    let p2 = string_pop.pop();
    println!("删除字符 pop() -> {:?}", p2); // 输出:Some('中')
    println!("剩余字符串: {}", string_pop);
}

说明:

  • push_str():将一个字符串追加到当前字符串的末尾。
  • push():追加一个字符。
  • insert():在指定索引位置插入字符。
  • insert_str():在指定索引位置插入另一个字符串。
  • replace():替换字符串中的指定子串,返回一个新的字符串。
  • pop():删除字符串的最后一个字符并返回它。如果字符串为空,返回 None

字节、字符和字符串的区别

  • 字节(byte):字节是存储单位,通常是 8 位。一个字节可以表示 256 个不同的值。字节是对数据进行低层次处理时的基本单位。

  • 字符(char):字符是文本中的一个单个符号。Rust 使用 char 类型来表示字符,每个 char 类型的值占 4 个字节,表示一个 Unicode 字符(支持多种语言和符号)。但在实际的编码中,Rust 对字符串采用 UTF-8 编码,也就是每个字符所占的字节数是变化的(1 - 4)。这样有助于大幅降低字符串所占用的内存空间。

  • 字符串(String):字符串是由字符组成的序列。在 Rust 中,String 类型是动态分配的,可以增长和修改。而 &str 是字符串的切片,通常是不可变的引用。

示例:字节与字符的对比

let s = String::from("Hello, 世界!");

// 获取字符串的字节长度
println!("字节数:{}", s.len());  // 输出字节数,取决于 UTF-8 编码

// 获取字符数
let char_count = s.chars().count();
println!("字符数:{}", char_count);  // 输出字符数

在这段代码中,s.len() 返回字符串的字节长度,而 s.chars().count() 则返回字符串的字符数。由于某些字符(例如汉字)使用多个字节进行编码,因此字节长度和字符数是不相等的。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。工作机会可加v:__0xE__