2012-06-11 12 views
7

Uczę się Go, a ja jestem nieco zdezorientowany, kiedy używać wskaźników. Konkretnie, po zwróceniu struct z funkcji, kiedy jest właściwe, aby zwrócić samą instancję struct i kiedy jest właściwe zwrócenie wskaźnika do struct?Kiedy to jest dobry pomysł, aby zwrócić wskaźnik do struktury?

Przykładowy kod:

type Car struct { 
    make string 
    model string 
} 

func Whatever() { 
    var car Car 

    car := Car{"honda", "civic"} 

    // ... 

    return car 
} 

Jakie są sytuacje, w których chciałbym wrócić wskaźnik, a gdzie ja nie chcą? Czy istnieje dobra zasada?

+4

To nie jest C ... –

+0

Czy te same zasady nie mają zastosowania? – Carson

+2

nie, różne zasady dla różnych języków. Każdy język ma swoje ograniczenia, a ja osobiście nie znam Go, więc nie mogę mówić za to, ale wiem, że w C, zwracanie wskaźnika do obiektu przydzielonego na stosie jest gigantycznym nie-nie. –

Odpowiedz

12

Są dwie rzeczy, które chcesz mieć na uwadze, wydajność i API.

W jaki sposób używany jest samochód? Czy to obiekt, który ma państwo? Czy to duża struktura? Niestety nie można odpowiedzieć, gdy nie mam pojęcia, czym jest samochód. Prawdę mówiąc, najlepiej jest zobaczyć, co robią inni i je skopiować. W końcu masz przeczucie tego typu rzeczy. Opiszę teraz trzy przykłady ze standardowej biblioteki i wyjaśnię, dlaczego uważam, że wykorzystali to, co zrobili.

  1. hash/crc32: Funkcja crc32.NewIEEE() zwraca typ wskaźnika (faktycznie, interfejs, ale typ bazowy jest wskaźnikiem). Wystąpienie funkcji mieszającej ma stan. Podczas pisania informacji do skrótu, sumuje dane, więc po wywołaniu metody Sum(), da ci stan tej jednej instancji.

  2. time: Funkcja time.Date zwraca strukturę Time. Czemu? Czas to czas. Nie ma stanu. To jest jak liczba całkowita, w której można je porównać, wykonać na nich wstępne matematykę itp. Projektant interfejsu API zdecydował, że modyfikacja czasu nie zmieni aktualnego, ale zmieni się na nowy. Jako użytkownik biblioteki, jeśli chcę mieć czas za miesiąc, chciałbym nowego obiektu czasu, a nie zmienić obecnego, który mam. Czas to także tylko 3 słowa. Innymi słowy, jest mały i nie byłoby żadnego zwiększenia wydajności w używaniu wskaźnika.

  3. : jest interesująca. Możemy w zasadzie zgodzić się, że kiedy modyfikujesz big.Int, często będziesz potrzebować nowego. A big.Int nie ma wewnętrznego stanu, więc dlaczego jest wskaźnikiem? Odpowiedź to po prostu wydajność. Programiści zdali sobie sprawę, że duże int są ... duże. Ciągłe przydzielanie każdej operacji matematycznej może nie być praktyczne. Postanowili więc użyć wskaźników i pozwolić programistom zdecydować, kiedy przydzielić nową przestrzeń.

Czy odpowiedziałem na twoje pytanie? Prawdopodobnie nie. Jest to decyzja projektowa, którą należy opracować indywidualnie dla każdego przypadku. Używam biblioteki standardowej jako przewodnika podczas projektowania własnych bibliotek. To wszystko sprowadza się do osądu i tego, jak można oczekiwać, że kod klienta użyje twoich typów.

+1

musiałem to przeczytać kilka razy, aby zdać sobie sprawę, że jest to naprawdę dobra odpowiedź. Dziękuję Ci. – Carson

+0

Świetna odpowiedź. Jestem nowy w Go i zostałem tutaj polecony przez Stephena z # go-nuts na IRC. –

1

Bardzo losely wyjątki mogą pokazać się w szczególnych okolicznościach:

  • zwrócić wartość, gdy jest to naprawdę niewielka (nie więcej niż kilku słowach).
  • Zwraca wskaźnik, gdy narzut kopiowania znacznie zaszkodziłby wydajności (rozmiar jest dużo słów).
2

Często, gdy chcesz naśladować styl obiektowy, gdzie masz "obiekt", który przechowuje stan i "metody", które mogą zmienić obiekt, to masz funkcję "konstruktora", która zwraca wskaźnik do struktury (należy go traktować jako "odniesienie do obiektu", podobnie jak w innych językach OO). Metody Mutatora musiałyby być metodami typu wskaźnik-do-struktury, a nie samym typem struktury, w celu zmiany pól "obiektu", więc wygodnie jest mieć wskaźnik do struktury struct zamiast struct sama wartość, aby wszystkie "metody" były w zestawie metod.

Na przykład, aby naśladować coś takiego w Javie:

class Car { 
    String make; 
    String model; 
    public Car(String myMake) { make = myMake; } 
    public setMake(String newMake) { make = myMake; } 
} 

Będziesz często zobaczyć coś takiego w ruchu:

type Car struct { 
    make string 
    model string 
} 
func NewCar(myMake string) *Car { 
    return &Car{myMake, ""} 
} 
func (self *Car) setMake(newMake string) { 
    self.make = newMake 
} 
+0

, więc z go, czy nie powinienem używać słowa kluczowego "new"? powinienem zwrócić referencję, jak to zilustrowałeś? jestem zdezorientowany twoim przykładem. – Carson

+0

@Carson: cóż, możesz użyć 'new', ale' new' inicjalizuje wartość do wartości zerowej typu, która dla structs to wszystkie pola zainicjalizowane na wartość zerową. To może nie być prawidłowo zainicjowana wartość dla wszystkich typów. Jeśli zerowa wartość jest w porządku dla zainicjowania twojego typu, możesz po prostu użyć 'new'; ale jeśli twój typ potrzebuje niestandardowego "konstruktora", powinieneś zdefiniować swoją własną funkcję, tak jak pokazałem, – newacct

Powiązane problemy