Sistemul de scufundare profundă în sistemul de tip go

Go are un sistem de tip foarte interesant. Evită clasele și moștenirea în favoarea interfețelor și compoziției, dar pe de altă parte nu are șabloane sau generice. Modul în care se ocupă de colecții este, de asemenea, unic. 

În acest tutorial veți afla despre intrările și ieșirile sistemului de tip Go și despre cum să îl utilizați în mod eficient pentru scrierea codului Go clar și idiomatic.

Imaginea mare a sistemului Go Type

Sistemul tip Go acceptă paradigme procedurale, orientate spre obiecte și funcționale. Are un sprijin foarte limitat pentru programarea generică. În timp ce Go este un limbaj cu adevărat static, acesta oferă suficientă flexibilitate pentru tehnicile dinamice prin interfețe, funcții de primă clasă și reflecție. Sistemul de tip Go este lipsit de capacitățile care sunt comune în majoritatea limbilor moderne:

  • Nu există niciun tip de excepție, deoarece tratarea erorilor Go se bazează pe codurile de returnare și interfața de eroare. 
  • Nu există supraîncărcarea operatorului.
  • Nu există suprasolicitare a funcției (același nume de funcție cu parametri diferiți).
  • Nu există parametri funcțional opțional sau implicit.

Aceste omisiuni sunt toate prin design pentru a face Go cât mai simplu posibil.

Alias ​​de tip

Puteți să introduceți tipuri de alias în Go și să creați tipuri distincte. Nu puteți atribui o valoare a tipului de bază unui tip aliasat fără conversie. De exemplu, cesiunea var b int = a în următorul program cauzează o eroare de compilare din cauza tipului Vârstă este un alias de int, dar este nu un int:

pachet de tip principal Variaza int func main () Var a Varsta = 5 var b int = a Ieșire: tmp / sandbox547268737 / main.go: 8: 

Puteți să generați declarații de tip de grup sau să utilizați o singură declarație pe rând:

tip IntIntMap hartă [int] int StringSlice [] tip șir (Dimensiune uint64 șir de text CoolFunc func (int, b bool) (int, eroare)) 

Tipuri de bază

Toți suspecții obișnuiți sunt aici: bool, string, numere întregi și nesemnate cu dimensiuni explicite de biți, numere de puncte în virgulă (32 bit și 64 de biți) și numere complexe (64 biți și 128 biți).

bool șir int int8 int16 int32 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // alias pentru uint8 rune // alias pentru int32, reprezintă un punct de cod Unicode float32 float64 complex64 complex128 

Siruri de caractere

String-urile din Go sunt codate în UTF8 și pot reprezenta astfel orice caracter Unicode. Pachetul de șiruri oferă o serie de operații de șir. Iată un exemplu de a lua o serie de cuvinte, de a le transforma într-un caz adecvat și de a le adăuga la o propoziție.

pachetul principal de import ("fmt" "strings") func principal () cuvinte: = [] șir "i", "LikE", "COLORS", "RED", "bLuE" , "GrEEn" properCase: = [] șir  pentru i, w: = cuvinte în interval (dacă i == 0 properCase = append (properCase, strings.Title (w) string.ToLower (w)) teză: = strings.Join (properCase, "") + "." fmt.Println (teză)

pointeri

Du-te are indicii. Indicatorul zero (vedeți valorile zero mai târziu) este zero. Puteți obține un indicator la o valoare folosind & operatorul și să te întorci folosind * operator. Poți să ai și pointeri la indicii.

pachetul principal de import ("fmt") tip S struct a float64 b șir func principal () x: = 5 px: = & x * px = 6 fmt.Println (x) ppx: = & px ** ppx = 7 fmt .Println (x) 

Object-Oriented Programming 

Go sprijină programarea orientată pe obiecte prin interfețe și structuri. Nu există clase și nici o ierarhie de clasă, deși puteți încorpora structuri anonime în structuri, care oferă un fel de moștenire singulară. 

Pentru o explorare detaliată a programării orientate pe obiecte în Go, verificați Let's Go: Programare orientată pe obiecte în Golang.

interfeţe

Interfețele reprezintă piatra de temelie a sistemului Go. O interfață este doar o colecție de semnături de metode. Fiecare tip care implementează toate metodele este compatibil cu interfața. Iată un exemplu rapid. Formă interfata defineste doua metode: GetPerimeter () și GetArea (). Pătrat obiect implementează interfața.

()) uint ) uint return s.side * 4 func (s * Square) GetArea () uint retur s.side * s.side 

Interfața goală interfață este compatibil cu orice tip deoarece nu există metode necesare. Interfața goală poate apoi să indice către orice obiect (similar cu obiectul Java sau C / C ++ void pointer) și este adesea folosit pentru tastarea dinamică. Interfețele sunt întotdeauna pointeri și întotdeauna indică un obiect concret.

Pentru un întreg articol despre interfețele Go, citiți: Cum să definiți și să implementați o interfață Go.

structs

Structs sunt tipurile definite de utilizator. Un struct conține câmpuri numite, care pot fi tipuri de bază, tipuri de indicatori sau alte tipuri de structuri. De asemenea, puteți încorpora o structură anonimă în alte structuri ca o formă de moștenire a implementării. 

În exemplul următor, structurile S1 și S2 sunt încorporate în structura S3, care are și propria sa structură int câmp și un pointer la propriul tip:

pachetul principal de import ("fmt") tip S1 struct f1 int tip S2 struct f2 int tip S3 struct S1 S2 f3 int f4 * S3 func main () S1 = 6, 7, nil fmt.Printin (s) Ieșire: & 5 6 7  

Aserțiuni de tip

Aserțiunile de tip vă permit să convertiți o interfață la tipul său de beton. Dacă cunoașteți deja tipul de bază, puteți să-l afirmați. Dacă nu sunteți sigur, puteți încerca mai multe afirmații de tip până când veți descoperi tipul potrivit. 

În următorul exemplu, există o listă de lucruri care conțin șiruri și valori non-șir reprezentate ca o felie de interfețe goale. Codul iterează peste toate lucrurile, încercând să convertească fiecare element într-un șir și să stocheze toate șirurile într-o felie separată pe care o imprimă în cele din urmă.

pachetul principal de import "fmt" func main () things: = [] interfață  "hi", 5, 3.8, "there", nil, "! : = intervalul lucrurilor s, ok: = t (șirul) dacă ok strings = append (șiruri, s) fmt.Println 

Reflecţie

The Go Reflectați pachetul vă permite să verificați direct tipul de interfață fără afirmații de tip. De asemenea, puteți extrage valoarea unei interfețe și o puteți converti într-o interfață dacă doriți (nu la fel de utilă). 

Iată un exemplu similar cu exemplul precedent, dar în loc de a imprima șirurile, le numără pur și simplu, deci nu este nevoie să le convertiți interfață la şir. Cheia cheamă reflect.Type () pentru a obține un obiect de tip, care are a Drăguț() metodă care ne permite să detectăm dacă avem de-a face cu un șir sau nu.

pachetul principal de import ("fmt" "reflect") func principal () things: = [] interfață  "hi", 5, 3.8, "there", zero, "!" stringCount: t: = lucruri din interval tt: = reflect.TypeOf (t) dacă tt! = nil && tt.Kind () == reflect.String stringCount ++ fmt.Println ("String count:" stringCount) 

funcţii

Funcțiile sunt cetățeni de primă clasă în Go. Aceasta înseamnă că puteți atribui funcții variabilelor, puteți trece funcții ca argumente altor funcții sau le puteți returna ca rezultate. Acest lucru vă permite să utilizați stilul de programare funcțional cu Go. 

Următorul exemplu demonstrează câteva funcții, GetUnaryOp () și GetBinaryOp (), care returnează funcțiile anonime selectate aleator. Programul principal decide dacă are nevoie de o operație unară sau de o operație binară bazată pe numărul de argumente. Stochează funcția selectată într-o variabilă locală numită "op" și apoi o invocă cu numărul corect de argumente. 

pachetul de import principal ("fmt" "math / rand") tip UnaryOp func (a int) int tip BinaryOp func (a, b int) int func GetBinaryOp () BinaryOp if rand.Intn (a, b int) int return a + b else returnați funcția (a, b int) int return a - b func GetUnaryOp () UnaryOp if rand.Intn (= returneaza int (a int) int return -a altceva return func int int return a * a func  (A) = 1 op: = getUnaryOp () result = op (a)  [0]) altfel op: = GetBinaryOp () rezultatul = op (a [0], a [1]) fmt.Println (rezultat) 

canale

Canalele sunt un tip de date neobișnuit. Vă puteți gândi la ele ca cozi de mesaje folosite pentru trecerea mesajelor între gorutine. Canalele sunt puternic tastate. Acestea sunt sincronizate și au suport de sintaxă dedicat pentru trimiterea și primirea mesajelor. Fiecare canal poate fi numai pentru recepție, doar pentru trimitere sau bidirecțional. 

Canalele pot fi, de asemenea, tamponate opțional. Puteți să repetați mesajele într-un canal care utilizează intervalul, iar rutinele pot bloca pe mai multe canale în același timp, utilizând operația de selectare versatilă. 

Iată un exemplu tipic în care suma pătratelor dintr-o listă de numere întregi este calculată în paralel de două rutine, fiecare dintre acestea fiind responsabile de jumătate din listă. Funcția principală așteaptă rezultatele din ambele rutine și apoi adaugă sumele parțiale pentru total. Rețineți cum este canalul c este creat folosind face() funcția încorporată și modul în care codul citește și scrie pe canal prin intermediul funcției speciale <- operator.

pachetul principal de import "fmt" func sum_of_squares (s [] int, c chan int) sumă: = 0 pentru _, v: = interval s sum + = v * v c <- sum // send sum to c  func main()  s := []int11, 32, 81, -9, -14 c := make(chan int) go sum_of_squares(s[:len(s)/2], c) go sum_of_squares(s[len(s)/2:], c) sum1, sum2 := <-c, <-c // receive from c total := sum1 + sum2 fmt.Println(sum1, sum2, total)  

Aceasta este doar răzuirea suprafeței. Pentru o examinare detaliată a canalelor, consultați:

Colecții

Go are mai multe colecții generice încorporate care pot stoca orice tip. Aceste colecții sunt speciale și nu vă puteți defini propriile colecții generice. Colecțiile sunt tablouri, felii și hărți. Canalele sunt, de asemenea, generice și pot fi considerate și colecții, dar au proprietăți destul de unice, așadar prefer să le discut separat.

Arrays

Arrays sunt colecții de dimensiuni fixe de elemente de același tip. Iata cateva tablouri:

pachetul principal de import "fmt" func main () a1: = [3] int 1, 2, 3 var a2 [3] int a2 = a1 fmt.Println (a1) fmt.Println (a2) = 7 fmt.Println (a1) fmt.Println (a2) a3: = [2] interfață  3, "hello" fmt.Println (a3) 

Dimensiunea matricei face parte din tipul său. Puteți copia matrice de același tip și dimensiune. Copia este valabilă. Dacă doriți să stocați elemente de diferite tipuri, puteți utiliza trapa de evacuare a unei serii de interfețe goale.

felii

Arrays sunt destul de limitate datorită dimensiunii lor fixe. Sectiile sunt mult mai interesante. Vă puteți gândi la felii ca niște măști dinamice. Sub capace, felii folosesc o matrice pentru stocarea elementelor lor. Aveți posibilitatea să verificați lungimea unei felii, să adăugați elemente și alte felii, și cele mai distractive dintre toate să puteți extrage subsecțiuni similare cu felierea lui Python:

pachetul principal de import "fmt" func main () s1: = [] int 1, 2, 3 var s2 [] int s2 = s1 fmt.Printin (s1) fmt.Println (s2) 1] = 7 // Ambele s1 și s2 indică aceeași matrice fmt.Println (s1) fmt.Println (s2) fmt.Println (len (s1)) // Slice s1 s3: = s1 [1: s1)] fmt.Println (s3) 

Când copiați felii, trebuie doar să copiați referința la aceeași matrice de bază. Când tăiați, sub-slice-ul încă indică aceeași matrice. Dar când adăugați, veți obține o felie care indică o nouă matrice.

Puteți itera peste matrice sau felii folosind o buclă obișnuită cu indexuri sau folosind intervale. Puteți crea, de asemenea, felii într-o anumită capacitate, care va fi inițializată cu valoarea zero a tipului de date utilizând face() funcţie:

pachetul principal de import "fmt" func main () // Creați o felie de 5 booleani inițializate la false s1: = make ([] bool, 5) fmt.Println (s1) s1 [3] = true s1 [ true fmt.Println ("Iterați folosind standard pentru buclă cu index") pentru i: = 0; eu < len(s1); i++  fmt.Println(i, s1[i])  fmt.Println("Iterate using range") for i, x := range(s1)  fmt.Println(i, x)   Output: [false false false false false] Iterate using standard for loop with index 0 false 1 false 2 false 3 true 4 true Iterate using range 0 false 1 false 2 false 3 true 4 true 

Hărți

Hărțile sunt colecții de perechi cheie-valoare. Puteți să le atribuiți hărți literale sau alte hărți. De asemenea, puteți crea hărți goale utilizând face funcția încorporată. Accesați elementele folosind paranteze pătrate. Hărțile acceptă utilizarea iterației gamă, și puteți testa dacă există o cheie prin încercarea de accesare a acesteia și verificarea celei de-a doua valori opționale de returnare booleană.

pachetul principal de import ("fmt") func principal () // Crearea hărții folosind o hartă literală m: = harta [int] șir 1: "unul", 2: "două", 3: "trei" Atribuiți unui element cu tasta m [5] = "5" // Accesați elementul prin tasta fmt.Println (m [2]) v, ok: = m [4] dacă ok fmt.Println (v) .Printin ("Lipsă cheie: 4") pentru k, v: = interval m fmt.Println (k, ":", v 3: trei 

Rețineți că iterația nu este în ordinea de creare sau inserare.

Valori zero

Nu există tipuri neinitializate în Go. Fiecare tip are o valoare zero predefinită. Dacă o variabilă de tip este declarată fără atribuirea unei valori, atunci ea conține valoarea ei zero. Aceasta este o caracteristică importantă de siguranță.

Pentru orice tip T, * Nou (T)va întoarce o valoare zero a T.

Pentru tipurile booleene, valoarea zero este "falsă". Pentru tipurile numerice, valoarea zero este ... zero. Pentru felii, hărți și indicatori, este zero. Pentru structuri, este un struct în care toate câmpurile sunt inițializate la valoarea lor zero.

(* new (int)) fmt.Println (* new ([new (bool)) fmt.Println (* ]) x: = * nou ([] string) dacă x == nil fmt.Println ("Secțiunile neinitializate sunt nuluri") y: = * (hărți neinitializate sunt de asemenea nul) fmt.Println (* new (S)) 

Ce este vorba despre șabloane sau generice?

Nu merge nimeni. Aceasta este probabil cea mai frecventă plângere despre sistemul de tip Go. Designerii Go sunt deschiși la această idee, dar nu știu încă cum să o pună în aplicare fără a încălca celelalte principii de proiectare care stau la baza limbii. Ce puteți face dacă aveți nevoie de câteva tipuri de date generice? Iată câteva sugestii:

  • Dacă aveți doar câteva instanțiări, luați în considerare doar crearea de obiecte concrete.
  • Utilizați o interfață goală (va trebui să introduceți înapoi la tipul dvs. de beton la un moment dat).
  • Utilizați generarea de coduri.

Concluzie

Go are un sistem de tip interesant. Designerii Go au luat decizii explicite de a rămâne pe partea simplă a spectrului. Dacă sunteți serios la programarea Go, ar trebui să investești timpul și să învățați despre sistemul de tip și propriile sale idiosincrazii. Va merita bine timpul.

Cod