Rust - 字符串
Rust - 字符串
Section titled “Rust - 字符串”文本是一种复杂的数据类型,Rust 采用两部分方法来处理它,提供安全性、速度和对内存的控制。两种主要的字符串类型是:
&str(字符串切片):一个不可变、固定大小的字符串数据的视图。它是对始终是有效 UTF-8 字节序列的引用。String:一个可变、可增长、拥有所有权的字符串。它是一个堆分配的缓冲区,可以被修改。
理解这两者之间的区别是编写地道 Rust 代码的基础。
字符串字面量(&str)
Section titled “字符串字面量(&str)”字符串字面量,例如 "Hello, world!",类型是 &str。字符串字面量的文本直接硬编码到你的程序的二进制文件中。它们是不可变的,并具有 'static 生命周期,这意味着它们保证在整个程序运行期间都有效。
fn main() { // 类型 `&str` 是指向该字符串数据的切片。 let greeting: &str = "Hello, Rust developers!"; println!("{}", greeting);
// 我们也可以让编译器推断类型。 let location = "Planet Earth"; println!("We are live from {}.", location);}String:拥有所有权的字符串类型
Section titled “String:拥有所有权的字符串类型”String 类型是 Rust 标准库的一部分。它专为需要拥有字符串数据、修改字符串或在编译时不知道字符串内容(例如,用户输入)的情况而设计。
你可以通过几种常见方式创建 String:
fn main() { // 1. 创建一个新的空 String let mut new_string = String::new(); new_string.push_str("This is a new"); new_string.push(' '); // 推入一个字符 println!("new_string: {}", new_string);
// 2. 使用 String::from() 从字符串字面量创建 let from_literal = String::from("string from a literal."); println!("from_literal: {}", from_literal);
// 3. 使用 .to_string() 将 &str 转换为 String let str_slice = "Another way to do it."; let to_string_version = str_slice.to_string(); println!("to_string_version: {}", to_string_version);}常用的 String 方法
Section titled “常用的 String 方法”| 方法 | 签名 | 描述 |
|---|---|---|
new() | pub const fn new() -> String | 创建一个新的空 String。 |
push_str() | pub fn push_str(&mut self, string: &str) | 将一个字符串切片(&str)追加到此 String 的末尾。 |
push() | pub fn push(&mut self, ch: char) | 将一个 char 追加到此 String 的末尾。 |
replace() | pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String | 用另一个字符串替换所有匹配的模式,并返回一个新的 String。 |
len() | pub fn len(&self) -> usize | 返回 String 的字节长度(而非字符长度)。 |
trim() | pub fn trim(&self) -> &str | 返回一个移除了前导和尾随空白字符的字符串切片。 |
split_whitespace() | pub fn split_whitespace(&self) -> SplitWhitespace | 返回一个迭代器,它根据空白字符分割子字符串。 |
chars() | pub fn chars(&self) -> Chars | 返回一个迭代器,它遍历字符串的 char。 |
你可以使用 + 运算符或 format! 宏来组合字符串。format! 宏通常更受欢迎,因为它更具可读性并且不获取其参数的所有权。
fn main() { let s1 = String::from("Hello, "); let s2 = String::from("world!");
// 使用 `+` 会获取 s1 的所有权,因此在此之后 s1 不能再被使用。 // `+` 运算符使用 `add` 方法,其签名是:fn add(self, s: &str) -> String // 这就是为什么 s2 必须是一个引用(`&s2`)。 let s3 = s1 + &s2; // println!("{}", s1); // 这将导致编译错误:值在此处移动后被借用
println!("Concatenated with +: {}", s3);
// 使用 `format!` 宏通常更简洁,并且不会获取所有权。 let s4 = String::from("tic"); let s5 = String::from("tac"); let s6 = String::from("toe"); let formatted = format!("{}-{}-{}", s4, s5, s6);
println!("Formatted: {}", formatted); println!("s4 is still available: {}", s4); // s4 仍然有效!}关于索引的注意事项
Section titled “关于索引的注意事项”与其他一些语言不同,Rust 不允许你通过整数索引(例如 my_string[0])访问 String 中的字符。这是因为 String 是 UTF-8 编码的,并且字符的字节长度可能是可变的。例如,'a' 是 1 字节,但 'é' 是 2 字节,而 '🦀' 是 4 字节。索引可能指向一个不是有效字符开头的位置,这将是不安全的。
相反,你应该迭代或创建切片。
fn main() { let hello = "Здравствуйте"; // 一个俄语字符串
// 不要这样做:如果索引不在字符边界上,它将崩溃。 // let slice = &hello[0..1]; // 这将是无效的!
// 切片必须在字符边界上。 // 第一个字符 'З' 长 2 字节。 let slice = &hello[0..2]; println!("Slice of first char: {}", slice);
// 处理字符的最佳方式是迭代。 println!("遍历字符:"); for c in hello.chars() { print!("{} ", c); } println!();}在编写函数时,将 &str 作为参数而非 String 通常会更灵活。这使得你的函数可以同时处理 String 类型和 &str 切片,而无需不必要的内存分配。这是可能的,因为 Rust 可以自动将 &String 转换为 &str(一个称为解引用强制转换的概念)。
// 这个函数很灵活,因为它接受一个字符串切片。fn log_message(message: &str) { println!("LOG: {}", message);}
fn main() { let owned_string = String::from("An owned message."); let string_literal = "A literal message.";
// 我们可以将这两种类型传递给函数! log_message(&owned_string); log_message(string_literal);}