位元詩人 [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 運算遵守以下邏輯:

p q Result
true true true
true false false
false true false
false false false

OR 運算遵守以下邏輯:

p q Result
true true true
true false true
false true true
false false false

NOT 運算遵守以下邏輯:

p Result
true false
false true

如果覺得記上述表格很困難,只要記得「所有條件為真時,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 :
* / %
+ -
<< >>
&
^
|
== != < > <= >=
&&
||
.. ...
<-
=

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

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。