rust -- 复合类型

314 阅读11分钟

字符串

在 Rust 中,字符串(String)是一个动态可扩展的 UTF-8 编码的字符序列,字符串类型为 String
切片是对数组、字符串或其他类似集合类型的引用,以及一个范围(slice)指定了要引用的部分。字符串切片则是一个不可变的 UTF-8 编码的字符序列。切片类型为 &str

字符串相关的常见操作如下:

  1. 创建字符串:
  • 使用 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);

相关的方法和功能如下:

  1. 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),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。
  2. &str 相关方法:

    • len():返回字符串切片的长度。
    • is_empty():检查字符串切片是否为空。
    • chars():返回一个迭代器,用于遍历字符串切片中的每个字符。
    • split(char):根据指定的字符将字符串切片拆分成一个迭代器。
    • trim():移除字符串切片两端的空白字符。
    • contains(&str):检查字符串切片是否包含指定的子字符串。

String::from 创建的字符串拥有动态分配的能力,可以动态改变长度,但需要通过堆内存进行存储。而直接使用 "" 赋值的字符串切片是对字符串字面量的引用,不拥有数据所有权,长度不可变。使用时需要根据需求选择使用哪种数据类型。

当涉及到字符串操作时,Rust 提供了一些常用的方法和功能,让我们继续了解:

  1. 字符串拼接:

    • 使用 + 运算符进行字符串拼接,但要注意拼接操作会消耗左右两侧的字符串。示例:
    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"
    
  2. 字符串迭代:

    • 使用 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!
    
  3. 提取子串:

    • 使用切片(slice)可以提取字符串的一部分,表示为一个范围 [start..end)。示例:
    let hello_world = String::from("Hello, World!");
    let slice = &hello_world[0..5]; // 提取从索引 0 到索引 4 的子串,即 "Hello"
    println!("{}", slice);  // Hello
    
  4. 字符串搜索和替换:

    • 使用 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!"
    
  5. 字符串删除

  • 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 是切片结束的索引(排除)。

  1. 创建切片:

    • 使用范围表示法 .. 创建切片,从一个集合或字符串中提取子部分。示例:
    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]
    }
    
  2. 使用切片:

    • 使用 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]"
    
  3. 使用切片的方法:

    • split_at():根据给定的索引将切片分成两个独立的切片。
    • split():根据给定的分隔符将切片拆分成多个子切片。
    • contains():检查切片是否包含指定的元素。
    • starts_with()ends_with():检查切片是否以指定的元素开始或结束。
    • join():将切片中的元素连接为一个字符串。
    • to_owned()to_string():将切片转换为 String

元组

元组是由多种类型组合到一起形成的,它是复合类型,元组的长度及元组中元素的顺序都是固定的。

  1. 创建元组
let tup: (i32, f64, u8) = (500, 6.4, 1);

如上,元组用括号将多个类型组合到一起,变量 tup 被绑定了一个元组值 (500, 6.4, 1),该元组的类型是 (i32, f64, u8)

  1. 用模式匹配解构元组

元组中对应的值会绑定到变量 x, y, z

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}
  1. 用 . 来访问元组
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;
}
  1. 可以使用元组返回多个值
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>

  1. 创建数组
  • 直接定义
let a = [1, 2, 3];
  • 数组声明类型
// 数组类型是通过方括号语法声明,`i32` 是元素类型,分号后面的数字 `3` 是数组长度
let a: [i32; 3] = [1, 2, 3];
  • 初始化一个某个值重复出现 N 次的数组
let a = [1; 3];  // `a` 数组包含 `3` 个元素,这些元素的初始化值为 `1`

由于它的元素类型大小固定,且长度也是固定,因此数组 array 是存储在栈上

  1. 访问数组

由于数组是连续存放元素的,我们可以通过索引的方式来访问存放其中的元素。

let a = [1, 2, 3]; 
let first = a[0]; // 获取a数组第一个元素 1
let second = a[1]; // 获取第二个元素 2
  1. 数组切片

数组切片允许我们引用数组的一部分,同字符串切片类似。

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字符串切片也同理
  1. 动态数组

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>是否为空。