乐趣区

Golang-学习笔记参数类型传递机制

  • 布尔型,整型,指针
    布尔型和整型一般不用考虑指针类型,因为它们的对象很小,在内存上的开销可以忽略不计。只有想修改同一个变量的值才用指针传递。
  • 数组
    从内存和性能的角度上看,在函数间传递数组的开销是十分巨大的,当这个变量是一个数组时,不管这个数组有多长都会被完整复制,然后传递给函数
  • 字典,切片,通道
    这三个类型都是指向指针类型(指向一个底层的数据结构),可以当成是 *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 页

退出移动版