Go指针语法详细解析 | Go语言编程指南

silverwq
2022-07-12 / 0 评论 / 317 阅读 / 正在检测是否收录...

使用场景

使用指针,出于以下两个目的:

  1. 指针变量复制和拷贝的是地址,所以比较轻量,对于大的结构体变量,这样拷贝会比较快。
  2. 在生命周期的任何位置可以修改原变量的值

示例

函数内部改变外部变量值

package main

import "fmt"

func main() {
    v1 := "武沛齐"
    // 参数传递,是将值复制一份给形参
    // 所以形参里面是个副本,修改里面的值不会改变外面的值的值
    changeData(&v1)
    // 哈哈哈
    fmt.Println(v1)
}

func changeData(data *string) {
    *data = "哈哈哈"
}

获取控制台输入:

package main

import "fmt"

func main() {
    var userName string
    fmt.Println("请输入你的名字:")
    fmt.Scanf("%s", &userName) // 在Scanf里改变userName的值
    // 打印输入的值
    fmt.Println(userName)
}

指针类型声明

  1. 指针类型和其它的类型一样,也是个数据类型,任何类型(包括指针类型)都有一个对应的指针类型。
  2. 声明指针类型,只需要在该类型前面加个 *,默认零值是 nil
  3. 普通类型的变量存储的是该类型的值,指针类型的变量存储的是该类型的内存地址! 😎
var v1 string // string类型
var v2 *string // string类型的指针类型

var v1 int // int类型
var v2 *int // int类型的指针类型

举例说明,指针的指针类型,这个不常用,了解即可。

因为指针变量本身也是个变量,自己也有地址,所以可以对指针类型取地址。

name := "武沛齐"
var p1 *string = &name
// 两个**表示,是指针的指针类型
var p2 **string = &p1
// 3个星表示是指针的指针的指针类型
var p3 ***string = &p2

// 指针的指针的指针类型,取值要相应的加3个*
***p3 = "哈哈哈"
fmt.Println(name)

取地址

所有变量都有内存地址,只需要在对应的变量前面加个 & 就可以取变量的地址。这个地址可以存在对应指针类型变量上。

var name string = "武沛齐"
var pointer *string = &name

还可以用 new 函数进行取值,可以在内存中初始化一个该类型的变量,并且返回该变量的内存地址。

func main() {
    p := new(int)
    fmt.Println(p) // 0xc00000e0d8
    *p = 100
    fmt.Println(*p)
}

空指针

因为指针类型的零值是 nil,即:空指针。如果忘记初始化,就容易 panic:

  1. 空指针并不会有语法错误,运行到该位置的时候才会 panic(panic: runtime error: invalid memory address or nil pointer dereference),所以需要特别注意。
  2. 空值代表什么都没有,使用空变量会 panic:

以下空指针会 panic

调用空指针的成员方法

package main

import "fmt"

type A struct {
}

func (a A) Hello() {
    fmt.Println("hello world")
}

type B struct {
    a *A
}

func main() {
    b := B{} // 初始化的时候,b的零值是nil
    b.a.Hello() 
}

直接使用空指针

func main() {
    var p *int
    fmt.Println(*p) // 直接获取空指针值
}

数组第一个元素的指针

数组中的每个元素的地址是连续的,数组的地址,等于数组的第一个元素的地址。

dataList := [3]int{11, 22, 33}
// 0xc000012180  0xc000012180
fmt.Printf("%p %p", &dataList, &dataList[0])

&dataList&dataList[0] 虽然地址是一样的,但是却是不同的类型

  1. &dataList*[3]int 类型
  2. &dataList[0]*int 类型

内存地址偏移

指针类型不能直接算数计算,需要依赖 unsafe.Pointeruintptr 进行转换后计算

dataList := [3]int8{11, 22, 33}

// 1 获取第一个元素的指针
var firstDataPtr *int8 = &dataList[0]

// 2 转成pointer类型
ptr := unsafe.Pointer(firstDataPtr)

// 3 转成uintptr类型,然后才能进行内存地址计算,也就是+1个字节,取第二个索引元素的地址
targetAddr := uintptr(ptr) + 1

// 4 转为pointer类型
newPtr := unsafe.Pointer(targetAddr)

// 5 转为对应的指针类型
value := (*int8)(newPtr)

// 6 打印第二个元素的值,22
fmt.Println(*value)

结构体的指针计算,会比较麻烦,要使用 unsafe.Sizeof 计算出结构体每个字段的长度

type User struct {
    name string
    age  int
}

func main() {
    dataList := [3]User{{}, {name: "huang"}, {age: 11}}
    var fistDataPtr *User = &dataList[0]
    ptr := unsafe.Pointer(fistDataPtr)
    targetAddr := uintptr(ptr) + unsafe.Sizeof(dataList[0].name) + unsafe.Sizeof(dataList[0].age)
    newPtr := unsafe.Pointer(targetAddr)

    value := (*User)(newPtr)
    // dataList[1]
    fmt.Println(*value)
}
0

评论 (0)

取消