在设计和编写库时,何时应该使用指针作为参数,何时不应该使用呢?

huangapple go评论70阅读模式
英文:

In terms of design and when writing a library, when should I use a pointer as an argument, and when should I not?

问题

抱歉,如果我的问题看起来很愚蠢。我的背景是PHP、Ruby、Python、Lua等语言,对于实际场景中的指针一无所知。

根据我在互联网上的阅读和我在一个问题中得到的回答(https://stackoverflow.com/questions/35000292/when-is-a-pointer-idiomatic/35000714#35000714),我理解到:

  • 当复制大量数据时应该使用指针。不要获取整个对象层次结构,而是接收其地址并访问它。
  • 当你有一个修改结构体的函数时,必须使用指针。

所以,指针似乎是一件很好的事情:我应该总是将它们作为函数参数获取,因为它们非常轻量级,如果我最终不需要修改结构体上的任何内容,那也没关系。

然而,直观地看这个陈述,我可以感觉到它听起来非常可怕,但我不知道为什么。

那么,作为一个设计结构体及其相关函数或仅仅是函数的人,我应该在什么时候接收一个指针?在什么时候接收一个值,为什么?

换句话说,当我的NewAuthor方法返回&Author{ ... }时,我应该在什么时候返回Author{ ... }?我的函数在什么时候应该以指向作者的指针作为参数,什么时候应该只获取类型为Author的值(副本)?

英文:

Sorry if my question seems stupid. My background is in PHP, Ruby, Python, Lua and similar languages, and I have no understanding of pointers in real-life scenarios.

From what I've read on the Internet and what I've got as responses in a question I asked (https://stackoverflow.com/questions/35000292/when-is-a-pointer-idiomatic/35000714#35000714), I have understood that:

  • Pointers should be used when copying large data. Instead of getting the whole object hierarchy, receive its address and access it.
  • Pointers have to be used when you have a function on a struct that modifies it.

So, pointers seem like a great thing: I should just always get them as function arguments because they are so lightweight, and it's okay if I somehow end up not needing to modify anything on the struct.

However, looking at that statement intuitively, I can feel that it sounds very creepy, and yet I don't know why.

So, as someone who is designing a struct and its related functions, or just functions, when should I receive a pointer? When should I receive a value, and why?

In other words, when should my NewAuthor method return &Author{ ... }, and when should it return Author{ ... }? When should my function get a pointer to an author as an argument, and when should it just get the value (a copy) of type Author?

答案1

得分: 3

指针和值都有权衡之处。

一般来说,指针会指向系统中的某个内存区域,无论是函数堆栈中想要传递指向局部变量的指针,还是堆上的某个位置。

func A() {
    i := 25
    B(&i) // A 设置堆栈帧以调用 B,
          // 它复制 i 的地址,以便 B 以后可以查找它。
    // 此时,i 等于 30
}
func B(i *int){
     // 这里,i 指向 A 的堆栈帧。
     // 为了执行这个操作,我查看我的变量 "i",
     // 看到它指向的内存地址,然后查看该地址以获取值 25。
     // 该地址可能在内存的另一页上,导致我不得不从主内存中查找它(这很慢)。
     println(10 + (*i)) 
     
     // 由于我有 A 的局部变量的地址,我可以修改它。
     *i = 30
}

使用指针需要我不断地对其进行解引用,以查看它指向的数据。有时你不在乎,有时它非常重要。这真的取决于应用程序。

如果该指针需要频繁解引用(例如:你传入一个数字用于多个不同的计算),那么你将不断地付出代价。

与使用值相比:

func A() {
    i := 25
    B(i) // A 设置堆栈帧以调用 B,将值 25 复制进去
    // i 仍然是 25,因为 A 给 B 传递的是值的副本,而不是地址。
}
func B(i int){
     // 这里,i 简单地在堆栈上。我不需要做任何操作来使用它。
     println(10 + i) 

     // 由于 i 在这里是 B 的堆栈上的值,所以在 B 的作用域之外不可见修改
     i = 30
}

由于没有需要解引用的内容,使用局部变量基本上是免费的。

传递值的缺点在于,如果这些值很大,因为将数据复制到堆栈上并不是免费的。

对于 int 来说,这是相当的,因为指针的大小与 "int" 相同。对于结构体或数组,你需要复制所有数据。

此外,堆栈上的大型对象可能会使堆栈变得更大。Go 使用堆栈重新分配来处理这个问题,但在高性能场景中,这可能对性能产生太大的影响。

还有一个数据安全方面的问题(无法修改通过值传递的内容),但我觉得在大多数代码库中通常不是问题。

基本上,如果你的问题已经可以通过 Ruby、Python 或其他语言解决,那么这些性能细节并不是特别重要。

一般来说,将结构体作为指针传递通常会做出 "正确的事情",同时学习该语言。

对于其他所有类型或希望保持为只读的内容,请传递值。

当然,也有例外情况,但最好在需要时学习这些例外情况,而不是试图一次重新定义你的世界。如果这样说得通的话。

英文:

There's tradeoffs for both pointers and values.

Generally speaking, pointers will point to some other region of memory in the system. Be it the stack of the function that wants to pass a pointer to a local variable or some place on the heap.

func A() {
    i := 25
    B(&i) // A sets up stack frame to call B,
          // it copies the address of i so B can look it up later.
    // At this point, i is equal to 30
}
func B(i *int){
     // Here, i points to A's stack frame.
     // For this to execute, I look at my variable "i", 
     //   see the memory address it points to, then look at that to get the value of 25.
     // That address may be on another page of memory, 
     // causing me to have to look it up from main memory (which is slow).
     println(10 + (*i)) 
     
     // Since I have the address to A's local variable, I can modify it.
     *i = 30
}

Pointers require me to de-reference them constantly whenever I was to see the data it points to. Sometimes you don't care. Other times it matters a lot. It really depends on the application.

If that pointer has to be de-referenced a lot (ie: you pass in a number to use in a bunch of different calcs), then you keep paying the cost.

Compared to using values:

func A() {
    i := 25
    B(i) // A sets up the stack frame to call B, copying in the value 25
    // i is still 25, because A gave B a copy of the value, and not the address.
}
func B(i int){
     // Here, i is simply on the stack.  I don't have to do anything to use it.
     println(10 + i) 

     // Since i here is a value on B's stack, modifications are not visible outside B's scpe
     i = 30
}

Since there's nothing to dereference, it's basically free to use the local variable.

The down side of passing values happens if those values are large because copying data to the stack isn't free.

For an int it's a wash because pointers are "int" sized. For a struct, or an array, you are copying all the data.

Also, large objects on the stack can make the stack extra big. Go handles this well with stack re-allocation, but in high performance scenarios, it may be too much of an impact to performance.

There's a data safety aspect as well (can't modify something I pass by value), but I don't feel that is usually an issue in most code bases.

Basically, if your problem was already solvable by ruby, python or other language without value types, then these performance nuances don't super-matter.

In general, passing structs as pointers will usually do "the right thing" while learning the language.

For all other types, or things that you want to keep as read-only, pass values.

There are exceptions to that rule, but it's best that you learn those as needs arise rather than try to redefine your world all at once. If that makes sense.

答案2

得分: 1

你可以在任何需要的地方使用指针,有时候你可能不想改变你的数据。它可以代表抽象数据,而且你可能不想显式地复制数据。只需按值传递,并让编译器完成其工作即可。

英文:

Simply you can use pointers anywhere you want, sometimes you don't want to change your data. It may stand for abstract data, and you don't want to explicitly copy the data. Just pass by value and let compiler do its job.

huangapple
  • 本文由 发表于 2016年1月26日 04:00:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/35001226.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定