Typy
Dzięki typom rozróżniamy właściwości wartości składowanych pod zmiennymi. Np. gdy zmienna jest typu int8 to wiemy, że może zawierać każdą liczbę całkowitą z zakresu od -128 do 127.
Typy pre-deklarowane
W Go jest 12 typów numerycznych, poniżej ich rozpiska:
uint8 liczby całkowite nieujemne zapisywane na 8 bitach (od 0 do 255) uint16 liczby całkowite nieujemne zapisywane na 16 bitach (od 0 do 65535) uint32 liczby całkowite nieujemne zapisywane na 32 bitach (od 0 do 4294967295) uint64 liczby całkowite nieujemne zapisywane na 64 bitach (od 0 do 18446744073709551615) int8 liczby całkowite zapisywane na 8 bitach (od -128 do 127) int16 liczby całkowite zapisywane na 16 bitach (od -32768 do 32767) int32 liczby całkowite zapisywane na 32 bitach (od -2147483648 do 2147483647) int64 liczby całkowite zapisywane na 64 bitach (od -9223372036854775808 do 9223372036854775807) float32 liczby zmiennoprzecinkowe zapisane na 32 bitach (8 bitów na wykładnik 23 na mantysę i 1 na znak) float64 liczby zmiennoprzecinkowe zapisane na 64 bitach (11 bitów na wykładnik 52 na mantysę i 1 na znak) complex64 liczby zespolone z 32bitową zmiennoprzecinkową częścią rzeczywistą i taką samą częścią urojoną complex128 liczby zespolone z 64bitową zmiennoprzecinkową częścią rzeczywistą i taką samą częścią urojoną byte alias typu uint8
Mamy także typ bool, który przyjmuje wartości true (prawda) i false (fałsz)
oraz string.
Stringi, czyli łańcuchy znaków, są niezmienialne, oznacza to, że nie możemy zmienić części stringa, jeśli chcemy zmienić choć jeden znak musimy wygenerować nową wartość.
I na tym się kończy lista typów pre-deklarowanych (czyli takich, które są dostępne od ręki) pozostałe typy takie jak array, struct, slice, chan i interface musimy zadeklarować jako własne, lub posługiwać się ich literałami (tj. wpisywać ich definicję w kod programu) nazywamy je typami złożonymi.
Tworzenie własnych typów i nadawanie im nazw
Typy nazwane tworzymy przez deklarację zaczynającą się od słowa type po nim
musi nastąpić identyfikator typu a na końcu sama definicja typu:
type myInt int8
Od teraz możemy używać myInt zamiast int8 w kodzie programu, choć musimy się liczyć z konsekwencjami omówionymi w paragrafie o własnościach typów.
Typy złożone
Typ struct
Struty to typy zawierające sekwencje elementów określonych typów dostępnych za pośrednictwem nazw:
struct { a int8 b int32 }
Wartości struktur o tego typu będą miały dwa pola a (typu int8) i b
(typu int32), do których można się odwoływać po kropce. Na przykład:
type point struct { x int y int } p := new(point) p.x = 1 p.y = 2 fmt.Println(p.x + p.y)//wypisze na ekran 3
Typy funkcyjne
Funkcje też mogą mieć swoje typy. Definicja typu funkcji składa się z słowa
kluczowego func i następującej po nim sygnaturze funkcji (czyli liście
parametrów i wartości zwracanych). Np. funkcje które przyjmują dwa parametry
typu int i zwracają jedną wartość typu int mogą być określone typem:
type intfun func (a, b int) int
O tym czym dokładnie jest sygnatura funkcji wyczytasz więcej w rozdziale o funkcjach.
Typy tablicowe
Tablice to ponumerowane sekwencje elementów określonego typu, tablice mają z góry określoną pojemność, której nie da się zmienić. Typ tablic zawierających 10 bajtów zapiszemy tak:
[10]byte
Czyli każdą deklarację zaczynamy od ustalonej liczby elementów ujętej w nawiasy kwadratowe po której następuje deklaracja typów elementów jakie się w niej znajdują. Napisałem deklaracja typów elementów, ponieważ elementem tablicy może być np. inna tablica:
[10][2]int8
Taki zapis informuje nas o tym, że mamy poczynienia z 10 elementową tablicą 2 elementowych tablic z wartościami typu int8.
Połączmy wiedzę z rozdziału o zmiennych z tym czego dowiedzieliśmy się do tej pory. Zadeklarujemy zmienną typu tablicowego zawierającego 10 elementów typu byte:
var byteArr [10]byte
Jak pamiętamy zmienna byteArr została zainicjowana tzw. wartością "zero-typu", w tym przypadku będzie to tablica wypełniona zerami (gdyż elementy są typu liczbowego a zero typu liczbowego to 0).
Żeby zainicjować zmienną własnymi wartościami musimy się uciec do tzw. literałów (słowo literał będzie się często powtarzać na tych stronach, ciężko od niego uciec, więc polecam się z nim oswoić):
var byteArr [10]byte = [...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
Typy wycinków
Wycinki (slice) do złudzenia przypominają tablicę, ale tak na prawdę są indeksowaną sekwencją referencji do elementów tablicy. O samych slicesach więcej przeczytasz w osobnym artykule, tutaj tylko napiszę jak się deklaruje typ slice. A deklaruje się podobnie jak tablice z taką różnicą, że nie określamy jej rozmiaru, czyli nawiasy kwadratowe przed typem elementów pozostają puste. Np. wycinek intów zadeklarujemy tak:
[]int
Typy mapowań
Mapy to typy przyporządkowujące jednym wartościom inne. Definicję rozpoczynamy od
słowa map po nim następuje typ kluczy ujęty w nawiasy kwadratowe a na końcu
wskazujemy typ wartości. Np. możemy zmapować liczby z ich słownymi odpowiednikami,
czyli będziemy chcieli zmapować typ int na string. Taki typ definiujemy następująco:
map[int]string
Typy kanałów
Kanały to typ którego wartości służą do przekazywania informacji tj. można je wysyłać przez kanał i z niego otrzymywać. O szczegółach działania kanałów napiszę w innym artykule, tu się skupię na deklaracji typu kanału.
Kanał może zostać zadeklarowany na 3 sposoby:
-
uniwersalny, można z niego wyciągać i wkładać dane
chan TYP
-
do którego komunikaty są wysyłane
chan <- TYP
-
z którego tylko odbieramy komunikaty
<- chan TYP
Dla ścisłości: zmienna typu chan TYP może być użyta gdy wymagany jest typ
<- chan TYP lub chan <- typ, zmienia się tylko możliwość jej wykorzystania -
tylko do odbierania lub tylko do wysyłania komunikatów.
Typ interface
Interfejsy to zbiory metod, mówimy że typ spełnia dany interface gdy implementuje wszystkie zebrane w nim metody. Więcej o metodach i interfejsach w artykule o obiektowości. Typ interfejsu deklarujemy następująco: zaczynamy od słowa interface a pomiędzy klamerkami wypisujemy deklaracje metod. Deklaracja metody składa się z nazwy metody i sygnatury funkcji. Np:
interface Stringer { String() string }
Typy wskaźnikowe
Każdy typ ma odpowiadający mu typ wskaźnikowy. Typ wskaźnikowy oznaczamy *
przed resztą deklaracji typu. Np. wskaźnik do wartości typu int będzie
zadeklarowany następująco:
*int
Warto wiedzieć, że wskaźniki w Go są bezpieczne - nie można ich przesuwać jak w c/c++. Dodatkowo, wartości wskaźnikowych można używać prawie tak samo jak zwykłych bo dereferencja jest robiona w locie i nie trzeba przeprowadzać jej jawnie. Bardzo to wygodne :-)
Własności typów i ich wartości
Dwa typy mogą być w dwóch rodzajach relacji między sobą: są identyczne lub ich wartości są przypisywalne.
Identyczność typów
Nazwane typy mogą są identyczne tylko same ze sobą. Tj. mimo iż deklaracje różnią się tylko nadaną nazwą typu nie spełniają relacji identyczności. Np.
type T0 int type T1 int
T0 i T1 są nieidentyczne, tak samo jak int jest nieidentyczny z T0
Zadeklarowane w różnych miejscach nienazwane typy mogą być identyczne tylko wtedy gdy odpowiadające im literały są identyczne, tj. mają taką samą strukturę a ich elementy są identycznych typów. Np.:
*intnie jest identyczny zint, to zupełnie dwa różne typy[2]intbędzie identyczne z innym typem[2]int.[2]intnie jest identyczne z[3]int, bo ma więcej elementów.[2]intnie jest identyczne ztype T [2]int, bo literał wymaga od tego drugiego podania typu.[2]*intnie będzie identyczny z[2]intbo*intnie jest identyczny zint- typ
struct{a, b int}jest identyczny zstruct{a int, b int} - typ
struct{b, a int}nie jest identyczny zstruct{a int, b int}różnią się kolejnością pól - dwa typy funkcji są identyczne gdy ich typy i kolejność parametrów i wartości zwracanych są identyczne (nazwa nie jest brana pod uwagę przy porównaniu)
- typy mapowe są identyczne gdy ich typy kluczy i wartości są identyczne
- dwa typy kanałów są identyczne gdy ich wartości są identycznych typów i mają zgodne kierunki
Przypisywalność
O przypisywalności mówimy wtedy gdy wartość x jednego typu X może być
przypisana do zmiennej v typu T. Zachodzi to gdy spełniony jest
jeden z warunków:
- typ x jest identyczny z T
- gdy X określa taki sam typ jak T i przynajmniej jeden (T lub X) jest nienazwany
- gdy T jest interfejsem a x spełnia ten interface
- gdy x jest kanałem typu V oraz T jest typem kanału z identycznymi typami elementów dodatkowo przynajmniej jeden z nich jest typem nienazwanym
- gdy x jest wartością nil a T jest wskaźnikiem, funkcją, slice, mapą, kanałem lub interfejsem
- gdy x jest stałą reprezentowalną przez typ T
Wszystko o języku programowania Go