最近在学习golang,对指针方面的一些概念有些迷惑,下了些功夫把相关的知识学习了一下,这里做个总结。
主要还是关于值传递和引用传递方面的东西,基本可以分为4种,只论结果,其他的网上已经说烂了。
值类型值传递
无论是golang、java、c#,值类型在作为参数传递时,都是深拷贝直接传递值的,因为值类型的内存分配在栈中,内存地址直接保存了值类型的真实值,在作为参数传递的时候,直接拷贝其值,所以在方法体内部修改时,不会对外部产生影响,当用其对另一个变量赋值时,也是直接拷贝其值,修改新的变量不会对旧变量产生影响。
package main
func foo(x int) {
x = 100
}
func main() {
x := 1
foo(x)
println(x) // 1
y := x
y = 200
println(x) // x is still 1
println(y) // y is now 200
}
值类型引用传递
值类型的引用传递实际上就是强行搞一个对值类型的引用,像上述那样用等号赋值是不行的,那样会直接拷贝一个新的变量,在c#中,我们可以通过ref关键字来创建并传递值类型的引用,如果需要直接创建值类型的引用,则需要像如下方式进行拆装箱:
int x = 1;
int y = (int)(object)x;
而在golang等有指针的语言下,可以直接通过&关键字获取值类型的指针,在使用指针作为值类型的引用,进行赋值和传参时,需要注意的是,只有修改其内部值有效,而直接将该引用指向新的内存地址是无效的:
package main
type someStruct struct {
value int
}
func foo1(x *someStruct) {
x.value = 100
}
func foo2(y *someStruct) {
y = &someStruct{100}
}
func main() {
x := someStruct{1}
foo1(&x)
println(x.value) // x.value is 100
y := someStruct{1}
foo2(&y)
println(y.value) // y.value is 1
}
这是因为修改时是直接对值的内存进行修改,如果你将引用的指向改变为新的内存,那对于方法外部则是无感的。
引用类型值传递
对于引用类型的值传递,就不得不说一句:“一切传引用本质都是传值”,是不是有点迷惑了,这就要先理解引用类型的变量里面存的是啥了,引用类型的变量的值,实际上存的是实际变量的内存地址,也就是引用,比如object x = new object(),x里面存的实际上是object的内存地址,当作为参数传递的时候,会将x内存储的指,也就是object的引用传递给方法体内部,同时和值类型的引用传递一样,当你改变引用的实际地址时,不会对外部生效,如果仅进行修改,则会对外部生效:
package main
import "fmt"
func foo1(someMap map[string]string) {
someMap["a"] = "2"
}
func foo2(someMap map[string]string) {
someMap = make(map[string]string)
}
func main() {
someMap1 := make(map[string]string)
someMap1["a"] = "1"
foo1(someMap1)
fmt.Println(someMap1["a"]) // "2"
someMap2 := make(map[string]string)
someMap2["b"] = "2"
foo2(someMap2)
fmt.Println(someMap2["b"]) // "2"
}
可以看到foo2方法内对someMap2重新赋值的操作没有生效,但foo1方法内对someMap1修改value的操作生效了。
引用类型引用传递
引用类型的引用传递实际上就是传递引用的内存地址的真实的值,听起来像绕口令一样,实际上可以理解为就是把真实的内存地址传递到方法体了,简单来说就是引用类型值传递传递的是引用本身的值,而引用类型引用传递传递的则是真实的值,无论是修改还是改变真实的值都会影响到外部,注意这里的改变都是基于原内存地址的改变,实际上改变的是真实内存地址存储的值:
package main
import "fmt"
func foo1(someMap *map[string]string) {
(*someMap)["a"] = "1"
}
func foo2(someMap *map[string]string) {
(*someMap) = make(map[string]string)
fmt.Printf("someMap改变后的实际地址 %p \r\n", someMap) // 0xc000006030
fmt.Printf("someMap的引用变量地址 %p \r\n", *someMap) // 0xc00007c4b0
}
func main() {
someMap1 := make(map[string]string)
someMap1["a"] = "1"
foo1(&someMap1)
fmt.Println(someMap1["a"]) // 1
someMap2 := make(map[string]string)
someMap2["b"] = "2"
fmt.Printf("someMap改变前的实际地址 %p \r\n", &someMap2) // 0xc000006030
foo2(&someMap2)
fmt.Println(someMap2["b"]) // ""
}