前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rust字符串

rust字符串

作者头像
zy010101
发布2023-01-13 11:00:51
8150
发布2023-01-13 11:00:51
举报
文章被收录于专栏:程序员

字符串类型

诸位在入门rust的时候,要认真,因为字符串类型在rust中有好几种,一不小心就搞混了类型,导致代码编译报错。好在有强大的rust-analyzer和vscode帮助我们。我们直接通过一段代码来开始认识rust的字符串类型。

代码语言:javascript
复制
fn main() {
    let s = "Hello, Rust string!";      // s是&str类型
    println!("{s}");

    let s = "Hello, Rust string!".to_string();  // s是String类型
    println!("{s}");
    
    let s1 = &s;                    // &String类型
    println!("{s1}");
    
    let s2 = &s[0..5];              // &str类型
    println!("{s2}");

    // let s1 = s[0..5];       error 这应该是什么类型?是str吗?那么如何使用str类型?
}

字符串切片引用类型(&str)

首先,我们还是从字符串字面值来谈起,在rust中,字符串字面值常量的类型是&str,这个类型称之为“字符串切片引用”。字符串字面值是特殊的,它实际上存储在可执行程序的只读内存段中(rodata)。通过&str可以引用rodata中的字符串。同样,对于在堆上存放的字符串String类型,也可以通过&str来引用其中的部分。就和python的切片类似。但是如果想要直接使用str类型,是不行的,只能通过Box<str>来使用。例如上面通过切片引用&s[0..5]来使用s的第1个字节到第5个字节的内容。索引下标是从0开始的,范围是区间[0, 5),注意这个区间是左闭右开的。例如:

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[2..5];
println!("{slice}");

这段代码中的slice是&str类型,切片引用了s的第3个字节到第5个字节的内容。即输出llo,在rust的切片中,下标也不能超过字符串长度的边界,否则会导致运行时错误。例如:

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[2..8];
println!("{slice}");

这段代码在执行的时候会直接panic,异常信息如下所示:

代码语言:javascript
复制
thread 'main' panicked at 'byte index 8 is out of bounds of `hello`', src/main.rs:23:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

它告诉我们索引8超出了"hello"的界限。我们再次尝试让起始索引越界。

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[-1..5];
println!("{slice}");

这次将无法通过编译,编译时报错如下。

rust要求索引必须是usize类型的,这意味着索引不能是负数。另外,如果起始索引是0,可以简写为&s[..3];同样如果终止索引是String的最后一个字节,那么可以简写为&s[1..];因此如果要引用整个String,那么可以简写为&s[..]。下面是一个简单的演示,它们每组都是等价的。

代码语言:javascript
复制
let slice = &s[0..3];
println!("{slice}");
let slice = &s[..3];
println!("{slice}");

let len = s.len();
let slice = &s[1..len];
println!("{slice}");
let slice = &s[1..];
println!("{slice}");

let slice = &s[0..len];
println!("{slice}");
let slice = &s[..];
println!("{slice}");

最后,字符串切片引用的索引必须落在字符之间的边界位置,但是由于rust的字符串是UTF-8编码的,因此必须要小心。后文会讲述如何取出UTF-8编码的一个字符。切片类型是对集合的部分引用,不仅有字符串切片引用,其它的集合类型也有。

字符串类型(String)

Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)

Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是上文提到的字符串切片引用。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。

前面说个,str类型被存放在了rodata区域,无法被修改。而String是一个可增长,可变且具有所有权的utf-8编码的字符串。

String和&str的相互转换

前文已经看到了如何将&str转为String,例如:

代码语言:javascript
复制
let s = String::from("hello");
let s = "hello".to_string();

将String转为&str前面也演示了,例如:

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[..2];
println!("{slice}");    // 直接打印,没有解引用。

其中实际上还有一个问题,可能有部分读者已经注意到了,那就是我们直接打印了slice这个切片引用,而没有解引用。实际上这是因为deref 隐式强制转换,这是由编译器帮我们完成的。而且你不能直接使用str类型。如果手动解引用,会引起编译错误。

不能使用字符串索引

由于rust的字符串类型是utf-8编码的,如果允许使用索引来取出字符串中的某个字符,那么这将牺牲一部分性能,而rust期望索引操作的时间复杂度是O(1)。因此,你不能像python那样使用索引去访问第n个字符。当然rust提供了其它方式来获取其中某个字符。例如chars方法。

操作字符串

操作字符串,主要是针对String类型来进行的。无非就是涉及到增删改查。

创建String字符串

通过官方文档可以得知,我们可以有下面三种方式从字符串字面值来创建一个新的String字符串。

代码语言:javascript
复制
let s = "Hello".to_string();
let s = String::from("world");
let s: String = "also this".into();

追加

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。例如:

代码语言:javascript
复制
fn main() {
    let mut s = String::from("Hello ");
    s.push('r');
    println!("追加字符 push() -> {}", s);

    s.push_str("ust!");
    println!("追加字符串 push_str() -> {}", s);
}

插入

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量。例如:

代码语言:javascript
复制
fn main() {
    let mut s = String::from("Hello rust!");
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);
    s.insert_str(6, " I like");
    println!("插入字符串 insert_str() -> {}", s);
}

这俩方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串),索引从 0 开始计数,如果越界则会发生错误。它们都是在原字符串上做修改,因此原字符串必须可变。

替换

replace

该方法可适用于 String 和 &str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。

代码语言:javascript
复制
fn main() {
    let string_replace = String::from("I like rust. Learning rust is my favorite!");
    let new_string_replace = string_replace.replace("rust", "RUST");
    dbg!(new_string_replace);
    let s = "12345";
    let new_s = s.replace("3", "t");
    dbg!(new_s); 
}
1

dbg!是rust提供的调试使用的宏,方便rust使用者进行打印输出。它会打印出其所在的文件,代码行,变量名。非常便于调试。

replacen

该方法可适用于 String 和 &str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。例如:

代码语言:javascript
复制
fn main() {
    let string_replace = "I like rust. Learning rust is my favorite!";
    let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
    dbg!(new_string_replacen);
}

replace_range

该方法仅适用于 String 类型。replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。例如:

代码语言:javascript
复制
fn main() {
    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");
    dbg!(string_replace_range);
}

这个方法在字符串中的字符都是由ASCII组成的时候,是非常有用的。但是如果存在非ASCII编码的字符时,就需要计算出正确的utf-8字符的起始位置和结束位置,否则会造成运行时错误。

删除

与字符串删除相关的方法有 4 个,他们分别是 pop(),remove(),truncate(),clear()。这四个方法仅适用于 String 类型。

pop

删除并返回字符串的最后一个字符(按字符处理,不是字节),该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。我们在这里都是用dbg!这个宏来打印的,他能够将Option<char>类型打印出来,后面我们再来介绍Option类型。

代码语言:javascript
复制
fn main() {
    let mut string_pop = String::from("rust pop 中文!");
    let p1 = string_pop.pop();
    let p2 = string_pop.pop();
    dbg!(p1);
    dbg!(p2);
    dbg!(string_pop);
}

remove

删除并返回字符串中指定位置的字符,该方法是直接操作原来的字符串,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

代码语言:javascript
复制
fn main() {
    let mut string_remove = String::from("测试remove方法");
    println!(
        "string_remove 占 {} 个字节",
        std::mem::size_of_val(string_remove.as_str())
    );
    // 删除第一个汉字
    string_remove.remove(0);
    // 下面代码会发生错误
    // string_remove.remove(1);
    // 直接删除第二个汉字
    // string_remove.remove(3);
    dbg!(string_remove);
}

truncate

删除字符串中从指定位置开始到结尾的全部字符,该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。例如:

代码语言:javascript
复制
fn main() {
    let mut string_truncate = String::from("测试truncate");
    string_truncate.truncate(3);
    dbg!(string_truncate);
}

clear

清空字符串,该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。例如:

代码语言:javascript
复制
fn main() {
    let mut string_clear = String::from("string clear");
    string_clear.clear();
    dbg!(string_clear);
}

连接

使用 + 或者 += 连接字符串

使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。(类似于C++的运算符重载)+ 和 += 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰。例如:

代码语言:javascript
复制
fn main() {
    let string_append = String::from("hello ");
    let string_rust = String::from("rust");
    // // &string_rust会自动解引用为&str,这是因为deref coercing特性。这个特性能够允许把传进来的&String,在API执行之前转成&str。
    let result = string_append + &string_rust;
    let mut result = result + "!";
    result += "!!!";

    println!("连接字符串 + -> {}", result);
}

这里出现了deref coercing这个特性,这是为了使用起来更方便,但是个人认为deref coercing是一个不一致性设计。另外一点是add函数的函数原型是fn add(self, s: &str) -> String,self 是 String 类型的字符串,因此string_append在经过连接运算(+)之后,转移了所有权,导致string_append被释放。

使用 format! 连接字符串

format! 这种方式适用于 String 和 &str,和C/C++提供的sprintf函数类似。例如:

代码语言:javascript
复制
fn main() {
    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);
}

format宏使用起来更加方便,而且你可以自由的连接多个字符串,并且由于宏使用的是引用,因此不会发送所有权的转移。

标准库中String和&str有非常多的方法,可以在rust官方文档中进行查看。rust的官方文档编写的算是非常Nice的,几乎每个函数都有例子。

在这里插入图片描述
在这里插入图片描述

在这里,你能几乎能找到关于rust的一切。最常用的可能就是标准库,cargo书册和编译错误索引表。

关于字符串的其他部分

  1. 我们可以通过转义的方式 \ 输出 ASCII 和 Unicode 字符
代码语言:javascript
复制
fn main() {
    // 通过 \ + 字符的十六进制表示,转义输出一个字符
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // \u 可以输出一个 unicode 字符
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!(
        "Unicode character {} (U+211D) is called {}",
        unicode_codepoint, character_name
    );
}

将一个物理行字符串拆分到多行

使用\连接两行(多行)为一个物理行。

代码语言:javascript
复制
fn main() {
    let long_string = "String literals
                    can span multiple lines.
                    The linebreak and indentation here \    
                    can be escaped too!";
    println!("{}", long_string);
}

这段代码输出如下所示:

String literals和can span multiple lines.是分开的两行,而The linebreak and indentation here和can be escaped too!由\连接为一个物理行。

原始字符串

现在的大多数语言中都提供了原始字符串的语法,rust也不例外,在字符串前面加上小写字母r,那么字符串在被使用的时候将不会发生转义。

代码语言:javascript
复制
fn main() {
    println!("{}", "hello \x52\x75\x73\x74");           // 输出hello Rust
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";    // 原始字符串
    println!("{}", raw_str);        // 输出Escapes don't work here: \x3F \u{211D}
}

字符中出现双引号

在C/C++中,字符串中出现双引号的时候,需要进行转义;rust提供了r#这种方式来解决这个问题,当然你也可以使用转义的方式。

代码语言:javascript
复制
fn main() {
    // 如果字符串包含双引号,可以在开头和结尾加 #
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 如果还是有歧义,可以继续增加#,没有限制
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

字符串和字符数组

由于rust的字符串是uft-8编码的,而String类型不允许以字符为单位进行索引。有时候会不方便。String提供了chars()方法遍历字符以及bytes()方法遍历字节。另外就是,如果需要从String中获取子串是比较困难的,标准库没有提供相关的方法。 你需要在 crates.io 上搜索 utf8 来寻找想要的功能。可以使用utf8_slice这个库。当然了另一种解决方案就是使用字符数组,这样就可以非常方便的进行操作了。

参考资料

rust圣经

rust标准库String

rust标准库str

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字符串类型
    • 字符串切片引用类型(&str)
      • 字符串类型(String)
        • String和&str的相互转换
          • 不能使用字符串索引
            • 操作字符串
              • 创建String字符串
              • 追加
              • 插入
              • 替换
              • 删除
            • 连接
              • 关于字符串的其他部分
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档