Go har et veldig interessant type system. Det skaper klasser og arv til fordel for grensesnitt og sammensetning, men på den annen side har den ikke maler eller generikk. Måten den håndterer samlinger er også unik.
I denne opplæringen lærer du om innspillene i Go-typen og hvordan du effektivt kan bruke den til å skrive tydelig og idiomatisk Go-kode.
Go-typen systemet støtter de prosessorienterte, objektorienterte og funksjonelle paradigmene. Den har svært begrenset støtte for generisk programmering. Mens Go er et bestemt statisk språk, gir det nok fleksibilitet for dynamiske teknikker via grensesnitt, førsteklasses funksjoner og refleksjon. Go's type system mangler evner som er vanlige på de fleste moderne språk:
Disse utelatelsene er alt av design for å gjøre Go så enkelt som mulig.
Du kan aliastyper i Go og opprette forskjellige typer. Du kan ikke tilordne en verdi av den underliggende typen til en aliased type uten konvertering. For eksempel oppgaven var b int = a
i følgende program forårsaker en kompileringsfeil fordi typen Alder
er et alias av int, men det er ikke en int:
pakke hovedtype Alder int func main () var en alder = 5 var b int = a Utgang: tmp / sandbox547268737 / main.go: 8: kan ikke bruke en (type alder) som type int i oppgave
Du kan gruppere typeerklæringer eller bruke en deklarasjon per linje:
skriv IntIntMap-kart [int] int StringSlice [] strengtype (Størrelse uint64 Tekststreng CoolFunc func (int, b bool) (int, feil))
Alle de vanlige mistenkte er her: bool, streng, heltall og usignerte heltall med eksplisitte bitstørrelser, flytende punktnumre (32-bit og 64-bit) og komplekse tall (64-bit og 128-bit).
bool streng int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // alias for uint8 rune // alias for int32, representerer et Unicode koden punkt float32 float64 complex64 complex128
Strings i Go er UTF8 kodet og kan dermed representere alle Unicode-tegn. Strengerpakken gir en rekke strengoperasjoner. Her er et eksempel på å ta en rekke ord, konvertere dem til riktig tilfelle, og bli med i en setning.
pakke hovedinnføring ("fmt" "strenger") func main () ord: = [] streng "i", "LikE", "Colours:", "RED", "bLuE", "AnD" , "GrEEn" properCase: = [] streng for i, w: = rekkevidde ord hvis jeg == 0 properCase = append (properCase, strings.Title (w)) ellers properCase = append (properCase, strenger.ToLower (w)) setning: = strings.Join (properCase, "") + "." fmt.Println (setning)
Go har pekere. Nullpekeren (se nullverdier senere) er null. Du kan få en peker til en verdi ved hjelp av &
operatør og komme tilbake med *
operatør. Du kan også ha pointers til pekere.
pakke hovedinnføring ("fmt") type S struktur a float64 b streng func main () x: = 5 px: = & x * px = 6 fmt.Println (x) ppx: = & px ** ppx = 7 fmt .Println (x)
Go støtter objektorientert programmering via grensesnitt og strukturer. Det er ingen klasser og ingen klassehierarki, selv om du kan legge inn anonyme strukturer i strukturer som gir en slags enkelt arv.
For en detaljert utforskning av objektorientert programmering i Go, sjekk ut La oss gå: Objektorientert programmering i Golang.
Grensesnitt er hjørnesteinen i Go-typen. Et grensesnitt er bare en samling metodesigner. Hver type som implementerer alle metodene, er kompatibel med grensesnittet. Her er et raskt eksempel. De Form
grensesnitt definerer to metoder: GetPerimeter ()
og GetArea ()
. De Torget
objektet implementerer grensesnittet.
type Form grensesnitt GetPerimeter () uint GetArea () uint type Firkantet struktur side uint func (s * Firkant) GetPerimeter () uint return s.side * 4 func (s * Firkant) GetArea () uint return s.side * s.side
Det tomme grensesnittet grensesnitt
er kompatibel med enhver type fordi det ikke er noen metoder som kreves. Det tomme grensesnittet kan da peke på et hvilket som helst objekt (ligner Java objekt eller C / C + + tomrom) og brukes ofte til dynamisk skriving. Grensesnitt er alltid pekere og peker alltid på et konkret objekt.
For en hel artikkel om Go-grensesnitt, sjekk ut: Hvordan definere og implementere et Go-grensesnitt.
Strukturer er Gos brukerdefinerte typer. En struktur inneholder navngitte felter, som kan være grunnleggende typer, pekertyper eller andre struktetyper. Du kan også integrere strukturer anonymt i andre strukturer som en form for implementeringsarv.
I følgende eksempel er S1- og S2-strukturene innebygd i S3-strukturen, som også har sin egen int
feltet og en peker til sin egen type:
pakke hovedinnføring ("fmt") type S1 struktur f1 int type S2 struktur f2 int type S3 struktur S1 S2 f3 int f4 * S3 func main () s: = og S3 S1 5, S2 6, 7, null fmt.Println (s) Utgang: & 5 6 7
Type påstander lar deg konvertere et grensesnitt til sin betongtype. Hvis du allerede kjenner den underliggende typen, kan du bare hevde det. Hvis du ikke er sikker, kan du prøve flere typer påstander til du oppdager riktig type.
I det følgende eksemplet er det en liste over ting som inneholder strenger og ikke-strengverdier som er representert som et stykke tomme grensesnitt. Koden iterates over alle tingene, prøver å konvertere hvert element til en streng og lagre alle strengene i et eget stykke som det til slutt skriver ut.
pakke hovedinnføring "fmt" func main () ting: = [] grensesnitt "hei", 5, 3.8, "der", null, "!" strenger: = [] streng for _, t : = rekkevidde ting s, ok: = t. (streng) hvis ok strenger = legg til (strenger, s) fmt.Println (strenger) Utgang: [hei der!]
The Go reflektere
pakken lar deg sjekke typen av grensesnitt uten typiske påstander. Du kan også trekke ut verdien av et grensesnitt og konvertere det til et grensesnitt hvis du ønsker det (ikke så nyttig).
Her er et lignende eksempel på forrige eksempel, men i stedet for å skrive ut strengene, teller det bare dem, så det er ikke nødvendig å konvertere fra grensesnitt
til string
. Nøkkelen ringer reflect.Type ()
for å få et typeobjekt, som har a Snill()
metode som gjør det mulig for oss å oppdage om vi snakker om en streng eller ikke.
pakke hovedinnføring ("fmt" "reflektere") func main () ting: = [] grensesnitt "hei", 5, 3.8, "der", null, "!" stringCount: = 0 for _ t: = rekkefølge ting tt: = reflektere.TypeOf (t) hvis tt! = nil && tt.Kind () == reflektere. String stringCount ++ fmt.Println ("String count:", stringCount)
Funksjoner er førsteklasses statsborgere i Go. Det betyr at du kan tilordne funksjoner til variabler, sende funksjoner som argumenter til andre funksjoner, eller returnere dem som resultater. Det gjør at du kan bruke funksjonell programmeringsstil med Go.
Følgende eksempel viser et par funksjoner, GetUnaryOp ()
og GetBinaryOp ()
, som returnerer anonyme funksjoner valgt tilfeldig. Hovedprogrammet bestemmer om det er behov for en enhetlig operasjon eller en binær operasjon basert på antall argumenter. Den lagrer den valgte funksjonen i en lokal variabel kalt "op" og deretter påkaller den med riktig antall argumenter.
pakke main import ("fmt" "math / rand") type UnaryOp func (en int) int type BinaryOp func (a, b int) int func GetBinaryOp () BinaryOp hvis rand.Intn (2) == 0 return func (a, b int) int return a + b else return func (a, bint) int return a - b func GetUnaryOp () UnaryOp hvis rand.Intn (2) == 0 return func (en int) int return -a else return func (en int) int return a * a func main () arguments: = [] [] int 4,5 6, 9, 7,18, 33 varresultat int for _, a: = rekkevidde argumenter hvis len (a) == 1 op: = GetUnaryOp () resultat = op [0]) ellers op: = GetBinaryOp () resultat = op (a [0], en [1]) fmt.Println (resultat)
Kanaler er en uvanlig datatype. Du kan tenke på dem som meldingskøer som brukes til å sende meldinger mellom goroutiner. Kanaler er sterkt skrevet. De er synkroniserte og har dedikert syntaksstøtte for sending og mottak av meldinger. Hver kanal kan bare være mottak, send-bare eller toveis.
Kanaler kan også eventuelt bufres. Du kan iterere over meldingene i en kanal som bruker rekkevidde, og gå rutiner kan blokkere på flere kanaler samtidig ved hjelp av allsidig velgoperasjon.
Her er et typisk eksempel hvor summen av kvadrater i en liste med heltall beregnes parallelt med to gå rutiner, hver ansvarlig for halvparten av listen. Hovedfunksjonen venter på resultater fra begge turrutiner, og legger deretter opp de delvise summene for totalen. Legg merke til hvordan kanalen c
er opprettet ved hjelp av gjøre()
innebygd funksjon og hvordan koden leser fra og skriver til kanalen via spesielle <-
operatør.
pakke hovedinnføring "fmt" func sum_of_squares (s [] int, c chan int) sum: = 0 for _, v: = rekkevidde 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)
Dette skraper bare overflaten. For en detaljert gjennomgang av kanaler, sjekk ut:
Go har flere innebygde generiske samlinger som kan lagre alle typer. Disse samlingene er spesielle, og du kan ikke definere dine egne generiske samlinger. Samlingene er arrays, skiver og kart. Kanaler er også generiske og kan betraktes som samlinger, men de har noen ganske unike egenskaper, så jeg foretrekker å diskutere dem separat.
Arrays er faste størrelse samlinger av elementer av samme type. Her er noen arrays:
pakke hovedinnføring "fmt" func main () a1: = [3] int 1, 2, 3 var a2 [3] int a2 = a1 fmt.Println (a1) fmt.Println (a2) a1 [1] = 7 fmt.Println (a1) fmt.Println (a2) a3: = [2] grensesnitt 3, "hei" fmt.Println (a3)
Arrangementets størrelse er en del av sin type. Du kan kopiere arrayer av samme type og størrelse. Kopien er av verdi. Hvis du vil lagre varer av forskjellig type, kan du bruke escape-luken av en rekke tomme grensesnitt.
Arrays er ganske begrenset på grunn av deres faste størrelse. Skiver er mye mer interessante. Du kan tenke på stykker som dynamiske arrays. Under dekslene bruker skiver en matrise for å lagre elementene. Du kan sjekke lengden på et stykke, legge til elementer og andre skiver, og mest moro av alt du kan trekke ut stykker som ligner på Python-snitting:
pakke hovedinnføring "fmt" func main () s1: = [] int 1, 2, 3 var s2 [] int s2 = s1 fmt.Println (s1) fmt.Println (s2) // Endre s1 s1 [ 1] = 7 // Både s1 og s2 peker til samme underliggende array fmt.Println (s1) fmt.Println (s2) fmt.Println (len (s1)) // Slice s1 s3: = s1 [1: len s1)] fmt.Println (s3)
Når du kopierer stykker, kopierer du bare referansen til samme underliggende array. Når du skar, peker undersnittet fremdeles på samme rekkefølge. Men når du legger til, får du et stykke som peker på et nytt utvalg.
Du kan iterere over arrays eller skiver ved hjelp av en vanlig sløyfe med indekser eller ved hjelp av intervaller. Du kan også lage skiver i en gitt kapasitet som skal initialiseres med nullverdien av datatypen deres ved hjelp av gjøre()
funksjon:
pakke hovedinnføring "fmt" func main () // Lag et stykke 5 booleaner initialisert til falskt s1: = lage ([] bool, 5) fmt.Println (s1) s1 [3] = true s1 [4] = ekte fmt.Println ("Iterate using standard for loop with index") for i: = 0; Jeg < 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
Kart er samlinger av nøkkelverdier par. Du kan tilordne dem kartbokstav eller andre kart. Du kan også lage tomme kart ved hjelp av gjøre
innebygd funksjon. Du får tilgang til elementer ved hjelp av firkantede parenteser. Maps støtter iterering ved hjelp av område
, og du kan teste om en nøkkel eksisterer ved å prøve å få tilgang til den og kontrollere den andre valgfrie boolske returverdien.
pakke hovedinnføring ("fmt") func main () // Lag kart med et kart bokstavlig m: = kart [int] streng 1: "en", 2: "to", 3: "tre" // Tilordne element ved nøkkel m [5] = "fem" // Tilgangspost med nøkkel fmt.Println (m [2]) v, ok: = m [4] hvis ok fmt.Println (v) else fmt .Println ("Manglende nøkkel: 4") for k, v: = rekkevidde m fmt.Println (k, ":", v) Utgang: to Manglende nøkkel: 4 5: fem 1: en 2: to 3: tre
Legg merke til at iterasjon ikke er i opprettelses- eller innføringsordre.
Det finnes ingen uninitialiserte typer i Go. Hver type har en forhåndsdefinert nullverdi. Hvis en variabel av en type er erklært uten å tilordne den en verdi, inneholder den sin nullverdi. Dette er en viktig type sikkerhetsfunksjon.
For enhver type T
, * Nye (T)
vil returnere en null verdi på T
.
For boolske typer er nullverdien "false". For numeriske typer er nullverdien ... null. For skiver, kart og pekere er det null. For strukturer er det en struktur der alle feltene initialiseres til nullverdien.
pakke hovedinnføring ("fmt") type S struktur a float64 b streng func main () fmt.Println (* ny (bool)) fmt.Println (* ny (int)) fmt.Println ] streng)) fmt.Println (* ny (kart [int] streng)) x: = * ny ([] streng) hvis x == nil fmt.Println ("Uninitialized slices are null") y: = * ny (kart [int] streng) hvis y == nil fmt.Println ("Uninitialized maps are also null") fmt.Println (* new (S))
Go har ingen. Dette er trolig den vanligste klagen om Go's type system. Go-designerne er åpne for ideen, men vet ennå ikke hvordan de skal implementeres uten å bryte med de andre designprinsippene som ligger til grunn for språket. Hva kan du gjøre hvis du har dårlig behov for noen generiske datatyper? Her er noen forslag:
Go har et interessant type system. Go-designerne tok tydelige beslutninger om å forbli på den enkle siden av spekteret. Hvis du er seriøs om Go programmering, bør du investere tid og lære om sitt type system og dets idiosyncrasies. Det vil være vel verdt tiden din.