字符串
在 Rust 中,字符串(String)是一个动态可扩展的 UTF-8 编码的字符序列,字符串类型为 String。
切片是对数组、字符串或其他类似集合类型的引用,以及一个范围(slice)指定了要引用的部分。字符串切片则是一个不可变的 UTF-8 编码的字符序列。切片类型为 &str。
字符串相关的常见操作如下:
- 创建字符串:
- 使用
String::new()创建一个空字符串。 - 使用
String::from("content")创建一个带有初始内容的字符串。
let empty_str = String::new();
let hello_str = String::from("Hello, World!");
使用 String::from 创建的字符串是一个动态分配的数据结构,它在堆上存储字符串内容,并且可以根据需要进行扩展和缩小。这意味着它可以根据实际情况动态改变长度。
let mut s = String::from("Hello"); // 创建一个可变的字符串
s.push_str(", World!"); // 添加更多内容到字符串尾部
println!("{}", s); // 输出 "Hello, World!"
相比之下,使用 &str 创建的字符串切片是对已有字符串的引用,并且固定在了程序的位置。字符串切片不拥有数据的所有权,因此不能进行动态的扩展和缩小。
let s: &str = "Hello, World!"; // 创建一个字符串切片
println!("{}", s);
相关的方法和功能如下:
-
String相关方法:new():创建一个空的String对象。from():将一个字符串字面量或其他类型的对象转换为String类型。push_str(&str):将一个字符串切片追加到String对象的尾部。原有的字符串上追加,不会返回新的字符串。push(char):将一个字符追加到String对象的尾部。原有的字符串上追加,不会返回新的字符串。len():返回字符串的长度。is_empty():检查字符串是否为空。chars():返回一个迭代器,用于遍历字符串中的每个字符。replace():用于String和&str类型。方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。replacen():用于String和&str类型。方法接收三个参数,前两个参数与replace()方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。replace_range:仅适用于String类型。接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。
-
&str相关方法:len():返回字符串切片的长度。is_empty():检查字符串切片是否为空。chars():返回一个迭代器,用于遍历字符串切片中的每个字符。split(char):根据指定的字符将字符串切片拆分成一个迭代器。trim():移除字符串切片两端的空白字符。contains(&str):检查字符串切片是否包含指定的子字符串。
String::from创建的字符串拥有动态分配的能力,可以动态改变长度,但需要通过堆内存进行存储。而直接使用""赋值的字符串切片是对字符串字面量的引用,不拥有数据所有权,长度不可变。使用时需要根据需求选择使用哪种数据类型。
当涉及到字符串操作时,Rust 提供了一些常用的方法和功能,让我们继续了解:
-
字符串拼接:
- 使用
+运算符进行字符串拼接,但要注意拼接操作会消耗左右两侧的字符串。示例:
let hello = String::from("Hello"); let world = String::from("World"); let hello_world = hello + " " + &world; // 注意 `&world` 的借用操作 println!("{}", hello_world); // 输出 "Hello World"- 使用
format!宏进行字符串拼接,它不会消耗原始字符串并返回一个新的String对象。示例:
let hello = String::from("Hello"); let world = String::from("World"); let hello_world = format!("{} {}", hello, world); println!("{}", hello_world); // 输出 "Hello World" - 使用
-
字符串迭代:
- 使用
chars()方法可以遍历字符串中的每个字符,返回一个字符的迭代器。示例:
let hello_world = String::from("Hello"); for ch in hello_world.chars() { println!("{}", ch); } //H //e //l //l //o- 使用
split()方法按指定的分隔符拆分字符串,并返回一个迭代器。示例:
let hello_world = String::from("Hello, World!"); let parts: Vec<&str> = hello_world.split(", ").collect(); for part in parts { println!("{}", part); } //Hello //World! - 使用
-
提取子串:
- 使用切片(slice)可以提取字符串的一部分,表示为一个范围
[start..end)。示例:
let hello_world = String::from("Hello, World!"); let slice = &hello_world[0..5]; // 提取从索引 0 到索引 4 的子串,即 "Hello" println!("{}", slice); // Hello - 使用切片(slice)可以提取字符串的一部分,表示为一个范围
-
字符串搜索和替换:
- 使用
contains()方法检查字符串中是否包含指定的子串。示例:
let hello_world = String::from("Hello, World!"); if hello_world.contains("Hello") { println!("Contains Hello"); }- 使用
replace()方法替换字符串中的指定子串。示例:
let hello_world = String::from("Hello, World!"); let new_string = hello_world.replace("Hello", "Hi"); println!("{}", new_string); // 输出 "Hi, World!" - 使用
-
字符串删除
pop—— 删除并返回字符串的最后一个字符remove—— 删除并返回字符串中指定位置的字符,返回删除位置的字符串,只有一个参数,即该字符起始索引位置。truncate—— 删除字符串中从指定位置开始到结尾的全部字符clear—— 清空字符串
fn main() {
let mut string_pop = String::from("hello world!");
let s1 = string_pop.pop();
dbg!(s1);
dbg!(string_pop);
let mut string_remove = String::from("hello world!");
string_remove.remove(0);
string_remove.remove(1);
dbg!(string_remove); // string_remove = "elo world!"
let mut string_truncate = String::from("hello world!");
string_truncate.truncate(3);
dbg!(string_truncate);
let mut string_clear = String::from("hello world!");
string_clear.clear();
dbg!(string_clear);
}
运行结果
[src\main.rs:5] s1 = Some(
'!',
)
[src\main.rs:6] string_pop = "hello world"
[src\main.rs:11] string_remove = "elo world!"
[src\main.rs:15] string_truncate = "hel"
[src\main.rs:19] string_clear = ""
切片
在 Rust 中,切片(slice)是一种不可变引用,用于借用集合(如字符串或数组)中一部分元素的连续序列。切片提供了对数据的安全访问方式,而无需拥有全部权威。
切片使用范围表示法 [start..end] 来指定其包含的元素范围,其中 start 是切片开始的索引(包括),end 是切片结束的索引(排除)。
-
创建切片:
- 使用范围表示法
..创建切片,从一个集合或字符串中提取子部分。示例:
let numbers = [1, 2, 3, 4, 5]; let slice = &numbers[1..4]; // 包含索引 1、2、3,即 [2, 3, 4]- 使用方法
get()以及get_mut()通过索引创建可选的切片引用,返回Some包含切片的引用或None。示例:
let numbers = [1, 2, 3, 4, 5]; let slice = numbers.get(1..4); if let Some(slice) = slice { println!("{:?}", slice); // [2, 3, 4] } - 使用范围表示法
-
使用切片:
- 使用
len()方法获取切片的长度。示例:
let numbers = [1, 2, 3, 4, 5]; let slice = &numbers[1..4]; println!("Length: {}", slice.len()); // 3"- 遍历切片的元素使用
iter()方法创建迭代器。示例:
let numbers = [1, 2, 3, 4, 5]; let slice = &numbers[1..4]; for num in slice.iter() { println!("{}", num); } //2 //3 //4- 通过索引访问切片的元素。示例:
let numbers = [1, 2, 3, 4, 5]; let slice = &numbers[1..4]; let second = slice[1]; println!("Second element: {}", second); // 3- 修改切片的值。示例:
let mut numbers = [1, 2, 3, 4, 5]; { let slice = &mut numbers[1..4]; slice[0] = 10; slice[2] = 30; } println!("{:?}", numbers); // 输出 "[1, 10, 3, 30, 5]" - 使用
-
使用切片的方法:
split_at():根据给定的索引将切片分成两个独立的切片。split():根据给定的分隔符将切片拆分成多个子切片。contains():检查切片是否包含指定的元素。starts_with()和ends_with():检查切片是否以指定的元素开始或结束。join():将切片中的元素连接为一个字符串。to_owned()和to_string():将切片转换为String。
元组
元组是由多种类型组合到一起形成的,它是复合类型,元组的长度及元组中元素的顺序都是固定的。
- 创建元组
let tup: (i32, f64, u8) = (500, 6.4, 1);
如上,元组用括号将多个类型组合到一起,变量 tup 被绑定了一个元组值 (500, 6.4, 1),该元组的类型是 (i32, f64, u8)。
- 用模式匹配解构元组
元组中对应的值会绑定到变量 x, y, z上
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
- 用 . 来访问元组
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
- 可以使用元组返回多个值
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len); // The length of 'hello' is 5.
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回字符串的长度
(s, length)
}
数组
将多个类型相同的元素依次组合在一起,就是一个数组。
数组的三个原则:
- 长度固定:数组的长度是固定的,初始化后长度就固定了,无法改变。
- 元素必须有相同的类型:数组的元素必须具有相同的类型,即所有元素都是同一类型的。
- 依次线性排列:数组的元素在内存中是连续存储的,可以通过下标来访问数组中的任意元素。
注意:这里说的数组是 Rust 的基本类型,是固定长度的,如果你需要一个可以动态增长的数据结构,应该使用Rust的动态数组
Vec<T>。
- 创建数组
- 直接定义
let a = [1, 2, 3];
- 数组声明类型
// 数组类型是通过方括号语法声明,`i32` 是元素类型,分号后面的数字 `3` 是数组长度
let a: [i32; 3] = [1, 2, 3];
- 初始化一个某个值重复出现 N 次的数组
let a = [1; 3]; // `a` 数组包含 `3` 个元素,这些元素的初始化值为 `1`
由于它的元素类型大小固定,且长度也是固定,因此数组 array 是存储在栈上
- 访问数组
由于数组是连续存放元素的,我们可以通过索引的方式来访问存放其中的元素。
let a = [1, 2, 3];
let first = a[0]; // 获取a数组第一个元素 1
let second = a[1]; // 获取第二个元素 2
- 数组切片
数组切片允许我们引用数组的一部分,同字符串切片类似。
let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
assert_eq!(slice, &[2, 3]);
slice 的类型是&[i32],数组的类型是[i32;5]:
- 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
- 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
- 切片类型
[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str字符串切片也同理
- 动态数组
Vec<T>是一个动态数组,它提供了一个能够动态增长和缩小的数组类型,可以容纳任意数量的类型为T的元素。
以下是一些关于Vec<T>的重要知识点:
- 动态大小:与Rust的固定大小数组不同,
Vec<T>的大小是动态的,可以根据需要增加或减少。 - 在堆上分配:
Vec<T>的数据存储在堆上,而不是栈上。这意味着它的内存分配是动态的,可能会随着元素的增加而增加。 - 连续存储:
Vec<T>的元素在内存中连续存储,这使得访问任何元素的时间复杂度为O(1)。 - 堆和栈的交互:当你创建一个
Vec<T>时,实际上在栈上创建了一个指向堆上数据结构的引用。
下面是一个使用Vec<T>的简单示例:
rust复制代码
fn main() {
// 创建一个空的Vec<i32>
let mut vec: Vec<i32> = Vec::new();
// 向Vec中添加元素
vec.push(1);
vec.push(2);
vec.push(3);
// 访问Vec中的元素
println!("First element: {}", vec[0]);
// 遍历Vec中的元素
for &num in &vec {
println!("Number: {}", num);
}
// 修改Vec中的元素
vec[0] = 10;
println!("First element after modification: {}", vec[0]);
// 删除Vec中的元素
vec.remove(1);
println!("After removal: {:?}", vec);
}
使用Vec<T>的主要方法:
new():创建一个新的空Vec<T>。push(element):向Vec<T>的末尾添加一个元素。pop():从Vec<T>的末尾移除一个元素并返回它。get(index):返回Vec<T>中指定索引处的元素。set(index, element):将Vec<T>中指定索引处的元素替换为另一个元素。remove(index):从Vec<T>中移除指定索引处的元素并返回它。len():返回Vec<T>中的元素数量。is_empty():检查Vec<T>是否为空。