目录
🐭前言
Go 语言的指针是一种直接操作内存地址的机制,但相较于 C/C++,Go 的指针设计更加安全和简洁,避免了常见的内存错误(如野指针、悬垂指针)。
1.什么是指针🌳
我们都知道,变量 是一种使用方便的占位符,用于引用计算机内存地址。Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。而指针是一个特殊的变量,用于存储了另一个变量的内存地址。具体原理可参考第2节当中的 代码示例。
2.使用指针🌳
指针的基本使用语法如下:
var 指针名 *数据类型
🌶 例如:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
使用 & 操作符获取变量的地址,使用 *
操作符访问指针指向的值。下面请看一段 示例代码 具体了解指针的原理。
package main
import "fmt"
func main() {
a := 10
var p *int
fmt.Println("变量a的内存地址:", &a)
p = &a
fmt.Println("指针p存储的内容:", p)
fmt.Println("指针p所指向内存地址的元素值", *p)
}
上述代码最终输出结果如下:
变量a的内存地址: 0xc00000a0f8
指针p存储的内容: 0xc00000a0f8
指针p所指向内存地址的元素值 10
🍒 解释:
计算机当中的内存空间可以被划分为一个个小方块,每一个小方块都对应一个地址。这就好比一栋小区楼房,每家每户都有属于自己的独一无二的门牌号。
当执行语句 a := 10 时,就好比有人搬家入住楼房,入住之后,他就会获得属于自己家的门牌号。计算机会为变量 a 随机分配一个空闲的内存空间的地址,将10放入该地址空间中。
所以此时,我们用取址符 & 访问变量a的地址,输出就为 0xc00000a0f8。
由于 a 是int类型,因此当我们想用指针指向变量 a 时,指针的数据类型也得是int,讲究“门当户对”,由于指针的本质是存储变量的地址,所以我们执行语句 p = &a ,给指针变量p赋值,此时指针p存储的值就是变量a的地址。
所以此时,指针p的存储值就是变量a的地址,输出就为 0xc00000a0f8。
而
*
操作符的作用就是解引用指针,就好比依据别人家的门牌号,上门拜访。*p 的本质就是访问指针p所指向的内存空间中对应地址所存储的值。
new函数 🌸
new(T) 分配内存并返回指向该类型的指针,初始值为对应数据类型的默认值。例如:
p := new(int) // p 是 *int 类型,指向的 int 值为 0
3.空指针🌳
当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。空指针的判断可用如下语句:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
4.指针数组 🌳
在 Go 语言中,指针数组是一个数组,其元素类型为指针(即每个元素存储的是另一个变量的内存地址)。与普通数组不同,指针数组的每个元素指向内存中的某个值,而不是直接存储值本身。这种结构常用于高效操作多个变量的内存地址,或在函数间共享数据。
// 声明一个长度为3的指针数组,每个元素是 *int 类型
var ptrArr [3]*int
注意:指针数组元素默认初始值为 nil
,此时使用 * 解引用会触发 panic 错误。
指针数组需要为每个元素分配指向具体变量的地址。初始化方式有以下几种:
(1) 直接赋值地址:
a, b, c := 10, 20, 30
ptrArr := [3]*int{&a, &b, &c}
(2) 使用 new 分配内存:
var ptrArr [3]*int
for i := 0; i < 3; i++ {
ptrArr[i] = new(int) // 为每个元素分配内存
*ptrArr[i] = i * 10 // 赋值
}
指针数组的访问与修改,通常需要 * 解引符参与,用于获取对应地址所存储的元素。
fmt.Println(*ptrArr[0]) // 输出第一个元素指向的值
*ptrArr[1] = 100 // 修改第二个元素指向的值
// 可以更换指针的指向目标
d := 200
ptrArr[2] = &d // 让第三个元素指向新的变量 d
通过指针数组传递内存地址,避免值拷贝:
func process(arr [3]*int) {
for i := 0; i < len(arr); i++ {
*arr[i]++ // 修改原数据
}
}
func main() {
a, b, c := 1, 2, 3
ptrArr := [3]*int{&a, &b, &c}
process(ptrArr)
fmt.Println(a, b, c) // 输出 2, 3, 4
}
注意以下是一个典型的错误例子,使用指针时,请确保确保指针指向的内存未被提前释放:
func createArray() [2]*int {
var arr [2]*int
for i := 0; i < 2; i++ {
val := i * 10 // val 是局部变量,函数返回后内存失效
arr[i] = &val // 错误!悬垂指针
}
return arr
}
Go 的数组是固定长度的,无法动态扩展。若需动态长度,应使用切片。也就是指针切片,用法与指针数组相似,这里不过多介绍,关于切片的介绍,具体可看:Go语言切片
5.指向指针的指针🌳
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。
声明格式如下:
var 指针名 **数据类型;
🍊示例代码:
package main
import "fmt"
func main() {
var a int
var ptr *int
var pptr **int
a = 3000
/* 指针 ptr 地址 */
ptr = &a
/* 指向指针 ptr 地址 */
pptr = &ptr
/* 获取 pptr 的值 */
fmt.Printf("变量 a = %d\n", a )
fmt.Printf("指针变量 *ptr = %d\n", *ptr )
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}
上述代码最终输出结果如下:
变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000
6.函数的引用传递🌳
具体用法可参见:GO语言学习-函数 当中第三章中的引用传递部分。