在转到函数中按引用和值传递

Nic*_*ock 9 pointers go

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

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

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

这对我来说没有意义。

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

 databaseFunction(DatabaseType db) {
      // do something
}
Run Code Online (Sandbox Code Playgroud)

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

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

func PrintPerson(p *Person) 只接收指针地址(引用)

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

Adr*_*ian 28

首先,从技术上讲,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!
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @krulik,这是看待它的一种方式,但对大多数开发人员来说没有帮助。由于“Java 中的所有对象都是引用”,因此任何时候传递一个对象时,都“传递了一个引用”。因此,引用传递语义适用于绝大多数 Java 代码。 (6认同)
  • @Adrian Java 也总是按值传递 (3认同)
  • @juanpa.arrivilillaga 我的回答是“虽然 Java 通过引用传递*对象*”。`int` 是一个原语,而不是一个对象。Java 中的所有对象都是引用。虽然从技术上讲引用是按值传递的,但它们仍然是引用,这意味着 Java 开发人员的经验主要是传递引用。Go 中的结构“不是”引用,尽管它们在某些方面“看起来”像 Java 对象(具有字段和方法)。对于习惯使用类 Java 语言的开发人员来说,这是一个有用的比较和区分。 (3认同)
  • @juanpa.arrivillaga 此页面上唯一出现的短语“通过引用调用”出现在您的评论中,所以我不确定您指的是什么。我的观点正如我之前所解释的:结构是值,与来自其他语言的开发人员可能习惯的对象不同,它们是引用。 (3认同)
  • 传递指针的另一个原因是减少传递的值的大小。指针是单个机器字(4 或 8 个字节,取决于您的系统架构)的大小。一个 sql.DB 是一个结构,它的大小可达 180 字节左右。将大量数据复制到函数调用中。 (2认同)
  • 180b 确实不是那么多,但是是的,在某些情况下这是使用指针的正当理由。一般来说,除非分析表明它解决了真正的问题,否则我不会使用这种推理。过早优化,万恶之源,yadda yadda。 (2认同)
  • @Kaedys:180 字节非常微不足道。单个缓存线基本上是空闲的,大多数常见的 CPU 在一次突发中最多传输 8 条缓存线。简单地取消引用指针可能需要更长的时间,或者不会,这取决于数据位置和缓存状态。我通过删除指针优化了许多代码。首先使用指针来实现其功能。 (2认同)
  • 我没有在答案中提到内存节省,因为这听起来像是一个新的 Go 开发人员在问,而且这样的建议很容易导致过度使用或误用某个功能。虽然在某些情况下,通过传递指针“可能”有助于节省内存,但这些情况很少见,应该通过直接测量来发现。对于学习该语言的人来说,我认为最好不要考虑使用指针的原因,而只关注指针语义作为使用它们的原因。 (2认同)

Ker*_* SB 9

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

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

func RealSwap(a *int, b *int) {
  *a, *b = *b, *a
}
Run Code Online (Sandbox Code Playgroud)

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