Странное поведение int внутри структуры

Допустим, у нас есть такая структура (одна из самых простых):

type some struct{
    I uint32
}

И мы хотим иметь переменную этого типа и атомарно увеличивать цикл for (возможно, в другой горутине, но теперь история другая). Я делаю следующее:

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

Мы получаем то, что ожидали, пока все хорошо, но если мы объявим функцию для этого типа следующим образом:

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

и вызовите эту функцию в приведенном выше примере (строка [1]), значение не увеличивается, и мы просто получаем нули. Вопрос очевиден - почему?

Это должно быть что-то основное, но, поскольку я новичок, я этого не понимаю.


person Gonzalez    schedule 05.03.2016    source источник
comment
Подход к указателям языка Go немного сбивает с толку. В Go вы определяете приемник метода, чтобы указать, к какой структуре прикрепить определенную функцию, чтобы сделать ее доступной для вызова в качестве метода.   -  person Tal Avissar    schedule 05.03.2016


Ответы (2)


Спецификация языка программирования 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}

Перейти к разделу часто задаваемых вопросов (FAQ)

Когда параметры функции передаются по значению?

Как и во всех языках семейства C, в Go все передается по значению. То есть функция всегда получает копию передаваемой вещи, как если бы существовал оператор присваивания, присваивающий значение параметру. Например, передача значения int в функцию создает копию int, а передача значения указателя создает копию указателя, но не данных, на которые он указывает.

Должен ли я определять методы для значений или указателей?

func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

Программистов, не привыкших к указателям, различие между этими двумя примерами может сбить с толку, но на самом деле ситуация очень проста. При определении метода для типа получатель (s в приведенных выше примерах) ведет себя точно так же, как если бы он был аргументом метода. Определять ли получателя как значение или как указатель — это тот же вопрос, что и должен ли аргумент функции быть значением или указателем. Есть несколько соображений.

Во-первых, и это самое главное, нужно ли модифицировать получатель в методе? Если это так, получатель должен быть указателем. (Срезы и карты действуют как ссылки, поэтому их история немного более тонкая, но, например, для изменения длины среза в методе получатель все равно должен быть указателем.) В приведенных выше примерах, если pointerMethod изменяет поля s, вызывающая сторона увидит эти изменения, но метод valueMethod вызывается с копией аргумента вызывающей стороны (это определение передачи значения), поэтому вносимые им изменения будут невидимы для вызывающей стороны.

Кстати, получатели указателей идентичны ситуации в Java, хотя в Java указатели скрыты под обложками; это приемники значения Go, которые необычны.

Во-вторых, рассмотрение эффективности. Если приемник большой, например, большая структура, будет намного дешевле использовать приемник-указатель.

Далее последовательность. Если некоторые методы этого типа должны иметь приемники указателей, остальные тоже должны иметь, чтобы набор методов был непротиворечивым независимо от того, как используется тип. Подробнее см. в разделе о наборах методов.

Для таких типов, как базовые типы, срезы и небольшие структуры, приемник значений очень дешев, поэтому, если семантика метода не требует указателя, приемник значений эффективен и понятен.

person peterSO    schedule 05.03.2016

Ваша функция должна получить указатель на значение, которое будет увеличено, таким образом, вы не передаете копию структуры, и на следующей итерации я может быть увеличен.

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)
}
person Dominic St-Pierre    schedule 05.03.2016