Go指针与指针(双指针)
指针是编程语言中经常使用的概念。它是一个变量,它存储了另一个变量的内存地址。在Go中,指针声明时使用“*”号。指针可以用来传递地址,以及在函数中动态分配内存空间。
如何声明一个指针
在Go中,声明一个指针非常容易。只需要在变量类型前面添加一个星号(*)即可声明一个指针。例如,以下代码演示了如何声明一个指向整数的指针:
package main
import "fmt"
func main() {
var a int = 10 // 定义整数类型变量 a
var p *int // 定义整数类型的指针 p
p = &a // p 指向 a 变量的地址
fmt.Printf("p 指向的地址为:%x\n", p)
fmt.Printf("p 的值为:%d\n", *p)
}
在上面的代码中,我们首先声明了一个整数变量a,并将其初始化为10。然后我们声明了一个整数指针变量p,并将其设置为指向a变量的地址。最后,我们可以通过打印出指针变量p的值和指针变量p所指向的变量的值来验证指针是否正确。
输出结果为:
p 指向的地址为:20818a220
p 的值为:10
什么是指针的指针
指针的指针也被称为双指针。它是指一个指针变量,它存储的是另一个指针变量的地址。在Go中,可以通过在指针类型前面添加一个星号(*)来声明一个指针的指针。
以下是一个简单的例子,演示如何在Go中声明一个指针的指针:
package main
import "fmt"
func main() {
var a int = 10
var p *int = &a // 指向整数变量a的指针
var pp **int = &p // 指向指针变量p的指针
fmt.Printf("a变量的地址:%x\n", &a)
fmt.Printf("p指向的地址:%x\n", p)
fmt.Printf("pp指向的地址:%x\n", pp)
fmt.Printf("pp指向的值:%d\n", **pp)
}
在上面的例子中,我们首先声明了一个整数变量a,然后声明了一个整数指针变量p,并将其设置为指向a变量的地址。接下来,我们声明了一个指向指针变量p的指针变量pp,并设置其为指向p变量的地址。
最后,我们通过在pp指针变量前面添加两个星号来获取a变量的值:
输出结果为:
a变量的地址:20818a220
p指向的地址:20818a220
pp指向的地址:20818a228
pp指向的值:10
在Go中使用指针的指针
因为指针的指针可以用来表示一个指针变量的地址,所以在Go中使用指针的指针可以很方便地实现动态内存分配和传递结构体或其他大型数据。
以下代码演示如何在Go中使用指针的指针来分配动态内存空间:
package main
import "fmt"
func allocateMemory(ppp ***int) {
var a int = 10
var p *int = &a
var pp **int = &p
*ppp = pp
}
func main() {
var p **int
allocateMemory(&p)
fmt.Printf("p 指向的值为:%d\n", **p)
}
在上面的代码中,我们定义了一个函数allocateMemory,该函数使用三级指针变量ppp来接受一个指向指针的指针。在函数中,我们首先声明了一个整数变量a,然后声明了一个整数指针变量p,并将其设置为指向a变量的地址。接下来,我们声明了一个指向指针变量p的指针变量pp,并设置其为指向p变量的地址。最后,我们通过向ppp指针所指向的地址传递pp指针变量来给外部变量赋值。
在主函数中,我们声明了一个二级指针变量p,并将其传递给allocateMemory函数。函数会将pp指针变量的地址赋值给p变量,最后,我们打印出p所指向变量的值,以验证指针的指针是否正确:
输出结果为:
p 指向的值为:10
双指针的实际应用
在实际应用中,双指针通常用于以下几个方面:
- 动态内存分配
使用指针的指针可以使用动态内存分配,它可以在运行时动态分配内存空间。例如,在Go语言中,使用指针的指针来动态分配一个二维数组:
package main
import "fmt"
func allocateMemory(ppp **int, rows int, cols int) {
*ppp = make([]int, rows*cols)
}
func main() {
var rows int = 3
var cols int = 2
var p **int
allocateMemory(&p, rows, cols)
for i := 0; i < rows*cols; i++ {
fmt.Printf("%d ", *(*p+i))
}
}
在上面的代码中,我们定义了一个函数allocateMemory,该函数使用二级指针变量ppp来接受指针的地址、行数和列数。在函数中,我们调用了make函数来分配一维数组,将得到的内存地址通过ppp指针来传递给外部变量。
在主函数中,我们声明了一个指针的指针变量p,并将其传递给allocateMemory函数。函数会在运行时动态分配一维数组,并将数组的起始地址通过p所指向的指针变量来传递给外部变量。最后,我们打印出二维数组的值来验证指针的指针是否正确。
- 多级指针
使用指针的指针可以实现多级指针。例如,在Go语言中,我们可以使用指针的指针来实现三级指针:
package main
import "fmt"
func allocateMemory(pppp ****int) {
var a int = 10
var p *int = &a
var pp **int = &p
var ppp ***int = &pp
*ppp = ppp
}
func main() {
var ppp ***int
allocateMemory(&ppp)
fmt.Printf("ppp 指向的值:%d\n", ***ppp)
}
在上面的代码中,我们定义了一个函数allocateMemory,该函数使用四级指针变量pppp来接受一个指向三级指针变量的指针。在函数中,我们定义了一个整数变量a,然后声明了一个指向整数变量a的指针p,接着定义了一个指向指针变量p的指针pp,最后定义了一个指向指针变量pp的指针ppp。最后,我们将ppp指向的地址传递给外部的pppp指针。
在主函数中,我们声明了一个三级指针变量ppp,并将其传递给allocateMemory函数。函数会将ppp指向指针变量ppp的地址赋值给外部变量。最后,我们打印出ppp所指向变量的值,以验证指针的指针是否正确:
输出结果为:
ppp 指向的值:10
- 嵌套结构体
使用指针的指针可以减少内存占用,特别是在嵌套结构体中。例如,在Go语言中,我们可以使用指针的指针来嵌套结构体:
package main
import "fmt"
type Address struct {
city string
state string
}
type Person struct {
name string
age int
address *Address
}
func main() {
var a Address = Address{city: "北京", state: "北京市"}
var p Person = Person{name: "张三", age: 20, address: &a}
var pp *Person = &p
fmt.Printf("pp 变量的地址:%x\n", pp)
fmt.Printf("pp 所指向的值的名称:%s\n", (*pp).name)
fmt.Printf("pp 所指向的值的地址:%x\n", (*pp).address)
fmt.Printf("pp 所指向的值的地址下的 city 值:%s\n", (*(*pp).address).city)
}
在上面的代码中,我们定义了一个Address结构体,并在Person结构体中使用指针类型的变量来保存Address结构体的地址。接着,我们创建了一个Person变量p,并将其地址赋值给指向Person类型变量的指针pp。
最后,我们打印出pp变量的地址、它所指向的变量的名称、地址以及地址下的city值,以验证指针的指针是否正确:
输出结果为:
pp 变量的地址:20818a200
pp 所指向的值的名称:张三
pp 所指向的值的地址:208182f80
pp 所指向的值的地址下的 city 值:北京
结论
在Go语言中,我们可以使用指针和指针的指针来传递、动态分配内存空间和表示嵌套结构体等。通过您所学到的指针和指针的指针的知识,可以帮助您更好地理解程序运行过程中的内存管理和数据交互。在实际开发过程中,坚持良好的编程习惯和有良好的内存管理规划也非常重要,以提高代码的可读性、可维护性和可扩展性。