Like Share Discussion Bookmark Smile

J.J. Huang   2025-02-20   Getting Started Golang 02.資料結構   瀏覽次數:次   DMCA.com Protection Status

Go | 初步了解「空值」應用

💬 簡介

在 Go 語言中,nil 是一個特殊的值,代表著空、無效或不存在。nil 常見於指標、切片、映射、通道等引用型別中,並且其操作方式在程式中有著不可忽視的影響。

本文將介紹 nil 的概念,並探討如何在 Go 中合理地使用和處理空值。

圖片來源:Gophers(地鼠造型的原創者為 Renee French)


✨ nil 的基本概念

在 Go 中,nil 是一個預設值,表示沒有分配或指向任何資料。不同型別的變數在初始化時如果沒有明確賦值,會自動被設為 nilnil 並非單一數值,而是對於不同型別的特殊表示:

  • 指標型別:表示指向空地址。
  • 切片、映射、通道:表示這些資料結構尚未指向任何有效資料。
  • 函式:表示空的函式,即沒有指向實作的函式。
  • 介面:表示介面未指向任何具體實作。

1️⃣ 宣告與初始化為 nil

在 Go 中,當你宣告一個變數但不給它賦值時,對於指標、切片、映射等型別,這些變數會自動初始化為 nil

1
2
3
4
5
6
7
8
9
var ptr *int
var slice []int
var m map[string]int
var ch chan int

fmt.Println(ptr) // 輸出: <nil>
fmt.Println(slice) // 輸出: []
fmt.Println(m) // 輸出: map[]
fmt.Println(ch) // 輸出: <nil>

📝 如上所示,指標、切片、映射和通道型別在初始化時若未賦值,則為 nil

2️⃣ 使用 nil 進行檢查

有時候,我們需要檢查變數是否為 nil,這通常發生在處理指標或其他引用型別時。如果沒有適當處理 nil 值,可能會導致運行時錯誤。

1
2
3
4
5
6
7
if ptr == nil {
fmt.Println("指標為 nil")
}

if len(slice) == 0 {
fmt.Println("切片為空")
}

📝 在這個範例中,我們檢查了指標是否為 nil,並判斷切片是否為空。

3️⃣ nil 無法直接比較

在 Go 中,nil 是無法進行比較的。以下是一個簡單的例子,試圖比較 nilnil

1
2
3
4
5
6
7
8
9
package main

import (
"fmt"
)

func main() {
fmt.Println(nil == nil) // 錯誤!Go 中 nil 不能直接比較
}

執行結果會顯示錯誤訊息:

1
2
# command-line-arguments
.\main.go:8:21: invalid operation: nil == nil (operator == not defined on untyped nil)

這和 Python 等動態語言的行為不同。在 Python 中,兩個 None 值會始終相等:

1
2
>>> None == None
True

🛠️ nil 在不同型別中的應用

1️⃣ 指標與 nil

指標在 Go 中是一個常見的型別,表示某個變數的記憶體地址。如果指標為 nil,則代表它不指向任何有效的記憶體位置。

  • 範例:檢查指標是否為 nil
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import "fmt"

    func main() {
    var ptr *int
    if ptr == nil {
    fmt.Println("指標為 nil,尚未初始化")
    } else {
    fmt.Println("指標指向資料")
    }
    }

    📝 在這個範例中,我們檢查了指標是否為 nil,如果為 nil 則顯示未初始化。

2️⃣ 切片與 nil

切片是 Go 中的動態數組類型,當切片為 nil 時,它代表的是空的切片,這通常與容量為 0 的切片區別開來。

  • 範例:切片為 nil
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import "fmt"

    func main() {
    var slice []int
    if slice == nil {
    fmt.Println("切片為 nil")
    }
    }

    📝 在這個範例中,切片 slice 為 nil,並未指向任何資料。

3️⃣ 映射與 nil

映射(map)是一種無序的鍵值對集合。在 Go 中,map 若為 nil,則無法進行操作,並會導致運行時錯誤。

  • 範例:檢查映射是否為 nil
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import "fmt"

    func main() {
    var m map[string]int
    if m == nil {
    fmt.Println("映射為 nil,尚未初始化")
    }
    }

    📝 如果映射為 nil,我們無法對其進行存取操作,必須先初始化。

4️⃣ 函式與 nil

函式型別在 Go 中也有 nil 值,當函式為 nil 時,表示它沒有指向任何具體的實作。

  • 範例:檢查函式是否為 nil
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import "fmt"

    func main() {
    var f func()
    if f == nil {
    fmt.Println("函式為 nil")
    }
    }

    📝 在這個範例中,我們檢查了一個函式變數是否為 nil


🚀 nil 的應用範例

1️⃣ 使用 nil 處理錯誤

nil 常用於函式返回錯誤時的處理。在 Go 中,許多標準函式會返回 nil 或錯誤類型作為返回值,來表示某些操作是否成功。

  • 範例:返回 nil 和錯誤處理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    import "fmt"

    func findValue(m map[string]int, key string) (int, error) {
    value, ok := m[key]
    if !ok {
    return 0, fmt.Errorf("鍵 %s 不存在", key)
    }
    return value, nil
    }

    func main() {
    m := map[string]int{"a": 1, "b": 2}
    value, err := findValue(m, "c")
    if err != nil {
    fmt.Println("錯誤:", err)
    } else {
    fmt.Println("找到的值:", value)
    }
    }

    📝 在這個範例中,我們使用 nil 和錯誤處理,當 map 中找不到對應的鍵時,我們返回錯誤。


📚 Go 中的零值與 nil 的區別

在 Go 中,變數的初始值(零值)會依據其資料型別不同而有所不同,並不全都是 nil

1️⃣ 基本資料型別的零值

在 Go 中,對於 基本資料型別(如 intfloat64boolstring)而言,變數在宣告但未初始化時會被賦予各自的「零值」,而非 nil

  • int 的零值是 0
  • float64 的零值是 0.0
  • bool 的零值是 false
  • string 的零值是空字串 ""

2️⃣ 指標與引用型別的零值

然而,指標類型(例如 *int*string)以及 引用型別(如 切片映射通道函式介面)則會有 nil 作為它們的零值。

  • 指標類型(*int*string 等)會預設為 nil,表示指向的記憶體位置尚未初始化。

  • 切片([]T)、映射(map[K]V)、通道(chan T)、函式(func())和 介面(interface{})等資料結構類型,若未初始化,也會是 nil

  • 範例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    import "fmt"

    func main() {
    var a int // 零值是 0
    var b float64 // 零值是 0.0
    var c bool // 零值是 false
    var s string // 零值是 ""
    var p *int // 零值是 nil
    var arr []int // 零值是 nil
    var m map[int]string // 零值是 nil

    fmt.Println(a) // 0
    fmt.Println(b) // 0.0
    fmt.Println(c) // false
    fmt.Println(s) // 空字串
    fmt.Println(p) // nil
    fmt.Println(arr) // nil
    fmt.Println(m) // nil
    }

    📝 這說明了在 Go 中,只有 指標類型和某些引用型別(如切片、映射、通道等)會預設為 nil,而其他的基本資料型別則會有對應的零值。


💡 進階技巧與注意事項

🧭 進階技巧:nil 的大小

不同型別的 nil 值在記憶體中的佔用大小是不同的。這是因為每種資料型別都有不同的記憶體佈局。以下範例展示了各種資料型別的 nil 值所佔用的記憶體大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"unsafe"
)

func main() {
var p *struct{}
fmt.Println(unsafe.Sizeof(p)) // 8

var s []int
fmt.Println(unsafe.Sizeof(s)) // 24

var m map[int]bool
fmt.Println(unsafe.Sizeof(m)) // 8

var c chan string
fmt.Println(unsafe.Sizeof(c)) // 8

var f func()
fmt.Println(unsafe.Sizeof(f)) // 8

var i interface{}
fmt.Println(unsafe.Sizeof(i)) // 16
}

📝 執行結果顯示了不同資料型別的 nil 值佔用的記憶體大小。這些大小會根據架構的不同有所差異,

  • 但通常在 64 位架構下會有如下結果:
    • 指標類型:8 bytes
    • 切片、映射、通道、函式:8 bytes
    • 介面類型:16 bytes

⚠️ nil 的陷阱

  • nil 與空切片區別:切片為 nil 和長度為 0 的切片並不相同。nil 切片沒有分配記憶體,長度為 0 的切片則已分配記憶體,只是其長度為 0。
  • 映射的初始化:map 型別若為 nil,則無法進行 map[key] 操作,需要先使用 make 函式初始化。
  • 介面與 nil:介面型別的 nil 不僅表示沒有值,還表示介面中內部的結構體指標也是 nil

🎯總結

nil 是 Go 中一個非常重要且常用的概念,它代表了「空」或「無效」,並在許多資料結構中扮演重要角色。理解 nil 的應用能幫助我們在處理指標、切片、映射等資料結構時更加小心並避免錯誤。

回顧以上內容,我們學習了如何檢查並處理 nil,以及如何避免常見的陷阱。隨著你對 nil 概念的熟悉,將能夠更靈活地應用它。

最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。


註:以上參考了
Go