第四章 Rust 復(fù)雜數(shù)據(jù)結(jié)構(gòu)

Rust 基礎(chǔ)入門指南:為 Solana 合約學(xué)習(xí)鋪路 https://learnblockchain.cn/column/113

一、字符串

動態(tài)字符串切片

字符串切片是引用類型,類型為 &str,它通過索引或范圍來指定字符串的一部分,提供了對字符串的引用,而不引入額外的內(nèi)存開銷。切片并不擁有字符串的內(nèi)容,因此不會消耗額外的內(nèi)存。

fn main() {
    let s: String = String::from("Rust is powerful!");

    // 從索引 0 開始,獲取前 4 個字節(jié)
    let slice1: &str = &s[0..4];

    // 默認(rèn)從索引 0 開始,獲取前 4 個字節(jié)
    let slice2: &str = &s[..4];

    let len: usize = s.len();

    // 從索引 5 開始,獲取到字符串末尾
    let slice3: &str = &s[5..len];

    // 默認(rèn)從索引 5 開始,獲取到字符串末尾
    let slice4: &str = &s[5..];

    // 獲取整個字符串的切片
    let slice5: &str = &s[0..len];

    // 同上,獲取整個字符串的切片
    let slice6: &str = &s[..];

    // 輸出結(jié)果
    println!("slice1: {}", slice1);  // 輸出: Rust
    println!("slice2: {}", slice2);  // 輸出: Rust
    println!("slice3: {}", slice3);  // 輸出: is powerful!
    println!("slice4: {}", slice4);  // 輸出: is powerful!
    println!("slice5: {}", slice5);  // 輸出: Rust is powerful!
    println!("slice6: {}", slice6);  // 輸出: Rust is powerful!

    let chinese_string = "編程語言";
    // 錯誤示例:試圖獲取 "編" 的前兩個字節(jié),但 "編" 占用 3 個字節(jié)
    // let wrong_slice = &chinese_string[0..2]; // 編譯通過,但運行時會導(dǎo)致 panic

    // 正確示例:獲取 "編" 的完整字節(jié)范圍
    let correct_slice = &chinese_string[0..3];
    println!("正確的切片: {}", correct_slice); // 輸出 "編"
}

索引范圍是前閉后開的,即包含開始位置,但不包含結(jié)束位置。

字符串字面量與動態(tài)字符串

字符串字面量是在代碼中直接寫死的字符串,比如 "Rust is awesome!"。它們在程序編譯時就已經(jīng)確定并固定下來,類型為 &str。與動態(tài)字符串不同,字符串字面量是不可變的。

字符串字面量在編譯時已知大小,因此它的生命周期與整個程序的運行期一致。這使得它們在內(nèi)存中更加高效,而無需像動態(tài)字符串那樣進(jìn)行內(nèi)存分配。

fn main() {
    // 字符串字面量轉(zhuǎn)動態(tài)字符串
    let s1: String = "Rust is cool".to_string();
    let s2: String = String::from("Rust is powerful");

    // 動態(tài)字符串轉(zhuǎn)字符串字面量
    let s3: &str = s1.as_str();

    println!("s1: {}", s1);  // 輸出: Rust is cool
    println!("s2: {}", s2);  // 輸出: Rust is powerful
    println!("s3: {}", s3);  // 輸出: Rust is cool
}

動態(tài)字符串操作

fn main() {
    let mut s = String::from("Hello");

    // 追加字符串,修改原來的字符串
    s.push_str(", world");
    println!("追加字符串 push_str() -> {}", s);

    // 追加單個字符
    s.push('!');
    println!("追加字符 push() -> {}", s);

    // 在指定位置插入字符,修改原來的字符串
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);

    // 在指定位置插入字符串
    s.insert_str(6, " how are you?");
    println!("插入字符串 insert_str() -> {}", s);

    // 替換字符串中的某個子串,返回新字符串
    let str_old = String::from("I like rust, rust is great!");
    let str_new = str_old.replace("rust", "Rust");
    println!("原字符串:{}, 新字符串:{}", str_old, str_new);

    // pop 刪除操作,修改原來的字符串
    let mut string_pop = String::from("刪除操作,rust 中文!");
    // 刪除末尾字符
    let p1 = string_pop.pop();
    println!("刪除字符 pop() -> {:#?}", p1); // 輸出:Some('!')
    println!("剩余字符串: {}", string_pop);
    
    // 刪除末尾字符,再次 pop
    let p2 = string_pop.pop();
    println!("刪除字符 pop() -> {:?}", p2); // 輸出:Some('中')
    println!("剩余字符串: {}", string_pop);
}

二、元組

元組是一種復(fù)合類型,它將多個不同類型的值組合在一起,并且長度和順序都是固定的。它們通過圓括號 ( ) 定義,元素之間用逗號 , 分隔。

想象你有一個旅行背包,里面裝著不同種類的物品,比如護(hù)照(字符串)、現(xiàn)金(整數(shù))、指南針(浮點數(shù))。無論里面的物品是什么類型,它們都屬于同一個背包(元組),并且位置固定。

定義元組

// 創(chuàng)建一個包含不同類型元素的元組
let info: (i32, bool, f64, &str) = (42, true, 3.14, "Rust");
// 元組可以嵌套
let nested_tuple: (u8, (char, &str)) = (1, ('A', "嵌套"));

訪問元組元素

fn main() {
    let person: (&str, i32, f64) = ("Alice", 30, 1.65);
    
    // 解構(gòu)方式獲取元組元素
    let (name, age, height) = person;
    println!("{} is {} years old and {}m tall.", name, age, height);
    
    // 使用索引訪問元組元素
    println!("Name: {}, Age: {}, Height: {}", person.0, person.1, person.2);
}

作為函數(shù)返回值

fn square_and_cube(n: i32) -> (i32, i32) {
    (n * n, n * n * n)
}

fn main() {
    let (square, cube) = square_and_cube(3);
    println!("Square: {}, Cube: {}", square, cube);
}

三、結(jié)構(gòu)體

結(jié)構(gòu)體是一種自定義數(shù)據(jù)類型,可以組織多個相關(guān)的字段,使代碼更清晰易讀??梢詫⒔Y(jié)構(gòu)體看作是人物檔案,每個字段代表一個特定信息,比如姓名、年齡、職業(yè)等。

基礎(chǔ)語法

// 定義結(jié)構(gòu)體
struct Book {
    title: String,
    author: String,
    pages: u32,
    is_hardcover: bool,
}

fn main() {
    // 實例化
    let rust_book = Book {
        title: String::from("The Rust Programming Language"),
        author: String::from("Steve Klabnik and Carol Nichols"),
        pages: 552,
        is_hardcover: true,
    };
    println!("Book: {} by {}", rust_book.title, rust_book.author);

    // 通過已有結(jié)構(gòu)體創(chuàng)建新實例
    let book2 = Book {
        title: String::from("Advanced Rust"),
        ..rust_book
    };

    println!("{} - {} pages", book2.title, book2.pages);
}

元組結(jié)構(gòu)體(Tuple Struct)

如果你不需要字段名稱,但仍然希望使用結(jié)構(gòu)體的特性,可以使用元組結(jié)構(gòu)體。

struct Coordinates(i32, i32, i32);

fn main() {
    let origin = Coordinates(0, 0, 0);
    println!("Origin is at ({}, {}, {})", origin.0, origin.1, origin.2);
}

四、枚舉

枚舉是一種用戶自定義的數(shù)據(jù)類型,它允許一個類型有多個不同的可能值,每個值稱為一個變體(variant)。

基礎(chǔ)語法

enum CoffeeSize {
    Small,
    Medium,
    Large,
}

enum Message {
    Text(String),
    Move { x: i32, y: i32 },
    ChangeColor(i32, i32, i32),
}

fn main() {
    let my_coffee = CoffeeSize::Medium;
    match my_coffee {
        CoffeeSize::Small => println!("You chose a small coffee."),
        CoffeeSize::Medium => println!("You chose a medium coffee."),
        CoffeeSize::Large => println!("You chose a large coffee."),
    }

    let msg1 = Message::Text(String::from("Hello, Rust!"));
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::ChangeColor(255, 0, 0);
}

Option 枚舉

Rust 沒有 null,取而代之的是 Option<T> 枚舉,用于表示可能為空的值。它的定義如下:

// 它有兩個枚舉值,Some(T): 包含一個具體的值 T,以及None: 表示沒有值。
enum Option<T> {
    None,
    Some(T),
}
fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

fn main() {
    match divide(10.0, 2.0) {
        Some(result) => println!("Result: {}", result),
        None => println!("Cannot divide by zero!"),
    }
}

五、數(shù)組

數(shù)組是由多個相同類型的元素組合而成的集合。在 Rust 中,數(shù)組主要分為兩類:

  • 靜態(tài)數(shù)組 (array):分配在棧上,長度固定,訪問速度快。
  • 動態(tài)數(shù)組 (Vector):分配在堆上,長度可變,但操作會帶來額外的性能開銷。

靜態(tài)數(shù)組的特點類似于一個固定大小的書架,一旦確定其大小,就無法再改變。相比之下,動態(tài)數(shù)組更像是一個可以隨時調(diào)整大小的抽屜,可以根據(jù)需要擴(kuò)展或縮小。

靜態(tài)數(shù)組

fn main() {
    // 由編譯器推斷類型
    let a = [10, 20, 30, 40, 50];

    // 顯式指定類型和長度
    let b: [i32; 5] = [10, 20, 30, 40, 50];

    // 創(chuàng)建包含相同元素的數(shù)組
    let c = [7; 5]; // c = [7, 7, 7, 7, 7]

    // 靜態(tài)數(shù)組:長度固定,存儲在棧上
    let a = [5u8; 5]; // a = [5, 5, 5, 5, 5]

    // 非基礎(chǔ)類型的數(shù)組需要使用 `std::array::from_fn` 來初始化
    let b: [String; 3] = std::array::from_fn(|_| "hello".to_string());
    // b = ["hello", "hello", "hello"]

    // 數(shù)組的元素訪問
    let c: [u8; 5] = [15, 25, 35, 45, 55];
    let first = c[0]; // first = 15
    let second = c[1]; // second = 25

    // 訪問數(shù)組越界元素(會導(dǎo)致編譯錯誤)
    // let none_element = c[100];

    // 二維數(shù)組:每個元素都是一個 `[u8; 5]` 類型的數(shù)組
    let arrays: [[u8; 5]; 2] = [a, c];
}

動態(tài)數(shù)組

動態(tài)數(shù)組 Vec<T> 是 Rust 中可變長度的集合,允許在運行時動態(tài)調(diào)整大小。與 String 不同,Vec<T> 是通用的,可存儲任意類型的元素。

fn main() {
    // 顯式聲明類型
    let v1: Vec<i32> = Vec::new();
    // v1.push(1); 編譯錯誤

    // 讓編譯器推斷類型。
    let mut v2 = Vec::new();
    v2.push(10);

    // 使用 vec! 宏創(chuàng)建并初始化
    let v3 = vec![10, 20, 30];

    // 使用 [初始值;長度] 來創(chuàng)建數(shù)組
    let v4 = vec![1; 3]; // v4 = [1, 1, 1]

    // 使用from語法創(chuàng)建數(shù)組
    let v5 = Vec::from([1, 1, 1]);

    let mut v = vec![1, 2, 3, 4, 5];

    // 訪問元素
    let first: &i32 = &v[0];
    println!("第一個元素是 {}", first);

    // 通過 get 方法安全訪問
    if let Some(last) = v.get(4) {
        println!("最后一個元素是 {}", last);
    }

    // 修改元素
    for i in &mut v {
        *i *= 2;
    }
    println!("修改后的 v: {:?}", v); // [2, 4, 6, 8, 10]

    // 刪除元素
    v.pop();
    println!("刪除最后一個元素后: {:?}", v); // [2, 4, 6, 8]

    v.remove(1);
    println!("刪除索引 1 處的元素后: {:?}", v); // [2, 6, 8]

    /* 自動擴(kuò)容 */
    let mut v = vec![1, 2, 3, 4];

    println!("初始容量: {}", v.capacity());
    println!("初始 v[0] 的地址: {:p}", &v[0]);

    v.push(5);
    println!("添加一個元素后,容量: {}", v.capacity());
    println!("添加后 v[0] 的地址: {:p}", &v[0]);

    v.push(6);
    println!("再次添加元素后,容量: {}", v.capacity());
    println!("再次添加后 v[0] 的地址: {:p}", &v[0]);
}

初始時 Vec 的容量可能是 4,存儲在堆內(nèi)存中,而棧上存儲著指向堆的指針。當(dāng) push(5) 觸發(fā)擴(kuò)容時,Vec 會申請更大的空間(通常是當(dāng)前容量的 2 倍),并將數(shù)據(jù)復(fù)制到新的地址。

六、hashmap

// 由于 HashMap 并沒有包含在 Rust 的 prelude 庫中,所以需要手動引入
use std::collections::HashMap;

fn main() {
    // 創(chuàng)建一個HashMap,用于存儲學(xué)生姓名和他們的年齡
    let mut student_ages = HashMap::new();
    student_ages.insert("Alice", 20);
    student_ages.insert("Bob", 19);
    student_ages.insert("Charlie", 21);

    // 創(chuàng)建一個指定容量的 HashMap,用于存儲學(xué)生姓名和他們的身高
    // 這樣可以預(yù)先分配內(nèi)存,減少后續(xù)插入時的內(nèi)存分配開銷
    let mut student_heights = HashMap::with_capacity(4);
    student_heights.insert("Alice", 165);
    student_heights.insert("Bob", 180);
    student_heights.insert("Charlie", 175);
    student_heights.insert("David", 170);

    // 打印 HashMap 的內(nèi)容
    println!("學(xué)生年齡: {:?}", student_ages);
    println!("學(xué)生身高: {:?}", student_heights);
}

復(fù)雜一點的例子:

use std::collections::HashMap;

fn main() {
    // 創(chuàng)建一個包含用戶姓名和積分的列表
    let user_list = vec![("Alice", 300), ("Bob", 250), ("Eve", 150), ("Mallory", 50)];

    // 將列表轉(zhuǎn)換為 HashMap
    let mut user_map: HashMap<&str, i32> = user_list.into_iter().collect();
    println!("用戶積分: {:?}", user_map);

    // 獲取元素
    let alice_points = user_map["Alice"];
    println!("Alice 的積分: {}", alice_points);

    // 使用 get 方法安全獲取元素
    let alice_points = user_map.get("Alice");
    println!("Alice 的積分: {:?}", alice_points);

    // 嘗試獲取不存在的鍵
    let trent_points = user_map.get("Trent");
    println!("Trent 的積分: {:?}", trent_points);

    // 覆蓋已有的值,insert 會返回舊值
    let old = user_map.insert("Alice", 400); // 更新 Alice 的積分
    assert_eq!(old, Some(300)); // 確認(rèn)舊值是 300

    // 使用 entry 方法插入或獲取值
    let v = user_map.entry("Trent").or_insert(100); // Trent 不存在,插入 100
    assert_eq!(*v, 100);

    let v = user_map.entry("Trent").or_insert(200); // Trent 已存在,不會修改
    assert_eq!(*v, 100);

    // 打印更新后的 HashMap
    println!("更新后的用戶積分: {:?}", user_map);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容