【问题标题】:Passing by reference and value in Go to functions在 Go 函数中通过引用和值传递
【发布时间】:2018-04-28 00:34:25
【问题描述】:

我对在 Go 中通过引用和值传递有点困惑。

我已经看到对类型前面的 * 的解释。

  • 在类型名称前面,表示声明的变量将存储该类型的另一个变量的地址(不是该类型的值) 类型)。

这对我来说没有意义。

在 Java 中,如果我将数据库实例传递给函数,我会这样做

 databaseFunction(DatabaseType db) {
      // do something
}

但是在 go 示例中,我已经通过了。

func PutTasks(db *sql.DB) echo.HandlerFunc {

}

为什么我们需要在类型前面加上星号?

根据这个备忘单,我找到了。

func PrintPerson(p *Person) 只接收指针地址 (参考)

我不明白为什么我只想发送一个指针地址作为参数。

【问题讨论】:

    标签: pointers go


    【解决方案1】:

    首先,Go 在技术上只有值传递。传递指向对象的指针时,您是按值传递指针,而不是按引用传递对象。差异是微妙的,但有时是相关的。例如,您可以覆盖对调用者没有影响的指针值,而不是取消引用并覆盖它指向的内存。

    // *int means you *must* pass a *int (pointer to int), NOT just an int!
    func someFunc(x *int) {
        *x = 2 // Whatever variable caller passed in will now be 2
        y := 7
        x = &y // has no impact on the caller because we overwrote the pointer value!
    }
    

    关于您的问题“为什么我们需要在类型前面加上星号?”:星号表示该值是指向sql.DB 的类型指针,而不是sql.DB 类型的值。这些不能互换!

    为什么要发送一个指针地址?这样您就可以在函数的调用者和函数体之间共享值,函数内部所做的更改会反映在调用者中(例如,指针是“setter”的唯一方式方法可以作用于对象)。 Java 总是通过引用传递对象,而 Go 总是通过值传递(即它在函数中创建值的 副本);如果您将某些内容传递给函数,并且该函数修改了该值,则调用者将看不到这些更改。如果您希望更改传播到函数外部,则必须传递一个指针。

    另请参阅:Go tour section on PointersGo spec section on pointersGo spec section on the address operators

    【讨论】:

    • 传递指针的另一个原因是减小传递值的大小。指针是大小上的单个机器字(4 或 8 个字节,取决于您的系统架构)。 sql.DB 是一种结构,其大小最大为 180 字节左右。需要将大量数据复制到函数调用中。
    • 180b 确实不是很多,但是是的,在某些情况下这是使用指针的正当理由。一般来说,除非分析表明它解决了一个真正的问题,否则我不会使用这种推理。过早的优化,万恶之源,yadda yadda。
    • @Kaedys:180 字节是微不足道的。单个高速缓存行基本上是免费的,大多数常见的 CPU 在一次突发中传输多达 8 行。简单地取消引用指针可能需要更长的时间,或者不需要,这取决于数据位置和缓存状态。我通过删除指针优化了许多代码。首先使用指针来实现其功能。
    • @Adrian Java 也总是按值传递
    • @krulik 这是一种看待它的方式,但对大多数开发人员来说没有帮助。因为Java 中的所有对象都是引用,所以任何时候你传递一个对象,你都是在传递一个引用。因此,引用传递语义适用于绝大多数 Java 代码。
    【解决方案2】:

    引用语义的目的是允许函数在其自身范围之外操作数据。比较:

    func BrokenSwap(a int, b int) {
      a, b = b, a
    }
    
    func RealSwap(a *int, b *int) {
      *a, *b = *b, *a
    }
    

    当您调用BrokenSwap(x, y) 时,没有任何效果,因为该函数接收并操作数据的私有副本。相比之下,当您调用RealSwap(&x, &y) 时,您实际上交换了调用者xy 的值。在调用站点显式获取变量的地址会告知读者这些变量可能会发生变异。

    【讨论】:

      【解决方案3】:

      通过引用传递:- 当您将相同的变量以不同的名称传递给函数时。

      下面的例子来自 C++(因为 Go 没有这个概念),其中 a 和 a1 是同一个变量。

      void swap(int& a1, int& b1)
      {
          int tmp = a1;
          a1 = b1;
          b1 = tmp;
      }
       
      int main()
      {
          int a = 10, b = 20;
          swap(a, b);
          cout << "a " << a << " b " << b ;
      }
      

      Go 将所有内容作为数据传递(意味着它将数据从当前活动帧复制到新函数的新活动帧)。因此,如果您传递值,它会复制该值,并且优点是可以防止意外修改。当它传递变量的地址时,它也复制到新的指针变量中,但由于指针的大小更小,因此具有效率优势。

      【讨论】:

        猜你喜欢
        • 2022-01-11
        • 2016-04-11
        • 2016-12-20
        • 1970-01-01
        • 2013-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多