- 布尔型,整型,指针
布尔型和整型一般不用考虑指针类型,因为它们的对象很小,在内存上的开销可以忽略不计。只有想修改同一个变量的值才用指针传递。 - 数组
从内存和性能的角度上看,在函数间传递数组的开销是十分巨大的,当这个变量是一个数组时,不管这个数组有多长都会被完整复制,然后传递给函数 - 字典,切片,通道
这三个类型都是指向指针类型(指向一个底层的数据结构),可以当成是 *T 类型使用。
判断是否需要创建副本
-
参数类型 T 副本创建 (按值传递)
type Duck struct { Age int Name string } func passV(b Duck) { //参数类型 *T 副本创建,不会修改原始变量的值 b.Age++ b.Name = "Great" + b.Name fmt.Printf("传入修改后的Duck:\t %+v, \t内存地址:%p\n", b, &b) } func main() { parrot := Duck{Age: 1, Name: "Blue"} fmt.Printf("原始的Duck:\t\t %+v, \t\t内存地址:%p\n", parrot, &parrot) passV(parrot) fmt.Printf("调用后原始的Duck:\t %+v, \t\t内存地址:%p\n", parrot, &parrot) } /* 原始的Duck: {Age:1 Name:Blue}, 内存地址:0xc000056440 传入修改后的Duck: {Age:2 Name:GreatBlue}, 内存地址:0xc0000564a0 调用后原始的Duck: {Age:1 Name:Blue}, 内存地址:0xc000056440 */
可以看到在 passV 函数中,在 T 类型作为参数的时候,传递的 parrot 参数会将它的副本(内存地址:0xc0000564a0)传递给 passV 函数,在这个函数内对参数的改变不会影响原始变量的地址(内存地址:内存地址:0xc000056440)
-
参数类型 *T 副本创建 (按引用传递)
type Duck struct { Age int Name string } func passP(b *Duck) { //参数类型 *T 副本创建,修改原始变量的值 b.Age++ b.Name = "Great" + b.Name fmt.Printf("传入修改后的Bird:\t %+v, \t内存地址:%p, 指针的内存地址: %p\n", *b, b, &b) } func main() { parrot := &Duck{Age: 1, Name: "Blue"} fmt.Printf("原始的Bird:\t\t %+v, \t\t内存地址:%p, 指针的内存地址: %p\n", *parrot, parrot, &parrot) passP(parrot) fmt.Printf("调用后原始的Bird:\t %+v, \t内存地址:%p, 指针的内存地址: %p\n", *parrot, parrot, &parrot) } /* 原始的Bird: {Age:1 Name:Blue}, 内存地址:0xc0000044c0, 指针的内存地址: 0xc000006028 传入修改后的Bird: {Age:2 Name:GreatBlue}, 内存地址:0xc0000044c0, 指针的内存地址: 0xc000006038 调用后原始的Bird: {Age:2 Name:GreatBlue}, 内存地址:0xc0000044c0, 指针的内存地址: 0xc000006028 */
可以看到在 passP 中,在 T 类型作为参数的时候,传递的 parrot 参数会创建指针的副本(指针的内存地址: 0xc000006038)和原始变量的指针地址(指针的内存地址:0xc000006028),都指向同一个内存(实际)地址(0xc0000044c0)。显然,在函数内对 T 的改变会影响到原始变量的值,因为它是在同一个对象的操作。
如何选择 T 还是 *T
- 如果变量是数组或者结构体,使用类型 T 创建副本会影响性能,增加内存开销,可以使用指针类型 *T 传递
- 如果使用类型 T ,Golang 编译器则尽量将对象分配到栈上,而 *T 则可能被分配到对象上,会影响垃圾回收
- 如果不希望修改变量,就选择传递值类型 T ;如果希望修改原始变量的值,那么就选择指针类型 *T
【注】:请参考书中 《Go语言编程入门与实战技巧》P109页
有疑问加站长微信联系(非本文作者)