日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Rust指南之泛型與特性詳解_Rust語言

作者:微涼秋意 ? 更新時間: 2022-11-20 編程語言

前言

在上篇Rust 文章中涉及到了泛型的知識,那么今天就來詳細介紹一下Rust 中的泛型與特性。泛型是一個編程語言不可或缺的機制,例如在C++ 語言中用模板來實現泛型。泛型機制是編程語言用于表達類型抽象的機制,一般用于功能確定、數據類型待定的類,如鏈表、映射表等。

1、泛型

泛型是具體類型或其他屬性的抽象代替:

  • 所編寫的泛型代碼并非最終程序運行的代碼,而是一種模板,含有一些"占位符"
  • 編譯器在編譯的時候將"占位符" 替換為具體的數據類型

優點:

提高代碼復用能力

  • 減少代碼重復

1.1、在函數中定義泛型

例如,定義一個對整型數字選擇排序的函數:

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 = [3, 4, 6, 8, 1];
    println!("max = {}", max(&a));
}
//運行結果:max = 8

這是一個簡單的取最大值程序,可以用于處理 i32 數字類型的數據,但無法用于 f64 類型的數據。

通過使用泛型我們可以使這個函數可以利用到各個類型中去:

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]
}

實際上,并不是所有的數據類型都可以比大小。當T被自定義的結構體或者枚舉等類型替代時,這段代碼肯定就會報錯。所以這段代碼并不是用來運行的,而是用來描述一下函數泛型的語法格式。

1.2、結構體中的泛型

結構體泛型舉例:點坐標結構體,T 表示描述點坐標的數據類型:

struct Point<T> {
    x: T,
    y: T
}

fn main() {
    let p1 = Point {x: 1, y: 2};
	let p2 = Point {x: 1.0, y: 2.0};
}

使用時并沒有聲明類型,這里使用的是自動類型機制,但不允許出現類型不匹配的情況如下:

let p = Point {x: 1, y: 2.0};

x 與 1 綁定時就已經將 T 設定為 i32,所以不允許再出現 f64 的類型。如果我們想讓 x 與 y 用不同的數據類型表示,可以使用兩個泛型標識符

struct Point<T1, T2> {
    x: T1,
    y: T2
}

1.3、枚舉類中的泛型

在枚舉類中表示泛型的方法諸如 OptionResult

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

枚舉類的具體使用可參考本專欄的文章,有較為詳細的講解。

1.4、方法中的泛型

結構體與枚舉類都可以定義方法,那么方法也應該實現泛型的機制,否則泛型的類將無法被有效的方法操作。

struct Point<T> {
    x: T,
    y: T
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 2, y: 4 };
    println!("p.x = {}", p.x());
}
//運行結果:p.x = 1

注意,impl 關鍵字的后方必須有 <T>,因為它后面的 T 是以之為榜樣的。

我們也可以為其中的一種泛型添加方法:

impl Point<i64> {
    fn x(&self) -> i64 {
        self.x
    }
}

impl 塊本身的泛型并沒有阻礙其內部方法具有泛型的能力

例如:

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

方法 mixup 將一個 Point<T, U> 點的 x 與 Point<V, W> 點的 y 融合成一個類型為 Point<T, W> 的新點。

2、特性

特性(trait)概念接近于 Java 中的接口(Interface),但兩者不完全相同。特性與接口相同的地方在于它們都是一種行為規范,可以用于標識哪些類有哪些方法。

特性在 Rust 中用 trait 表示:

trait Descript {
    fn describe(&self) -> String;
}

Descript 規定了實現者必需有 describe(&self) -> String 方法。

例如:

struct Person {
    name: String,
    age: u16
}

impl Descript for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

格式:

  • impl <特性名> for <所實現的類型名>

Rust 同一個類可以實現多個特性,每個 impl 塊只能實現一個

2.1、默認特性

這是特性與接口的不同點:

  • 接口只能規范方法而不能定義方法
  • 特性可以定義方法作為默認方法
    • 因為是"默認",所以對象對于是否重新定義方法是自由的

舉個例子:

trait Descript {
    fn describe(&self) -> String {
        String::from("[Object]")
    }
}

struct Person {
    name: String,
    age: u8
}

impl Descript for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

fn main() {
    let zhangsan = Person {
        name: String::from("kuangtu"),
        age: 28
    };
    println!("{}", zhangsan.describe());
}
//運行結果:kuangtu 28

如果將 impl Descript for Person 塊中的內容去掉,那么運行結果就是 [Object]

2.2、特性做參數

很多情況下我們需要傳遞一個函數做參數,例如回調函數、設置按鈕事件等。在 Java 中函數必須以接口實現的類實例來傳遞,在 Rust 中可以通過傳遞特性參數來實現:

fn output(object: impl Descript) {
    println!("{}", object.describe());
}

任何實現了 Descript 特性的對象都可以作為這個函數的參數,這個函數沒必要知道傳入對象有沒有其他屬性或方法,只需要了解它一定有 Descript 特性規范的方法就可以了。當然,此函數內也無法使用其他的屬性與方法。

特性參數還可以用這種等效語法實現:

fn output<T: Descriptive>(object: T) {
    println!("{}", object.describe());
}

這是一種風格類似泛型的語法糖,這種語法糖在有多個參數類型均是特性的情況下十分實用:

fn output_two<T: Descriptive>(arg1: T, arg2: T) {
    println!("{}", arg1.describe());
    println!("{}", arg2.describe());
}

特性作類型表示時如果涉及多個特性,可以用 + 符號表示,例如:

fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)

注意:僅用于表示類型的時候,并不可以在 impl 塊中使用。

復雜的實現關系可以使用 where 關鍵字簡化,例如:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)

可以簡化為:

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug

泛型通過與特性的結合可以實現上面任意類型值比較的案例:

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, 7.0, 4.0, 2.0];
    println!("maximum of arr is {}", max(&arr));
}
//運行結果:maximum of arr is 7

Tip: 由于需要聲明 compare 函數的第二參數必須與實現該特性的類型相同,所以 Self (注意大小寫)關鍵字就代表了當前類型(不是實例)本身。

2.3、特性做返回值

格式如下:

fn person() -> impl Descript {
    Person {
        name: String::from("Cali"),
        age: 24
    }
}

注意:特性做返回值只接受實現了該特性的對象做返回值且在同一個函數中所有可能的返回值類型必須完全一樣。

比如結構體 A 與結構體 B 都實現了特性 Trait,下面這個函數就是錯誤的:

fn some_function(bool bl) -> impl Descriptive {
    if bl {
        return A {};
    } else {
        return B {};
    }
}

原文鏈接:https://blog.csdn.net/m0_58618795/article/details/127190345

欄目分類
最近更新