在 Go 函数中按引用和值传递

Passing by reference and value in Go to functions

提问人:N P 提问时间:11/15/2017 最后编辑:ruakhN P 更新时间:7/11/2023 访问量:51819

问:

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

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

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

这对我来说没有意义。

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

 databaseFunction(DatabaseType db) {
      // do something
}

但是,在我拥有的 go 示例中,它是这样传递的。

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

}

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

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

func PrintPerson(p *Person) 仅接收指针地址 (参考资料)

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

指针 转到

评论


答:

28赞 Kerrek SB 11/15/2017 #1

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

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

72赞 Adrian 11/15/2017 #2

首先,从技术上讲,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.DBsql.DB

为什么要发送指针地址?这样,您就可以在函数的调用方和函数体之间共享值,并在函数内部所做的更改反映在调用方中(例如,指针是“setter”方法可以对对象工作的唯一方式)。这实际上也是你的 Java 代码正在做的事情;在 Java 中,您总是通过引用(指针)访问对象,因此 Java 会自动执行此操作,而不是让您显式指示它。但是在 Go 中,您也可以通过指针访问对象,因此您必须显式。如果调用函数并直接传入对象,则该函数将获得该对象的副本,如果该函数修改该对象,则调用方将看不到这些更改。因此,如果希望更改在函数外部传播,则必须传递指针。这样,指针将被复制,但它指向的对象将被共享。

另请参阅:指针的 Go 导览部分、指针的 Go 规范部分、地址运算符的 Go 规范部分

评论

12赞 Kaedys 11/15/2017
传递指针的另一个原因是减小所传递值的大小。指针的大小是单个机器字(4 或 8 个字节,具体取决于您的系统体系结构)。一个 sql.DB 是一种结构,其大小约为 180 字节。要复制到函数调用中的大量数据。
5赞 Adrian 11/15/2017
180b 真的不多,但是是的,在某些情况下,这是使用指针的正当理由。一般来说,我不会使用这种推理,除非分析表明它解决了一个真正的问题。过早的优化,万恶之源,yadda yadda。
5赞 JimB 11/15/2017
@Kaedys:180字节是微不足道的。单个缓存行基本上是免费的,大多数常见的 CPU 在一次突发中最多传输 8 行。简单地取消引用指针可能需要更长的时间,或者不需要,这取决于数据位置和缓存状态。我通过删除指针优化了许多代码。首先使用指针来实现其功能。
10赞 krulik 3/11/2020
@Adrian Java 也始终按值传递
6赞 Adrian 3/11/2020
@krulik这是一种看待它的方式,但对大多数开发人员来说却无济于事。由于 Java 中的所有对象都是引用,因此每当传递对象时,您都是在传递引用。因此,按引用传递语义适用于绝大多数 Java 代码。
1赞 Sumer 12/6/2021 #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 将所有内容作为数据传递(意味着它将数据从当前活动帧复制到新函数的新活动帧)。因此,如果您传递值,它会复制该值,并且优点是不会意外修改。当它传递变量的地址时,它也会复制到新的指针变量中,但由于指针的大小较小,因此具有效率优势。