沙滩星空的博客沙滩星空的博客

彻底搞懂Go语言中指针的使用

指针简介

一个 指针变量A 指向了另一个 变量B内存地址。这个内存地址,往往是在内存中存储的 变量B的值的起始位置

可以理解为: 指针变量A 所在的内存地址,保存着 变量B 的内存地址。

如果对指针的使用,理解得还很朦胧。希望此文可以拨云见日。

Go语言数据类型

值类型: int, float, bool, string, 数组, 结构体

引用类型(指针类型): 指针、切片、map、函数、channel

变量的值传递和引用传递

  • 变量 值传递 到函数内,函数内对变量的改动,无法改变 函数外原有变量的值.
  • 变量 引用传递 到函数内,函数内对变量的改动,可以改变 函数外原有变量的值.

对于指针变量,只有改变指针所引用的地址,才算是改变了指针的值。

因此,指针类型 的变量,进行 值传递 时,指针所指向的变量的值 是允许更改的。

因为 指针类型 的变量,保存着另一个 变量的地址。改变另一个 变量的值,该地址并未发生改变。

go-pointer.png

如上图所示: 变量b 指向 变量a的地址,即 &a。而 &b 则指向 变量b的地址&b指针的指针

代码语句: b := &a, 让系统为 指针变量b ,开辟了一块独立的内存空间来保存它。

下面以 结构体指针切片指针 为例,分别展开论述。如果懒得看代码,可直接看最下面的结论。

结构体指针

package main

import "fmt"

type Car struct {
    Brand string
    Attr  map[string]interface{}
}

func main() {
    c := Car{Brand: "BYD"}
    UpdateWithValue(c)
    fmt.Printf("---Car(%+v)---c.Attr(%p)---UpdateWithValue--\n", c, c.Attr)
    UpdateWithPointer(&c)
    fmt.Printf("---Car(%+v)---c.Attr(%p)--UpdateWithPointer--\n", c, c.Attr)
    ChangeMap(c)
    fmt.Printf("---Car(%+v)---c.Attr(%p)--ChangeMap--\n", c, c.Attr)
}

func UpdateWithValue(c Car) {
    c.Attr = map[string]interface{}{"brand": "BENZ", "change": "UpdateWithValue"}
    c.Brand = "BENZ"
    c.Attr["BENZ111"] = "B111"
}

func UpdateWithPointer(c *Car) {
    // 任意更改有效
    c.Attr = map[string]interface{}{"brand": "AUDI", "change": "UpdateWithPointer"}
    c.Brand = "AUDI"
    c.Attr["ext"] = "extAUDI"
}

func ChangeMap(c Car) {
    // 引用类型的属性,不改变引用指针,改变指针对应的值,更改有效。
    // 更改字典键值对,未改变指针。更改有效
    c.Attr["brand"] = "BUICK"
    c.Attr["change"] = "ChangeMap"

    // 字典重新赋值,改变了指针。故赋值无效。
    c.Attr = map[string]interface{}{"brand": "BUICK", "change": "ChangeMap"}
    // 重新赋值后,已更改了指针。后续再更改字典键值对,更改同样无效。
    c.Attr["change"] = "ChangeMapChange"

    // 字符串等值传递的属性,更改无效
    c.Brand = "BUICK"
}

结构体值传递时,指针属性不可重新赋值,但可更改键值对。

Go练兵场在线演示

输出:

---Car({Brand:BYD Attr:map[]})---c.Attr(0x0)---UpdateWithValue--
---Car({Brand:AUDI Attr:map[brand:AUDI change:UpdateWithPointer ext:extAUDI]})---c.Attr(0xc0000a01b0)--UpdateWithPointer--
---Car({Brand:AUDI Attr:map[brand:BUICK change:ChangeMap ext:extAUDI]})---c.Attr(0xc0000a01b0)--ChangeMap--

结论

  • 结构体指针传递时,结构体各属性的值可以任意更改
  • 结构体值传递时,值类型的属性(字符串,数字等),不可更改
  • 结构体值传递时,引用类型的属性(指针,字典等),不可更改原有引用(不可重新赋值),但可修改引用指针所对应的值(修改原有字典的键值对)

切片指针

package main

import "fmt"

func main() {
    var val []int
    for i := 0; i < 3; i++ {
        UpdateWithValue(val, i)
        fmt.Printf("--val(%+v)---point(%p)--cap(%d)--UpdateWithValue--\n", val, val, cap(val))
        UpdateWithPointer(&val, i)
        fmt.Printf("--val(%+v)---point(%p)--cap(%d)--UpdateWithPointer--\n", val, val, cap(val))
    }

    fmt.Println("------------使用make函数指定切片长度------------------------")

    vv := make([]int, 3)
    for j := 0; j < 3; j++ {
        UpdateWithValue(vv, j)
        fmt.Printf("--val(%+v)---point(%p)--cap(%d)--UpdateWithValue--\n", vv, vv, cap(vv))
        UpdateWithPointer(&vv, j)
        fmt.Printf("--val(%+v)---point(%p)--cap(%d)--UpdateWithPointer--\n", vv, vv, cap(vv))
    }

}

func UpdateWithValue(val []int, add int) {
    if len(val) > 1 {
        val[0] = 33
    }
    val = append(val, add)
}

func UpdateWithPointer(val *[]int, add int) {
    if len(*val) > 1 {
        (*val)[0] = 333
    }
    *val = append(*val, add)

}

Go练兵场在线演示

输出:

--val([])---point(0x0)--cap(0)--UpdateWithValue--
--val([0])---point(0xc0000b8010)--cap(1)--UpdateWithPointer--
--val([0])---point(0xc0000b8010)--cap(1)--UpdateWithValue--
--val([0 1])---point(0xc0000b8040)--cap(2)--UpdateWithPointer--
--val([33 1])---point(0xc0000b8040)--cap(2)--UpdateWithValue--
--val([333 1 2])---point(0xc0000be020)--cap(4)--UpdateWithPointer--
------------使用make函数指定切片长度------------------------
--val([33 0 0])---point(0xc0000ba018)--cap(3)--UpdateWithValue--
--val([333 0 0 0])---point(0xc0000bc060)--cap(6)--UpdateWithPointer--
--val([33 0 0 0])---point(0xc0000bc060)--cap(6)--UpdateWithValue--
--val([333 0 0 0 1])---point(0xc0000bc060)--cap(6)--UpdateWithPointer--
--val([33 0 0 0 1])---point(0xc0000bc060)--cap(6)--UpdateWithValue--
--val([333 0 0 0 1 2])---point(0xc0000bc060)--cap(6)--UpdateWithPointer--

结论

  • 切片为引用类型(指针类型)
  • 不想改变切片容量时,传递切片使用 值传递
  • 切片因长度增大而扩容(切片容量改变)时,切片指针(切片为指针类型)会改变
  • 使用 append 函数可能改变切片容量,传递切片应使用 指针传递

encoding/json 包

package main

import (
    "encoding/json"
    "fmt"
)

type Man struct {
    Age  int
    Name string
}

func main() {
    man := Man{}
    jjj := `{"Age":26, "Name":"Tom"}`
    json.Unmarshal([]byte(jjj), &man)
    fmt.Printf("Hello(%+v)\n", man)
    mann, err := json.Marshal(man)
    fmt.Printf("--jsonMan(%s)--err(%v)---\n", string(mann), err)

    mannn, errr := json.Marshal(&man)
    fmt.Printf("--jsonMan(%s)--err(%v)---\n", string(mannn), errr)
}

输出:

Hello({Age:26 Name:Tom})
--jsonMan({"Age":26,"Name":"Tom"})--err(<nil>)---
--jsonMan({"Age":26,"Name":"Tom"})--err(<nil>)---
  • 解析json字符串: 使用指针类型的变量
  • Go变量转json: 变量指针非指针均可

Go语言数据类型 https://blog.csdn.net/weixin_44211968/article/details/121221309
未经允许不得转载:沙滩星空的博客 » 彻底搞懂Go语言中指针的使用

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址