Jaki jest sens posiadania wskaźników w Go?

100

Wiem, że wskaźniki w Go pozwalają na mutację argumentów funkcji, ale czy nie byłoby prostsze, gdyby przyjęły tylko referencje (z odpowiednimi stałymi lub zmiennymi kwalifikatorami). Teraz mamy wskaźniki i niejawne przekazywanie przez odniesienie dla niektórych typów wbudowanych, takich jak mapy i kanały.

Czy coś mi brakuje, czy też wskazówki w Go to tylko niepotrzebna komplikacja?

zaraz
źródło
1
Oto pytanie, które może pomóc w wyjaśnieniu: stackoverflow.com/questions/795160/ ... Istnieje różnica między przekazywaniem referencji przez wartość a przekazywaniem naprawdę przez referencję.
R. Martinho Fernandes
1
Uwaga: pytanie dotyczy Javy, ale dotyczy również tutaj.
R. Martinho Fernandes
1
„a dla niektórych wbudowanych typów, takich jak mapy i kanały, niejawne przekazywanie przez odniesienie”. Nie, w Go wszystko jest wartością przekazaną. Niektóre typy są (nieformalnie określane jako) typami referencyjnymi, ponieważ mają wewnętrzny zmienny stan.
newacct
Problem z tym pytaniem polega na tym, że „referencje” nie są pojedynczą rzeczą o dobrze zdefiniowanych właściwościach. Termin „referencje” jest bardzo niejasny. W odpowiedziach widzimy, jak wiele osób wczytuje różne rzeczy w słowo „referencje”. Więc to pytanie powinno szczegółowo wyjaśniać, jakie są różnice między wskaźnikami Go a odniesieniami, które ma na myśli pytanie.
mtraceur

Odpowiedzi:

36

Bardzo podoba mi się przykład wzięty z http://www.golang-book.com/8

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}

w przeciwieństwie do

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x is 0
}
Piotr Kochański
źródło
42
Pytanie brzmiało „dlaczego mamy wskaźniki zamiast referencji ” i nie rozumiem, dlaczego ten przykład nie działałby z referencjami.
AndreKR
@AndreKR Ponieważ możemy wybrać, czy przekazać przez referencję, czy przez wartość. Istnieją przypadki, w których oba mogą być pożądane.
JDSweetBeat
9
@DJMethaneMan To „wskaźniki a referencje”, a nie „wskaźniki a przekazana wartość”!
AndreKR
Jako komentarz boczny, przekazanie przez odwołanie zostało dodane w C # 2.0 za pomocą słowa kluczowego „ref”. Oczywiście wskazówki są nadal wygodniejsze w niektórych przypadkach, ponieważ możemy mieć wskaźnik do wskaźnika do wskaźnika ...
robbie fan,
Nie rozumiem, dlaczego Go ma być jednym z najłatwiejszych popularnych języków, a mimo to zawierają taką „funkcję”… To zagmatwane i wydaje się niepotrzebne, przynajmniej osobom, które to tutaj wskazują.
Akito
33

Wskaźniki są przydatne z kilku powodów. Wskaźniki umożliwiają kontrolę nad układem pamięci (wpływa na wydajność pamięci podręcznej procesora). W Go możemy zdefiniować strukturę, w której wszyscy członkowie są w ciągłej pamięci:

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

W tym przypadku Pointstruktury są osadzone w LineSegmentstrukturze. Ale nie zawsze możesz bezpośrednio osadzać dane. Jeśli chcesz obsługiwać struktury, takie jak drzewa binarne lub lista połączona, musisz obsługiwać jakiś rodzaj wskaźnika.

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java, Python itp. Nie ma tego problemu, ponieważ nie pozwala na osadzanie typów złożonych, więc nie ma potrzeby składniowego rozróżniania między osadzaniem a wskazywaniem.

Problemy ze strukturami języka Swift / C # rozwiązane za pomocą wskaźników Go

Możliwą alternatywą osiągnięcia tego samego jest rozróżnienie między structi, classjak C # i Swift. Ale to ma ograniczenia. Chociaż zwykle można określić, że funkcja przyjmuje strukturę jakoinout parametr, aby uniknąć kopiowania struktury, nie pozwala to na przechowywanie odniesień (wskaźników) do struktur. Oznacza to, że nigdy nie możesz traktować struktury jako typu referencyjnego, jeśli uznasz to za przydatne, np. Do utworzenia alokatora puli (patrz poniżej).

Niestandardowy alokator pamięci

Korzystając ze wskaźników, możesz również utworzyć własny alokator puli (jest to bardzo uproszczone, ponieważ usunięto wiele sprawdzeń, aby pokazać tylko zasadę):

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode

  nextFreeNode *TreeNode; // For memory allocation
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

Zamień dwie wartości

Wskaźniki pozwalają również na implementację swap. To jest zamiana wartości dwóch zmiennych:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

Wniosek

Java nigdy nie była w stanie w pełni zastąpić C ++ w programowaniu systemów w miejscach takich jak Google, po części dlatego, że wydajności nie można dostroić w takim samym stopniu z powodu braku możliwości kontrolowania układu i wykorzystania pamięci (błędy w pamięci podręcznej znacząco wpływają na wydajność). Go miał na celu zastąpienie C ++ w wielu obszarach i dlatego musi obsługiwać wskaźniki.

Erik Engheim
źródło
7
C # umożliwia przekazywanie struktur przez odwołanie. Zobacz słowa kluczowe „ref” i „out”.
olegz
1
Ok, więc to jest jak Szybki. Zastanowię się nad sposobem zaktualizowania mojego przykładu.
Erik Engheim
29

Odwołań nie można ponownie przypisać, podczas gdy wskaźniki mogą. Samo to sprawia, że ​​wskaźniki są przydatne w wielu sytuacjach, w których nie można użyć odwołań.

zildjohn01
źródło
17
Możliwość ponownego przypisania odniesień jest kwestią implementacji specyficzną dla języka.
crantok
28

Go ma być lakonicznym, minimalistycznym językiem. Zaczęło się więc od wartości i wskaźników. Później, z konieczności, dodano niektóre typy referencyjne (wycinki, mapy i kanały).


Język programowania Go: projektowanie języka Często zadawane pytania: Dlaczego mapy, wycinki i kanały są odniesieniami, a tablice są wartościami?

„Jest wiele historii na ten temat. Na początku mapy i kanały były wskaźnikami składniowymi i nie można było zadeklarować ani użyć instancji niebędącej wskaźnikiem. Ponadto zmagaliśmy się z tym, jak powinny działać tablice. Ostatecznie zdecydowaliśmy, że ścisłe oddzielenie wskaźników i wartości sprawiło, że język stał się trudniejszy w użyciu. Wprowadzenie typów referencyjnych, w tym wycinków obsługujących postać referencyjną tablic, rozwiązało te problemy. Typy referencyjne dodają do języka trochę godnej pożałowania złożoności, ale mają duży wpływ na użyteczność: Go bardziej produktywny i wygodny język, kiedy zostały wprowadzone ”.


Szybka kompilacja jest głównym celem projektowania języka programowania Go; to ma swoje koszty. Wydaje się, że jedną z ofiar jest możliwość oznaczania zmiennych (z wyjątkiem podstawowych stałych czasu kompilacji) i parametrów jako niezmiennych. Został poproszony, ale został odrzucony.


golang-nuts: idź na język. Kilka uwag i wątpliwości.

„Dodanie const do systemu typów wymusza jego pojawienie się wszędzie i wymusza usunięcie go wszędzie, jeśli coś się zmieni. Chociaż oznaczanie obiektów jako niezmiennych może przynosić pewne korzyści, nie sądzimy, że kwalifikator typu const jest odpowiedni iść."

peterSO
źródło
FWIW, „typy referencyjne” w Go również można ponownie przypisać. Są bardziej jak ukryte wskazówki?
Matt Joiner
1
Są po prostu specjalną składnią dla struktur, które zawierają wskaźnik (i długość, pojemność, ...).
mk12