首页 > 编程笔记

Go语言指针的定义和使用(入门必读)

Go 语言中的指针,是一个用来指向内存地址的变量,如下图所示:


图 1 指针图解

在定义变量的时候,计算机都会为变量自动分配内存地址,指针用来存储这些变量的内存地址。

注意,由于不同类型的数据占用不同的存储空间,导致内存地址分配大小各不相同,所以指针只能存放同一类型变量的内存地址,比如整型的指针只能存储整型变量的内存地址。

Go 语言的指针变量也会分配内存地址,但它的值用来存放其他变量的内存地址,指针变量分为两种:类型指针和切片指针,两者说明如下:
Go 语言对指针的使用不同于 C 语言,它对指针设置了约束和拆分,但仍拥有指针高效访问的特点,并且不会发生指针偏移,从而避免了非法修改数据的问题,并且指针的释放和回收也是由 Go 语言的资源回收机制实现。

在学习 Go 语言的指针之前,还需要了解指针的相关概念,如指针变量、指针类型、指针赋值和指针取值。
1) 指针也称为指针变量,即用来存放内存地址的变量,一般情况下,内存地址的数据格式以 0xcXXXXXXX 表示,如 0xc0000180a8 或 0xc0000ac058 等。指针是一个变量,也有自己的内存地址,它存放的内存地址是另一个变量的内存地址,这一概念必须梳理清楚。

2) 指针类型是指针存放的内存地址的大小,比如指针 a 定义为 int 类型,它只能存放整型变量的内存地址,所以在使用指针的时候必须声明指针类型,确保指针只能存放一种数据类型。

3) 指针赋值将某个变量的内存地址赋值给指针,在某个变量前面使用取地址操作符“&”即可获取变量的内存地址。

4) 指针取值从指针变量中通过某个变量的内存地址获取对应的数值,只需在指针变量前面使用取值操作符“*”即可。

指针的定义

在 Go 语言中使用关键字 var 定义指针变量,在指针变量的数据类型前面加上符号“*”,语法格式如下:
var name *type
语法说明如下:
根据指针的定义语法,我们可以定义不同数据类型的指针变量,示例代码如下:
package main

import "fmt"

func main() {
    // 定义int类型的指针变量
    var pint *int
    fmt.Printf("指针值为:%v,空间地址:%v\n", pint, &pint)
    // 定义float64类型的指针变量
    var pfloat *float64
    fmt.Printf("指针值为:%v,空间地址:%v\n", pfloat, &pfloat)
    // 定义string类型的指针变量
    var pstr *string
    fmt.Printf("指针值为:%v,空间地址:%v\n", pstr, &pstr)
    // 定义bool类型的指针变量
    var pbool *bool
    fmt.Printf("指针值为:%v,空间地址:%v\n", pbool, &pbool)
    // 定义byte类型的指针变量
    var pbyte *byte
    fmt.Printf("指针值为:%v,空间地址:%v\n", pbyte, &pbyte)
}
运行上述代码,结果为:

指针值为:<nil>,空间地址:0xc0000d8018
指针值为:<nil>,空间地址:0xc0000d8028
指针值为:<nil>,空间地址:0xc0000d8030
指针值为:<nil>,空间地址:0xc0000d8038
指针值为:<nil>,空间地址:0xc0000d8040

当指针定义之后,它仅仅是一个特殊变量,Go 语言自动为其分配内存地址,但它的值是空值(Go 语言的空值以 nil 表示),也称为空指针。

定义指针还可以使用内置函数方法 new() 实现,但定义的指针会为其设置默认值,比如定义字符串类型的指针,它将会指向一个空字符串的内存地址;定义一个整型类型的指针,它将会指向一个数值为 0 的内存地址,示例如下:
package main

import "fmt"

func main() {
    ptr := new(int)
    fmt.Printf("ptr指向的变量值为:%v,空间地址:%v\n", *ptr, &ptr)
}
运行上述代码,结果为:

ptr指向的变量值为:0,空间地址:0xc000006028

指针的使用

在 Go 语言编程中,所有变量先定义后使用,当指针变量定义之后,下一步对变量进行赋值和取值操作。

指针赋值是设置指针变量的值,但指针变量的值只能是某个变量的内存地址;指针取值是通过指针变量的值得到某个变量的内存地址,再从内存地址获取该变量的值。

指针赋值与取值的语法格式如下:
var name int = 200
var ptr *int
// 指针赋值,将name的内存地址赋值给ptr
ptr = &name
// 指针取值,在ptr前面使用“*”获取name的值
name1 := *ptr
语法说明如下:
指针赋值和取值是通过取地址操作符“&”和取值操作符“*”实现的,它们是一对互补操作符。“&”取出内存地址,“*”根据内存地址取出对应的数值。

我们根据语法格式编写应用示例,代码如下:
package main

import "fmt"

func main() {
    var a int = 200
    fmt.Printf("变量a的空间地址:%v\n", &a)
    // 定义int类型的指针变量
    var pint *int
    fmt.Printf("指针值为:%v,空间地址:%v\n", pint, &pint)
    pint = &a
    fmt.Printf("指针值为:%v,空间地址:%v\n", pint, &pint)
    fmt.Printf("指针值的值为:%v,空间地址:%v\n", *pint, &pint)
}
运行上述代码,结果为:

变量a的空间地址:0xc0000ac058
指针值为:<nil>,空间地址:0xc0000d8020
指针值为:0xc0000ac058,空间地址:0xc0000d8020
指针值的值为:200,空间地址:0xc0000d8020

从上述示例看到,指针 pint 分别经过定义、赋值和取值操作,说明如下:
指针赋值和取值的过程通过图解方式演示,如下图所示。


图 2 指针赋值与取值图解

综上所述,指针是 Go 语言的一种特殊变量,它存放的数据是计算机的内存地址,它的数据来自某个变量的内存地址,通过取地址操作符“&”将某个变量的内存地址完成赋值。如果直接从指针取值,只能获得某个变量的内存地址,若要获得某个变量的数值,需要在指针前面使用取值操作符“*”。

取值操作符“*”不仅能通过指针获取某个变量的数值,还能通过指针修改某个变量的数值,示例如下:
package main

import "fmt"

func main() {
    var b int = 100
    var pint *int
    fmt.Printf("指针存放的变量值为:%v,空间地址:%v\n", pint, &pint)
    // 将变量b的内存地址赋值给指针pint
    pint = &b
    fmt.Printf("指针存放的变量值为:%v,空间地址:%v\n", *pint, &pint)
    // 通过取值操作符“*”修改变量b的值
    *pint = 666
    fmt.Printf("指针存放的变量值为:%v,空间地址:%v\n", *pint, &pint)
}
运行上述代码,结果为:

指针存放的变量值为:<nil>,空间地址:0xc000006028
指针存放的变量值为:100,空间地址:0xc000006028
指针存放的变量值为:666,空间地址:0xc000006028

推荐阅读