go指针和函数传参

关于参数传递

其实go的参数传递,核心就是一句话:
go里所有参数传递都是值传递,既把参数复制一份放到函数里去用。
PS: 传的指针也是值拷贝!(具体总结继续往下看)

go的函数传参,不管参数是什么类型,都会复制一份,然后新的参数在函数内部被使用。

不像其他语言,有的时候传参只是传递一个原来参数的引用,在函数内部操作变量,其实还是操作的原变量。go内不会直接的操作原变量。

先说结论,go里面都是值传递,假如传的是参数是切片类型的slice,函数里面用 slice := append(slice, “123”) 函数返回后,原来传进去的slice是没有123的:

func add(slice []string) {
    
    
  slice := append(slice,123”)
}

调用:
var slice = []string{
    
    }
add(slice)

slice 还是空的!!!

是不是会联想到Java的List当做参数传进去,随他list引用指向别的东西不改变原有list,但是list.add()是可以改变原有的list的内容的!
其实你看go里面 slice := append(slice,123”),不是slice自身调用append、函数来添加,而是把append的结果赋值给slice,
这里就涉及到引用的改变了!

其实我觉得这里是 面向对象(Java是纯面向对象的)和面向过程的区别:
面向对象的方法都是用对象()作为调用方的:比如  list.add(123)
面向过程的思想是: add(list, 123), add没有所属的对象!

但是对于slice而言,如果只是改动某一项的值是可以的:


package main

import "fmt"

func main() {
    
    
	x := []int{
    
    1, 2, 3}
	f1(x)
	fmt.Println(x) //prints [7 2 3]
	f2(x)
	fmt.Println(x) //prints [7 2 3]

}

func f1(arr []int) {
    
    
	arr[0] = 7
}
func f2(arr []int) {
    
    
	arr = append(arr, 4)
}


关于指针

相比于C里的指针,go内部的指针一个被简化过的指针,指针可以取值获取其变量;变量可以取地址获取一个指针类型的值。 但是不可以对指针执行 地址的加减操作(unsafe.Pointer 可以,不在本次讨论范围之内)。

我觉得这个简化挺好,保留了参数传递时避免大变量的优势,又去掉了复杂性。

下面来通过实例具体说明
之所以用 切片做示例,是因为 切片引用类型,也就是说切片内部有一个指针 指向底层放数据的数组。 类似于一个指针变量。由于它的这个特性,更能说明问题。

错误示例1

package main
import "log"

var str []string

func main()  {
    
    
	setVal(str)
	log.Println(str)
}

//需要在这里赋值str,但是又不能直接引用 str
func setVal(val []string)  {
    
    
	val = []string{
    
    "a", "b"}
}

结果是空数组
虽然切片是引用类型,还是没有复制成功。 原因就是,在传参之前这个切片并没有被赋值,它内部的指针是一个空的。传参的时候,在setVal函数栈里面复制了一个切片变量val,这个val指向setVal函数内的实例变量。但是setVal 内对val的操作并不影响原来的切片变量str

错误示例2

package main

import "log"

var str *[]string

func main()  {
    
    
	setVal(str)
	log.Println(str)
}

//需要在这里赋值str,但是又不能直接引用 str
func setVal(val *[]string)  {
    
    
	val = &[]string{
    
    "a", "b"}
}

这个例子,看似使用了指针,你可能觉得指针作为参数应该会对原参数产生效果,但其实不会,最后打印str的结果是 nil!!

其错误原因与上一个例子基本一致,虽然传递的是一个 指针。但是这个指针val只是原参数str的复制品,这个新的指针和原来的指针指向同一内容,但是你改变val指向的内容,并不会使得str指向的东西改变!! (PS:这和Java里的引用传递,copy一份引用是一样的,具体看本人另一篇博客:Java值引用

正确版本:

package main

import "log"
var str []string

func main()  {
    
    
	setVal(&str)
	log.Println(str)
}

//需要在这里赋值str,但是又不能直接引用 str
func setVal(val *[]string)  {
    
    
	*val = []string{
    
    "a", "b"}
}

这个正确的例子主要有两点:
1.调用setVal的时候,传的是地址 2.setVal里面是取地址进行赋值操作的。

上面第二个错误例子是函数栈里val是一个单独的指针,但是这个例子,val接受到的就是str的地址,然后用*访问这个内存地址,就能取到原参数str了(因为是同一块内存), 所以其操作会对原参数产生影响。

所以,这里其实有个tip:
就是如果不用取地址,又解析地址的方式传递和使用参数,那么传递的数据是要复制一份的,如果数据量很大的话,可能效率比较低,所以在对象较大时像上面第三种方法一样传递指针能够提升性能,减少内存使用。(省去了内存拷贝)

参考:跳转

猜你喜欢

转载自blog.csdn.net/qq_43778308/article/details/113806054