Rust-Collections und Strings

Sammlungen sind die häufigste Form der Datenspeicherung in Datenstrukturen. Die Rust-Standardbibliothek bietet eine reichhaltige Auswahl an Sammlungstypen, die Entwicklern helfen, Datenstrukturoperationen zu handhaben.

Vektor

Ein Vektor ist eine einzelne Datenstruktur, die mehrere Werte speichert, und er speichert Werte desselben Typs linear im Speicher.

Ein Vektor ist eine lineare Liste, in Rust als Vec<T> dargestellt.

Vektoren werden ähnlich wie Listen verwendet. Wir können einen Vektor eines angegebenen Typs auf diese Weise erstellen:

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量
let vector = vec![1, 2, 4, 8];     // 通过数组创建向量

Bei der Verwendung linearer Listen müssen wir oft Anfügeoperationen durchführen. Das Anfügen ist jedoch im Wesentlichen dasselbe wie die push-Operation des Stacks, daher haben Vektoren nur die push-Methode, um ein einzelnes Element anzufügen:

, wird der Vektor einen Fehler zurückgeben.

fn main() {
    let mut vector = vec![1, 2, 4, 8];
    vector.push(16);
    vector.push(32);
    vector.push(64);
    println!("{:?}", vector);
}

Ausgabe:

[1, 2, 4, 8, 16, 32, 64]

Die append-Methode wird verwendet, um einen Vektor an das Ende eines anderen anzuhängen:

Beispiel

fn main() {
    let mut v1: Vec<i32> = vec![1, 2, 4, 8];
    let mut v2: Vec<i32> = vec![16, 32, 64];
    v1.append(&mut v2);
    println!("{:?}", v1);
}

Ausgabe:

[1, 2, 4, 8, 16, 32, 64]

Die get-Methode wird verwendet, um einen Wert aus einem Vektor abzurufen:

Beispiel

fn main() {
    let mut v = vec![1, 2, 4, 8];
    println!("{}", match v.get(0) {
        Some(value) => value.to_string(),
        None => "None".to_string()
    });
}

Ausgabe:

1

Da die Länge eines Vektors nicht logisch abgeleitet werden kann, kann die get-Methode nicht garantieren, dass ein Wert zurückgegeben wird, daher ist der Rückgabetyp der get-Methode das Option-Enum, das None sein kann.

Dies ist eine sichere Möglichkeit, einen Wert zu erhalten, aber es ist etwas umständlich zu schreiben. Wenn garantiert werden kann, dass der Index innerhalb des gültigen Bereichs des Vektors liegt, kann auch die Array-Indizierungssyntax verwendet werden:

Beispiel

fn main() {
    let v = vec![1, 2, 4, 8];
    println!("{}", v[1]);
}

Ausgabe:

2

Aber wenn wir versuchen, auf v zuzugreifen

Über einen Vektor iterieren:

Beispiel

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
            println!("{}", i);
    }
}

Ausgabe:

100
32
57

Wenn Sie den Wert einer Variable während der Iteration ändern müssen:

Beispiel

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

String

Der Typ String wurde bis zu diesem Kapitel ausgiebig verwendet, daher sind viele seiner Methoden den Lesern bereits vertraut. Dieses Kapitel stellt hauptsächlich String-Methoden und UTF-8-Eigenschaften vor.

Erstellen eines neuen Strings:

let string = String::new();

Konvertieren von Basistypen in Strings:

let one = 1.to_string();         // 整数到字符串
let float = 1.3.to_string();     // 浮点数到字符串
let slice = "slice".to_string(); // 字符串切片到字符串

Strings, die UTF-8-Zeichen enthalten:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

Anhängen an einen String:

let mut s = String::from("run");
s.push_str("oob"); // 追加字符串切片
s.push('!');       // 追加字符

Strings mit dem +-Zeichen verketten:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

Diese Syntax kann auch String-Slices enthalten:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

Verwenden des format!-Makros:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

String-Länge:

let s = "hello";
let len = s.len();

Hier ist der Wert von len 5.

let s = "你好";
let len = s.len();

Hier ist der Wert von len 6. Da Chinesisch UTF-8-kodiert ist, ist jedes Zeichen 3 Bytes lang, daher ist die Länge 6. Rust unterstützt jedoch UTF-8-Zeichenobjekte. Wenn Sie die Anzahl der Zeichen zählen möchten, können Sie die Zeichenkette zunächst in eine Sammlung von Zeichen umwandeln:

let s = "hello你好";
let len = s.chars().count();

Hier ist der Wert von len 7, da insgesamt 7 Zeichen vorhanden sind. Das Zählen von Zeichen ist viel langsamer als das Ermitteln der Byte-Länge.

Iteration über eine Zeichenkette:

Beispiel

fn main() {
    let s = String::from(hello Chinese);
    for c in s.chars() {
        println!("{}", c);
    }
}

Ausgabe:

h
e
l
l
o
中
文

Abrufen eines einzelnen Zeichens aus einer Zeichenkette:

Beispiel

fn main() {
    let s = String::from(EN Chinese);
    let a = s.chars().nth(2);
    println!("{:?}", a);
}

Ausgabe:

Some('中')

Hinweis: Die Funktion nth ist eine Methode, um einen Wert aus einem Iterator abzurufen. Bitte verwenden Sie sie nicht auf diese Weise während der Iteration! Denn die Länge jedes Zeichens in UTF-8 ist nicht unbedingt gleich!

Wenn Sie eine Teilzeichenkette aus einer Zeichenkette extrahieren möchten:

Beispiel

fn main() {
    let s = String::from(EN Chinese);
    let sub = &s[0..2];
    println!("{}", sub);
}

Ausgabe:

EN

Beachte jedoch, dass diese Verwendung ein UTF-8-Zeichen aufteilen kann! Dies wird einen Fehler verursachen:

Beispiel

fn main() {
    let s = String::from(EN Chinese);
    let sub = &s[0..3];
    println!("{}", sub);
}

Ausgabe:

thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside '中' (bytes 2..5) of `EN中文`', src\libcore\str\mod.rs:2069:5 
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Map

Maps sind in anderen Sprachen weit verbreitet. Die am häufigsten verwendete ist die Schlüssel-Wert-Hashmap.

Erstellen einer neuen Hashmap:

Beispiel

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    println!("{}", map.get("color").unwrap());
}

Hinweis: Der Grund, warum wir den generischen Typ der Hashmap nicht deklariert haben, ist der automatische Typinferenzmechanismus von Rust.

Ausgabe:

red

Die insert- und get-Methoden sind die beiden am häufigsten verwendeten Methoden einer Map.

Maps unterstützen Iteratoren:

Beispiel

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    for p in map.iter() {
        println!("{:?}", p);
    }
}

Ausgabe:

("color", "red") 
("size", "10 m^2")

Die iterierten Elemente sind Tupel, die Schlüssel-Wert-Paare darstellen.

Rusts map ist eine sehr praktische Datenstruktur. Wenn man die insert-Methode verwendet, um ein neues Schlüssel-Wert-Paar hinzuzufügen, und derselbe Schlüssel bereits existiert, wird der entsprechende Wert direkt überschrieben. Wenn man „sicher einfügen“ möchte, das heißt, die Einfügung erst ausführen, nachdem bestätigt wurde, dass ein Schlüssel derzeit nicht existiert, kann man Folgendes tun:

map.entry("color").or_insert("red");

Das bedeutet: Wenn es kein Schlüssel-Wert-Paar mit dem Schlüssel "color" gibt, füge es hinzu und setze den Wert auf "red"; andernfalls wird es übersprungen.

Wenn bestätigt wurde, dass ein Schlüssel existiert, und man den zugehörigen Wert direkt ändern möchte, gibt es einen schnelleren Weg:

Beispiel

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, "a");
   
    if let Some(x) = map.get_mut(&1) {
        *x = "b";
    }
}
Weitere Erweiterungen