Wartości

Często spotykam się z tym, że programiści utożsamiają zmienną z jej zawartością. A jest to gruba nieprawda: zmienna to tylko nazwa (identyfikator) dzięki której możemy się odwołać do konkretnej wartości (która jest trzymana gdzieś w pamięci). Na przykładach:

a := "wartość"

Zmienna a wskazuje na wartość którą jest ciąg znaków "wartość". To że nie ma zmiennej wskazującej na jakąś wartość nie oznacza, że nie mamy takiej wartości zapisanej w pamięci, choć jest to mocna wskazówka dla garbage collectora, by taka pamięć zwolnić. Zmienna określonego typu może przechowywać tylko wartości które są "przypisywalne" do tego typu.

Sposoby tworzenia wartości

Są trzy podstawowe sposoby tworzenia wartości (a co za tym idzie alokowania pamięci)

  • zastosowanie wbudowanej funkcji new()
  • zastosowanie wbudowanej funkcji make()
  • zastosowanie literału

Napisałem "podstawowe", a to dlatego, że gdy operujemy na slice możemy "przez przypadek" utworzyć całkiem nową wartość. Dokładniej zgłębimy temat w artykule o wycinkach.

Wszystkie się od siebie różnią dlatego omówimy je osobno.

new()

To nietypowa funkcja dla Go, bo jako argument przyjmuje typ, a nie wartość, a co gorsza zwraca nową wartość ale innego typu niż jej przekazaliśmy :-)

A tak na poważnie: funkcja new zajmuje się alokowaniem pamięci dla wartości, której typ został przekazany w wywołaniu i zwraca wskaźnik do niej. W kodzie wygląda to tak:

type point struct {
    a int
    b int
}
var p *point
p = new(point)

Zauważ, że zadeklarowaliśmy p jako wskaźnik do wartości typu point.

Funkcja new inicjalizuje wartość na tak zwane zero typu, czyli jeśli typem był struct to wszystkie pola tej struktury zostaną wyzerowane; a jeśli typem jest int to zwróci wskaźnik do nowo utworzonej wartości zero.

Właśnie ze względu na tę ostatnią cechę nie używamy new dla typów referencyjnych: map, slice, chan, bo zerem dla typu referencyjnego jest nil zmienna wskazująca na nil nie jest zbyt użyteczna :-)

make()

Dla typów referencyjnych tj. map, slice i chan została stworzona inna funkcja: make, której oprócz typu przekazujemy początkowe właściwości:

  • dla slice to będzie długość (len) i pojemność (cap), przy czym pojemność jest opcjonalna:

    sli := make([]int, len, cap)
    sli2 := make([]int, len)
  • dla map to wstępna ilość elementów (cap), opcjonalna

    m := make(map[int]string, cap)
    m1 := make(map[int]string)
  • dla chan możemy określić wielkość bufora elementów (len)

    ch := make(chan int, len)//kanał z buforem
    ch1 := make(chan int)//kanał bez bufora

make zwraca wartość zgodną z typem przekazanym w wywołaniu.

Literały

Ze względu na objętość omówienie literałów wyłączyłem do osobnego artykułu