[Rust] 程式設計教學:運算子 (Operator)

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    在程式語言中,運算子通常會用符號而非文字來表示。通常運算子無法拆分成更小的部分,可視為程式語言的基本指令。本章介紹 Rust 的運算子,這些運算子是 Rust 中實際執行工作的基礎元件。

    Rust 包括以下運算子:

    • 代數運算子
    • 二元運算子
    • 比較運算子
    • 布林運算子
    • 轉型運算子
    • 指派運算子
    • 複合指派運算子
    • 其他運算子

    本文將逐一介紹這些運算子。

    代數運算子

    包括以下運算子:

    • +:加
    • -:減
    • *:乘
    • /:除
    • %:取餘數

    見以下短例:

    fn main() {
        assert_eq!(2 + 1, 3);
        assert_eq!(5 - 1, 4);
        assert_eq!(2 * 3, 6);
        assert_eq!(6 / 3, 2);
    }
    

    在本例中,我們使用 assert_eq! 代替 println!,這個函式在程式兩邊不相等時會引發錯誤,好處是由程式替我們檢查值是否正確,而不需人工確認。本書中部分代碼會使用這種風格來表示。

    然而,由於電腦的限制,浮點數在電腦內的表示是不精確的。例如,0.3 - 0.2 的結果 不會剛好是 0.1 而有一個微小的誤差。見以下範例:

    fn main() {
        assert_eq!(0.3 - 0.2, 0.1);
    }
    

    本程式引發了以下錯誤,表示浮點數的運算是不精確的:

    thread 'main' panicked at 'assertion failed: `(left == right)` (left: `0.09999999999999998`, right: `0.1`)'
    

    比較好的方法,是比較兩數相減的誤差值,當誤差值的絕對值夠小時,表示條件成立。見以下範例:

    use std::f64;
     
    fn main() {
        assert!(f64::abs((0.3 - 0.2) - 0.1) < 1e-10);
    }
    

    本程式即可順利執行。在本程式中,我們使用 assert!,這個函式在其內條件式不為真時引發錯誤,使用方式類似 assert_eq!

    二元運算子

    包括以下運算子:

    • &:bitwise AND
    • |:bitwise inclusive
    • ^:bitwise exclusive OR
    • <<:左移
    • >>:右移

    二元運算是以二進位數來運算,和平常使用的十進位數不同,有興趣的讀者可參考計算機概論等相關書籍。以下是短例:

    fn main() {
        assert_eq!(0b0011u32 & 0b0101, 0b0001);
        assert_eq!(0b0011u32 | 0b0101, 0b0111);
        assert_eq!(1u32 << 5, 32);
    }
    

    平常我們較少使用二元運算,因為比較不直觀。但二元運算的速度比十進位運算快,對速度有所要求時可以考慮使用。

    比較運算子

    對於支援的型別,也可比較其大小。包括

    • ==:等於
    • !=:不等於
    • >:大於
    • <:小於
    • >=:大於等於
    • <=:小於等於

    以下是短例:

    fn main() {
        assert!(2 + 3 == 5);
        assert!(2 + 3 != 4);
        assert!(4 + 1 > 3);
        assert!(4 + 1 < 7);
        assert!(3 + 2 >= 4);
        assert!(3 + 2 <= 6);
    }
    

    布林運算子

    布林運算子是用來結合兩個以上的條件式,包括

    • &&:AND
    • ||:OR
    • !:NOT

    AND 運算遵守以下邏輯:

    pqResult
    truetruetrue
    truefalsefalse
    falsetruefalse
    falsefalsefalse

    OR 運算遵守以下邏輯:

    pqResult
    truetruetrue
    truefalsetrue
    falsetruetrue
    falsefalsefalse

    NOT 運算遵守以下邏輯:

    pResult
    truefalse
    falsetrue

    如果覺得記上述表格很困難,只要記得「所有條件為真時,AND 才為真;只要有條件為真時,OR 即為真」。結合上述基本邏輯,可撰寫更複雜的條件敘述。

    以下為範例:

    fn main() {
        assert_eq!(true && true, true);
        assert_eq!(true && false, false);
        assert_eq!(false && true, false);
        assert_eq!(false && false, false);
    
        assert_eq!(true || true, true);
        assert_eq!(true || false, true);
        assert_eq!(false || true, true);
        assert_eq!(false || false, false);
    
        assert_eq!(!true, false);
        assert_eq!(!false, true);
    }
    

    轉型運算子

    轉型運算子 as 是用來轉換資料的型別。由於 Rust 的安全設計,不能直接用整數和浮點數相互運算,而要透過明確的轉型,這和大部分的程式語言不同。下列程式看似正確:

    use std::f64;
     
    fn main() {
        assert!(f64::abs(1.0 - 1) < 1e-10);
    }
    

    本程式卻引發了以下錯誤:

    error[E0277]: the trait bound `{float}: std::ops::Sub<{integer}>` is not satisfied
    

    這個錯誤訊息,包含一個新的概念。在 Rust,運算子是透過 trait 的機制來達成,若沒有實作相關的 trait,則無法進行相關的運算。我們會在後續的章節介紹 trait。

    我們將程式改寫如下:

    use std::f64;
     
    fn main() {
        assert!(f64::abs(1.0 - (1 as f64)) < 1e-10);
    }
    

    經過轉型,本程式為 f64 型別間的運算,即可正確執行。

    指派運算子

    我們已經在前一章看過指派運算子 = 了,在宣告變數時通常也會一併賦值。

    fn main() {
        let x = 3;
    }
    

    注意:不要將指派運算子 = 和相等運算子 == 搞混。

    複合指派運算子

    複合指派運算子是將代數運算子或二元運子算以及指派運算子合併,簡化程式碼。例如,以下程式碼:

    fn main() {
        let mut x = 3;
        x = x + 1;
        assert_eq!(x, 4);
    }
    

    可以簡化為:

    fn main() {
        let mut x = 3;
        x += 1;  // Compound assignment
        assert_eq!(x, 4);
    }
    

    其他運算子

    這裡列出其他筆者未提到的運算子:

    • 負號運算子 -:將數字的正負號反轉
    • 解參考運算子 *:得到參考所指向的值
    • 參考運算子 && mut:得到某個值的參考

    我們會在後續的章節討論參考 (reference)。

    註:Rust 的參考類似 C 或 C++ 的指標。

    運算子優先順序

    Rust 運算子的優先順序,由高至低,如下:

    as :
    * / %
    + -
    << >>
    &
    ^
    |
    == != < > <= >=
    &&
    ||
    .. ...
    <-
    =
    

    筆者不會刻意去記運算子的優先順序。只要在程式碼中減少過度複雜的敘述,即可減少因運算子優先順序造成的混淆。如果某些敘述較複雜,用中括號 () 將運算優先順序提高即可。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk
    標籤: OPERATOR, RUST