一、前言
在Go编程中,指针是一个重要的概念。它允许我们直接访问和操作内存中的数据。Go中指针的使用相对简单,但仍然需要理解一些重要概念和技巧。本文将逐步介绍指针的各个方面,帮助读者更好地掌握Go语言中的指针操作。
二、内容
2.1 内存地址
在Go中,每个变量在运行时都拥有一个地址,这个地址就代表该变量在内存中的位置。我们可以使用&
符号来获取该变量在内存中的地址(取地址操作)。
对于取地址操作来说,格式如下:
go
ptr := &val
可以看到, val
是被取地址的变量,其内存地址被指针变量 ptr
来接收。如果 val
的数据类型是 type
,那么指针变量 ptr
的类型则是 *type
,称为 type
的指针类型。
指针的值是带有
0x
(即十六进制前缀)的一组数据。
举个例子:
go
package main
import "fmt"
func main() {
// 声明一个整数变量
var val int = 666
// 使用 & 符号获取变量 val 的内存地址,并将其存储在一个指针变量 Ptr 中
Ptr := &val
// 打印变量 val 的值和其内存地址
fmt.Printf(" val : %d\n", val)
fmt.Printf("&val : %p\n", &val)
// 打印指针变量 Ptr 的值(即变量 val 的内存地址)
fmt.Printf(" Ptr : %p\n", Ptr)
}
运行上述代码,你将看到类似以下输出:
bash
val : 666
&val : 0xc000018088
Ptr : 0xc000018088
可以看到,在这个例子中:
- 我们首先声明了一个整数变量
val
,并将其设置为666。 - 然后,我们使用
&
符号创建一个指针变量Ptr
,该指针变量存储了变量val
的内存地址。 - 最后,我们使用
fmt.Printf
函数打印了变量val
的值和内存地址,以及指针变量Ptr
的值(即变量val
的内存地址)。
这里的 ptr
的类型就是 *int
。
小结:一个指针变量是可以指向任何一个值的内存地址。内存的具体地址值会因计算机和操作系统而异。指针变量指向的值的内存地址在32位机器上是占用4个字节,而在64位机器上占用8个字节。
2.2 指针声明
前面讲过,一个指针变量呢,指的是该变量指向了一个值的内存地址。
类似变量和常量,我们在使用指针的时候也喜欢声明。格式如下:
go
var varName *varType
其中:
varName
是指针变量名varType
是指针的类型*
用于指定该变量是一个指针
比如,下面的例子是有效的指针声明:
go
var p *int // 指向整型的指针
上述变量 p
就是一个指向整数值的指针。同时,该指针变量也有一个默认值,那就是 nil
。
新定义的指针变量(无任何指向),其默认值为
nil
。类似其他语言中的null
指针。
需要注意的是,在Go语言中,没有像C语言那样的指针运算。因此p++
(假设p
是一个指针)会被解释为(*p)++
,即首先获取指针指向的值,然后对该值进行加一操作。
2.3 值传递
说到指针,我们就不得不提到传递数据的方式。事实上严格来讲,在Go中,只有一种传递,那就是值传递。在Go语言中,函数参数始终是按照值传递的,这意味着函数接收的是参数的副本,而不是原始参数本身。当一个变量被当做参数进行传递时,就会创建该变量的副本,然后传递给函数(你可以看到这个副本的地址和变量的地址是不同的)。
但是为什么还提到指针呢?
其实,当变量被当做指针进行传递时,一个新的指针就被创建了,该指针指向了变量指向的内存地址。所以,你可以将这个指针当做原始变量的指针的副本。
所以可以这么理解:Go总是创建一个副本进行值传递,只不过该副本可能是变量的副本,也可能是变量指针的副本。
2.4 再谈指针
指针变量和普通变量的区别在哪里?对于初学者来说,理解指针的概念是编写Go代码的重要一步。
事实上,普通变量就像是内存的别名,是编程语言提供的一种方式,用于方便地表示以及访问内存中的数据。因此我们可以不用关心变量具体的内存地址(编译器会自动分配和管理)。
而指针,则是存储变量内存地址的变量。
举个例子,假设你是一名图书管理员,图书馆里有很多书架,每个书架上都有许多书籍。你需要管理这些书籍,并且有时需要在不知道具体书籍名称的情况下查找它们。那么此时每个书籍都是内存中的数据,而书籍的编号则是内存地址。你作为图书管理员,可以通过这些编号来找到对应书架上的书籍,而不必根据具体的书籍名称。
类似地,你也可以通过使用指针来访问内存中的数据,而不必关心数据的具体变量名称。在某些情况下,如动态分配内存或者处理复杂数据结构时,指针的用处是很大的。
2.5 new()
创建指针的另一种方式:使用 new()
函数。
new()
函数用于在堆上为一个新的数据结构分配内存,并返回一个指向该内存地址的指针。这个指针的类型是你传递给 new()
函数的类型。
举个例子:
go
package main
import "fmt"
func main() {
// 使用 new() 函数创建一个指向字符串的指针
str := new(string)
// 为指针所指向的内存分配一个字符串值
*str = "Hello-World-Go"
// 通过指针访问字符串值
fmt.Println(*str) // Hello-World-Go
}
可以看到,我们使用 new(string)
创建了一个指向字符串的指针 str
。接着我们为该指针指向的内存分配了一个字符串值。通过 *str
可以访问并打印这个字符串的值。
new()
函数确保为指针分配了内存,并将其初始化为类型的零值。需要注意的是,Go语言中通常更推荐使用短变量声明(:=
)来创建指针,除非有特定的需求需要使用new()
函数。
另外,我们可能会被问到 make()
和 new()
的区别。
首先,这两个函数都是用于内存分配的。其中,make()
函数用于对一些内置数据结构的初始化,比如切片、映射和通道等,返回的是引用类型的本身(因为是引用类型,也不必返回其指针)。
接着,new()
函数则是用于类型的内存分配,并且内存对应的值为类型零值,其返回的是指向该类型的指针。
2.6 空指针
前面讲过,当一个指针被定义后没有分配到任何变量(无指向),那么这个指针变量的值就是 nil
。
nil
指针也称为空指针,相当于其他语言中的 null
、NULL
一样,都指代零值或者空值。
go
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr) // 0
}
判断一个指针是否空指针的方式:
go
if(ptr == nil) {
// ptr 是空指针
// ...
}
三、总结
本文介绍了在Go语言中使用指针的基本概念,包括如何获取内存地址、指针的声明和使用、值传递的原理以及空指针的概念。通过本文的学习,读者应该能够更自信地在Go语言中使用指针,并理解指针在内存管理和数据操作中的重要作用。