结构体中int的奇怪行为

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

Strange behaviour of int inside a struct

问题

假设我们有这样一个结构体(最简单的之一):

type some struct{
    I uint32
}

我们想要创建一个该类型的变量,并在for循环中对其进行原子递增(可能在另一个goroutine中,但现在情况不同)。我按照以下方式进行操作:

q := some{0}
for i := 0; i < 10; i++ {
    atomic.AddUint32(&q.I, 1) // 递增 [1]
    fmt.Println(q.I)
}

到目前为止,我们得到了我们期望的结果,一切都很好。但是,如果我们声明一个该类型的函数如下:

func (sm some) Add1(){
    atomic.AddUint32(&sm.I, 1)
}

并在上面的示例中调用此函数(第1行),值将不会递增,我们只会得到零。显然,问题是为什么会这样?

这可能是一些基础知识问题,但由于我刚接触Go语言,我还没有意识到原因。

英文:

Let's say we have this kind of a struct (one of the simplest ever):

type some struct{
	I uint32
}

And we want to have a variable of that type and to atomically increment in for loop (possibly in another goroutine but now the story is different). I do the following:

q := some{0}
for i := 0; i &lt; 10; i++ {
		atomic.AddUint32(&amp;q.I,1) // increment [1]
		fmt.Println(q.I)
}

We're getting what we'd expect, so far so good, but if we declare a function for that type as follows:

func (sm some) Add1(){
	atomic.AddUint32(&amp;sm.I,1)
}

and call this function in the above sample (line 1) the value isn't incremented and we just get zeros. The question is obvious - why?

This has to be something basic but since I am new to go I don't realize it.

答案1

得分: 7

《Go编程语言规范》

调用

在函数调用中,函数值和参数按照通常的顺序进行求值。在它们求值之后,调用的参数按值传递给函数,并且被调用的函数开始执行。当函数返回时,函数的返回参数按值传递回调用函数。

接收器sm some按值传递给方法,当从方法返回时,副本将被丢弃。使用指针接收器。

例如,

package main

import (
	"fmt"
	"sync/atomic"
)

type some struct {
	I uint32
}

func (sm *some) Add1() {
	atomic.AddUint32(&sm.I, 1)
}

func main() {
	var s some
	s.Add1()
	fmt.Println(s)
}

输出:

{1}

《Go常见问题解答(FAQ)》

函数参数何时按值传递?

与C语言系列的所有语言一样,Go中的所有内容都是按值传递的。也就是说,函数总是得到被传递的东西的副本,就好像有一个赋值语句将值分配给参数一样。例如,将int值传递给函数会创建int的副本,将指针值传递给函数会创建指针的副本,但不会创建指针所指向的数据的副本。

我应该在值上定义方法还是在指针上定义方法?

func (s *MyStruct) pointerMethod() { } // 在指针上定义的方法
func (s MyStruct) valueMethod() { }   // 在值上定义的方法

对于不习惯使用指针的程序员来说,这两个示例之间的区别可能会令人困惑,但实际情况非常简单。在类型上定义方法时,接收器(上述示例中的s)的行为就像它是方法的参数一样。因此,是否将接收器定义为值或指针是相同的问题,就像函数参数是值还是指针一样。有几个考虑因素。

首先,最重要的是,方法是否需要修改接收器?如果需要修改接收器,则接收器必须是指针。(切片和映射作为引用,因此它们的情况稍微复杂一些,但例如要在方法中更改切片的长度,接收器仍然必须是指针。)在上面的示例中,如果pointerMethod修改s的字段,调用者将看到这些更改,但valueMethod使用调用者的参数的副本进行调用(这就是传递值的定义),因此它所做的更改对调用者是不可见的。

顺便说一下,指针接收器与Java中的情况完全相同,尽管在Java中指针是隐藏在底层的;而Go的值接收器才是不寻常的。

其次是效率的考虑。如果接收器很大,比如一个大的结构体,使用指针接收器会更加高效。

接下来是一致性。如果类型的某些方法必须具有指针接收器,那么其余的方法也应该具有指针接收器,以便无论如何使用该类型,方法集都是一致的。有关详细信息,请参阅有关方法集的部分。

对于基本类型、切片和小型结构体等类型,值接收器非常便宜,因此除非方法的语义要求指针,否则值接收器是高效和清晰的。

英文:

> The Go Programming Language Specification
>
> Calls
>
> In a function call, the function value and arguments are evaluated in
> the usual order. After they are evaluated, the parameters of the call
> are passed by value to the function and the called function begins
> execution. The return parameters of the function are passed by value
> back to the calling function when the function returns.

The receiver sm some is passed by value to the method and the copy is discarded when you return from the method. Use a pointer receiver.

For example,

package main

import (
	&quot;fmt&quot;
	&quot;sync/atomic&quot;
)

type some struct {
	I uint32
}

func (sm *some) Add1() {
	atomic.AddUint32(&amp;sm.I, 1)
}

func main() {
	var s some
	s.Add1()
	fmt.Println(s)
}

Output:

{1}

> Go Frequently Asked Questions (FAQ)
>
> When are function parameters passed by value?
>
> As in all languages in the C family, everything in Go is passed by
> value. That is, a function always gets a copy of the thing being
> passed, as if there were an assignment statement assigning the value
> to the parameter. For instance, passing an int value to a function
> makes a copy of the int, and passing a pointer value makes a copy of
> the pointer, but not the data it points to.
>
> Should I define methods on values or pointers?
>
> func (s *MyStruct) pointerMethod() { } // method on pointer
> func (s MyStruct) valueMethod() { } // method on value
>
> For programmers unaccustomed to pointers, the distinction between
> these two examples can be confusing, but the situation is actually
> very simple. When defining a method on a type, the receiver (s in the
> above examples) behaves exactly as if it were an argument to the
> method. Whether to define the receiver as a value or as a pointer is
> the same question, then, as whether a function argument should be a
> value or a pointer. There are several considerations.
>
> First, and most important, does the method need to modify the
> receiver? If it does, the receiver must be a pointer. (Slices and maps
> act as references, so their story is a little more subtle, but for
> instance to change the length of a slice in a method the receiver must
> still be a pointer.) In the examples above, if pointerMethod modifies
> the fields of s, the caller will see those changes, but valueMethod is
> called with a copy of the caller's argument (that's the definition of
> passing a value), so changes it makes will be invisible to the caller.
>
> By the way, pointer receivers are identical to the situation in Java,
> although in Java the pointers are hidden under the covers; it's Go's
> value receivers that are unusual.
>
> Second is the consideration of efficiency. If the receiver is large, a
> big struct for instance, it will be much cheaper to use a pointer
> receiver.
>
> Next is consistency. If some of the methods of the type must have
> pointer receivers, the rest should too, so the method set is
> consistent regardless of how the type is used. See the section on
> method sets for details.
>
> For types such as basic types, slices, and small structs, a value
> receiver is very cheap so unless the semantics of the method requires
> a pointer, a value receiver is efficient and clear.

答案2

得分: 2

你的函数需要接收一个指向要增加的值的指针,这样你就不会传递结构体的副本,在下一次迭代中可以增加I的值。

package main

import (
    "sync/atomic"
    "fmt"
)

type some struct{
    I uint32
}

func main() {
    q := &some{0}
    for i := 0; i < 10; i++ {
        q.Add1()
        fmt.Println(q.I)
    }
}

func (sm *some) Add1(){
    atomic.AddUint32(&sm.I, 1)
}
英文:

Your function need to receive a pointer for the value to be incremented, that way you are not passing a copy of the struct and on next iteration the I can be incremented.

package main

import (
&quot;sync/atomic&quot;
&quot;fmt&quot;
)

type some struct{
    I uint32
}

func main() {
q := &amp;some{0}
for i := 0; i &lt; 10; i++ {
        q.Add1()
        fmt.Println(q.I)
}
}

func (sm *some) Add1(){
    atomic.AddUint32(&amp;sm.I,1)
}

huangapple
  • 本文由 发表于 2016年3月5日 20:57:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/35814424.html
匿名

发表评论

匿名网友

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

确定