在Rust中,字符串是一个非常重要的基础类型,它与其他语言中的字符串有所不同,因为Rust对字符串的内存管理非常严格,注重性能和安全性。Rust提供了几种不同类型的字符串,最常见的有String和&str。下面详细介绍一下字符串的各种用法。
字符串类型
Rust主要有两种类型的字符串:String和&str。
String是一个可变的堆分配的字符串类型,存储在堆上允许动态增长。String可以通过push等方法修改内容,因此它是一个可变类型。由于是堆分配的,它会自动管理内存,并且会在超出作用域时释放。
fn main() {
let mut s = String::from("Hello");
s.push_str(", World!"); // 拼接字符串
println!("{}", s); // 输出 "Hello, World!"
}
&str是一个不可变的字符串切片(slice),它通常引用某个字符串的片段,或者作为函数的参数来传递。&str是一个借用(borrowed)类型,因此它是一个不可变类型。
它可以指向存储在静态内存(如字符串字面量)或者堆上的数据(如 String 的一部分)。
fn main() {
let s: &str = "Hello, World!";
println!("{}", s);
}
&str字符串字面量通常存储在静态内存中是不可变的。String 类型存储在堆上支持动态增长和修改。
字符串的基本操作
1.创建字符串
fn main() {
let s1 = String::from("Hello");
//to_string()是&str类型的一个方法,返回一个String类型的字符串
let s2 = "world".to_string();
println!("{}, {}", s1, s2);
}
2.拼接字符串
拼接字符串时可以使用push_str或+运算符。push_str()可以将一个&str字符串追加到String的末尾。+运算符可以将一个String和一个&str拼接,返回新的String。
fn main() {
let mut s1 = String::from("Hello");
let s2 = " world";
s1.push_str(s2);
println!("{}", s1);
let s3 = String::from("Rust");
let s4 = " is great!";
//使用+运算符时,s3的所有权会被转移给result,因此s3在拼接后不再有效
let result = s3 + s4;
println!("{}", result);
}
3.访问和切片字符串
由于Rust的字符串是UTF-8编码的,所以切片时要确保切割的边界是有效的字符边界。
fn main() {
let s = String::from("Hello, world!");
let hello = &s[0..5]; // 字符串切片 hello 前五个字符
println!("{}", hello);
let world = &s[7..12]; //切片从索引 7 到 12(不包括 12)
println!("{}", world); //输出 "world"
}
4.字符串遍历
Rust字符串的迭代是基于字符(而不是字节)进行的。
fn main() {
let s = String::from("Hello");
for c in s.chars() {
println!("{}", c); // 输出每个字符
}
}
5.查找子串
fn main() {
let s = String::from("Hello, world!");
//find() 返回一个 Option<usize>,表示子串在字符串中的位置
let index = s.find("world").unwrap(); // 返回子串的起始位置
println!("Found 'world' at index: {}", index);
}
6.字符和字节处理
Rust的String类型和&str类型都遵循UTF-8编码,因此处理多字节字符时需要小心。
fn main() {
let s = "你好,世界!";
//bytes() 方法返回字节迭代器,可以用于逐个访问 UTF-8 编码的字节
for b in s.bytes() {
println!("{}", b); // 输出每个字节的值
}
}
7.字符串的内存管理
Rust的String类型自动管理内存。它会在超出作用域时自动释放内存,但它也允许你手动控制内存的分配和释放。
fn main() {
let s = String::from("Hello");
println!("{}", s);
} // 在这里s会被自动释放
8.字符串比较
fn main() {
//Rust中支持对String和&str进行直接比较(基于字面值的相等性比较)
let s1 = String::from("Hello");
let s2 = "Hello";
if s1 == s2 {
println!("Strings are equal");
} else {
println!("Strings are different");
}
}
9.插入操作
//由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,需要定义mut属性
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);
}
10.替换操作
//1.replace方法 该方法是返回一个新的字符串,而不是操作原来的字符串。
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);
}
//2.replacen方法该方法是返回一个新的字符串,而不是操作原来的字符串
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);
}
//3.replace_range 该方法是直接操作原来的字符串 替换指定范围内的字符串
fn main() {
let mut string_replace_range = String::from("I like rust!");
string_replace_range.replace_range(7..8, "R");
dbg!(string_replace_range);
}
使用场景分析:
使用replace
- 当你需要替换所有匹配的子串时。
- 适用于替换的次数未知或替换所有出现的场景。
- 当你希望保留原始字符串不变时,因为replace返回新的字符串。
使用replacen
- 当你只想替换指定次数的匹配时。
- 比如在某些情况下,替换多个匹配但不希望替换全部时。它能避免不必要的替换操作,从而提高性能。
使用replace_range
- 当你知道要替换的具体位置(即索引范围)时。
- 用于修改字符串中的特定位置,而不是匹配某个子串。
- 适用于处理特定位置的字符(例如在某个位置插入或替换部分内容)。
11.删除操作
//1.pop删除并返回字符串的最后一个字符 该方法是直接操作原来的字符串
fn main() {
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();
let p2 = string_pop.pop();
}
//2.remove 删除并返回字符串中指定位置的字符 该方法是直接操作原来的字符串
//如果参数所给的位置不是合法的字符边界,则会发生错误
n main() {
let mut string_remove = String::from("测试remove方法");
// 删除第一个汉字
string_remove.remove(0);
dbg!(string_remove);
}
//3.truncate删除字符串中从指定位置开始到结尾的全部字符 该方法是直接操作原来的字符串
fn main() {
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
dbg!(string_truncate);
}
//4.clear清空字符串 该方法是直接操作原来的字符串
fn main() {
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
}
12.使用 format! 连接字符串
//format! 这种方式适用于 String 和 &str
fn main() {
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
}
13.字符串转义
可以通过转义的方式\输出ASCII和Unicode字符
fn main() {
// 通过 \ + 字符的十六进制表示,转义输出一个字符
let byte_escape = "I'm writing \x52\x75\x73\x74!";
// \u 可以输出一个 unicode 字符
let unicode_codepoint = "\u{211D}";
let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
//不转义字符串的调用方法
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
//如果字符串包含双引号,可以在开头和结尾加 #
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
//如果字符串中包含 # 号,可以在开头和结尾加多个 # 号,最多加255个,只需保证与字符串中连续 # 号的个数不超过开头和结尾的#号的个数即可
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
}
Rust字符串是以UTF-8编码存储的,这意味着每个字符的大小可能不同。操作字符串时需要确保它是有效的Unicode字符集。String类型的内存是堆分配的,所以尽量避免频繁的分配和复制。如果需要修改大量数据,考虑使用String。
总的来说,Rust 的字符串处理非常灵活,支持多种方式进行字符串操作,特别是在内存安全和性能上做了很多优化。String是堆上分配的可变字符串,而&str是不可变的切片类型。Rust提供了丰富的字符串操作方法,既支持常见的拼接、切片和遍历,也处理了 Unicode 和字节级别的字符串操作。