欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Golang的指针类型传递

程序员文章站 2022-06-22 20:28:13
...

关于Golang的指针传递

首先,我们先来看一段代码

package main

import "fmt"

func main(){
	var i []int
	try(i)
	fmt.Println(i) //[]
}

func try(i []int){
	i = append(i, 1)
}

在这里,理论上来说本应该打印[1],因为切片为一个指针,在try函数对其进行了append,其本来指向的地址也应该改变。但是,这里却为空

Golang的值传递

首先,Go这门语言对参数的传递都是值传递的,也就是说,在传递切片i的时候,函数只是复制了它本身的引用(其本身是个指针),如:

package main

import "fmt"

func main(){
	var i []int=[]int{0}
	try(i)
	fmt.Printf("%p\n",&i[0]) // 0xc00000a0b0
	fmt.Printf("%p\n",&i) // 0xc0000044a0

}

func try(i []int){
	fmt.Printf("%p\n",&i[0]) // 0xc00000a0b0
	fmt.Printf("%p\n",&i) // 0xc0000044c0
}

这里你就可能明白了,对切片的传递,其实是传递的引用,也就相当于新建一个指针,进而指向该切片。

但是,既然都指向了该切片了,理论上append该切片的话,也应该能打印出来,而一开头代码却无法打印,我们接着往下看

切片的特性

老规矩,先看一段代码

package main

import "fmt"

func main() {
	i:=[]int{1}
	try(i)
}

func try(i []int) {
	fmt.Printf("%p\n", &i[0])  // 0xc00000a0b0
	i = append(i, 1)
	fmt.Printf("%p\n", &i[0])  // 0xc00000a0e0
}

很明显。如果了解切片特性的人就知道,在GO的切片容量不足时,对其进行append,会新开辟一片空间,进而复制原来的新加入的,再由append返回地址。

这也就是为什么一开头的代码无法打印出新加入值的原因。

那么,我们给初始化的切片足够容量,是不是就可以打印了?

func main() {
	i:=make([]int,1,2)
	try(i)
	fmt.Println(i)  // [0]
    fmt.Printf("%p\n",&i[0]) // 0xc00000a0b0
}

func try(i []int) {
	i = append(i, 1)
	fmt.Println(i) // [0,1]
    fmt.Printf("%p\n",&i[0]) // 0xc00000a0b0
}

很明显,虽然两个切片头的地址都一样,但还是打印不了,

对于这一点,我们得知道切片在GO代码中的是如何定义的

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

我们前面说了GO是指传递的,也就是说,虽然函数中的切片指向的是源切片,但是它的len和cap却是一个值,换句话说,我虽然append了原切片,但是在主函数中的len和cap却没有改变,改变的只是原函数中的。

因此,我们要想获取函数中添加的1,也不是不可以

func main() {
	i:=make([]int,1,2)
	try(i)
	ptr:=uintptr(unsafe.Pointer(&i[0]))
	fmt.Println(*(*int)(unsafe.Pointer(ptr + 8))) // 1
}

func try(i []int) {
	i = append(i, 1)
}

这里,我们将切片的第一个元素的指针往后移8字节,就找到新添加的了。

总结

Go对于切片的操作虽然是传递的引用,但由于切片的特性,若在传递的函数中对其添加操作,其原函数中的切片是无法察觉的。因此我们尽量避免对切片参数进行添加这种情况。

相关标签: Golang golang