Як надрукувати значення покажчика об’єкта Go? Що означає значення покажчика?


83

Я просто граюся з Go і ще не маю хорошої ментальної моделі того, коли структури передаються за значенням або за посиланням.

Це може бути дуже тупе запитання, але я просто хочу трохи поекспериментувати і подивитися, чи працюю я ще над тим самим об’єктом, чи зробив його копію (передав за значенням).

Чи є спосіб надрукувати покажчик (або внутрішній ідентифікатор, якщо значення вказівника змінюється gc) об’єкта?

package main

import ( "runtime" )

type Something struct {
    number int
    queue chan int
}

func gotest( s *Something, done chan bool ) {
    println( "from gotest:")
    println( &s )
    for num := range s.queue {
        println( num )
        s.number = num
    }
    done <- true
}

func main() {
    runtime.GOMAXPROCS(4)
    s := new(Something)
    println(&s)
    s.queue = make(chan int)
    done := make(chan bool)
    go gotest(s, done)
    s.queue <- 42
    close(s.queue)
    <- done
    println(&s)
    println(s.number)
}

дає на моєму вікні (компільована версія 8g):

0x4930d4
from gotest:
0x4974d8
42
0x4930d4
42

Чому значення вказівника з підпрограми go показує інше значення? Кількість вихідного об’єкта змінилася, тому вона працювала з тим самим об’єктом. Чи є спосіб побачити ідентифікатор об’єкта, який є постійним?

Відповіді:


116

Аргументи функції Go передаються за значенням.

По-перше, давайте відкинемо недоречні частини вашого прикладу, щоб ми могли легко побачити, що ви просто передаєте аргумент за значенням. Наприклад,

package main

import "fmt"

func byval(q *int) {
    fmt.Printf("3. byval -- q %T: &q=%p q=&i=%p  *q=i=%v\n", q, &q, q, *q)
    *q = 4143
    fmt.Printf("4. byval -- q %T: &q=%p q=&i=%p  *q=i=%v\n", q, &q, q, *q)
    q = nil
}

func main() {
    i := int(42)
    fmt.Printf("1. main  -- i  %T: &i=%p i=%v\n", i, &i, i)
    p := &i
    fmt.Printf("2. main  -- p %T: &p=%p p=&i=%p  *p=i=%v\n", p, &p, p, *p)
    byval(p)
    fmt.Printf("5. main  -- p %T: &p=%p p=&i=%p  *p=i=%v\n", p, &p, p, *p)
    fmt.Printf("6. main  -- i  %T: &i=%p i=%v\n", i, &i, i)
}

Вихід:

1. main  -- i  int: &i=0xf840000040 i=42
2. main  -- p *int: &p=0xf8400000f0 p=&i=0xf840000040  *p=i=42
3. byval -- q *int: &q=0xf8400000d8 q=&i=0xf840000040  *q=i=42
4. byval -- q *int: &q=0xf8400000d8 q=&i=0xf840000040  *q=i=4143
5. main  -- p *int: &p=0xf8400000f0 p=&i=0xf840000040  *p=i=4143
6. main  -- i  int: &i=0xf840000040 i=4143

У функції main, iє intзмінною в місці пам'яті ( &i) 0xf800000040з початковим значенням ( i) 42.

У функції main, pє вказівником на intзмінну в місці пам'яті ( &p) 0xf8000000f0зі значенням ( p= &i), 0xf800000040яке вказує на intзначення ( *p= i) 42.

У функції main, byval(p)виклик функції , яка присвоює значення ( p= &i) 0xf800000040аргументу в осередку пам'яті ( &p) 0xf8000000f0до функції byvalпараметра qв осередку пам'яті ( &q) 0xf8000000d8. Іншими словами, для byvalпараметра виділяється пам’ять і йому присвоюється qзначення main byvalаргументу p; значення pі qспочатку однакові, але змінні pі qрізні.

У функції byval, використовуючи pointer q( *int), який є копією pointer p( *int), integer *q( i) встановлюється на нове значення int 4143. В кінці перед поверненням. вказівник qвстановлюється на nil(нульове значення), що не впливає, pоскільки qє копією.

У функції main, pє вказівником на intзмінну в місці пам'яті ( &p) 0xf8000000f0зі значенням ( p= &i), 0xf800000040яке вказує на нове intзначення ( *p= i) 4143.

У функції main, iє intзмінною в місці пам'яті ( &i) 0xf800000040з кінцевим значенням ( i) 4143.

У вашому прикладі mainзмінна функції, яка sвикористовується як аргумент gotestвиклику функції , не є такою ж, як gotestпараметр функції s. Вони мають однакову назву, але є різними змінними з різними сферами дії та місцями пам'яті. Параметр функції sприховує аргумент виклику функції s. Ось чому у своєму прикладі я назвав аргументи та змінні параметрів pі qвідповідно, щоб підкреслити різницю.

У вашому прикладі ( &s) 0x4930d4- це адреса місця в пам'яті для змінної sу функції, mainяка використовується як аргумент для виклику функції gotest(s, done), і 0x4974d8є адресою місця в пам'яті для gotestпараметра функції s. Якщо ви встановите параметр s = nilв кінці функції gotest, це не вплине на змінну sin main; sв mainі sв gotest- різні місця пам'яті. Що стосується типів, &sє **Something, sє *Somethingі *sє Something. &sє вказівником на (адреса розташування пам'яті) s, який є вказівником на (адреса розташування пам'яті) анонімної змінної типуSomething. З точки зору цінностей, main.&s != gotest.&s, main.s == gotest.s, main.*s == gotest.*s, і main.s.number == gotest.s.number.

Вам слід скористатися порадою мудреця MKB і припинити використання println(&s). Використовуйте fmtпакет, наприклад,

fmt.Printf("%v %p %v\n", &s, s, *s)

Покажчики мають однакове значення, коли вони вказують на одне і те ж місце пам'яті; покажчики мають різні значення, коли вони вказують на різні місця пам'яті.


У моєму прикладі gotest приймає вказівник на "Щось", тому я припускаю, що це посилається на той самий об'єкт, і очевидно, що після того, як я змінив значення всередині рутинної програми, значення об'єкта також змінилося в основній функції . Друковане значення вказівника відрізняється.
Jeroen Dirks

@JamesDean У вашому прикладі ви друкуєте значення і тип вказівника ** Щось, що не є таким самим, як значення значення вказівника * Щось. Я переглянув свій приклад, щоб передати вказівник за значенням.
peterSO

@James Dean Ви роздрукували адресу вказівника (тобто вказівник на sвказівник), - вказівники передаються за значенням, адреса sне така, як s. Якщо ваша функція println( s )gotest інтегрувала, вона надрукувала б значення покажчика.
nos

О, тепер я бачу, що відбувається. Виконуючи println (& s), я друкував адресу вказівника замість значення вказівника. Якби я зробив println (и), він мав би показувати однаковий вказівник у функції main та go.
Jeroen Dirks,

@JamesDean Точно. У Go, як у C, для s * Щось, важливо знати різницю між & s, s та * s.
peterSO

6

У Go аргументи передаються за значенням.

package main

import "fmt"

type SomeStruct struct {
    e int
}

// struct passed by value
func v(v SomeStruct) {
    fmt.Printf("v: %p %v\n", &v, v)
    v.e = 2
    fmt.Printf("v: %p %v\n", &v, v)
}

// pointer to struct passed by value
func p(p *SomeStruct) {
    fmt.Printf("p: %p %v\n", p, *p)
    p.e = 2
    fmt.Printf("p: %p %v\n", p, *p)
}

func main() {
    var s SomeStruct
    s.e = 1
    fmt.Printf("s: %p %v\n", &s, s)
    v(s)
    fmt.Printf("s: %p %v\n", &s, s)
    p(&s)
    fmt.Printf("s: %p %v\n", &s, s)
}

Вихід:

s: 0xf800000040 {1}
v: 0xf8000000e0 {1}
v: 0xf8000000e0 {2}
s: 0xf800000040 {1}
p: 0xf800000040 {1}
p: 0xf800000040 {2}
s: 0xf800000040 {2}

2
type sometype struct { }
a := sometype {}
b := int(2)
println("Ptr to a", &a)
println("Ptr to b", &b)

4
Ви не повинні використовувати вбудований println, а використовувати щось відповідне з пакета fmt: golang.org/doc/go_spec.html#Bootstrapping
mkb

2

Як надрукувати значення покажчика об’єкта Go?

package main

import (
    "fmt"
)

func main() {
    a := 42
    fmt.Println(&a)
}

призводить до:

0x1040a124

Що означає значення покажчика?

За даними Вікіпедії :

Покажчик посилається на місце в пам'яті


1
package main

import "fmt"

func zeroval(ival int) {
     ival = 0
}

func zeroptr(iptr *int) {
     *iptr = 0
}

func main() {
    i := 1
    fmt.Println("initial:", i)
    zeroval(i)
    fmt.Println("zeroval:", i)
    //The &i syntax gives the memory address of i, i.e. a pointer to i.
    zeroptr(&i)
    fmt.Println("zeroptr:", i)
    //Pointers can be printed too.
    fmt.Println("pointer:", &i)
}

ВИХІД:

$ go run pointers.go
initial: 1
zeroval: 1
zeroptr: 0
pointer: 0x42131100
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.