整明白 Golang struct 例項化和賦值

Mr-houzi發表於2021-11-19

Golang 中 struct 各種例項化和賦值方式,一會兒是值傳遞,一會兒又是指標,讓人一頭霧水,於是我決定梳理一下,整個明白。

先定義一個結構體,下面結合程式碼進行講解。

package main

import "fmt"

type Person struct {
    Name string
    Age int
    Descprtion string
}

例項一

p 以最常規的方式例項化一個 struct,變數 p 得到一個 Person 結構體。

p := Person{}
p.Name = "小明"

fmt.Printf("p:%+v 變數地址:%p\n", p, &p)
fmt.Println("===========")

// result:
// p:{Name:小明 Age:0 Descprtion:} 變數地址:0xc000078480
// ===========

例項二

變數 p1 由 p 賦值而來,由於 Golang 語言是值傳遞,賦值後,對 p1 的修改並不會影響到 p;

從第一個輸出也可以看得出,Golang 的賦值並不存在像PHP變數賦值時的寫時複製(copy on write)機制。

p1 := p
fmt.Printf("p1:%+v 變數地址:%p\n", p1, &p1) // 不存在寫時複製
p1.Name = "小明p1"
fmt.Printf("p:%+v 變數地址:%p\n", p, &p)
fmt.Printf("p1:%+v 變數地址:%p\n", p1, &p1)
fmt.Println("===========")

// result:
// p1:{Name:小明 Age:0 Descprtion:} 變數地址:0xc0000784e0
// p:{Name:小明 Age:0 Descprtion:} 變數地址:0xc000078480
// p1:{Name:小明p1 Age:0 Descprtion:} 變數地址:0xc0000784e0
// ===========

例項三

利用取地址符將 p 的地址賦值給 p2,變數 p2 是一個指標,存放著指向 p 的地址。當 p2 修改了結構體中元素 Name 時,通過 p 訪問結構體對應的值也相應地發生了變化。

p2 := &p // 等同於 var p2 *Person = &p
fmt.Printf("p2:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p2, p2, &p2)
p2.Name = "小明p2"
fmt.Printf("p1:%+v 變數地址:%p\n", p, &p)
fmt.Printf("p2:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p2, p2, &p2)
fmt.Println("===========")

// result:
// p2:&{Name:小明 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078480 變數地址:0xc000006030
// p1:{Name:小明p2 Age:0 Descprtion:} 變數地址:0xc000078480
// p2:&{Name:小明p2 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078480 變數地址:0xc000006030
// ===========

例項四

變數 p3 由 new(Person) 得來。new 將開闢一塊記憶體,返回記憶體地址給 p3,也即 p3 是一個指向這塊記憶體的指標。

p3 是指向結構體的指標,它有兩種方式可以操作結構體,p3.Age = 3*p3 = Person{Name: "小明p3"}, 如果第二種方式後操作,將會覆蓋第一種方式對結構體的修改。

由於 p3 是指標,當 p3 賦值給 p5 時,p5 也將指向這塊記憶體地址。

p3 := new(Person)
fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
p3.Age = 3 // 等同於 (*p3).Age = 3
fmt.Println("================ 操作 Age ================")
fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
*p3 = Person{
    Name: "小明p3",
}
fmt.Println("================ 操作 Name ================")
fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
p5 := p3
fmt.Println("================ p5 := p3 ================")
fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
fmt.Printf("p5:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p5, p5, &p5)
p3.Name = "小明p3修改"
fmt.Println("================ p3 修改 ================")
fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
fmt.Printf("p5:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p5, p5, &p5)
fmt.Println("===========")

// result:
// p3:&{Name: Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006038
// ================ 操作 Age ================
// p3:&{Name: Age:3 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006038
// ================ 操作 Name ================
// p3:&{Name:小明p3 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006038
// ================ p5 := p3 ================
// p5:&{Name:小明p3 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006040
// ================ p3 修改 ================
// p3:&{Name:小明p3修改 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006038
// p5:&{Name:小明p3修改 Age:0 Descprtion:} 指標變數指向地址(變數值):0xc000078630 變數地址:0xc000006040
// ===========

示例五

p4 的例項化方式也將得到一個指標,這種例項化方式與 p3 的例項化是相同的,但 p4 的寫法更常使用。

p4 := &Person{
    Name: "小明p4",
}
fmt.Printf("%+v %p\n", p4, &p4)

// result:
// &{Name:小明p4 Age:0 Descprtion:} 0xc000006048

附完整程式碼:

package main

import "fmt"

type Person struct {
    Name string
    Age int
    Descprtion string
}

func main() {
    p := Person{}
    p.Name = "小明"

    fmt.Printf("p:%+v 變數地址:%p\n", p, &p)
    fmt.Println("===========")

    p1 := p
    fmt.Printf("p1:%+v 變數地址:%p\n", p1, &p1) // 不存在寫時複製
    p1.Name = "小明p1"
    fmt.Printf("p:%+v 變數地址:%p\n", p, &p)
    fmt.Printf("p1:%+v 變數地址:%p\n", p1, &p1)
    fmt.Println("===========")

    p2 := &p
    fmt.Printf("p2:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p2, p2, &p2)
    p2.Name = "小明p2"
    fmt.Printf("p1:%+v 變數地址:%p\n", p, &p)
    fmt.Printf("p2:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p2, p2, &p2)
    fmt.Println("===========")

    p3 := new(Person)
    fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
    p3.Age = 3 // 等同於 (*p3).Age = 3
    fmt.Println("================ 操作 Age ================")
    fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
    *p3 = Person{
        Name: "小明p3",
    }
    fmt.Println("================ 操作 Name ================")
    fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
    p5 := p3
    fmt.Println("================ p5 := p3 ================")
    fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
    fmt.Printf("p5:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p5, p5, &p5)
    p3.Name = "小明p3修改"
    fmt.Println("================ p3 修改 ================")
    fmt.Printf("p3:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p3, p3, &p3)
    fmt.Printf("p5:%+v 指標變數指向地址(變數值):%p 變數地址:%p\n", p5, p5, &p5)
    fmt.Println("===========")

    p4 := &Person{
        Name: "小明p4",
    }
    fmt.Printf("%+v %p\n", p4, &p4)
}

End!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章