English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Generics sind ein unverzichtbarer Mechanismus für eine Programmiersprache.
C++ Generics werden in der Sprache mit "Muster" implementiert, während C keine Generikum-Mechanismen hat, was die Erstellung komplexer Typprojekte in der Sprache C schwierig macht.
Das Generikum ist ein Mechanismus der Programmiersprache, der zur Darstellung von Typabstraktionen dient und in der Regel für Klassen mit festgelegten Funktionen und noch nicht bestimmten Datentypen verwendet wird, wie z.B. Listen, Hash-Tabellen usw.
Dies ist eine Methode zur Auswahl排序 für Ganzzahlige Zahlen
fn max(array: &[i32]) -> i32 {}} let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] } fn main() { let a = [2, 4, 6, 3, 1]; println!("max = {}", max(&a)); }
Running Result:
max = 6
Dies ist ein einfaches Programm zum Finden des größten Wertes, das zur Verarbeitung von i verwendet werden kann32 Daten des numerischen Typs, die jedoch nicht für f verwendet werden können64 Datenartige Daten. Durch die Verwendung von Generics können wir diese Funktion so gestalten, dass sie verschiedene Typen nutzen kann. Tatsächlich können jedoch nicht alle Datentypen verglichen werden, daher dient der folgende Codeabschnitt nicht dazu, ausgeführt zu werden, sondern nur dazu, die Syntax des Generics der Funktion zu beschreiben:
fn max<T>(array: &[T]) -> T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] }
In den zuvor gelernten Enum-Klassen Option und Result sind generisch.
Strukturen und Enum-Klassen in Rust können Generik-Mechanismus implementieren.
struct Punkt<T> { x: T, y: T }
Dies ist ein Strukturtyp für Punkt-Koordinaten, T stellt die numerische Typ für die Punkt-Koordinaten dar. Wir können es so verwenden:
let p1 = Punkt { x: 1, y: 2}; let p2 = Punkt { x: 1.0, y: 2.0};
Beim Verwenden wird der Typ nicht deklariert, hier wird das automatische Typensystem verwendet, aber es ist nicht erlaubt, Typenkonflikte aufzutreten wie folgt:
let p = Punkt { x: 1, y: 2.0};
x und 1 bei der Bindung wurde T bereits auf i gesetzt32daher ist es nicht erlaubt, f zu wiederholen64 Typ. Wenn wir x und y mit verschiedenen Datentypen darstellen möchten, können wir zwei Generik-Bezeichner verwenden:
struct Punkt<T1, T2> { x: T1, y: T2 }
Methoden zur Darstellung von Generik in Enum-Klassen wie Option und Result:
enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), }
Strukturen und Enum-Klassen können Methoden definieren, daher sollten Methoden auch das Generik-Mechanismus implementieren, andernfalls können generische Klassen nicht effektiv durch Methoden operiert werden.
struct Punkt<T> { x: T, y: T, } impl<T> Punkt<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Punkt { x: 1, y: 2 }; println!("p.x = {}", p.x()); }
Running Result:
p.x = 1
Beachten Sie, dass nach dem impl-Schlüssel <T> erforderlich ist, da T davon abhängt. Wir können jedoch Methoden für eine der Generiken hinzufügen:
impl Punkt<f64> { fn x(&self) -> f64 {}} self.x } }
Die Generik der Block selbst hindert nicht die Fähigkeit der internen Methoden, Generik zu haben:
impl<T, U> Punkt<T, U> { fn mixup<V, W>(self, andere: Punkt<V, W>) -> Punkt<T, W> { Punkt { x: selbst.x, y: andere.y, } } }
Die Methode mixup verbindet die x-Koordinate eines Punkts Point<T, U> mit der y-Koordinate eines Punkts Point<V, W> zu einem neuen Punkt vom Typ Point<T, W>.
Das Konzept von Eigenschaften (trait) ist dem Konzept von Interfaces in Java (Interface) ähnlich, aber sie sind nicht vollständig gleich. Eigenschaften und Interfaces haben gemeinsam, dass sie beide eine Verhaltensnorm sind und zur Identifizierung der Methoden der Klassen verwendet werden können.
Eigenschaften werden in Rust mit dem trait dargestellt:
trait Descriptive { fn describe(&self) -> String; }
Descriptive legt fest, dass der Implementierer eine Methode haben muss, die describe(&self) ist -> String-Methode.
Wir verwenden es, um eine Struktur zu implementieren:
struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
Die Formatierung ist:
impl <Eigenschaftsname> für <Typname>
In Rust kann eine Klasse mehrere Eigenschaften implementieren, jeder impl-Block kann jedoch nur eine Eigenschaft implementieren.
Der Unterschied zwischen Eigenschaften und Interfaces: Interfaces können nur Methoden规范ieren und nicht Methoden definieren, während Eigenschaften Methoden als Standardmethoden definieren können, da sie "Standard" sind, können Objekte Methoden entweder neu definieren oder die Standardmethoden verwenden, ohne sie neu zu definieren:
trait Descriptive { fn describe(&self) -> String { String::from("[Object]") } } struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } } fn main() { let cali = Person { name: String::from("Cali"), age: 24 }; println!("{}", cali.describe()); }
Running Result:
Cali 24
Wenn wir den Inhalt des Blocks impl Descriptive for Person entfernen, ist das Resultat des Laufs:
[Object]
Oft müssen wir eine Funktion als Parameter übergeben, z.B. Callback-Funktionen, Ereignisse von Set-Buttons usw. In Java muss die Funktion durch ein Beispiel einer Klasse, die das Interface implementiert, übergeben werden, in Rust kann dies durch Übergeben von Eigenschaftsparametern erreicht werden:
fn output(object: impl Descriptive) { println!("{}", object.describe()); }
Jeder Objekt, das die Eigenschaft Descriptive implementiert, kann als Parameter dieser Funktion übergeben werden. Diese Funktion muss nicht wissen, ob das übergebene Objekt andere Eigenschaften oder Methoden hat, sie muss nur sicherstellen, dass es die Methoden gemäß der Spezifikation der Eigenschaft Descriptive hat. Natürlich kann innerhalb dieser Funktion auch keine anderen Eigenschaften oder Methoden verwendet werden.
Eigenschaftsparameter können auch mit dieser äquivalenten Syntax realisiert werden:}}
fn output<T: Descriptive>(object: T) { println!("{}", object.describe()); }
Dies ist ein stilähnlicher Syntax-Hinweis für Generics, der in Fällen sehr nützlich ist, bei denen mehrere Parameter-Typen Eigenschaften sind:
fn output_two<T: Descriptive>(arg1: T, arg2: T) { println!("{}", arg1.describe()); println!("{}", arg2.describe()); }
Wenn eine Eigenschaft als Typ dargestellt wird und mehrere Eigenschaften beteiligt sind, kann mit + Symbolische Darstellung, z.B.:
fn notify(item: impl Summary + Display) fn notify<T: Summary + Display>(item: T)
Hinweis:Wird nur zur Darstellung von Typen verwendet, bedeutet das nicht, dass es im impl-Block verwendet werden kann.
Komplexe Implementierungsbeziehungen können mit dem Schlüsselwort "where" vereinfacht werden, z.B.:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)
kann vereinfacht werden:
fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug
Nachdem diese Syntax verstanden wurde, kann das "Maximalwert"-Beispiel im Kapitel "Generics" wirklich umgesetzt werden:
trait Comparable { fn compare(&self, object: &Self) -> i8; } fn max<T: Comparable>(array: &[T]) -> &T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i].compare(&array[max_index]) > 0 { max_index = i; } i += 1; } &array[max_index] } impl Comparable for f64 {}} fn compare(&self, object: &f64) -> i8 {}} if &self > &object { 1 } else if &self == &object { 0 } else { -1 } } } fn main() { let arr = [1.0, 3.0, 5.0, 4.0, 2.0]; println!("maximum of arr is {}", max(&arr)); }
Running Result:
maximum of arr is 5
Tip: Since the second parameter of the compare function must be the same type as the type that implements the trait, the Self (note the capitalization) keyword represents the current type itself (not the example).
The format for traits as return values is as follows:
fn person() -> impl Descriptive { Person { name: String::from("Cali"), age: 24 } }
But there is one point, traits as return values only accept objects that have implemented the trait as return values, and all possible return value types in the same function must be completely identical. For example, structures A and B both implement the Trait, and the following function is incorrect:
fn some_function(bool bl) -> impl Descriptive { if bl { else { } return B {}; } }
The impl feature is very powerful, we can use it to implement class methods. However, for generic classes, sometimes we need to distinguish between the methods implemented by the generic itself to decide which method to implement next:
struct A<T> {} impl<T: B + C> A<T> { fn d(&self) {} }
This code declares that the A<T> type must be implemented effectively in the impl block only if T has already implemented the B and C traits.