在编程语言中有两种传参方式,1. 传值 2. 传引用。有的资料中也会把传指针单独列出来。不同的传参方式决定了我们对函数入参的使用方式, 比如是不是可以直接修改参数值,调用方可以感知到变量的修改。传参方式也会影响对内存的使用效率,如果参数是一个大对象,按值传递方式就会创建1个完全一样的拷贝,使内存占用直接提升了1倍。先用1个函数测试一下golang中的传参方式究竟是按值传递还是按引用传递。
package mainimport "fmt"type MyType struct { Val int32}func main() { val := MyType { Val: 2, } fmt.Printf("before, val: %#v \n", val) changeStructValue(val) fmt.Printf("after: %#v \n", val)}func changeStructValue(t MyType) { t.Val += 1 fmt.Printf("val in changeStructValue: %#v \n", t)}
运行结果:
被调用函数内修改值后调用方的值没有改变。得出
结论:
Golang的传参方式是按值传递。计算机的世界有时候很简单,又很复杂,程序运行真的像我们想的那样,按值传递就可以随意修改入参的值而对原值不产生任何影响吗?来看下面的代码。
package mainimport "fmt"type MyType struct { Val int32}func main() { slice := []int32{1,2,3} fmt.Printf("before: %#v \n", slice) changeSliceValue(slice) fmt.Printf("after: %#v \n", slice)}func changeSliceValue(slice []int32) { if len(slice) == 0 { return } slice[0] += 1 fmt.Printf("val in changeSliceValue: %#v \n", slice)}
运行结果:
传递到changeSliceValue中的切片内容竟然被修改了?!在Golang中数组、切片、map都存在于一个结构体中,具体的数据存储在结构体指向的一块内存区域中,只要不做内存的重新分配和空间扩容,函数参数和原变量修改的就是同一块内存空间。想进一步了解的朋友可以探索一下interface类型。
结论:
不要轻易修改任何传入的map、slice、array类型变量
。 Golang 可以在类型上创建方法, 方法的 receiver 既可以是 Type, 也可以是 *Type, receiver是怎么传值的呢?官方文档中有介绍,应该把方法的 receiver 理解为一个特殊的参数,在方法运行时会为 receiver 创建1个拷贝, 所以它也是按值传递的。可以做一下验证。
package mainimport "fmt"type MyInt int32func (t MyInt) Print() { fmt.Printf("MyInt Print, pointer to t: %p, val: %d \n", &t, t) t = t + 1 fmt.Printf("MyInt Print, after change pointer to t: %p, val: %d \n", &t, t)}func methodPssparaType() { var t MyInt t = 99 fmt.Printf("before, addr: %p, val: %d \n", &t, t) t.Print() fmt.Printf("after, addr: %p, val: %d \n", &t, t)}func main() { methodPssparaType()}
运行结果:
如果想要在method中修改值就应该传递指针, 对于函数和方法都适用。下面列出几个 《FAQ》 中给出的在声明方法时的建议, 建议中使用 T 和 *T 区分 receiver 是值还是指针。(注意:T 和 *T 对应不同的方法集(method set), *T 包含在 T 上声明的方法)1. 是否要修改 receiver 的值。如果不需要修改receiver的值,应该在T上创建方法, 否则receive应该为 *T;2. 效率。如果 T 需要分配大量的内存空间,应该把方法建立在 *T 上。3. 一致性。如果存在 receiver 为 *T 的方法, 则应该改写 T 的方法到 *T 上。Golang传参方式的内容介绍完了。下面介绍Golang中很 tricky 的一个东西。猜想一下下面代码的运行结果是什么?
package mainimport "fmt"type GreetType struct{}func (t *GreetType) Greet() { fmt.Println("hello")}func callOnNilReceiver() { var t *GreetType = nil t.Greet()}func main() { callOnNilReceiver()}
运行结果:
竟然没有panic?具体解答放在下次推送中,有想法的朋友可以在公众号后台给我留言。