[Nim] 程式設計教學:變數 (Variable) 和資料型別 (Data Type)

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

    前言

    在本文中,我們介紹 Nim 語言的基本概念,像是變數 (variable)、資料型別 (data type) 等。

    實字 (Literal)

    實字 (literal) 是指固定的值,簡單地說,就是在程式碼中寫死資料的值,例如:

    • true (布林)
    • 3.14159 (數字)
    • "Hello World" (字串)

    我們在初階程式中會看到許多實字,這是為了說明方便。實際上線的程式裡,程式裡不會寫死那麼多值,而會從外部讀入資料。

    變數 (Variable)

    變數 (variable) 是程式中用來操作資料 (data) 的語法特性。我們可以把變數視為資料的標籤,標籤是變數名稱,透過標籤來使用資料。

    變數具有三項要素:

    • 變數的資料型別 (data type)
    • 變數的識別字 (identifier)
    • 變數的值 (value)

    變數的可變性

    在 Nim 程式中,宣告變數可能會用以下三種保留字之一:

    • var:宣告後值仍會變動的變數 (mutable)
    • let:宣告後值不再更動 (immutable)
    • const:僅能放入編譯期常數,值不再更動 (immutable)

    這三種宣告變數的方式主要影響變數的可變性。

    var 使用例如下:

    var msg = "Hello World"
    
    echo msg
    

    相對於 varconst 是不可變的。若修改 const 所宣告的值會引發程式錯誤:

    const pi = 3.14159
    
    # Error
    pi = 4
    

    由於 const 僅能放入編譯時期常數,以下程式是錯誤的:

    # Error
    const input = readLine(stdin)
    

    因為 readLine(stdin) 實際的值要到執行期才會知道,故不能用 const 來宣告。

    相對於 const,前例可以使用 let 來宣告,讀取後會轉為唯讀 (read-only) 狀態:

    let input = readLine(stdin)
    

    若變數在宣告後就不會更動,建議用 let 取代 var,以避免後續的程式對變數做出不當修改。

    Nim 語言對變數可變性的調整來自於函數式程式的概念。

    識別字的名稱

    Nim 變數僅能以英文字母或 UTF-8 字母開頭, 不能 以底線開頭。以下是一些合法的 Nim 變數:

    • var
    • n1
    • aLongVariable
    • a_long_variable
    • 變數

    由於一些 UTF-8 字母無法直接以鍵盤輸入,雖然可以用 UTF-8 字母,實務上較不建議使用。

    要注意的是,除了第一個字母外,Nim 的變數名稱不區分大小寫 (case-insensitive),也不區分撰碼風格 (style-insensitive)。如下例:

    let aLongVariable = 3
    
    # Case-insensitive
    assert(alongvariable == 3)
    
    # Style-insensitive
    assert(a_long_variable == 3)
    

    這樣的特性是好是壞,仍待商榷,在 Nim 討論區對此也有一些爭議。目前來說,不過度依賴這個特性,在 Nim 程式碼中保持一貫的撰碼風格,對維護專案來說較為有利。

    筆者個人習慣用駝峰式 (camel case) 來撰寫程式,仿照 Java 的命名風格,即類別和型別名稱為大寫開頭,方法為小寫開頭;但讀者可採用自己習慣的風格,Nim 編譯器不會強制規定程式碼風格。

    保留字 (Keywords)

    以下是 Nim 的保留字 (keywords),保留字不能做為變數名稱:

    addr and as asm atomic
    bind block break
    case cast concept const continue converter
    defer discard distinct div do
    elif else end enum except export
    finally for from func
    generic
    if import in include interface is isnot iterator
    let
    macro method mixin mod
    nil not notin
    object of or out
    proc ptr
    raise ref return
    shl shr static
    template try tuple type
    using
    var
    when while with without
    xor
    yield
    

    雖然保留字看起來不少,不需要刻意背誦保留字,在撰寫程式的過程中自然記住即可。編輯器也會協助標出顏色 (syntax highlighting),協助我們辨識保留字。

    資料型別 (Data Types)

    資料型別 (data type) 規範值在程式中可用的操作,像是數字間可加減或字串可相接等。Nim 支援以下內建型別:

    • 布林 (boolean):bool
    • 數字 (number)
      • 整數 (integer)
        • 帶號整數:int8int16int32int64int
        • 無號整數:uint8uint16uint32uint64uint
      • 浮點數 (floating point)
        • float32float64float
    • 字元 (character):char
    • 字串 (string):string
    • 列舉 (enumeration):enum
    • 陣列 (array) 和序列 (sequence)
    • 元組 (tuple):tuple
    • 集合 (set)
    • 參考 (reference) 和指標 (pointer):refptr
    • 程序 (procedure):proc
    • 物件 (object):object
    • void

    我們會簡略地介紹這些型別,先大略看過一次即可,不需強記其中的細節。

    布林 (Boolean)

    布林用於邏輯判斷中,只有 truefalse 兩種。Nim 不會將 0 或空陣列等值自動轉為布林,需明確寫出條件式。

    數字 (Number)

    數字型別相對應於電腦內部的數字儲存方式,所以才會有多種型別。對於一般使用下,倒不需要斤斤計較,整數使用 int,無號整數使用 uint,浮點數使用 float 即可,這三種型別的範圍會根據電腦硬體而自動調整。

    數字可表示為十進位數、十六進位數 0[xX]、八進位數 0o、二進位數 0[bB] 等。

    以下程式可找出整數和無號整數的範圍:

    # Using built-in strutils package.
    import strutils
    
    echo "int8 max: $1, min: $2".format(int8.high, int8.low)
    echo "int16 max: $1, min: $2".format(int16.high, int16.low)
    echo "int32 max: $1, min: $2".format(int32.high, int32.low)
    echo "int64 max: $1, min: $2".format(int64.high, int64.low)
    echo "int max: $1, min: $2".format(int.high, int.low)
    
    echo "" # Line separator
    
    echo "uint8 max: $1, min: $2".format(uint8.high, uint8.low)
    echo "uint16 max: $1, min: $2".format(uint16.high, uint16.low)
    echo "uint32 max: $1, min: $2".format(uint32.high, uint32.low)
    echo "uint64 max: $1, min: $2".format(uint64.high, uint64.low)
    echo "uint max: $1, min: $2".format(uint.high, uint.low)
    
    echo "" # Line separator
    
    echo "float32 max: $1, min: $2".format(float32.high, float32.low)
    echo "float64 max: $1, min: $2".format(float64.high, float64.low)
    echo "float max: $1, min: $2".format(float.high, float.low)
    

    數字計算的過程中,超出其極值,稱為溢位 (overflow) 或下溢 (underflow)。現在 Nim 會抓出這樣的錯誤。參考以下錯誤的短例:

    # Wrongly correct.
    assert(int.high == int.high + 1)
    

    該程式引發以下錯誤:

    Error: unhandled exception: over- or underflow [OverflowError]
    

    對於較大的數字運算,較好的方式是用大數運算套件,如 bignum 等。大數運算是用軟體來模擬數字儲存和運算,不受電腦硬體的限制,但運算速度相對較慢。

    字元 (Character)

    Nim 的字元型別是 1 byte,無法用來表示 UTF-8 字元,僅能用來表示 ASCII 字元。由於 Nim 另外有字串型別,我們較少直接操作字元。Nim 另外有 Rune 型別,即支援 Unicode 字元,該型別位於 unicode 模組中。

    字串 (String)

    Nim 的字串有兩種型別,一種是平常使用的 string,一種則是和 C 字串相容的 cstring,一般情形下,使用前者即可。\ (backslash) 後的字串帶有特殊意義,像是 "\n" 在類 Unix 系統中代表換行字元,因此,要表示 Windows 路徑,需用 \\,像是 C:\\Program Files\\Nim 來表示。若前綴 r,表示原字串 (raw string),如 r"C:\Program Files\Nim"

    以下範例表示多行字串:

    let text = """foo
      bar
      baz
    """
    
    echo text
    
    # Remove indention
    echo text.unindent
    

    要注意的是,雖然字串帶有長度,但 UTF-8 字元一個字元可能對應一到多個 byte,所以 len 方法得到的長度可能不如預期,解決的方法是透過 unicode 套件來呼叫 runeLen 方法。如下例:

    import unicode
    
    assert("早安,你好".len == 15)
    assert("早安,你好".runeLen == 5)
    

    如果只用到英文的話,就不需要額外引用 unicode 套件。

    列舉 (Enum)

    列舉主要用於有限個值的型別,像是星期只有七個、月份只有十二個等。使用列舉的好處主要在於透過編譯器提供型別檢查。

    陣列 (Array) 和序列 (Sequence)

    這兩者是同質性、線性容器 (collection),我們將於後續文章中介紹。

    元組 (Tuple)

    這是異質性、線性容器,我們將於後續文章中介紹。

    集合 (Set)

    Nim 內建的集合不是通用的容器,僅能用於少數型別,主要的賣點是效率較佳,可搭配某些演算法來使用。不過,Nim 另外提供一個較為通用的集合容器,但效率則沒內建的集合來得好。我們將於後文介紹。

    參考 (Reference) 和指標 (Pointer)

    這個中文譯名可能會引人誤會,筆者在此說明一下。Nim 的參考 (reference) 和指標 (pointer) 在精神上都類似於 C 的指標,差別在於前者有受垃圾回收管理,而後者無。我們將於後文介紹參考和指標。

    註:Nim 的參考和 C++ 的參考是不同的概念。

    程序 (Procedure)

    由於 Nim 的程序可做為第一級物件 (first-class object),即值 (value),所以 Nim 也支援函數式程式。我們將於後續文章介紹。

    註:Nim 的程序即一般程式語言的函式。

    物件 (Object)

    物件是來建立新的自訂型別,也是物件導向程式的基礎。我們將於後續文章介紹。

    void

    void 用來表示程序沒有回傳值,但 Nim 的程序不需強制標示 void,標示 void 僅是為了程式碼的可讀性。

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