Rust 从入门到摔门而出门 - 字符串类型

206 阅读7分钟

Rust 从入门到摔门而出门 - 字符串类型

Rust 中的字符串和其他的有所不同。

请看下面代码:

fn main() {
  let str = "Rust";
  console_log(str);
}

fn console_log(str: String) {
  println!("Hello, {}!", str);
}

上面的代码会报错,错误如下

 |
8  |   console_log(str);
   |   ----------- ^^^- help: try using a conversion method: `.to_string()`
   |   |           |
   |   |           expected `String`, found `&str`
   |   arguments to this function are incorrect
   |
note: function defined here
  --> src/main.rs:11:4
   |
11 | fn console_log(str: String) {
   |    ^^^^^^^^^^^ -----------

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error

编译器提示console_log函数需要一个String类型的数据,但是传入确是&str类型的;

解决方法:

fn main() {
  let str = String::from("hello world");
  console_log(str);
}

fn console_log(str: String) {
  println!("Hello, {}!", str);
}

或者

fn main() {
  let str = "Rust";
  console_log(str);
}

fn console_log(str: &str) {
  println!("Hello, {}!", str);
}

字符串类型种类

在Rust中有六种字符串类型:

  1. &str: 这是一个字符串切片,它是固定大小的,并且不能改变。它通常用于函数参数,让我们可以接收任何类型的字符串。
  2. String: 这是一个可增长的、可改变的、拥有所有权的、UTF-8 编码的字符串类型。它通常用于需要改变或者增长字符串的情况。
  3. OsStrOsString: 这两种类型用于与操作系统进行交互。OsStr 是不可变的,而 OsString 是可变的。它们并不一定是 UTF-8 编码的。
  4. CStringCStr:这两种类型用于与 C 语言库交互。CString 是可变的,而 CStr 是不可变的。它们是 null 结尾的,这与其他 Rust 字符串类型不同。

在 开发中常用的类型 &strString,两者的主要区别在于:

  • 所有权:String 是一个拥有所有权的类型,当 String 类型的变量离开其作用域时,Rust 将自动释放其内存。而 &str 类型的变量则没有所有权,它只是一个对现有字符串数据的引用。

  • 可变性:String 是可变的,可以通过 push_str()push() 等方法来修改。而 &str 是不可变的。

  • 存储位置:String 类型的数据存储在堆上,而 &str 类型的数据存储在栈上。

  • 性能:由于 &str

是对现有字符串的引用,因此在处理大量字符串数据时,使用 &str 类型可能会比使用 String 类型更高效。

字符串切片

字符串切片(&str)是对 String 的引用,它是不可变的,也就是说你不能改变它的内容。字符串切片的主要用途是作为函数的参数,这样函数就可以接受任何形式的字符串。

你可以通过索引操作来获取一个字符串的切片。例如:

let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];

创建切片语法: [开始索引..终止索引]

  • let hello = &s[..5];:开始索引不填写默认从0开始
  • let world = &s[6..];:终止索引不填写默认到最后一个字节

注意: 字符串切片中如果是中文等特殊语言需要注意,一个中文在 UTF-8 中占用三个字节。

let s = String::from("你好");
let world = &s[0..1]; // Error

上面代码执行会报错,错误信息如下:

warning: unused variable: `world`
 --> src/main.rs:8:9
  |
8 |     let world = &s[0..1]; // Error
  |         ^^^^^ help: if this is intentional, prefix it with an underscore: `_world`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: `playground` (bin "playground") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
$ ./playground
thread 'main' panicked at src/main.rs:8:19:
byte index 1 is not a char boundary; it is inside '你' (bytes 0..3) of `你好`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

字符串操作

Rust 中 添加、删除、替换、插入、连接。

添加(PUSH)

在可变字符串尾部添加内容。

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

    str.push_str("rust");
    println!("追加字符串 push_str() -> {}", str);
    //输出: 追加字符串 push_str() -> Hello rust
    str.push('!');
    println!("追加字符 push() -> {}", str);
    //输出: 追加字符 push() -> Hello rust!
}

插入(insert)

插入方法 insertinsert_str,参数 (插入位置, 插入内容)

fn main() {
    let mut str = String::from("Hello rust!");
    str.insert(5, ','); // 插入位置, 内容
    println!("插入字符 insert() -> {}", str);
    // 输出:插入字符 insert() -> Hello, rust!
    str.insert_str(6, " I like");// 插入位置, 内容
    println!("插入字符串 insert_str() -> {}", str);
    // 输出:插入字符串 insert_str() -> Hello, I like rust!
}

替换

把字符串的某个内容替换。

  1. replace方法有两个参数,第一个填入要替换的字符串,第二个填入替换成的字符串, 返回新的字符串。
fn main() {
    let string_replace = String::from("Hello, rust. rust is the best language!");
    let new_string_replace = string_replace.replace("rust", "RUST");
    println!("输出: {}",new_string_replace);
    //输出: Hello, RUST. RUST is the best language!
}
  1. replacen方法接收三个参数,第一个填入要替换的字符串,第二个填入替换成的字符串,第三个参数表示替换的个数,返回新的字符串。
fn main() {
    let string_replace = String::from("Hello, rust. rust is the best language!");
    let new_string_replace = string_replace.replacen("rust", "RUST", 1);
    println!("输出: {}",new_string_replace);
    //输出: Hello, RUST. rust is the best language!
}
  1. replace_range方法接收两个参数,第一个参数是要替换字符串的范围(Range),第二个填入替换成的字符串,该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。
fn main() {
    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");
    println!("输出: {}",string_replace_range);
    //输出: I like Rust!
}

删除

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

  1. pop: 删除并返回字符串的最后一个字符, 变量必须是可变变量(mut)。
fn main() {
    let mut string_pop = String::from("Rust 是最棒的语言!");
    let p1 = string_pop.pop();
    dbg!(p1);
    // p1 = Some(
    //    '!',
    // )
    let p2 = string_pop.pop();
    dbg!(p2);
    // p2 = Some(
    //    '言',
    // )
    println!("输出: {}", string_pop)
    // 输出: Rust 是最棒的语
}
  1. remove: 删除指定位置的字符索引开始位置 参数传入指定字符,非合法字符串 边界会报错
fn main() {
    let mut string_remove = String::from("你好吗Rust是最好的语言");
    
    // 删除第一个汉字 你
    string_remove.remove(0);
    
    // 下面代码会发生错误 因为一个汉字字符3
    // string_remove.remove(1);
    
    // 删除汉字 好,上面的你删除了字符串的切片下标改了
    string_remove.remove(0);
    
    // 如果想删除 R, 参数3 因为汉字3个字符
    string_remove.remove(3);
    
    println!("输出: {}", string_remove)
    // 输出: ust是最好的语言
}
  1. truncate: 删除字符串中从指定位置开始到结尾的全部字符 参数传入指定字符,非合法字符串 边界会报错
fn main() {
    let mut string_remove = String::from("你好吗Rust是最好的语言");
    
    // 删除第一个汉字后面的汉字 
    string_remove.truncate(3);
    
    println!("输出: {}", string_remove)
    // 输出: 你
}

4.clear:清空字符串内容。

fn main() {
    let mut string_remove = String::from("你好, Rust是最好的语言");
    
    // 清空字符串 
    string_remove.clear();
    
    println!("输出: {}", string_remove)
    // 输出: 
}

5.++=: 连接字符串。

fn main() {
    let string_test1 =  String::from("你好,");
    let string_test2 =  String::from("Rust是最好的语言");
    let mut string_test3 = string_test1 + &string_test2;

    string_test3 += "!";
    
    println!("输出: {}", string_test3)
    // 输出: 你好,Rust是最好的语言!
}

字符串

在 Rust 中 String类型,为了是一个可增长、可变的、拥有所有权的字符串类型。它在堆上分配内存,因此可以动态地改变大小。 当你需要一个可以修改的字符串时,通常会使用 String 类型。

在 Rust 中 &str 是一个固定大小的字符串切片,通常用于引用字符串的一部分。它是不可变的,也就是说你不能改变它的内容。这是因为 &str 是对现有数据的引用,而不是拥有自己的数据。如果它被允许改变引用的数据,那么任何其他引用相同数据的代码都可能会受到影响,这会导致错误和不一致。

这种设计好处:&str 非常适合用于函数参数,因为你可以安全地传递对数据的引用,而不必担心数据会被修改。同时,因为 &str 是固定大小的,所以它可以在栈上存储,这比在堆上分配内存更快

String 和 &str 的设计选择反映了 Rust 的关注点:安全性、并发性和内存效率。