5.指针

5.指针

5.1 指针概念

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

0501-指针示意图.png

    在定义变量的时候,计算机会为变量自动分配内存地址,指针则是用来存储这些变量的内存地址。为什么在 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

    以上代码示意图如下所示:

0502-指针赋值和取值图解.png

    综上所述,指针是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的值是某个变量的内存地址,示意图如下所示:

0503-指针的指针.png

    指针的指针的语法如下所示:

// 定义指针的指针
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,或扫描下面的二维码添加关注:

posted @ 2025-07-22 15:58  Surpassme  阅读(15)  评论(0)    收藏  举报