Literały

Słowo literał w specyfikacjach Go jest odmieniane przez wszystkie przypadki, dlatego warto się z nim zaprzyjaźnić.

Literał to nic innego jak "wartość zapisana w kodzie programu".

Niektórych nawet doświadczonych programistów może dziwić czemu właśnie w tych dokumentach jest tak często używane. Dzieje się tak, ponieważ w Go nie ma klas, konstruktorów i innych cudów OOP, a wpisywanie literałów zamiast tworzenia wartości przez wbudowaną funkcję new() (lub make()) jest często wygodniejsze i dużo czytelniejsze.

Ci którzy mieli do czynienia z JavaScriptem (lub Pythonem) powinni być przyzwyczajeni do inicjalizacji tablic przez [], lub tworzenia obiektów (a w Pythonie dictów) za pomocą {}.

W Go praktyka tworzenia wartości za pomocą literałów odnosi się do wszystkich typów, prócz kanałów (chan).

Poniżej przegląd literałów z podziałem na "rodziny"

Literały numeryczne

Mamy trzy rodzajów literałów numerycznych

Liczby całkowite, możemy zapisać w podstawie dziesiętnej, ósemkowej i szesnastkowej

42 //42 zapis o podstawie dziesiętnej
052 //42 w podstawie ósemkowej (hex)
0x2A //42 w podstawie szesnastkowej

Liczby zmiennoprzecinkowe (float32 lub float64) zapisujemy w jednym z poniższych formatów

10. //obowiązkowa kropka po części dziesiętnej,
    // bez kropki to literał liczby całkowitej
0.1 //z lewej strony kropki część całkowita po prawej część dziesiętna
.1  //to co wyżej można pisać bez części całkowitej
1e-1 //możemy też używać postaci z wykładnikiem (1e-1 == 0.1 == .1)

Liczby zespolone, różnią się od zmiennoprzecinkowych tym że dodana jest do nich część urojona. Część urojona składa się z cyfr oraz symbolu i. Przykłady:

1i //tylko część urojona 1
10+10i //liczba zespolona z częścią rzeczywistą 10 i częścią urojoną 10
.1+.1i //liczba zespolona z częścią rzeczywistą 0.1 i częścią urojoną 0.1
1e-1-1i

Literały znakowe

Warto uważnie przeczytać jak deklarujemy literały znakowe, bo kilka razy mocno się naciąłem na różnice między grawisem (`) a pojedynczym (') i podwójnym (") cudzysłowem :-)

W pojedynczym cudzysłowie zapisujemy literały znakowe.

'a'

Taki literał jest reprezentowany przez liczbę całkowitą typu int, a nie przez byte (jakby mogło się czasem zdawać). Dzieje się tak ponieważ kod źródłowy Go jest domyślnie przetwarzany jakby był w kodowaniu UTF-8. Jak wiemy, w UTF-8 możemy zapisywać znaki wielobajtowe np takie jak: 'ą','ć','ę','ł' etc.

Między apostrofami możemy definiować tylko jeden znak, choć może być reprezentowany przez wiele bajtów.

Gdyby jednak standardowy UTF-8 to było za mało, lub nie możemy polegać na kodowaniu pliku źródłowego możemy uciec się do alternatywnych sposobów zapisu takich jak:

'\141' //'a' w ósemkowo (maksymalny octal to 377, czyli 255 dziesiętnie)
'\x61' //'a' w szesnastkowo (tylko dwa hexy, więc 'ą' nie zapiszemy w ten sposób)
'\u0105' //'ą' jako mały unicode (cztery hexy)
'\U00000105' //'ą' jako duży unicode (osiem hexów)

Mamy do dyspozycji także pulę znaków specjalnych takich jak:

\a   U+0007 alarm lub dzwonek
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000b vertical tab
\\   U+005c backslash
\'   U+0027 single quote  (możliwy tylko jako literał znaku)
\"   U+0022 double quote  (możliwy tylko jako część łańcucha znaków)

Podwójny cudzysłów posłuży nam do zapisywania łańcuchów znaków. Reguły dotyczące poszczególnych form zapisów pojedynczych znaków nadal są w mocy, (czyli formy \u0123 \u00001234 i \x22 z powodzeniem mogą być używane w łańcuchach) nielegalne jest użycie nieescapowanych ", ani znaku nowej linii (których escapowanie pokazano w zestawieniu powyżej).

"ala ma kota"
/// to poniżej się nie kompiluje
"ala
  ma
kota"
//zwróci syntax error

Za to legalnym jest umieszczanie znaku nowej linii w literałach łańcuchów znakowych zawartych między gravisami `, jednak nie zapiszemy żadnego znaku z użyciem backslasha. ` sygnalizuje rozpoczęcie surowego (nie poddawanego obróbce) ciągu znaków

`ala
ma
kotki
dwa!\n`

Powyższy string reprezentuje każdy wyraz w osobnej linii jednak nie kończy się pustym wierszem!

Literały złożone

Nazywamy tak wszystkie wszystkie literały, które reprezentują w programie wartość złożoną to jest: wszelkiego rodzaju mapy, structy, arraye, slicesy (tylko chan i interface nie mają reprezentacji literału - zresztą nie miało by to sensu).

Co do zasady literały typów złożonych mają postać

typ {"klucz1": wartość, "klucz2": wartość2}

przy czym klucze są opcjonalne dla wszystkich typów poza mapami. W zależności od typu klucze mają inne znaczenie: dla struktur to pola, dla tablic indeksy, dla map klucze. Należy pamiętać, że albo decydujemy się pominąć klucze, albo stosować je w całej definicji literału.

Literały mapowań

Literały map są wyjątkowo mało ciekawe, mają tylko jedną prawidłową postać:

type strToInt map[string]int
stival := strToInt{"jeden": 1, "dwa": 2,"trzy":3}
stival2 := map[string]int{"a": 0, "b": 1}//z sygnaturą typu

Nota bene warto zauważyć, że typ literału złożonego może być zadeklarowany, a można go też wstawić "z palca".

Literały tablicowe

Przykładowe literały tablicowe wyglądają tak:

type intArr [2]int
arr1 := intArr {0: 1, 1: 2}
arr2 := [2]int{0: 1, 1: 2}

Warto pamiętać o tym, że Go domyślnie inicjuje wartości na zero typu, dzięki temu możemy zrobić tak:

arr3 := [100]int{50:1}

czyli zadeklarować tylko jedną wartość w 100 elementowej tablicy, reszta będzie zerami. Jak pisałem wyżej nie potrzebujemy kluczy w literałach tablicowych, możemy je pominąć i wpisywać tylko wartości, które będą indeksowane od 0

arr4 := [5]int{1,2,3,4,5}

Czasami nużące jest wpisywanie za każdym razem ilości elementów w deklaracji typu, twórcy zadbali także o to - literał tablicowy zamiast ilości elementów może zawierać ... a podczas kompilacji ilość elementów zostanie policzona.

arr5 := [...]int{1,2,3,4,5}

Literały wycinków

Literał slice różni się od tablic brakiem deklaracji ilości elementów, tym i w zasadzie tylko tym. Zatem dozwolone są wszystkie poniższe formy:

type intSli []int
sli := intSli{0: 1, 1: 2, 3: 4, 4: 5, 5: 6, 6: 3}
sli1 := []int{0: 1, 1: 2, 3: 4, 4: 5, 5: 6, 6: 3}
sli2 := []int{1, 2, 4, 5, 6, 3}

Literały struktur

Ten rodzaj literałów występuje najczęściej w kodzie Go, bo tworzenie instancji structów za pomocą literałów jest najzwyczajniej w świecie wygodne.

type point struct{
    x int
    y int
}
p1 := point{x: 1, y: 2}

Etykietami w tym przypadku są nazwy pola structa. Możemy pominąć nazwy pól, z tym że w takim przypadku należy pamiętać by wartości wpisywać zgodnie z kolejnością definicji typu structa, czyli:

type namedPoint struct {
    name string
    x int
    y int
}

np := namedPoint{"nazwa", 1, 1}

A tak swoją drogą: powyższa definicja typu wydaje się być nieco pokraczna, szczególnie gdy typ point mamy zadeklarowany przykład wcześniej. Możemy, w tym przypadku, skorzystać z osadzania structów:

type point struct {
    x int
    y int
}

type namedPoint {
    name string
    point
}

np := namedPoint{"nazwa", point{1,1}}

O osadzaniu więcej jest w innym artykule tu o tylko wspominam, by pokazać jak definiuje się literały structów z anonimowo osadzonym structem.

Literały funkcyjne

Także funkcje mają swoje literały, te do złudzenia przypominają definicje funkcji. Tak naprawdę to jedyna różnica między literałem a definicją funkcji jest taka, że literał to anonimowa funkcja, więc nie ma nazwy między słowem kluczowym func a sygnaturą, ani nie ma możliwości stania się metodą typu. Literał funkcyjny możemy przekazać do zmiennej np. tak:

f := func(a, b int ) int {
    return a + b;
}
f(2,2)//returns 4

Można go także wywołać w miejscu:

func(a, b int ) int {
    return a + b;
}(2,2)//returns 4

Najważniejszą cechą literałów funkcyjnych jest to że tworzą domknięcie (clousure) tak często i z powodzeniem wykorzystywane przez m.in. programistów JavaScript.