Golang 程式設計:使用運算子 (Operator)

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

    前言

    在程式設計中,運算子多使用符號 (symbol) 而非文字 (word) 來表示。運算子通常不能化約成更小的單位,在程式設計中將運算子視為該語言的基本指令。本文介紹 Go 語言中可用的運算子。

    自製簡易的斷言 (Assertion)

    許多程式設計教材會用格式化輸出 (formatted output) 來檢查程式,但筆者較推薦用斷言 (assertion) 來取代格式化輸出。

    當我們以斷言檢查程式碼時,若條件發生錯誤,斷言會發出警告訊息,有的版本的斷言還會直接終止程式。因為斷言是由電腦程式自主檢查,不需要人為判讀,日後易於自動化。此外,在撰寫斷言時,我們會把想檢查的項目直接寫入程式碼,藉由閱讀程式碼即可清楚表達出程式設計者的意圖。

    Go 語言沒有內建的斷言,有一些社群套件可用,像是 stretchr/testify 中的 assert。不過,只要了解斷言的原理,自己製作一個簡易版的斷言不會太困難。以下是一個實例,請讀者試著閱讀看看,我們會說明。

    package main
    
    import (
    	"fmt"
    	"os"
    	"runtime"
    )
    
    func main() {
    	assert(1+1 == 3, "1 + 1 should be 2")
    }
    
    // Home-made `assert`
    func assert(cond bool, msg string) {
    	_, f, l, _ := runtime.Caller(1)
    
    	if !cond {
    		fmt.Fprintf(os.Stderr, "Failed on (%s:%d): %s", f, l, msg)
    		os.Exit(1)
    	}
    }

    在主函式中,我們用自製的 assert 函式檢查 1 + 1 == 3 這個條件是否為真。由於這個條件為偽,實際執行程式時,會中止程式執行並吐出錯誤訊息。

    在自製的 assert 函式中,會接收 cond (布林) 和 msg (字串) 兩個參數。這個函式會執行簡單的斷言。

    我們先藉由 runtime 套件的 Caller 函式取得呼叫者的資訊,從中取出檔案名稱 f 和呼叫者所在的行數 l,並拋掉剩下的回傳值。在此處的呼叫者是主函式,被呼叫者是 assert 函式。由於我們預期呼叫的層次只有一層,故 Caller 函式的參數為 1

    實際執行斷言時,以 if 敘述檢查 cond 是否為真。當 cond 不為真時,在標準錯誤 (standard error) 印出錯誤訊息,接著呼叫 os 套件的 Exit 函式來提早離開主程式。

    如果讀者覺得這些細節過於複雜,也不用擔心。把 assert 函式當成立即可用的功能即可。我們在使用他人寫的函式時,並不會對每個函式都去追蹤其原始碼。只有在想了解函式的內部運作時,才會去觀看該函式的原始碼。

    代數運算子 (Arithmetic Operators)

    代數運算子用來進行基本的四則運算。以下是代數運算子:

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

    以下是簡短實例:

    package main
    
    import (
    	"fmt"
    	"math"
    	"os"
    	"runtime"
    )
    
    func main() {
    	assert(4+3 == 7, "4 + 3 should be 7")
    	assert(4-3 == 1, "4 - 3 should be 1")
    	assert(4*3 == 12, "4 * 3 should be 12")
    
    	assert(4/3 == 1, "4 / 3 should be 1")
    	assert(math.Abs(4.0/3.0-1.333333) < 0.00001, "4.0 / 3.0 should be 1.333333")
    
    	assert(4%3 == 1, "4 % 3 should be 1")
    }
    
    func assert(cond bool, msg string) {
    	_, f, l, _ := runtime.Caller(1)
    
    	if !cond {
    		fmt.Fprintf(os.Stderr, "Failed on (%s:%d): %s", f, l, msg)
    		os.Exit(1)
    	}
    }

    由於四則運算的原理相當簡單,讀者可試著自行閱讀程式碼。要注意在進行除法運算時,整數 (integer) 和浮點數 (floating point number) 會有不同的行為。

    由於浮點數內部儲存數字的方式和整數相異,浮點數運算可能會產生誤差,故我們在比較浮點數的運算結果時,不會直接用相等 == 來比較,而會確認運算結果的誤差在許可範圍內。我們使用 math 套件的 Abs 函式取得誤差的絕對值 (absolute value),以消除正負號所帶來的誤判。

    二元運算子 (Bitwise Operators)

    二元運算子也是代數運算子。但二元運算的概念和一般的代數運算有一些差異,故我們將其分開。以下是二元運算子:

    • &:bitwise AND
    • |:bitwise OR
    • ^:bitwise XOR
    • &^:bit clear
    • <<:左移 (left shift)
    • >>:右移 (right shift)

    由於二元運算在日常生活中不會接觸到,我們把運算過程寫在註解中,供讀者參考。

    package main
    
    import (
    	"fmt"
    	"os"
    	"runtime"
    )
    
    func main() {
    	/* 3 is 0011
    	   5 is 0101 */
    
    	/*    0011
    	   &) 0101
    	  ---------
    	      0001  */
    	assert((3 & 5) == 1, "3 & 5 should be 1")
    
    	/*    0011
    	   |) 0101
    	  ---------
    	      0111  */
    	assert((3 | 5) == 7, "3 | 5 should be 7")
    	
    	/*    0011
    	   ^) 0101
    	  ---------
    	      0110  */
    	assert((3 ^ 5) == 6, "3 ^ 5 should be 6")
    	
    	/* <<) 0000 0101
    	  ---------------
    	       0000 1010  */
    	assert((5 << 1) == 10, "5 << 1 should be 10")
    	
    	/* >>) 0000 0101
    	  ---------------
    	       0000 0010  */
    	assert((5 >> 1) == 2, "5 >> 1 should be 2")
    }
    
    func assert(cond bool, msg string) {
    	_, f, l, _ := runtime.Caller(1)
    
    	if !cond {
    		fmt.Fprintf(os.Stderr, "Failed on (%s:%d): %s", f, l, msg)
    		os.Exit(1)
    	}
    }

    二元運算會比一般的代數運算來得快,主要用於低階運算 (low-level computing) 中,日常生活不會用到,故我們不詳談。有興趣的讀者可自己查閱計算機概論等資料。

    比較運算子 (Comparison Operators)

    比較運算子用來比較兩項資料的大小,比較後會回傳布林值。以下是比較運算子:

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

    以下是簡短的實例:

    package main
    
    import (
    	"fmt"
    	"os"
    	"runtime"
    )
    
    func main() {
    	assert(4 == 4, "4 should be equal to 4")
    	assert(4 != 3, "4 should not be equal to 3")
    
    	assert(4 > 3, "4 should be greater than 3")
    	assert(4 >= 3, "4 should be greater than or equal to 3")
    
    	assert(4 < 5, "4 should be less than 5")
    	assert(4 <= 5, "4 should be less than or equal to 5")
    }
    
    func assert(cond bool, msg string) {
    	_, f, l, _ := runtime.Caller(1)
    
    	if !cond {
    		fmt.Fprintf(os.Stderr, "Failed on (%s:%d): %s", f, l, msg)
    		os.Exit(1)
    	}
    }

    邏輯運算子 (Logical Operators)

    邏輯運算子用於布林運算,包括以下三種運算子:

    • &&:且 (and)
    • ||:或 (or)
    • !:非 (not)

    以下是簡短的實例:

    package main
    
    import (
    	"fmt"
    	"os"
    	"runtime"
    )
    
    func main() {
    	assert((true && true) == true, "Wrong logic")
    	assert((true && false) == false, "Wrong logic")
    	assert((false && true) == false, "Wrong logic")
    	assert((false && false) == false, "Wrong logic")
    
    	assert((true || true) == true, "Wrong logic")
    	assert((true || false) == true, "Wrong logic")
    	assert((false || true) == true, "Wrong logic")
    	assert((false || false) == false, "Wrong logic")
    
    	assert((!true) == false, "Wrong logic")
    	assert((!false) == true, "Wrong logic")
    }
    
    func assert(cond bool, msg string) {
    	_, f, l, _ := runtime.Caller(1)
    
    	if !cond {
    		fmt.Fprintf(os.Stderr, "Failed on (%s:%d): %s", f, l, msg)
    		os.Exit(1)
    	}
    }

    由於運算子優先順序的議題,我們在進行邏輯運算時加上括號。

    位址運算子 (Address Operators)

    位址運算子有以下兩種:

    • *
    • &

    在不同情境,位址運算子有不同的意義。基礎的財經運算用不到位址運算子,日後有機會時會在介紹指標時用到位址運算子。

    接收運算子 (Receive Operator)

    接收運算子有以下符號:

    • <-

    接收運算子用在通道。基礎的財經運算用不到共時性程式,故不會用到接收運算子。

    型別轉換

    Go 語言為了避免不經意的錯誤,不能直接把不同型別的資料相結合。例如,在 Go 程式中不能把整數和浮點數直接相加。轉換型別的方式是用 T(x);像是 float(3) 會把整數 3 轉為浮點數 3.0

    結語

    藉由本文,讀者可了解 Go 語言的運算子使用方式。由於運算子是程式語言中的基本特性,讀者應試著練習一下運算子的用法。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk