首页 > 编程笔记 > Go语言笔记 阅读:25

Go语言指针用法详解(附带实例)

Go 语言中的指针学起来很容易,在 Go 语言中,使用指针执行某些任务更简单。

通过指针,Go 语言的开发者可以控制特定集合的数据结构、分配的数量及内存访问模式。指针对于性能的影响是不言而喻的,对于构建运行良好的系统也是非常重要的。在系统编程、操作系统或网络应用等领域,指针更是不可或缺的。

由于指针的应用较为复杂,Java、C#Python 等编程语言把指针替换为引用。Go 语言在保留指针的同时,对指针做了很多限制,例如不能对指针进行运算等。

指针和引用的区别在于指针是变量,在内存中具有存储位置;引用是一种形式,例如,引用变量是实际变量的别名,操作引用变量就是在操作实际变量,引用变量在内存中没有存储位置。

什么是指针

指针又称为指针变量,即指针本身是变量。

变量是用于存储数据的,那么指针存储的数据是什么呢?变量存储在一个或多个连续的内存地址中,如果把指针看作箭头,那么这个箭头指向的是某个变量的内存地址。因此,指针存储的数据就是某个变量的内存地址。

例如,声明一个 32 位的 int 类型变量 x,并将其初始化为 10。因为存储一个 32 位的 int 类型变量需要占用 4 个字节的内存,所以可以把变量 x 存储在如下图所示的 4 个字节中;其中,变量 x 的内存地址从 1 开始,到 4 结束。


图 1 把变量x存储在4个字节中

指针也是变量,它的值是变量的内存地址。虽然不同类型的变量占用不同数量字节的内存,但是不同类型的指针占用的是相同数量字节的内存。这是因为不同类型的指针的值均为表示某个变量的内存地址的数字,存储指针相当于存储数字,需要占用 4 个字节的内存。


图 2 把指针存储在4个字节中

如上图所示,变量 pointerX 表示用于存储变量 x 内存地址的指针,存储在 4 个字节中;其中,变量 pointerX 的内存地址从 5 开始,到 8 结束。因为变量 x 的内存地址是从 1 开始的,所以变量 pointerX 的值为 1。

“取地址”操作

指针变量可以存储任何一个变量的内存地址。变量占用内存的字节数量只取决于计算机系统的位数,与变量的值无关。计算机系统的位数是 32 位,变量占用 4 个字节的内存;计算机系统的位数是 64 位,变量占用 8 个字节的内存。

在 Go 语言中,指针的默认值为 nil。也就是说,如果指针没有存储任何变量的内存地址,那么就把这个指针的值默认设置为 nil。

程序中的每个变量在程序运行时都在内存中拥有一个存储位置。为了获取某个变量在内存中的存储位置,Go 语言提供了 & 字符。把 & 字符置于某个变量前,就能获取这个变量的内存地址(即这个变量在内存中的存储位置);Go语言把这种操作称作“取地址”操作。

“取地址”操作的语法格式如下:
ptr := &v
参数说明如下:
下面的实例演示如何对变量执行“取地址”操作。

【实例 1】获取变量的内存地址。首先,声明并初始化 int 类型且值为 10 的变量 number,以及 string 类型且值为 success 的变量 str。然后,分别格式化输出变量 number 的内存地址和变量 str 的内存地址。代码如下:
package main    // 声明 main 包

import "fmt"    // 导入 fmt 包,用于打印字符串

func main() {   // 声明 main()函数
    number := 10  // 声明并初始化 int 类型且值为 10 的变量 number
    str := "success"  // 声明并初始化 string 类型且值为 success 的变量 str
    fmt.Printf("变量 number 的内存地址是%p\n", &number)  // 格式化输出变量 number 的内存地址
    fmt.Printf("变量 str 的内存地址是%p", &str)        // 格式化输出变量 str 的内存地址
}
运行结果如下:

变量number的内存地址是0xc000016088
变量str的内存地址是0xc000042250

变量、指针(变量)和内存地址之间的关系是每个变量都拥有一个内存地址,指针(变量)的值就是某个变量的内存地址。

Go语言指针的使用方法

在 Go 语言中,指针的使用流程分为如下 3 个步骤:声明指针变量、初始化指针变量和访问指针变量的值。

1) 声明指针变量

在使用指针前,要使用 var 关键字声明指针。声明指针的语法格式如下:
var ptr_name *ptr_type
参数说明如下:
例如,使用 var 关键字分别声明指向 32 位的 int 类型指针变量 num,以及 64 位的 float 类型指针变量 flt_num。代码如下:
var num *int32 //指向32位的int类型指针变量num
var flt_num *float64 //指向64位的float类型指针变量flt_num

2) 初始化指针变量

前文中,使用 var 关键字声明指向 32 位的 int 类型指针变量 num。那么,能否使用赋值运算符“=”把 10 赋值给指针变量 num 呢?

下面将编写一个程序验证上述说法,代码如下:
package main

import "fmt"

func main() {
    var num *int32 //声明指向32位的int类型指针变量num
    *num = 10      //把 10 赋值给指针变量num
    fmt.Println("指针变量 num 指向的内存地址:", num)
}
运行结果如下图所示:


图 3 程序引发panic

由运行结果可知,使用赋值运算符“=”把 10 赋值给指针变量 num 的方式是不可行的。

那么,上述代码为什么会发 panic 呢?在 Go 语言中,指针变量是引用类型的变量。在使用引用类型的变量前,不仅要声明这个变量,还要为这个变量分配内存空间;否则,无法存储赋给这个变量的值。

Go 语言的内置函数 new() 可以给引用类型的变量分配内存空间。new() 函数的语法格式如下:
func new(Type) *Type

下例演示如何使用 new() 函数初始化指针变量。

【实例 2】使用 new() 函数初始化指针变量。首先,声明指向 32 位的 int 类型指针变量 num。然后,使用 new() 函数为指针变量 num 分配内存空间。接着,使用赋值运算符“=”把 10 赋值给指针变量 num。最后,打印指针变量 num 指向的内存地址。代码如下:
package main

import "fmt"

func main() {
    var num *int32 //指向32位的int类型指针变量num
    num = new(int32) //为指针变量num分配内存空间
    *num = 10       //把 10 赋值给指针变量num
    fmt.Println("指针变量 num 指向的内存地址:", num)
}
运行结果如下:

指针变量num指向的内存地址: 0xc00001608

注意,上述程序每次运行后的结果都是不同的,用以表示指针变量 num 在程序运行时指向的内存地址。

使用“短变量声明”的语法格式能够精简例 2 的代码,即把:
var num *int32
num = new(int32)
简化为:
num := new(int32) //声明指向32位的int类型的指针变量num,并为指针变量num分配内存空间
在实际开发中,并不经常使用内置函数 new() 初始化指针变量,这是因为通过内置函数 new() 得到的是指向某个类型的指针,如果不为这个指针赋值,那么这个指针的值就是这个类型的默认值。

例如,使用“短变量声明”的语法格式分别声明 int 类型的指针变量 num,以及 bool 类型的指针变量 bln,并使用内置函数 new() 为它们分配内存空间;分别打印指针变量 num 和 bln 的值。代码如下:
num := new(int) //声明 int 类型的指针变量 num,并为指针变量 num 分配内存空间
bln := new(bool) //声明 bool 类型的指针变量 bln,并为指针变量 bln 分配内存空间
fmt.Println(*num) //打印指针变量 num 的值
fmt.Println(*bln) //打印指针变量 bln 的值
运行结果如下:

0 //int 类型的默认值
false //bool 类型的默认值


既然使用内置函数 new() 初始化指针变量并不常用,那么初始化指针变量的常用方式是什么呢?为了回答这个问题,先通过编写代码,实现如下步骤:
代码如下:
var number int = 10
var ptr *int
ptr = &number
通过这 3 行代码,即可初始化指针变量 ptr,这就是初始化指针变量的常用方式。

使用“短变量声明”的语法格式可以进一步简化初始化指针变量 ptr 的代码:
number := 10
ptr := &number

Go语言空指针

当指针定义后且尚未分配任意变量时,它的值为 nil。nil 指针又称为空指针。nil 在概念上和其他语言的 null、None、NULL 一样都表示零值或空值。

使用 if 语句判断空指针有如下两种编码格式:
if(ptr != nil) //ptr 不是空指针
if(ptr == nil) //ptr 是空指针

下面演示如何判断指针是否为空指针。代码如下:
var ptr *int //ptr 不是空指针
if ptr != nil {
    fmt.Printf("ptr 的值为 :%x\n", ptr)
}
/* ptr 是空指针 */
if ptr == nil {
    fmt.Printf("ptr 是空指针!")
}
运行结果如下:

ptr 是空指针!

Go语言获取指针指向的变量的值

当对某个变量执行“取地址”操作时,要使用 & 字符,进而获取指向这个变量的内存地址的指针变量;也就是说,指针变量的值是这个变量的内存地址。

当对某个指针变量执行“取值”操作时,要使用 * 字符,进而获取这个指针变量指向的变量的值。

Go 语言把 & 字符称作取地址操作符;把 * 字符称作取值操作符。取地址操作符和取值操作符是一对互补操作符。

下例演示对变量执行“取地址”操作的过程,并演示对指针变量执行“取值”操作的过程。

【实例 3】取地址操作符和取值操作符的用法。使用“短变量声明”的语法格式声明并初始化 string 类型的变量 str;声明并初始化指向变量 str 内存地址的指针变量 ptr;分别格式化输出指针变量 ptr 的类型和变量 str 的内存地址;在对指针变量 ptr 执行“取值”操作,并把操作结果赋值给另一个 string 类型的变量 str_value 后,分别格式化输出变量 str_value 的类型和值。代码如下:
package main

import "fmt"

func main() {
    str := "知识就是力量"
    ptr := &str
    fmt.Printf("指针变量 ptr 的类型: %T\n", ptr)
    fmt.Printf("变量 str 的内存地址: %p\n", ptr)
    str_value := *ptr
    fmt.Printf("变量 str_value 的类型: %T\n", str_value)
    fmt.Printf("变量 str_value 的值: %s\n", str_value)
}
运行结果如下:

指针变量ptr的类型: *string
变量str的内存地址: 0xc000104220
变量str_value的类型: string
变量str_value的值: 知识就是力量

Go语言修改指针指向的变量的值

指针不仅可以获取其指向的变量的值,还可以修改其指向的变量的值。

在讲解如何使用指针修改变量值之前,按如下步骤编写程序:
代码如下:
package main

import "fmt"

func modifyValue(number int) {
    number = 11
}

func main() {
    number := 7
    modifyValue(number)
    fmt.Println("number =", number)
}
运行结果如下:

number = 7

通过运行结果不难发现,modifyValue() 函数没有把变量 number 的值修改为 11。那么,为了让 modifyValue() 函数实现既定功能应该如何修改程序呢?下面将使用指针修改 modifyValue() 函数。

指针指向的是变量的内存地址,而非变量的值。在使用*字符对某个指针执行“取值”操作后,就能够获取这个指针指向的变量的值。这样就可以修改这个指针指向的变量的值。

根据这个原理,即可修改上述程序,具体修改的内容如下:
上述程序经修改后,代码如下:
package main //声明 main 包

import "fmt" //导入 fmt 包,用于打印字符串

func modifyValue(number *int) { //定义修改变量的值的函数,参数是一个指针变量
    *number = 11 //使用*字符对这个指针变量执行取值操作,进而将其指向的变量的值修改为 11
}

func main() { //声明 main()函数
    number := 7 //声明并初始化值为 7 的 int 类型变量 number
    //调用用于修改变量的值的函数,并向其传递变量 number 的内存地址
    modifyValue(&number)
    fmt.Println("number =", number) //打印变量 number 的值
}
运行结果如下:

number = 11

通过声明、初始化变量和赋值运算符“=”交换两个变量的值。那么,有没有其他方式也能实现同样的功能呢?下面的实例演示如何使用指针交换两个变量的值。

【实例 4】定义用于交换两个变量的值的 exchangeValue() 函数。这个函数中有两个参数,即 int 类型的指针变量 i 和 j。使用 * 字符对指针变量 i 执行“取值”操作,将其指向的变量的值赋值给 int 类型的变量 k。使用 * 字符对指针变量 j 执行“取值”操作,将其指向的变量的值赋值给指针变量 i 指向的变量。把变量 k 的值赋值给指针变量 j 指向的变量。

在 main() 函数中,声明并初始化,值分别为 7 和 11 的两个 int 类型变量 x 和 y。格式化输出变量 x 和 y 的值。调用 exchangeValue() 函数,并向其传递变量 x 和 y 的内存地址。再次格式化输出变量 x 和 y 的值。

代码如下:
package main //声明 main 包

import "fmt" //导入 fmt 包,用于打印字符串

func exchangeValue(i, j *int) { //定义用于交换变量的值的函数,参数是两个指针变量
    k := *i //先使用*字符对指针变量 i 执行取值操作,再将其指向的变量的值赋值给 int 类型的变量 k
    *i = *j //先使用*字符对指针变量 j 执行取值操作,再将其指向的变量的值赋值给指针变量 i 指向的变量
    *j = k //把变量 k 的值赋值给指针变量 j 指向的变量
}

func main() { //声明 main()函数
    x, y := 7, 11 //声明并初始化两个 int 类型的变量 x 和 y,值分别为 7 和 11
    fmt.Printf("x = %d, y = %d\n", x, y) //格式化输出变量 x 和 y 的值
    //调用用于交换变量的值的函数,并将其传递变量 x 和 y 的内存地址
    exchangeValue(&x, &y)
    fmt.Printf("x = %d, y = %d\n", x, y) //格式化输出变量 x 和 y 的值
}
运行结果如下:

x = 7, y = 11
x = 11, y = 7

相关文章