Like Share Discussion Bookmark Smile

J.J. Huang   2025-03-14   Getting Started Golang 05.結構   瀏覽次數:次   DMCA.com Protection Status

Go | 結構的記憶體佈局解析

💬 簡介

在 Go 語言中,結構是用來組織資料的核心型別。不同於 Java 中的引用型別,Go 語言的結構資料在記憶體中是以連續區塊的形式存在的,這種記憶體佈局方式對效能有顯著的優勢。本文將深入解析結構的記憶體佈局,並介紹遞迴結構的應用,包括連結串列和二叉樹。

圖片來源:Gophers


🔍 結構的記憶體佈局

在 Go 語言中,結構及其成員資料是按順序連續儲存在記憶體中的。這意味著,即使結構內嵌了其他結構,這些結構的資料也會緊密排列在一起,這使得資料的存取更加高效。

相比之下,像 Java 中的物件,內部的屬性可能會分散在不同的記憶體位置,這樣的方式需要額外的間接引用,可能會降低效能。而 Go 的結構通過直接將資料以連續區塊的方式儲存,減少了這些開銷。

1️⃣ 使用 new 初始化的記憶體佈局

我們使用 new(Point) 創建了 Point 結構的指標 pPointer。使用 new() 時,Go 會為 Point 分配記憶體並將 xy 的欄位初始化為零值(對於 int 來說,零值是 0)。但與直接創建結構實例不同的是,new() 會返回一個指向這個結構的指標,而不是直接儲存資料。

  • 範例
    1
    2
    3
    4
    5
    6
    7
    8
    type Point struct {
    x, y int
    }

    func main() {
    pPointer := new(Point)
    // pPointer 會儲存結構資料的指標,並且欄位預設為零值
    }

圖片來源:Go入門指南

2️⃣ 使用結構實例初始化的記憶體佈局

📌 結構實例初始化的記憶體佈局

這個範例中,pPoint 結構的實例,並且它直接儲存在記憶體中的一個連續區塊中。這樣,xy 會緊密排列並儲存於一個記憶體區塊

  • 範例
    1
    2
    3
    4
    5
    6
    7
    type Point struct {
    x, y int
    }

    func main() {
    p := Point{x: 10, y: 20}
    }

📌 指標結構初始化的記憶體佈局

在這部分,當我們創建一個指向結構的指標時,指標會儲存在一個區塊,而實際的結構資料會儲存在另一個區塊。這與直接創建結構實例不同,因為指標是指向結構資料的位址。

  • 範例
    1
    2
    3
    4
    5
    6
    7
    type Point struct {
    x, y int
    }

    func main() {
    p := &Point{x: 10, y: 20}
    }

📝 當使用指標時,p 儲存的是指向結構資料的指標,它指向記憶體中的一個區塊,其中 xy 的值會被儲存。在這種情況下,p 的儲存空間僅佔用指標的大小(假設為 8 字節),而結構資料會儲存在另一塊區域中。

圖片來源:Go入門指南

3️⃣ 多層結構的存儲方式

📌 結構實例初始化的記憶體佈局

  • 範例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package main

    import "fmt"

    type Point struct {
    X int
    Y int
    }

    type Rect1 struct {
    Min, Max Point
    }

    func main() {
    // 創建一個 Rect1 實例,並初始化 Min 和 Max
    r := Rect1{
    Min: Point{X: 10, Y: 20},
    Max: Point{X: 50, Y: 60},
    }

    fmt.Println("Rectangle Min:", r.Min)
    fmt.Println("Rectangle Max:", r.Max)
    }

    📝 這個範例中,Rect1 結構包含了兩個 Point 結構。在記憶體中,MinMax 會被直接存儲在 Rect1 的連續記憶體區塊中。

📌 指標結構初始化的記憶體佈局

  • 範例:
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package main

    import "fmt"

    type Point struct {
    X int
    Y int
    }

    type Rect2 struct {
    Min, Max *Point
    }

    func main() {
    // 使用 new() 創建 Point 的指標
    minPoint := new(Point)
    maxPoint := new(Point)

    // 初始化這些指標指向的 Point 資料
    minPoint.X = 0
    minPoint.Y = 0
    maxPoint.X = 10
    maxPoint.Y = 20

    // 創建 Rect2 實例,並將 Min 和 Max 設為指向相應的 Point 指標
    r := Rect2{
    Min: minPoint,
    Max: maxPoint,
    }

    fmt.Println("Rectangle Min:", r.Min) // 顯示指標的地址
    fmt.Println("Rectangle Max:", r.Max) // 顯示指標的地址
    fmt.Println("Min X:", r.Min.X, "Min Y:", r.Min.Y) // 顯示指標指向的資料
    fmt.Println("Max X:", r.Max.X, "Max Y:", r.Max.Y) // 顯示指標指向的資料
    }

    📝 這個範例中,MinMaxPoint 型別的指標,因此在記憶體中,Rect2 會儲存指標的值,而不是直接儲存 Point 結構本身。

圖片來源:Go入門指南

4️⃣ 遞迴結構與記憶體佈局

Go 語言中的結構也可以引用自身,這讓我們能夠創建遞迴結構。這在實現如連結串列和二叉樹等資料結構時尤其有用。在這些資料結構中,每個節點通常會包含指向其他節點的指標,這些指標形成了資料元素之間的鏈接。

  • 範例:連結串列
    連結串列是由一系列節點組成,每個節點指向下一個節點。這是透過在結構中使用指標來實現的。

    1
    2
    3
    4
    type Node struct {
    data float64
    su *Node
    }

    📝 這個範例中,Node 結構包含一個 data 欄位存儲資料,並且有一個指標 su 指向下一個 Node 節點。這種方式讓我們可以動態地創建和操作結構,並且能夠節省記憶體,因為每個節點只需要存儲一個指標。

    圖片來源:Go入門指南

  • 範例:雙向連結串列
    雙向連結串列比單向連結串列多了一個指標,指向前一個節點。

    1
    2
    3
    4
    5
    type Node struct {
    pr *Node
    data float64
    su *Node
    }

    📝 這個範例中,Node 結構包含了兩個指標:pr 用於指向前一個節點,su 用於指向下一個節點。這樣,節點就可以在雙向鏈接中向前或向後遍歷。

  • 範例:二叉樹
    二叉樹是另一種常見的遞迴資料結構,每個節點最多有兩個子節點,分別是左子節點和右子節點。

    1
    2
    3
    4
    5
    type Tree struct {
    le *Tree
    data float64
    ri *Tree
    }

    📝 這個範例中,Tree 結構有兩個指標欄位:le 用於指向左子樹,ri 用於指向右子樹。這樣的結構使得每個節點能夠指向其左右的子樹,並且實現樹狀結構。

    圖片來源:Go入門指南


🔄 補充:遞迴結構的應用場景

🔗 鏈結串列(Linked List)

鏈結串列是一種線性資料結構,其中每個節點包含資料和指向下一個節點的指標。這是使用遞迴結構的經典例子。
每個節點是 Node 類型,它包含一個資料欄位和一個指向下個節點的指標,這樣就形成了鏈結結構。
在記憶體中,每個節點包含一個資料區塊和一個指向下一個節點的指標。這樣的結構非常靈活,可以在需要時動態地增刪節點。

  • 範例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    type Node struct {
    Data int
    Next *Node
    }

    func main() {
    // 創建一個包含資料的節點
    node1 := &Node{Data: 10}
    node2 := &Node{Data: 20}

    // 連接兩個節點
    node1.Next = node2

    // 可以通過指標遍歷鏈結串列
    fmt.Println("Node1 data:", node1.Data)
    fmt.Println("Node2 data:", node1.Next.Data)
    }

🌳 二叉樹(Binary Tree)

二叉樹是一種樹狀資料結構,每個節點最多有兩個子節點(左子節點和右子節點)。在 Go 中,這種結構也是使用遞迴結構來實現的。每個樹節點會持有指向左子節點和右子節點的指標。
在記憶體中,每個節點包含一個資料欄位以及兩個指向其他節點(左子節點和右子節點)的指標。

  • 範例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    type TreeNode struct {
    Value int
    Left *TreeNode
    Right *TreeNode
    }

    func main() {
    // 創建根節點
    root := &TreeNode{Value: 10}

    // 創建左子樹
    root.Left = &TreeNode{Value: 5}

    // 創建右子樹
    root.Right = &TreeNode{Value: 15}

    // 輸出根節點及其子節點的值
    fmt.Println("Root:", root.Value)
    fmt.Println("Left Child:", root.Left.Value)
    fmt.Println("Right Child:", root.Right.Value)
    }

🌍 圖(Graph)

圖是一種由節點和邊所構成的資料結構,其中邊連接著圖中的兩個節點。在 Go 中,如果要用結構來表示一個節點,並且節點間是有連結的,通常也會使用遞迴結構。這在處理複雜的圖結構(例如社交網絡、路徑搜尋等)時非常有用。
每個圖節點可能包含對其它節點的指標,並且這些指標可以是遞迴的,從而實現圖的結構。

  • 範例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type GraphNode struct {
    Value int
    Children []*GraphNode
    }

    func main() {
    // 創建節點
    node1 := &GraphNode{Value: 1}
    node2 := &GraphNode{Value: 2}
    node3 := &GraphNode{Value: 3}

    // 創建連結
    node1.Children = []*GraphNode{node2, node3}

    // 輸出節點的值及其子節點
    fmt.Println("Node1:", node1.Value)
    fmt.Println("Node1's Children:", node1.Children[0].Value, node1.Children[1].Value)
    }

🎯 總結

  • Go 語言中的結構資料是以連續區塊的形式存在於記憶體中的,這使得它們的效能優於像 Java 中的引用型別。
  • 當結構包含指標型別時,它們的記憶體佈局會有所不同,會儲存指標而非直接儲存資料。
  • 遞迴結構在實現複雜資料結構(如連結串列和二叉樹)時非常有用,並且能有效地使用記憶體。

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


註:以上參考了
Go
Go入門指南