前言
这是 【🔥前端学rust】 系列文章的第三篇文章了。其他内容如下:
继续学习 rust
的数据类型,学习 rust
的复合数据类型。
字符串类型
在 rust
中,字符串有两种主要的类型:String
和字符串切片 &str
。这两种类型各有特点,适用于不同的场景。
String
类型
String
是一个可变的字符串类型,位于堆上,可以动态增长。它是 std::string::String
的实例。
- 可变:
String
是可变的,可以动态地改变其内容和大小。 - 堆分配:
String
存储在堆上,因此它的长度在运行时是可变的。 - UTF-8 编码:
String
是以UTF-8编码的,因此它可以存储任何Unicode字符。 - 所有权:
String
拥有其内容,负责内存的分配和释放。
let mut s = String::new(); // 创建一个空字符串
let s = String::from("Hello, Rust!"); // 从字面量创建字符串
let mut s = String::from("Hello");
s.push_str(", Rust!"); // 追加字符串
s.push('!'); // 追加字符
字符串切片 &str
&str
是一个不可变的字符串引用,通常被称为“字符串切片”。它是 rust
中最常见的字符串类型之一,因为它轻便且不需要分配堆内存。
- 不可变:
&str
是不可变的,表示一段字符串数据的引用。 - 堆或栈中:
&str
可以是堆分配(如引用String
的内容)或栈上的字符串字面量。 - 通常用于借用:
&str
用于借用字符串数据而不被复制。
let s: &str = "Hello, Rust!"; // 字符串字面量
let s = String::from("Hello, Rust!");
let slice: &str = &s; // 从 String 创建 &str 切片
字符串转换
在 rust
中,经常需要在 &str
和 String
之间进行转换。
从 &str
到 String
:
let slice: &str = "Hello, Rust!";
let s = String::from(slice);
从 String
到 &str
:
let s = String::from("Hello, Rust!");
let slice: &str = &s; // 通过引用转换
字符串操作
在 rust
提供了许多有用的字符串方法,例如查找子字符串、替换字符等。
// 查找子字符串
fn main() {
let s: String = String::from("Hello, world!");
if let Some(pos) = s.find("world") {
println!("Found at position: {}", pos);
} else {
println!("Not found.");
}
}
// 替换字符
fn main() {
let s: String = String::from("Hello, world!");
let new_s = s.replace("world", "Rust");
println!("{}", new_s);
}
// 字符串的连接
let s1 = String::from("Hello, ");
let s2 = String::from("Rust!");
let s3 = s1 + &s2; // 使用 + 操作符会转移拥有权
// 字符串切片
let s = String::from("Hello, Rust!");
let hello = &s[0..5]; // 获取切片
使用场景
使用 String
当你需要一个可变的、拥有其内容的字符串时,比如在需要频繁修改字符串内容的情况下。使用 &str
作为函数参数时,可以提高性能,因为它不需要额外的内存分配和复制。根据需要的可变性和所有权,可以选择适合的字符串类型来使用。
元组(Tuple
)类型
在 rust
中,元组(Tuple
)是一种有序的数据结构,可以包含任意数量和类型的值。元组是固定长度的,一旦创建,就不能改变其长度或其元素的位置。元组的基本语法是使用圆括号 ()
将元素包围起来,元素之间用逗号 ,
分隔。因此,元组具有如下的特性:
- 序列性:元组中的元素是有序的,可以通过索引访问。
- 不同类型:同一个元组可以包含不同类型的数据,如整数、浮点数和字符等。
- 解构:可以使用模式匹配解构元组,方便地提取出各个元素。
- 固定大小:元组的大小在编译时是固定的,不能动态改变。
fn main() {
// 创建一个元组
let my_tuple: (i32, f64, char) = (500, 6.4, 'a');
// 访问元组元素
let (x, y, z) = my_tuple;
println!("x: {}, y: {}, z: {}", x, y, z);
// 通过索引访问元素
println!("First element: {}", my_tuple.0);
println!("Second element: {}", my_tuple.1);
println!("Third element: {}", my_tuple.2);
}
元组的解构
解构元组是指将元组中的各个元素分解到单独的变量中,这样可以更方便地使用这些值。
fn main() {
// 定义一个元组
let tuple = (500, 6.4, "hello");
// 解构元组
let (integer, float, string) = tuple;
println!("Integer: {}", integer);
println!("Float: {}", float);
println!("String: {}", string);
}
元组的嵌套
元组可以嵌套,即元组的某个元素本身也是一个元组。
fn main() {
// 定义一个嵌套的元组
let nested_tuple = ((1, 2), 3, "four");
// 访问嵌套元组中的元素
println!("First element of the inner tuple: {}", nested_tuple.0 .0);
println!("Second element of the inner tuple: {}", nested_tuple.0 .1);
println!("Third element: {}", nested_tuple.1);
println!("Fourth element: {}", nested_tuple.2);
}
元组作为函数返回值
元组也可以作为函数的返回值,这对于需要同时返回多个值的情况特别有用。
// 返回一个元组
fn calculate(x: i32, y: i32) -> (i32, i32) {
(x + y, x * y)
}
fn main() {
let result = calculate(5, 10);
println!("Sum: {}, Product: {}", result.0, result.1);
}
使用场景
元组在以下场景中非常有用:
- 当你需要返回多个值时,可以使用元组返回。
- 当你需要在函数内部组合不同类型的数据时,元组是一种简单的解决方案。
数组(Array
)类型
在 rust
中,数组(Array
)是另一种定长的、不可变的数据结构。数组是一种固定大小的连续内存区域,用于存储相同类型的元素。数组非常适合于存储固定数量的元素,并且可以在编译时确定其大小,且不可改变,这意味着你不能添加或删除元素。
数组的声明
数组的声明使用方括号 [ ]
,并且后面需要指定数组长度和数据类型。即 let array_name: [type; size] = [value1, value2, ..., valueN];
,其中 type
表示元素的类型,size
表示数组的大小(元素的数量),values
表示初始化数组的值列表。
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 声明一个包含 5 个 i32 类型元素的数组
let b = [0; 10]; // 声明一个包含 10 个元素且都是 0 的数组
数组元素的访问
你可以使用索引来访问数组中的元素,索引从 0 开始。
let a = [1, 2, 3, 4, 5];
let first = a[0]; // first 为 1
let second = a[1]; // second 为 2
数组的长度
可以使用 len
方法获取数组的长度。
let a = [1, 2, 3, 4, 5];
let length = a.len(); // length 为 5
数组的遍历
可以使用 for
循环遍历数组中的每个元素。
let a = [1, 2, 3, 4, 5];
for element in a.iter() {
println!("{}", element);
}
多维数组
rust
允许你创建多维数组。声明方式与一维数组类似,只是需要嵌套方括号。
let matrix: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; // 2 x 3 的二维数组
数组的切片
数组切片是对数组的引用,它可以让你动态访问数组的一部分。用 &
符号创建一个切片。
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..4]; // 切片包含元素 2, 3, 4
数组传递
在函数中传递数组时,可以传递数组的引用,以避免复制数组。
fn print_array(arr: &[i32]) {
for &item in arr.iter() {
println!("{}", item);
}
}
let a = [1, 2, 3, 4, 5];
print_array(&a);
若需在函数中接收固定大小的数组,可以这样定义:
fn print_fixed_array(arr: &[i32; 5]) {
for &item in arr.iter() {
println!("{}", item);
}
}
let a = [1, 2, 3, 4, 5];
print_fixed_array(&a);
向量(Vector
)类型
在 rust
中,向量(Vec<T>
)是一种动态数组,它可以在运行时动态地扩展或收缩。与固定大小的数组不同,向量的长度可以在程序运行时增加或减少。Vec
是 Rust 标准库 std::vec
中的一部分。向量具有以下特点:
- 动态大小: 向量的大小可以在运行时动态调整,可以进行添加、删除等操作。
- 连续内存: 向量中的元素在内存中是连续存储的,这使得它具有良好的缓存友好性。
- 所有权: Rust的所有权系统促使你对向量及其元素的所有权进行明确控制。
// 创建一个空的向量
let mut vec: Vec<i32> = Vec::new();
// 使用宏创建向量
let vec2 = vec![1, 2, 3, 4];
// 预留空间的向量
let mut vec3 = Vec::with_capacity(10);
向量的操作
rust
为向量提供了很多有用的方法。
// 添加元素:使用`push`方法添加元素到向量的末尾。
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
// 获取元素:使用索引或者`get`方法访问元素。
let first = &vec[0]; // 直接索引
let second = vec.get(1); // 使用get,返回Option
// 修改元素:向量中的元素可以通过索引直接修改。
let mut v: Vec<i32> = vec![1, 2, 3, 4, 5];
v[0] = 10; println!("{:?}", v);
// 删除元素: 使用pop方法从末尾移除元素,或使用remove从指定位置移除元素。
let last = vec.pop(); // 移除最后一个元素
if let Some(value) = vec.pop() {
println!("Popped: {}", value);
}
// 遍历:可以使用for循环遍历向量的元素。
for value in &vec {
println!("{}", value);
}
// 切片:向量可以通过切片(slice)来引用一个向量的部分。
let slice = &vec[1..3]; // 获取从索引1到2的切片
// 其他一些方法
1. len(): 获取向量中的元素数量。
2. is_empty(): 检查向量是否为空。
3. clear(): 清空向量中的所有元素。
4. extend(): 合并另一个可迭代对象的元素。
枚举(Enum
)类型
在 rust
中的枚举(Enum
)是一种非常强大和灵活的类型,允许你定义一组相关的值。枚举不仅能够定义一组离散的状态,还可以携带额外的数据。这种特性使得枚举非常适合用来表达复杂的业务逻辑和状态机。
枚举的声明
枚举由关键字 enum
定义,后面跟着枚举名和花括号 {}
,里面列出各个成员。每个成员可以有自己的名称,称为枚举的变体。成员可以携带数据,这些数据可以是任何类型。
enum EnumName {
Variant1,
Variant2(data_type),
Variant3 { field1: data_type1, field2: data_type2 },
}
使用枚举
要使用枚举,可以使用枚举的成员名。
enum Direction {
Up,
Down,
Left,
Right,
}
fn move_player(direction: Direction) {
match direction {
Direction::Up => println!("Moving up!"),
Direction::Down => println!("Moving down!"),
Direction::Left => println!("Moving left!"),
Direction::Right => println!("Moving right!"),
}
}
fn main() {
let dir = Direction::Up;
move_player(dir);
}
枚举带数据
rust
的枚举可以包含与它们的变体关联的数据。
enum Message {
Quit,
Write(String),
Move { x: i32, y: i32 },
}
fn process_message(message: Message) {
match message {
Message::Quit => println!("Quitting!"),
Message::Write(text) => println!("Writing: {}", text),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
}
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Write(String::from("Hello, World!"));
let msg3 = Message::Move { x: 10, y: 20 };
process_message(msg1);
process_message(msg2);
process_message(msg3);
}
rust
的枚举在很多地方都得到广泛应用,例如在错误处理(Result
枚举),可选值(Option
枚举)等场景。
结构体(Struct
)类型
在 rust
中,结构体(Struct
)是一种自定义的数据类型,用于组合多种类型的值。结构体(Struct
)是 rust
中一个非常重要的复合数据类型,这里简单介绍一下,后续会专门开一篇文章介绍。
结构体的定义
结构体使用 struct
关键字定义。
struct StructName {
field1: Type1,
field2: Type2,
// ...
}
创建结构体实例
可以通过使用构造函数语法来创建结构体的实例。
let person = Person {
name: String::from("Alice"),
age: 30,
};
访问属性
可以通过点操作符(.
)来访问结构体的属性。
println!("Name: {}, Age: {}", person.name, person.age);
结构体方法
使用 impl
关键字可以为结构体定义方法。方法的第一个参数是 self
,表示结构体的实例。
impl Person {
fn introduce(&self) {
println!("Hi, I'm {} and I'm {} years old.", self.name, self.age);
}
}
person.introduce();
结构体在 rust
中是一个非常强大的特性,允许开发者定义复杂的数据类型,并为其提供方法以实现更复杂的业务逻辑。
总结
复合类型在 rust
中功能非常强大,它们允许开发者将多种数据组合在一起,并提供了一种有组织的方式来处理复杂的数据结构。在实际应用中,选择合适的复合类型可以提高代码的可读性和可维护性。