5.指针
5.指针
5.1 指针概念
指针是一个指向内存地址的变量。示意图如下所示:

在定义变量的时候,计算机会为变量自动分配内存地址,指针则是用来存储这些变量的内存地址。为什么在 Go 语言定义变量必须声明数据类型?因为不同的数据占用不同的存储空间,导致内存分配大小各不相同,所以指针只能存放同一类型变量的内存地址。例如,整型指针只能存储整型变量的内存地址。
Go 语言的指针变量也会分配内存地址,但它的值用来存放其他变量的内存地址,指针变量分为两种:
- 类型指针:允许对数据进行修改,直接使用指针传递数据,无须复制数据,但指针变量不能进行偏移和运算
- 切片指针:指切片类型的指针,包含开始元素的指针地址、元素数量和容量等信息
Go 语言对指针的使用不同于 C 语言,Go 对指针进行了约束和拆分,但仍拥有指针高效访问的特性,且不会发生指针偏移。从而避免非法修改数据的问题,而且指针的释放和回收也是由 GO 语言的资源回收机制完成。
在 Go 语言中使用指针,需要了解以下几个概念:
- 指针
也称为指针变量,即用来存放内存地址的变量。内存地址的格式一般是以 0xcXXXX 表示。而指针本身也可以是一个变量,也有自己的内存地址,它存放的内存地址是另一个变量的内存地址,需要注意。
- 指针类型
指针类型是指针存放的内存地址大小,例如一个 int 类型指针,则只能存放整型变量的内存地址,所以在使用指针时必须声明指针类型,确保指针存放的是一种类型的数据
- 指针赋值
指针赋值是指将某个变量的内存地址赋值给指针。在某个变量前面使用取地址操作符&即可获取变量的内存地址
- 指针取值
指针取值是从指针变量中通过某个变量的内存地址获取对应的数值,只需在指针变量前面使用取值操作符*即可
5.2 指针定义和空指针
在 Go 语言中使用关键字var定义指针变量,并在指针变量的数据类型前添加*,语法格式如下所示:
var name *type
- name:代表指针变量名,可自行定义,但需要遵守标识符命名规则
- type:指针变量的类型,例如 int、字符串等
示例代码如下所示:
package main
import "fmt"
func main() {
// 定义整型类型的指针变量
var pInt *int
// 定义浮点类型的指针变量
var pFloat *float32
// 定义字符串类型的指针变量
var pStr *string
// 定义布尔类型的指针变量
var pBool *bool
// 定义byte类型的指针变量
var pByte *byte
fmt.Printf("整型指针变量的值:%v,内存地址:%p,数据类型为:%T\n", pInt, &pInt, pInt)
fmt.Printf("浮点型指针变量的值:%v,内存地址:%p,数据类型为:%T\n", pFloat, &pFloat, pFloat)
fmt.Printf("字符串类型指针变量的值:%v,内存地址:%p,数据类型为:%T\n", pStr, &pStr, pStr)
fmt.Printf("布尔类型指针变量的值:%v,内存地址:%p,数据类型为:%T\n", pBool, &pBool, pBool)
fmt.Printf("byte指针变量的值:%v,内存地址:%p,数据类型为:%T\n", pByte, &pByte, pByte)
}
代码运行结果如下所示:
整型指针变量的值:<nil>,内存地址:0xc000078028,数据类型为:*int
浮点型指针变量的值:<nil>,内存地址:0xc000078030,数据类型为:*float32
字符串类型指针变量的值:<nil>,内存地址:0xc000078038,数据类型为:*string
布尔类型指针变量的值:<nil>,内存地址:0xc000078040,数据类型为:*bool
byte指针变量的值:<nil>,内存地址:0xc000078048,数据类型为:*uint8
在定义指针之后,它仅仅是一个特殊变量,Go 语言会自动为其分配内存地址,但它的值当零值处理(指针类型的零值为 nil),也称之为空指针。
除通过var定义指针之外,还可以通过内置方法new()实现,且定义的指针会为其设置为默认值。例如定义字符串类型的指针,将会指向一个空字符串的内存地址,定义一个整型类型的指针,将会指向数值为 0 的内存地址。示例代码如下所示:
package main
import "fmt"
func main() {
pStr := new(string)
pInt := new(int)
fmt.Printf("字符串指针变量值为:%v,内存地址:%v,类型为%T\n", *pStr, &pStr, pStr)
fmt.Printf("整型指针变量值为:%v,内存地址:%v,类型为%T\n", *pInt, &pInt, pInt)
}
代码运行结果如下所示:
字符串指针变量值为:,内存地址:0xc000078028,类型为*string
整型指针变量值为:0,内存地址:0xc000078030,类型为*int
使用 var 和 new()方法定义指针的总结如下所示:
| 方式 | 默认值 | 备注信息 |
|---|---|---|
| var | 零值 | 指针类型的零值一般为nil |
| new() | 零值 | 默认值为该类型对应数据类型字面常量值 |
5.3 指针赋值和取值
在Go语言中,所有变量需要先定义后使用,当指针变量定义之后,则后续便可对变量进行各类操作。
- 指针赋值:设置指针变量的值,但指针变量的值只能是某个变量的内存地址
- 指针取值:通过指针变量的值找到某个变量的内存地址,再从内存地址中获取该变量的值
指针赋值与取值的语法格式如下所示:
var age int = 28
var pInt *int
// 指针赋值,将age的内存地址赋值给pInt
pInt = &age
// 指针取值,在pInt前面使用 * 获取 age 的值
myAge:=*pInt
- age: 代表变量名,数据类型为整型,变量值为 28
- pInt:为指针变量,数据类型为整型
- 指针赋值:通过取地址操作符 & 将变量age的内存地址赋值给指针变量pInt
- 指针取值:通过取值操作符 * 从指针变量pInt存储的内存地址获取变量age的值(注意这里包含了间接取值操作)
指针赋值和指针取值分别通过&和*进行实现,它们是一对互补操作符。
- &: 获取变量的内存地址
- *: 通过指针变量里面存储的内存地址获取相应的值
示例代码如下所示:
package main
import "fmt"
func main() {
var age int = 28
fmt.Printf("变量age的内存地址为:%v\n", &age)
var pInt *int
fmt.Printf("指针值为:%v,内存地址:%v\n", pInt, &pInt)
pInt = &age
fmt.Printf("指针值为:%v,内存地址:%v,指针变量指向的变量值为:%v\n", pInt, &pInt, *pInt)
}
代码运行结果如下所示:
变量age的内存地址为:0xc0000a6068
指针值为:<nil>,内存地址:0xc0000a8028
指针值为:0xc0000a6068,内存地址:0xc0000a8028,指针变量指向的变量值为:28
以上代码示意图如下所示:

综上所述,指针是Go语言中一种特殊的变量,其存放的是计算机的内存地址。它的数据来自某个变量的内存地址。可通过取地址操作符&将某个变量的内存地址赋值给指针,从而完成赋值。如果直接从指针中取值,则只能获取某个变量的内存地址,若想获得某个变量的数值,需要在指针前面使用取值操作符*。
取值操作符*不仅能通过指针获取某个变量的数值,还能通过指针修改某个变量的数值民。示例代码如下所示:
package main
import "fmt"
func main() {
a := 100
var pInt *int
fmt.Printf("指针存放的变量值为:%+v,指针自身的内存地址:%v\n", pInt, &pInt)
pInt = &a
fmt.Printf("指针存放的变量值为:%+v,指针自身的内存地址:%v,变量a的值为:%v\n", *pInt, &pInt, a)
// 通过指针修改变量值
*pInt = 200
fmt.Printf("指针存放的变量值为:%+v,指针自身的内存地址:%v,,变量a的值为:%v\n", *pInt, &pInt, a)
}
代码运行结果如下所示:
指针存放的变量值为:<nil>,指针自身的内存地址:0xc000194020
指针存放的变量值为:100,指针自身的内存地址:0xc000194020,变量a的值为:100
指针存放的变量值为:200,指针自身的内存地址:0xc000194020,,变量a的值为:200
5.4 切片指针
切片是一种特殊的数据结构,通过切片可以更方便管理和使用数据集合。而切片是围绕动态数组的概念构建,可以按需自动增长和缩小。
切片可以理解为动态数组,其长度可根据切片里面的元素动态调整
Go语言的切片指针是以切片表示的,切片的每个元素只能存放内存地址,其语法格式如下所示:
// 定义方式一:
var slice []*type
// 定义方式二:
slice:=[]*type{}
定义方式如下所示:
- slice: 代表指针变量名,可以自行命名,同时也需要遵守标识符命名规则
- type: 指针变量的数据类型,如数字、字符串等
示例代码如下所示:
package main
import "fmt"
func main() {
//定义一个空的字符串类型的切片指针
var pSlice []*string
fmt.Printf("切片指针元素:%+v,切片自身内存地址:%v\n", pSlice, &pSlice)
// 定义变量
name, location := "Surpass", "Shanghai"
fmt.Printf("变量name的内存地址%v,变量location的内存地址:%+v\n", &name, &location)
pSlice = append(pSlice, &name)
pSlice = append(pSlice, &location)
fmt.Printf("切片指针元素:%+v,切片自身内存地址:%+v\n", pSlice, &pSlice)
for _, v := range pSlice {
fmt.Printf("切片元素值:%+v,切片指针的元素所对应的值为:%v\n", v, *v)
}
// 修改切片元素的值
*pSlice[0] = "Surmount"
*pSlice[1] = "Wuhan"
fmt.Printf("修改后的元素值分别为:%+v,%+v\n", name, location)
}
代码运行结果如下所示:
切片指针元素:[],切片自身内存地址:&[]
变量name的内存地址0xc00002a2a0,变量location的内存地址:0xc00002a2b0
切片指针元素:[0xc00002a2a0 0xc00002a2b0],切片自身内存地址:&[0xc00002a2a0 0xc00002a2b0]
切片元素值:0xc00002a2a0,切片指针的元素所对应的值为:Surpass
切片元素值:0xc00002a2b0,切片指针的元素所对应的值为:Shanghai
修改后的元素值分别为:Surmount,Wuhan
通过最终输出的结果,可以总结出以下结论:
- 切片指针定义后,若没有设置初始值,默认为空。而切片指针是动态数组,其数据长度可以自动调整,Go语言不会自动分配内存地址,因此无法获取其切片指针的内存地址。
- 若要向切片指针添加元素,其值必须为切片指针对应类型的内存地址
- 若要获取切片指针元素对应的值,需要使用取值操作符
- 修改变量的值不会影响其内存地址
若未掌握切片指针的基本原理,在实际开发中程序很容易出现难以寻找的Bug,示例代码如下所示:
package main
import "fmt"
func main() {
var pIntSlice []*int
var num int
for i := 0; i < 5; i++ {
num = i
pIntSlice = append(pIntSlice, &num)
}
for _, v := range pIntSlice {
fmt.Printf("切片指针的元素%+v,元素对应的值%v\n", v,*v)
}
}
代码运行结果如下所示:
切片指针的元素0xc00000a0b8,元素对应的值4
切片指针的元素0xc00000a0b8,元素对应的值4
切片指针的元素0xc00000a0b8,元素对应的值4
切片指针的元素0xc00000a0b8,元素对应的值4
切片指针的元素0xc00000a0b8,元素对应的值4
在以上运行结果中,可以看出切片指针pIntSlice所有元素都是同一个内存地址,且元素所对应的值均为4,说明在每次循环中都是将同一个内存地址的变量写入到切换指针,导致变量的值不断被修改,直到最后一次循环。而切片指针所有元素都是来自于同一个变量,因此其内存地址和数值都是相同的。而如果要修复这种问题,只需要每次循环时,重新定义变量即可,代码如下所示:
package main
import "fmt"
func main() {
var pIntSlice []*int
for i := 0; i < 5; i++ {
var num int = i
pIntSlice = append(pIntSlice, &num)
}
for _, v := range pIntSlice {
fmt.Printf("切片指针的元素%+v,元素对应的值%v\n", v, *v)
}
}
代码运行结果如下所示:
切片指针的元素0xc00000a0b8,元素对应的值0
切片指针的元素0xc00000a0d0,元素对应的值1
切片指针的元素0xc00000a0d8,元素对应的值2
切片指针的元素0xc00000a0e0,元素对应的值3
切片指针的元素0xc00000a0e8,元素对应的值4
5.5 指针的指针
指针的指针是指一个指针变量指向另一个指针变量,另一个指针变量指向某个变量。例如指针A的值指向指针B的内存地址,指针B的值是某个变量的内存地址,示意图如下所示:

指针的指针的语法如下所示:
// 定义指针的指针
var name **type
// 从指针的指针获取变量的值
value:=**name
语法说明格式如下所示:
- name:代表指针变量名,使用两个 * 进行定义
- type: 指针变量的数据类型
- value: 从指针的指针获取变量的值,必须使用两个 * 实现
示例代码如下所示:
package main
import "fmt"
func main() {
s := "Surpass"
var pStr *string
var ppStr **string
pStr = &s
ppStr = &pStr
fmt.Printf("字符串s的值为:%s,内存地址:%+v\n", s, &s)
fmt.Printf("指针变量pStr的值为:%+v,内存地址:%+v\n", pStr, &pStr)
fmt.Printf("指针的指针ppStr的值为%+v,内存地址:%+v\n", ppStr, &ppStr)
fmt.Printf("指针的指针获取变量的值为:%+v\n", **ppStr)
}
代码运行结果如下所示:
字符串s的值为:Surpass,内存地址:0xc00002a280
指针变量pStr的值为:0xc00002a280,内存地址:0xc000078028
指针的指针ppStr的值为0xc000078028,内存地址:0xc000078030
指针的指针获取变量的值为:Surpass
根据以上代码运行结果,总结如下所示:
- 变量s的数据类型为字符串,其值为Surpass,内存地址为:0xc00002a280
- 指针pStr为字符串类型的指针变量,存放变量s的内存地址 0xc00002a280,指针pStr的内存地址:0xc000078028
- 指针的指针ppStr的类型为字符串类型,存放指针pStr的内存地址,指针的指针的内存地址:0xc000078030,如果从指针的指针ppStr中获取变量s的值,需要使用**ppStr
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

作者: Surpassme
来源: http://www.jianshu.com/u/28161b7c9995/
http://www.cnblogs.com/surpassme/
声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 ,否则保留追究法律责任的权利。如有问题,可发送邮件 联系。让我们尊重原创者版权,共同营造良好的IT朋友圈。

浙公网安备 33010602011771号