gå | |
---|---|
Sprog klasse | multithreaded , imperativ , struktureret , objektorienteret [1] [2] |
Udførelsestype | kompileret |
Dukkede op i | 10. november 2009 |
Forfatter | Robert Grismer , Rob Pike og Ken Thompson |
Udvikler | Google , Rob Pike , Ken Thompson , The Go Authors [d] og Robert Grismer [d] |
Filtypenavn _ | .go |
Frigøre |
|
Type system | streng , statisk , med typeslutning |
Blev påvirket | C [4] , Oberon-2 , Limbo , Active Oberon , Sequential Process Interaction Theory , Pascal [4] , Oberon [4] , Smalltalk [5] , Newsqueak [d] [6] , Modula-2 [6] , Alef [d] , APL [7] , BCPL , Modula og Occam |
Licens | BSD |
Internet side | go.dev _ |
OS | DragonFly BSD , FreeBSD , Linux , macOS , NetBSD , OpenBSD , Plan 9 , Solaris , Microsoft Windows , iOS , Android , AIX og Illumos |
Mediefiler på Wikimedia Commons |
Go (ofte også golang ) er et kompileret programmeringssprog med flere tråde udviklet internt af Google [8] . Udviklingen af Go begyndte i september 2007, med Robert Grismer , Rob Pike og Ken Thompson [9] , som tidligere arbejdede på Inferno -operativsystemudviklingsprojektet, direkte involveret i dets design . Sproget blev officielt introduceret i november 2009 . I øjeblikket er understøttelse af den officielle compiler udviklet af skaberne af sproget til operativsystemer FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX . [10] . Go understøttes også af gcc- kompilersættet , og der er flere uafhængige implementeringer. En anden version af sproget er under udvikling.
Navnet på det sprog, Google har valgt, er næsten det samme som navnet på programmeringssproget Go! , skabt af F. Gee. McCabe og C. L. Clark i 2003 [11] . Navnet diskuteres på Go-siden [11] .
På sprogets hjemmeside og generelt i internetpublikationer bruges ofte det alternative navn "golang".
Go-sproget blev udviklet som et programmeringssprog til at skabe højeffektive programmer, der kører på moderne distribuerede systemer og multi-core processorer. Det kan ses som et forsøg på at skabe en erstatning for C og C++ sprogene under hensyntagen til de ændrede computerteknologier og den akkumulerede erfaring i udviklingen af store systemer [12] . Med ordene fra Rob Pike [12] , "Go blev designet til at løse virkelige softwareudviklingsproblemer hos Google." Han opregner følgende som hovedproblemer:
De vigtigste krav til sproget var [13] :
Go blev skabt med en forventning om, at programmer på den ville blive oversat til objektkode og eksekveret direkte uden at kræve en virtuel maskine , så et af kriterierne for at vælge arkitektoniske løsninger var evnen til at sikre hurtig kompilering til effektiv objektkode og fraværet af overdreven krav til dynamisk support.
Resultatet var et sprog "som ikke var et gennembrud, men som alligevel var et fremragende værktøj til udvikling af store softwareprojekter" [12] .
Selvom en tolk er tilgængelig til Go , er der praktisk talt ikke noget stort behov for det, da kompileringshastigheden er hurtig nok til at tillade interaktiv udvikling.
Hovedtræk ved Go-sproget [9] :
Go indeholder ikke mange af de populære syntaktiske funktioner, der er tilgængelige i andre moderne applikationsprogrammeringssprog. I mange tilfælde er dette forårsaget af en bevidst beslutning fra udviklerne. Korte begrundelser for de valgte designbeslutninger kan findes i "Ofte stillede spørgsmål" [9] om sproget, mere detaljeret - i de artikler og diskussioner, der er offentliggjort på sprogets websted, under hensyntagen til forskellige designmuligheder. I særdeleshed:
Syntaksen for Go-sproget ligner den for C -sproget , med elementer lånt fra Oberon og scriptsprog .
Go er et sprog, der skelner mellem store og små bogstaver, med fuld Unicode-understøttelse af strenge og identifikatorer.
En identifikator kan traditionelt være en hvilken som helst ikke-tom sekvens af bogstaver, tal og en understregning, der starter med et bogstav og ikke matcher nogen af Go-nøgleordene. "Bokstaver" refererer til alle Unicode-tegn, der falder ind under kategorierne "Lu" (store bogstaver), "Ll" (små bogstaver), "Lt" (store bogstaver), "Lm" (modificerende bogstaver) eller "Lo" ( andre bogstaver), under "tal" - alle tegn fra kategorien "Nd" (tal, decimaltal). Der er således intet til hinder for at bruge kyrillisk i f.eks. identifikatorer.
Identifikatorer, der kun adskiller sig i store og små bogstaver, er forskellige. Sproget har en række konventioner for brugen af store og små bogstaver. Især bruges kun små bogstaver i pakkenavne. Alle Go søgeord er skrevet med små bogstaver. Variabler, der starter med store bogstaver, kan eksporteres (offentlige), og de, der starter med små bogstaver, kan ikke eksporteres (private).
Strengliteraler kan bruge alle Unicode-tegn uden begrænsninger. Strenge er repræsenteret som sekvenser af UTF-8- tegn .
Ethvert Go-program inkluderer en eller flere pakker. Pakken, som en kildekodefil hører til, er givet af pakkebeskrivelsen i begyndelsen af filen. Pakkenavne har de samme begrænsninger som identifikatorer, men kan kun indeholde små bogstaver. Goroutine-pakkesystemet har en træstruktur, der ligner et bibliotekstræ. Alle globale objekter (variabler, typer, grænseflader, funktioner, metoder, elementer af strukturer og grænseflader) er tilgængelige uden begrænsninger i den pakke, hvori de er erklæret. Globale objekter, hvis navne begynder med et stort bogstav, kan eksporteres.
For at bruge objekter eksporteret af en anden pakke i en Go-kodefil, skal pakken importeres ved hjælp af import.
pakke hoved /* Import */ import ( "fmt" // Standardpakke til formateret output "database/sql" // Importer indlejret pakke w "os" // Importer med alias . "math" // Importer uden kvalifikation ved brug af _ "gopkg.in/goracle.v2" // Pakken har ingen eksplicitte referencer i koden ) func main () { for _ , arg := range w . Args { // Adgang til Args-arrayet, der er erklæret i "os"-pakken via fmt -aliaset . Println ( arg ) // Kaldning af Println()-funktionen erklæret i pakken "fmt" med pakkenavn } var db * sql . db = sql . Open ( driver , dataSource ) // Navne fra den indlejrede pakke er kvalificeret // kun efter navnet på selve pakken (sql) x := Sin ( 1.0 ) // call math.Sin() - kvalifikation ved pakkenavnet math er ikke nødvendig // fordi den er importeret uden et navn // Der er ingen reference til "goracle.v2"-pakken i koden, men den vil blive importeret. }Den viser stierne til importerede pakker fra src-biblioteket i kildetræet, hvis position er givet af miljøvariablen GOPATH, mens det for standardpakker blot angives navnet. En streng, der identificerer en pakke, kan indledes med et alias, i hvilket tilfælde den vil blive brugt i kode i stedet for pakkenavnet. Importerede objekter er tilgængelige i den fil, der importerer dem med en fuld kvalifikation som " пакет.Объект". Hvis en pakke importeres med en prik i stedet for et alias, vil alle de navne, den eksporterer, være tilgængelige uden forbehold. Denne funktion bruges af nogle systemværktøjer, men dens brug af programmøren anbefales ikke, da eksplicit kvalifikation giver beskyttelse mod navnekollisioner og "umærkelige" ændringer i kodeadfærd. Det er ikke muligt uden kvalifikation at importere to pakker, der eksporterer det samme navn.
Importen af pakker i Go er stramt kontrolleret: Hvis en pakke importeres af et modul, skal der bruges mindst ét navn, der eksporteres af den pågældende pakke, i koden for det pågældende modul. Go-kompileren behandler import af en ubrugt pakke som en fejl; en sådan løsning tvinger udvikleren til konstant at holde importlisterne ajour. Dette skaber ingen vanskeligheder, da Go programmeringsstøtteværktøjer (editorer, IDE'er) normalt giver automatisk kontrol og opdatering af importlister.
Når en pakke indeholder kode, der kun bruges gennem introspektion , er der et problem: import af en sådan pakke er nødvendig for at inkludere den i programmet, men vil ikke blive tilladt af compileren, da den ikke er direkte tilgået. _Anonym import er tilvejebragt for sådanne tilfælde: " " (enkelt understregning) er angivet som et alias ; en pakke importeret på denne måde vil blive kompileret og inkluderet i programmet, hvis den ikke er eksplicit refereret i koden. En sådan pakke kan dog ikke bruges eksplicit; dette forhindrer importkontrol i at blive omgået ved at importere alle pakker som anonyme.
Et eksekverbart Go-program skal indeholde en pakke med navnet main, som skal indeholde en funktion main()uden parametre og en returværdi. Funktionen main.main()er "programmets krop" - dens kode køres, når programmet starter. Enhver pakke kan indeholde en funktion init() - den vil blive kørt, når programmet er indlæst, før det begynder at udføre, før en funktion kaldes i denne pakke og i enhver pakke, der importerer denne. Hovedpakken initialiseres altid sidst, og alle initialiseringer udføres før funktionen begynder at udføre main.main().
Go-pakkesystemet blev designet med den antagelse, at hele udviklingsøkosystemet eksisterer som et enkelt filtræ, der indeholder opdaterede versioner af alle pakker, og når nye versioner dukker op, bliver det fuldstændigt omkompileret. For applikationsprogrammering ved hjælp af tredjepartsbiblioteker er dette en ret stærk begrænsning. I virkeligheden er der ofte begrænsninger på de versioner af pakker, der bruges af en eller anden kode, såvel som situationer, hvor forskellige versioner (grene) af et projekt bruger forskellige versioner af bibliotekspakker.
Siden version 1.11 har Go understøttet såkaldte moduler . Et modul er en specielt beskrevet pakke, der indeholder information om dens version. Når et modul importeres, er den anvendte version fast. Dette giver byggesystemet mulighed for at kontrollere, om alle afhængigheder er opfyldt, automatisk opdatere importerede moduler, når forfatteren foretager kompatible ændringer til dem, og blokere opdateringer til ikke-bagudkompatible versioner. Moduler formodes at være en løsning (eller en meget lettere løsning) på problemet med afhængighedsstyring.
Go bruger begge typer kommentarer i C-stil: indlejrede kommentarer (begynder med // ...) og blokkommentarer (/* ... */). En linjekommentar behandles af compileren som en ny linje. Blok, placeret på en linje - som et mellemrum, på flere linjer - som en ny linje.
Semikolonet i Go bruges som en obligatorisk separator i nogle operationer (hvis, for, switch). Formelt set bør det også afslutte hver kommando, men i praksis er det ikke nødvendigt at sætte et sådant semikolon i slutningen af linjen, da compileren selv tilføjer semikolon til slutningen af hver linje, undtagen tomme tegn, i slutningen af linjen. identifikator, tal, en bogstavelig karakter, en streng, bruddet, fortsæt, gennemfald, returner nøgleord, en inkrementer eller dekrementeringskommando (++ eller --) eller en afsluttende parentes, firkant eller krøllet klammeparentes (en vigtig undtagelse er, at et komma er ikke inkluderet i ovenstående liste). Heraf følger to ting:
Sproget indeholder et ret standardsæt af simple indbyggede datatyper: heltal, flydende kommatal, tegn, strenge, booleaner og et par specielle typer.
HeltalDer er 11 heltalstyper:
Skaberne af sproget anbefaler kun at bruge standardtypen til at arbejde med tal inde i programmet int. Typer med faste størrelser er designet til at arbejde med data modtaget fra eller videregivet til eksterne kilder, når det er vigtigt for kodens rigtighed at angive en bestemt størrelse af typen. Typerne er synonymer byteog runeer designet til at arbejde med henholdsvis binære data og symboler. Typen uintptrer kun nødvendig for interaktion med ekstern kode, for eksempel i C.
Flydende kommatalFlydende kommatal er repræsenteret af to typer, float32og float64. Deres størrelse er henholdsvis 32 og 64 bit, implementeringen overholder IEEE 754- standarden . Rækken af værdier kan fås fra standardpakken math.
Numeriske typer med ubegrænset præcisionGo-standardbiblioteket indeholder også pakken big, som giver tre typer med ubegrænset præcision: big.Int, big.Ratog big.Float, som repræsenterer henholdsvis heltal, rationaler og flydende kommatal; størrelsen af disse tal kan være hvad som helst og er kun begrænset af mængden af tilgængelig hukommelse. Da operatører i Go ikke er overbelastet, implementeres beregningsoperationer på tal med ubegrænset præcision som almindelige metoder. Ydeevnen af beregninger med store tal er naturligvis væsentligt ringere end de indbyggede numeriske typer, men når man løser visse typer beregningsproblemer, kan brug af en pakke bigvære at foretrække frem for manuel optimering af en matematisk algoritme.
Komplekse talSproget giver også to indbyggede typer for komplekse tal, complex64og complex128. Hver værdi af disse typer indeholder et par reelle og imaginære dele med henholdsvis typer float32og float64. Du kan oprette en værdi af en kompleks type i kode på en af to måder: enten ved at bruge en indbygget funktion complex()eller ved at bruge en imaginær bogstavelig i et udtryk. Du kan få de reelle og imaginære dele af et komplekst tal ved at bruge funktionerne real()og imag().
var x kompleks128 = kompleks ( 1 , 2 ) // 1 + 2i y := 3 + 4i // 3 + 4i, hvor 4 er et tal efterfulgt af et i-suffiks // er en imaginær fmt- literal . Println ( x * y ) // udskriver "(-5+10i)" fmt . Println ( real ( x * y )) // udskriver "-5" fmt . Println ( billede ( x * y )) // vil udskrive "10" Booleske værdierDen boolske type booler ret almindelig - den inkluderer de foruddefinerede værdier trueog falseangiver henholdsvis sand og falsk. I modsætning til C er booleaner i Go ikke numeriske og kan ikke direkte konverteres til tal.
StringsStrengtypeværdier stringer uforanderlige byte-arrays, der indeholder UTF-8. Dette forårsager en række specifikke træk ved strenge (for eksempel i det generelle tilfælde er længden af en streng ikke lig med længden af den matrix, der repræsenterer den, dvs. antallet af tegn indeholdt i den er ikke lig med antallet af bytes i det tilsvarende array). For de fleste applikationer, der behandler hele strenge, er denne specificitet ikke vigtig, men i de tilfælde, hvor programmet direkte skal behandle specifikke runer (Unicode-tegn), unicode/utf8kræves en pakke, der indeholder hjælpeværktøjer til at arbejde med Unicode-strenge.
For alle datatyper, inklusive indbyggede, kan nye analoge typer erklæres, som gentager alle originalernes egenskaber, men som er inkompatible med dem. Disse nye typer kan også valgfrit deklarere metoder. Brugerdefinerede datatyper i Go er pointere (erklæret med symbolet *), arrays (erklæret med firkantede parenteser), strukturer ( struct), funktioner ( func), grænseflader ( interface), mappings ( map) og kanaler ( chan). Deklarationerne af disse typer specificerer typerne og muligvis identifikatorerne for deres elementer. Nye typer erklæres med søgeordet type:
type PostString string // Skriv "string", svarende til indbygget type StringArray [] string // Array type med string type elementer type Person struct { // Struct type name string // felt af standard strengtype post PostString // felt af den tidligere erklærede brugerdefinerede strengtype bdate time . Tid // felt af typen Tid, importeret fra pakken tid edate time . Tidschef * Person // pointer field infer [ ]( * Person ) // array field } type InOutString chan string // kanaltype til at sende strenge type CompareFunc func ( a , b interface {}) int // funktionstype.Siden version Go 1.9 er deklaration af typealiaser (aliaser) også tilgængelig:
type TitleString = streng // "TitleString" er et alias for den indbyggede type strengtype Integer = int64 // "Integer" er et alias for den indbyggede 64-bit heltalstypeEt alias kan erklæres for enten en systemtype eller enhver brugerdefineret type. Den grundlæggende forskel mellem aliaser og almindelige typedeklarationer er, at deklarationen skaber en ny type, som ikke er kompatibel med originalen, selvom der ikke tilføjes ændringer til den originale type i deklarationen. Et alias er blot et andet navn for den samme type, hvilket betyder, at aliaset og den originale type er fuldstændigt udskiftelige.
Strukturfelter kan have tags i beskrivelsen - vilkårlige sekvenser af tegn omgivet af bagerste anførselstegn:
// Struktur med feltmærker type XMLInvoices struct { XMLName xml . Navn `xml:"INVOICES"` Version int `xml:"version,attr"` Faktura [] * XMLInvoice `xml:"INVOICE"` }Tags ignoreres af compileren, men information om dem er placeret i koden og kan læses ved hjælp af funktionerne i pakken reflectinkluderet i standardbiblioteket. Typisk bruges tags til at levere typemarshaling til lagring og gendannelse af data på eksterne medier eller interaktion med eksterne systemer, der modtager eller transmitterer data i deres egne formater. Eksemplet ovenfor bruger tags behandlet af standardbiblioteket til at læse og skrive data i XML-format.
Syntaksen til at deklarere variabler er hovedsageligt løst i Pascals ånd: Deklarationen begynder med nøgleordet var, efterfulgt af variabelnavnet gennem separatoren, derefter, gennem separatoren, dens type.
gå | C++ |
---|---|
var v1 int const v2 string var v3 [ 10 ] int var v4 [ ] int var v5 struct { f int } var v6 * int /* pointer aritmetic ikke understøttet */ var v7 map [ string ] int var v8 func ( a int ) int | int v1 ; const std :: stringv2 ; _ /* om */ intv3 [ 10 ] ; int * v4 ; /* om */ struct { int f ; } v5 ; int * v6 ; std :: uordnet_kort v7 ; /* om */ int ( * v8 )( int a ); |
Variabel erklæring kan kombineres med initialisering:
var v1 int = 100 var v2 string = "Hej!" var v3 [ 10 ] int = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } var v4 [ ] int = { 1000 , 2000 , 12334 } var v5 struct { f int } = { 50 } var v6 * int = & v1 var v7 map [ string ] int = { "one" : 1 , "to" : 2 , "three" : 3 } var v8 func ( a int ) int = func ( a int ) int { returner a + 1 }Hvis en variabel ikke udtrykkeligt initialiseres , når den erklæres , initialiseres den automatisk til "nullværdi" for den givne type. Nullværdien for alle numeriske typer er 0, for en type string er det den tomme streng, for pointere er den nil. Strukturer initialiseres som standard med sæt nulværdier for hvert af felterne inkluderet i dem, arrayelementer initialiseres med nulværdier af den type, der er angivet i arraydefinitionen.
Annoncer kan grupperes:
var ( i int m float )Go-sproget understøtter også automatisk typeslutning . Hvis en variabel initialiseres, når den er erklæret, kan dens type udelades - typen af det udtryk, der er tildelt den, bliver variablens type. For bogstaver (tal, tegn, strenge) definerer sprogstandarden specifikke indbyggede typer, som hver sådan værdi tilhører. For at initialisere en variabel af en anden type, skal der anvendes en eksplicit typekonvertering på det bogstavelige.
var p1 = 20 // p1 int - det heltal literal 20 er af typen int. var p2 = uint ( 20 ) // p2 uint - værdi eksplicit castet til uint. var v1 = & p1 // v1 *int er en pointer til p1, for hvilken int-typen udledes. var v2 = & p2 // v2 *uint er en pointer til p2, som eksplicit initialiseres som et heltal uden fortegn.For lokale variabler er der en forkortet form for erklæring kombineret med initialisering ved hjælp af typeinferens:
v1 := 100 v2 := "Hej!" v3 := [ 10 ] int { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } v4 := [ ] int { 1000 , 2000 , 12334 } v5 := struct { f int 50 } v6 := & v1Go bruger symbolet som en opgaveoperator =:
a = b // Sæt variabel a til bSom nævnt ovenfor er der en form for definition af en variabel med automatisk typeinferens kombineret med initialisering, der udadtil ligner tildeling i Pascal :
v1 := v2 // ligner var v1 = v2Go-kompileren holder nøje styr på definitioner og opgaver og adskiller den ene fra den anden. Da omdefinering af en variabel med samme navn er forbudt i ét omfang, inden for en kodeblok, kan en variabel kun vises til venstre for tegnet :=én gang:
a := 10 // Erklæring og initialisering af en heltalsvariabel a. b := 20 // Erklæring og initialisering af en heltalsvariabel b. ... a := b // FEJL! Forsøg på at omdefinere en.Go gør det muligt at udføre flere opgaver parallelt:
i , j = j , i // Byt i- og j-værdier.I dette tilfælde skal antallet af variable til venstre for tildelingstegnet nøjagtigt svare til antallet af udtryk til højre for tildelingstegnet.
Parallel tildeling er også mulig ved brug af :=. Dets ejendommelighed er, at der blandt variablerne til venstre for tegnet :=kan være allerede eksisterende. I dette tilfælde vil nye variable blive oprettet, eksisterende vil blive genbrugt. Denne syntaks bruges ofte til fejlhåndtering:
x , err := SomeFunction () // Funktionen returnerer to værdier (se nedenfor), // to variable er erklæret og initialiseret. if ( fejl != nul ) { returner nul } y , err := SomeOtherFunction () // Kun y erklæres her, err er blot tildelt en værdi.I den sidste linje i eksemplet tildeles den første værdi, der returneres af funktionen, til den nye variabel y, den anden til den allerede eksisterende variabel err, som bruges gennem hele koden til at placere den sidste fejl returneret af de kaldte funktioner. Hvis ikke for denne funktion af operatoren :=, ville man i det andet tilfælde skulle erklære en ny variabel (for eksempel err2) eller separat erklære yog derefter bruge den sædvanlige parallelle tildeling.
Go implementerer "kopi-på-tildeling" semantik, hvilket betyder, at en opgave resulterer i at lave en kopi af værdien af den oprindelige variabel og placere denne kopi i en anden variabel, hvorefter værdierne af variablerne er forskellige og ændre en af dem ændrer ikke den anden. Dette gælder dog kun for indbyggede skalartyper, strukturer og arrays med en given længde (det vil sige typer, hvis værdier er allokeret på stakken). Arrays af ubestemt længde og mappings er allokeret på heapen , variabler af disse typer indeholder faktisk referencer til objekter, når de tildeles, kopieres kun referencen, men ikke selve objektet. Nogle gange kan dette føre til uventede virkninger. Overvej to næsten identiske eksempler:
type vektor [ 2 ] float64 // Længden af arrayet indstilles eksplicit v1 := vektor { 10 , 15.5 } // Initialisering - v1 indeholder selve arrayet v2 := v1 // Array v1 kopieres til array v2 v2 [ 0 ] = 25.3 // Ændrede kun v2 fmt -arrayet . Println ( v1 ) // Udskriver "[10 15.5]" - det originale array er ikke ændret. fmt . Println ( v2 ) // Udskriver "[25.3 15.5]"Her er typen vectordefineret som en matrix af to tal. Tildeling af sådanne arrays opfører sig på samme måde som tildeling af tal og strukturer.
Og i det følgende eksempel adskiller koden sig med præcis ét tegn: typen vectorer defineret som et array med en ubestemt størrelse. Men denne kode opfører sig helt anderledes:
type vektor [] float64 // Array med udefineret længde v1 := vektor { 10 , 15.5 } // Initialisering - v1 indeholder array reference v2 := v1 // Array reference er kopieret fra v1 til v2 v2 [ 0 ] = 25.3 / / Kan kun tænkes at ændre v2 fmt -arrayet . Println ( v1 ) // Udskriver "[25.3 15.5]" - det originale array er ÆNDRET! fmt . Println ( v2 ) // Udskriver "[25.3 15.5]"På samme måde som i det andet eksempel opfører kortlægninger og grænseflader sig. Desuden, hvis strukturen har et felt af en reference- eller grænsefladetype, eller et felt er en dimensionsløs matrix eller mapping, så vil kun referencen også blive kopieret, når der tildeles en sådan struktur, det vil sige, at felterne i forskellige strukturer begynder at pege på de samme objekter i hukommelsen.
For at undgå denne effekt skal du eksplicit bruge en systemfunktion, copy()der garanterer oprettelsen af en anden instans af objektet.
erklæres således:
func f ( i , j , k int , s , t streng ) streng { }Typerne af sådanne værdier er omgivet af parentes:
func f ( a , b int ) ( int , streng ) { return a + b , "addition" }Funktionsresultater kan også navngives:
func incTwo ( a , b int ) ( c , d int ) { c = a + 1 d = b + 1 retur }Navngivne resultater anses for at være erklæret umiddelbart efter funktionsoverskriften med nul begyndelsesværdier. Retursætningen i en sådan funktion kan bruges uden parametre, i hvilket tilfælde resultaterne, efter at være vendt tilbage fra funktionen, vil have de værdier, der blev tildelt dem under udførelsen. Så i eksemplet ovenfor vil funktionen returnere et par heltalsværdier, en større end dens parametre.
Flere værdier, der returneres af funktioner, tildeles variabler ved at angive dem adskilt med kommaer, mens antallet af variabler, som resultatet af funktionskaldet er tildelt, skal svare nøjagtigt til antallet af værdier, der returneres af funktionen:
første , anden := incTwo ( 1 , 2 ) // first = 2, second = 3 first := incTwo ( 1 , 2 ) // FORKERT - ingen variabel tildelt det andet resultatI modsætning til Pascal og C, hvor deklarering af en lokal variabel uden at bruge den senere, eller tab af værdien af en lokal variabel (når værdien tildelt til variablen så ikke læses nogen steder) kun kan forårsage en compiler-advarsel, betragtes denne situation i Go en sprogfejl og fører til, at det er umuligt at kompilere programmet. Dette betyder især, at programmøren ikke kan ignorere værdien (eller en af værdierne), der returneres af funktionen, blot ved at tildele den til en variabel og nægte at bruge den yderligere. Hvis det bliver nødvendigt at ignorere en af de værdier, der returneres af et funktionskald, bruges en foruddefineret pseudovariabel med navnet "_" (én understregning). Det kan angives hvor som helst, hvor der skal være en variabel, der tager en værdi. Den tilsvarende værdi vil ikke blive tildelt nogen variabel og vil simpelthen gå tabt. Betydningen af en sådan arkitektonisk beslutning er at identificere et muligt tab af beregningsresultater på kompileringsstadiet: en utilsigtet udeladelse af værdibehandling vil blive opdaget af compileren, og brugen af "_" pseudovariablen vil indikere, at programmøren bevidst ignoreret resultaterne. I det følgende eksempel, hvis kun én af de to værdier returneret af incTwo-funktionen er nødvendig, skal "_" angives i stedet for den anden variabel:
første := incTwo ( 1 , 2 ) // UGYLDIG først , _ := incTwo ( 1 , 2 ) // TRUE, andet resultat ikke brugtVariablen "_" kan angives i opgavelisten et vilkårligt antal gange. Alle funktionsresultater, der matcher "_" vil blive ignoreret.
Det udskudte opkald erstatter flere syntaktiske funktioner på én gang, især undtagelsesbehandlere og garanterede færdiggørelsesblokke. Et funktionskald forud for nøgleordet defer parametriseres på det punkt i programmet, hvor det er placeret, og udføres umiddelbart før programmet forlader omfanget, hvor det blev erklæret, uanset hvordan og af hvilken grund denne exit sker. Hvis en enkelt funktion indeholder flere defer-deklarationer, udføres de tilsvarende kald sekventielt efter funktionen slutter, i omvendt rækkefølge. Nedenfor er et eksempel på brug af defer som en garanteret færdiggørelsesblok [15] :
// Funktion, der kopierer filen func CopyFile ( dstName , srcName string ) ( skrevet int64 , err error ) { src , err := os . Åbn ( srcName ) // Åbn kildefilen hvis err != nul { // Check return // Hvis det mislykkes, returner med en fejl } // Hvis du kom hertil, blev kildefilen åbnet defer src . Luk () // Forsinket opkald: src.Close() vil blive kaldt, når CopyFile er fuldført dst , fejl := os . Opret ( dstName ) // Åbn destinationsfilen hvis fejl != nul { // Tjek og returner ved fejlretur } udskyd dst . Luk () // Forsinket opkald: dst.Close() kaldes når CopyFile er færdig returnio . _ Kopier ( dst , src ) // Kopier data og returner fra funktion // Efter alle operationer vil blive kaldt: først dst.Close(), derefter src.Close() }I modsætning til de fleste sprog med C-lignende syntaks, har Go ikke parenteser til betingede konstruktioner for, if, switch:
hvis i >= 0 && i < len ( arr ) { println ( arr [ i ] ) } ... for i := 0 ; i < 10 ; i ++ { } }Go bruger en loop-konstruktion til at organisere alle slags loops for.
for i < 10 { // loop med forudsætning, svarende til mens i C } for i := 0 ; i < 10 ; i ++ { // sløjfe med en tæller, svarende til for i C } for { // uendelig løkke // Afslutning af løkken skal håndteres manuelt, // gøres normalt med retur eller pause } for { // loop med postcondition ... // loop body if i >= 10 { // exit condition break } } for i , v := range arr { // loop gennem samlingen (array, slice, display) arr // i - index (eller key) for det aktuelle element // v - kopi af værdien af det aktuelle array-element } for i := range arr { // går gennem samlingen, er det kun indekset, der bruges } for _ , v := range arr { // loop through collection, brug kun elementværdier } for range arr { // Loop gennem samlingen uden variabler (samlingen bruges // kun som en iterationstæller). } for v := range c { // sløjfe gennem kanalen: // v vil læse værdier fra kanal c, // indtil kanalen lukkes af en samtidig // goroutine }Syntaksen for multiple choice-operatøren switchhar en række funktioner. Først og fremmest, i modsætning til C, er brugen af operatøren ikke påkrævet break: efter at den valgte gren er blevet behandlet, afsluttes udførelsen af operatøren. Hvis du tværtimod ønsker, at den næste gren skal fortsætte behandlingen efter den valgte gren, skal du bruge operatoren fallthrough:
switch værdi { case 1 : fmt . Println ( "One" ) fallthrough // Dernæst vil "case 0:"-grenen blive udført case 0 : fmt . println ( "Nul" ) }Her, når value==1to linjer vil blive vist, "En" og "Nul".
Valgudtrykket og dermed alternativerne i switch-sætningen kan være af enhver type, det er muligt at opregne flere muligheder i en gren:
skifte tegn [ kode ]. kategori { case "Lu" , "Ll" , "Lt" , "Lm" , "Lo" : ... case "Nd" : ... default : ... }Fraværet af et valgudtryk er tilladt, i hvilket tilfælde logiske betingelser skal skrives i alternativerne. Den første gren udføres, hvis betingelse er sand:
switch { case '0' <= c && c <= '9' : return c - '0' case 'a' <= c && c <= 'f' : return c - 'a' + 10 case 'A' <= c && c <= 'F' : returner c - 'A' + 10 }En vigtig detalje: Hvis en af grenene med betingelsen slutter med operatøren fallthrough, vil den næste efter denne gren blive behandlet, uanset om dens betingelse er opfyldt . Hvis du ønsker, at den næste gren kun skal behandles, hvis dens betingelse er opfyldt, skal du bruge sekventielle konstruktioner if.
Go-sproget understøtter ikke den strukturerede undtagelseshåndteringssyntaks, der er typisk for de fleste moderne sprog , som involverer at smide undtagelser med en speciel kommando (normalt throweller raise) og håndtere dem i en blok try-catch. I stedet anbefales det at bruge fejlreturn som et af funktionens resultater (hvilket er praktisk, da en funktion i Go kan returnere mere end én værdi):
Mange kritikere af sproget mener, at denne ideologi er værre end undtagelseshåndtering, da adskillige kontroller roder koden og ikke tillader al fejlhåndtering at blive koncentreret i blokke catch. Sprogets skabere betragter ikke dette som et alvorligt problem. En række fejlhåndteringsmønstre i Go er beskrevet (se f.eks. Rob Pikes artikel på den officielle Go-blog , russisk oversættelse ), som kan reducere mængden af fejlhåndteringskode.
Når der opstår fatale fejl, der umuliggør yderligere programudførelse (f.eks. division med nul eller adgang til array-grænser), opstår der en paniktilstand , som som standard fører til, at programmet går ned med en fejlmeddelelse og en opkaldsstaksporing. Panik kan fanges og håndteres ved hjælp af den udskudte udførelseskonstruktion deferbeskrevet ovenfor. Funktionskaldet, der er specificeret i defer, foretages før det aktuelle omfang forlades, også i tilfælde af panik. Inde i funktionen kaldet i deferkan du kalde en standardfunktion recover() - den stopper systembehandlingen af en panik og returnerer dens årsag i form af et objekt error, der kan behandles som en normal fejl. Men programmøren kan også genoptage en tidligere fanget panik ved at kalde standarden panic(err error).
// Programmet udfører en heltalsdeling // af dens første parameter med den anden // og udskriver resultatet. func main () { defer func () { err := recover () if v , ok := err .( error ); ok { // Håndtering af panik svarende til grænsefladefejl fmt . Fprintf ( os . Stderr , "Error %v \"%s\"\n" , err , v . Error ()) } else if err != nil { panic ( err ) // Håndtering af uventede fejl - re-raise the panik. } }() a , fejl := strconv . ParseInt ( os . Args [ 1 ], 10 , 64 ) hvis err != nil { panic ( err ) } b , err := strconv . ParseInt ( os . Args [ 2 ], 10 , 64 ) if err != nil { panic ( err ) } fmt . fprintf ( os . Stdout , "%d / %d = %d\n" , a , b , a / b ) }I eksemplet ovenfor kan der opstå fejl, når programargumenterne konverteres til heltal af funktionen strconv.ParseInt(). Det er også muligt at gå i panik, når man får adgang til os.Args-arrayet med et utilstrækkeligt antal argumenter, eller når man dividerer med nul, hvis den anden parameter er nul. For enhver fejlsituation genereres en panik, som behandles i opkaldet defer:
> dividere 10 5 10/5 = 2 > dividere 10 0 Error runtime.errorString "runtime error: heltal divider med nul" > dividere 10,5 2 Fejl *strconv.NumError "strconv.ParseInt: parsing "10.5": ugyldig syntaks" > divider 10 Error runtime.errorString "runtime error: index out of range"En panik kan ikke udløses i en parallel-eksekverende goroutine (se nedenfor), men håndteres i en anden. Det anbefales heller ikke at "passere" panik over en pakkegrænse.
Go's threading-model blev arvet fra Active Oberon -sproget baseret på Tony Hoares CSP ved hjælp af ideer fra Occam- og Limbo -sprogene [9] , men funktioner som pi-calculus og kanalisering er også til stede.
Go giver dig mulighed for at oprette en ny tråd for programafvikling ved hjælp af nøgleordet go , som kører en anonym eller navngivet funktion i en nyoprettet goroutine (Go's term for coroutines ). Alle goroutiner inden for samme proces bruger et fælles adresseområde, der udføres på OS-tråde , men uden hård binding til sidstnævnte, hvilket gør det muligt for en kørende goroutine at forlade en tråd med en blokeret goroutine (venter f.eks. på at sende eller modtage en besked fra en kanal) og fortsæt videre. Runtime-biblioteket inkluderer en multiplexer til at dele det tilgængelige antal systemkerner blandt goroutiner. Det er muligt at begrænse det maksimale antal fysiske processorkerner, som programmet vil blive udført på. Go-runtime-bibliotekets selvunderstøttelse af goroutiner gør det nemt at bruge et stort antal goroutiner i programmer, hvilket langt overstiger grænsen for antallet af tråde, der understøttes af systemet.
func server ( i int ) { for { print ( i ) tid . Sleep ( 10 ) } } go server ( 1 ) go server ( 2 )Lukninger kan bruges i et go udtryk .
var g int go func ( i int ) { s := 0 for j := 0 ; j < i ; j ++ { s += j } g = s }( 1000 )Til kommunikation mellem goroutiner bruges kanaler (den indbyggede type chan ), hvorigennem enhver værdi kan sendes. En kanal oprettes af den indbyggede funktion make(), som overføres kanalens type og (valgfrit) volumen. Som standard er kanallydstyrken nul. Sådanne kanaler er ubuffrede . Du kan indstille en hvilken som helst positiv heltalsvolumen for kanalen, så vil der blive oprettet en bufferkanal .
Et ubufret rør synkroniserer tæt læser- og forfattertråde, der bruger det. Når en forfattertråd skriver noget til et rør, holder den pause og venter, indtil værdien er blevet læst. Når en læsertråd forsøger at læse noget fra et rør, der allerede er skrevet til, læser den værdien, og begge tråde kan fortsætte med at udføre. Hvis der endnu ikke er skrevet nogen værdi til kanalen, holder læsetråden pause og venter på, at nogen skriver til kanalen. Det vil sige, ubuffrede rør i Go opfører sig på samme måde som rør i Occam eller rendezvous - mekanismen i Ada-sproget .
En bufferkanal har en værdibuffer, hvis størrelse er lig med kanalens størrelse. Når man skriver til et sådant rør, placeres værdien i rørets buffer, og writer-tråden fortsætter uden pause, medmindre rørets buffer er fuld på skrivetidspunktet. Hvis bufferen er fuld, suspenderes forfattertråden, indtil mindst én værdi er blevet læst fra kanalen. Læsertråden læser også en værdi fra et bufferet rør uden at holde pause, hvis der er ulæste værdier i rørets buffer; hvis kanalbufferen er tom, holder tråden pause og venter, indtil en anden tråd skriver en værdi til den.
Når du er færdig, kan kanalen lukkes med den indbyggede funktion close(). Et forsøg på at skrive til en privat kanal resulterer i panik, læsning fra en privat kanal sker altid uden pause og læser standardværdien. Hvis kanalen er bufferet og på tidspunktet for lukning indeholder den N tidligere skrevne værdier i bufferen, så udføres de første N læseoperationer, som om kanalen stadig var åben og læser værdierne fra bufferen, og først derefter vil læsningen fra kanalen returnere standardværdierne.
Operationen bruges til at sende en værdi til og fra en kanal <-. Når du skriver til en kanal, bruges den som en binær operator, når du læser - som en unær operator:
in := make ( chan string , 0 ) // Opret en ubufferet kanal ind ud := make ( chan int , 10 ) // Opret en bufferet kanal ud ... in <- arg // Skriv en værdi til kanalen i ... r1 := <- ud // læser fra kanalen ud ... r2 , ok := <- ud // læser med at tjekke om kanalen er lukket hvis ok { // hvis ok == sand - kanalen er åben ... } else { // hvis kanalen er lukket, så gør noget andet ... }Operationen med at læse fra en kanal har to muligheder: uden kontrol og med kontrol for lukning af kanalen. Den første mulighed (læser r1 i eksemplet ovenfor) læser simpelthen den næste værdi ind i variablen; hvis kanalen er lukket, vil standardværdien blive indlæst i r1. Den anden mulighed (læser r2) læser, ud over værdien, en boolsk værdi - ok kanalstatusflaget, som vil være sandt, hvis dataene placeret der af en strøm er blevet læst fra kanalen, og falsk, hvis kanalen er lukket, og dens buffer er tom. Med denne operation kan læsetråden bestemme, hvornår inputkanalen er lukket.
Aflæsning fra et rør understøttes også ved hjælp af for-range loop-konstruktionen:
// Funktionen starter parallellæsning fra inputkanalen i heltal og skriver // til outputkanalen kun de heltal, der er positive. // Returnerer outputkanalen. func positives ( in <- chan int64 ) <- chan int64 { out := make ( chan int64 ) go func () { // Sløjfen fortsætter indtil kanal ind er lukket for næste := range in { if next > 0 { ud <- næste } } luk ( ud ) }() returner ud }Ud over CSP eller i forbindelse med kanaliseringsmekanismen giver Go dig også mulighed for at bruge den sædvanlige model for synkroniseret interaktion af tråde gennem delt hukommelse ved at bruge typiske adgangssynkroniseringsværktøjer såsom mutexes . Samtidig advarer sprogspecifikationen dog mod ethvert forsøg på usynkroniseret interaktion af parallelle tråde gennem delt hukommelse, da compileren i mangel af eksplicit synkronisering optimerer dataadgangskoden uden at tage højde for muligheden for samtidig adgang fra forskellige tråde, hvilket kan føre til uventede fejl. For eksempel er skrivning af værdier til globale variabler i en tråd muligvis ikke synlige eller synlige i den forkerte rækkefølge fra en parallel tråd.
Overvej for eksempel programmet nedenfor. Funktionskoden er main()skrevet ud fra den antagelse, at den funktion, der lanceres i goroutinen setup(), vil skabe en struktur af typen T, initialisere den med strengen "hello, world", og derefter tildele en reference til den initialiserede struktur til den globale variabel g. B main()starter en tom sløjfe og venter på, at en gværdi, der ikke er nul, vises. Så snart den vises, main()udskriver en streng fra den struktur, der peges gpå, forudsat at strukturen allerede er initialiseret.
skriv T struct { msg string } varg * T _ func setup () { t : = new ( T ) t . msg = "hej verden" g = t } func main () { go setup () for g == nul { // VIRKER IKKE!!! } udskriv ( g . besked ) }I virkeligheden er en af to fejl mulig.
Den eneste korrekte måde at organisere dataoverførsel gennem delt hukommelse på er at bruge bibliotekssynkroniseringsværktøjer, som garanterer, at alle data skrevet af en af de synkroniserede streams før synkroniseringspunktet garanteres at være tilgængelige i en anden synkroniseret stream efter synkroniseringspunktet.
En funktion ved multithreading i Go er, at en goroutine ikke identificeres på nogen måde og ikke er et sprogobjekt, der kan henvises til, når man kalder funktioner, eller som kan placeres i en container. Følgelig er der ingen midler, der giver dig mulighed for direkte at påvirke udførelsen af en koroutine udefra, såsom at suspendere og derefter starte den, ændre prioritet, vente på færdiggørelsen af en koroutine i en anden og tvangsafbryde henrettelsen. Enhver handling på en goroutine (bortset fra at afslutte hovedprogrammet, som automatisk afslutter alle goroutiner) kan kun udføres gennem rør eller andre synkroniseringsmekanismer. Det følgende er eksempelkode, der starter adskillige goroutiner og venter på, at de fuldføres ved hjælp af WaitGroup-synkroniseringsobjektet fra synkroniseringssystempakken. Dette objekt indeholder en tæller, oprindeligt nul, som kan øges og formindskes, og en Wait()-metode, som får den aktuelle tråd til at pause og vente, indtil tælleren er nulstillet.
func main () { var wg sync . WaitGroup // Opret en ventegruppe. Startværdien af tælleren er 0 logger := log . New ( os . Stdout , "" , 0 ) // log.Logger er en trådsikker outputtype for _ , arg := range os . Args { // Gå gennem alle kommandolinjeargumenter wg . Tilføj ( 1 ) // Øg ventegruppetælleren med én // Kør en goroutine for at behandle arg-parameteren go func ( ordstreng ) { // Forsinket nedsættelse af ventegruppetælleren med én . // Sker når funktionen slutter. udskyde wg . Udført () logger . Println ( prepareWord ( word )) // Udfør bearbejdning og udskriv resultatet }( arg ) } wg . Vent () // Vent indtil tælleren i ventegruppe wg er nul. }Her, før oprettelsen af hver ny goroutine, øges tælleren for objektet wg med én, og efter færdiggørelsen af goroutinen dekrementeres den med én. Som et resultat vil der i løkken, der starter behandlingen af argumenter, blive tilføjet lige så mange enheder til tælleren, som der er lanceret goroutiner. Når løkken slutter, vil kald af wg.Wait() få hovedprogrammet til at pause. Efterhånden som hver af goroutinerne afsluttes, sænker den wg-tælleren med én, så hovedprogrammets ventetid slutter, når lige så mange goroutiner, som det kørte, er afsluttet. Uden den sidste linje ville hovedprogrammet, efter at have kørt alle goroutinerne, afslutte med det samme og afbryde udførelsen af dem, der ikke havde tid til at udføre.
På trods af tilstedeværelsen af multithreading indbygget i sproget, er ikke alle standardsprogobjekter trådsikre. Så standardkorttypen (visning) er ikke trådsikker. Skaberne af sproget forklarede denne beslutning med effektivitetshensyn, da sikring af sikkerhed for alle sådanne objekter ville føre til yderligere overhead, hvilket langt fra altid er nødvendigt (de samme operationer med kortlægninger kan være en del af større operationer, der allerede er synkroniseret af programmøren , og så vil den ekstra synkronisering kun komplicere og bremse programmet). Fra version 1.9 har synkroniseringsbibliotekspakken, som indeholder understøttelse af parallel behandling, tilføjet den trådsikre sync.Map-type, som kan bruges om nødvendigt. Du kan også være opmærksom på den trådsikre type, der bruges i eksemplet til at vise resultater log.Logger; den bruges i stedet for standard fmt-pakken, hvis funktioner (Printf, Println, og så videre) ikke er trådsikre og vil kræve yderligere synkronisering.
Der er ikke noget særligt nøgleord til at erklære en klasse i Go, men metoder kan defineres for enhver navngiven type, inklusive strukturer og basistyper som int , så i OOP-forstand er alle sådanne typer klasser.
skriv newInt intMetodedefinitionssyntaksen er lånt fra Oberon-2 sproget og adskiller sig fra den sædvanlige funktionsdefinition ved, at efter func nøgleordet erklæres den såkaldte "receiver" ( engelsk modtager ) i parentes , dvs. det objekt, for hvilket metode kaldes, og typen, som metoden tilhører. Hvorimod modtageren i traditionelle objektsprog er underforstået og har et standardnavn (i C++ eller Java er det "dette", i ObjectPascal er det "selv" osv.), i Go er det specificeret eksplicit, og dets navn kan være enhver gyldig Go-id.
type myType struct { i int } // Her er p modtageren i metoder af typen myType. func ( p * myType ) get () int { return p . i } func ( p * myType ) sæt ( i int ) { p . jeg = jeg }Der er ingen formel nedarvning af klasser (strukturer) i Go, men der er en teknisk tæt indlejringsmekanisme . I beskrivelsen af strukturen kan du bruge det såkaldte anonyme felt - et felt, hvor der ikke er angivet et navn, men kun en type. Som et resultat af en sådan beskrivelse vil alle elementer i den indlejrede struktur blive de samme navngivne elementer i den indlejrede struktur.
// Ny struct type type myType2 struct { myType // Anonymt felt giver indlejring af typen myType. // Nu indeholder myType2 i-feltet og metoderne get() og set(int). k int }I modsætning til klassisk nedarvning involverer inlining ikke polymorf adfærd (et objekt i en indlejringsklasse kan ikke fungere som et objekt i en indlejrbar klasse uden eksplicit typekonvertering).
Det er ikke muligt eksplicit at erklære metoder for en unavngiven type (syntaksen tillader simpelthen ikke at angive typen af modtageren i metoden), men denne begrænsning kan let omgås ved at inline den navngivne type med de nødvendige metoder.
Klassepolymorfi leveres i Go af grænseflademekanismen (svarende til fuldt abstrakte klasser i C++ ). En grænseflade er beskrevet ved hjælp af grænsefladenøgleordet; indeni (i modsætning til klassetypedeklarationer) erklærer beskrivelser de metoder, som grænsefladen giver.
skriv myInterface interface { get () int set ( i int ) }I Go er der ingen grund til eksplicit at angive, at en type implementerer en bestemt grænseflade. I stedet er reglen, at hver type, der leverer metoder defineret i en grænseflade, kan bruges som en implementering af denne grænseflade. Den ovenfor erklærede type myTypeimplementerer grænsefladen myInterface, selvom dette ikke er udtrykkeligt angivet nogen steder, fordi den indeholder metoder get()og set(), hvis signaturer matcher dem, der er beskrevet i myInterface.
Ligesom klasser kan grænseflader indlejres:
skriv mySecondInterface interface { myInterface // samme som eksplicit erklærer get() int; set(i int) change ( i int ) int }Her arver mySecondInterface-grænsefladen myInterface-grænsefladen (dvs. den erklærer, at den afslører metoderne inkluderet i myInterface) og erklærer desuden en native metode change().
Selvom det i princippet er muligt at bygge et Go-program ind i et hierarki af grænseflader, som det gøres i andre objektsprog, og endda at simulere nedarvning, betragtes dette som dårlig praksis. Sproget dikterer ikke en hierarkisk, men en kompositorisk tilgang til systemet af klasser og grænseflader. Strukturklasser med denne tilgang kan generelt forblive formelt uafhængige, og grænseflader kombineres ikke i et enkelt hierarki, men skabes til specifikke applikationer, om nødvendigt, indlejring af eksisterende. Den implicitte implementering af grænseflader i Go giver disse mekanismer ekstrem fleksibilitet og et minimum af tekniske vanskeligheder i deres brug.
Denne tilgang til arv er i tråd med nogle praktiske tendenser i moderne programmering. Så i den berømte bog "bande på fire" ( Erich Gamma og andre) om designmønstre , er det især skrevet:
Implementeringsafhængighed kan forårsage problemer, når du forsøger at genbruge en underklasse. Hvis selv et aspekt af den gamle implementering er uegnet til det nye domæne, så skal den overordnede klasse omskrives eller erstattes med noget mere passende. Denne afhængighed begrænser fleksibilitet og genanvendelighed. Problemet kan løses ved kun at arve fra abstrakte klasser, da de normalt ikke har nogen eller minimal implementering.
Der er intet koncept for en virtuel funktion i Go . Polymorfi er tilvejebragt af grænseflader. Hvis en variabel af en almindelig type bruges til at kalde en metode, så er et sådant kald statisk bundet, det vil sige, at den metode, der er defineret for denne særlige type, altid kaldes. Hvis metoden kaldes for en variabel af typen "interface", så er et sådant kald dynamisk bundet, og på udførelsestidspunktet metodevarianten, der er defineret for typen af objektet, der faktisk er tildelt på tidspunktet for kaldet variabel er valgt til lancering.
Dynamisk understøttelse af objektorienteret programmering til Go leveres af GOOP- projektet .
Evnen til at introspektere under kørsel, det vil sige adgang til og behandling af værdier af enhver type og dynamisk justering af de typer data, der behandles, er implementeret i Go ved hjælp af systempakken reflect. Denne pakke giver dig mulighed for at:
Pakken reflectindeholder også mange hjælpeværktøjer til at udføre operationer afhængigt af programmets dynamiske tilstand.
Faciliteter til hukommelsesadgang på lavt niveau er koncentreret i systempakken unsafe. Dets ejendommelighed er, at selvom det ligner en almindelig Go-pakke, implementeres det faktisk af compileren selv. Pakken unsafegiver adgang til den interne repræsentation af data og til "rigtige" hukommelsespointere. Det giver funktioner:
Pakken giver også en type unsafe.Pointer, der kan konverteres til en hvilken som helst pointer og kan konverteres til en pointer af enhver type, såvel som en standardtype uintptr , en usigneret heltalsværdi, der er stor nok til at gemme en fuld adresse på den aktuelle platform. Ved at konvertere markøren til unsafe.Pointerog derefter til uintptr, kan du få adressen som et heltal, som du kan anvende aritmetiske operationer på. Ved derefter at konvertere værdien tilbage til unsafe.Pointerog til en pointer til en bestemt type, kan du på denne måde få adgang næsten overalt i adresserummet.
De beskrevne transformationer kan være usikre, så de anbefales at undgå, hvis det er muligt. For det første er der åbenlyse problemer forbundet med fejlagtig adgang til det forkerte hukommelsesområde. En mere subtil pointe er, at på trods af brugen af pakken unsafe, bliver Go-objekter fortsat administreret af hukommelsesadministratoren og skraldeopsamleren. Konvertering af en pointer til et tal sætter denne pointer ud af kontrol, og programmøren kan ikke forvente, at en sådan konverteret pointer forbliver relevant på ubestemt tid. For eksempel at prøve at gemme en markør til et nyt typeobjekt som Тdette:
pT := uintptr ( usikker . Pointer ( ny ( T ))) // FORKERT!vil få objektet til at blive oprettet, markøren til det konverteret til et tal (som vil blive tildelt til pT). Det har dog pTen heltalstype og anses ikke af skraldeopsamleren for at være en pegepind til et oprettet objekt, så efter at handlingen er fuldført, vil hukommelsesstyringssystemet betragte dette objekt som ubrugt. Det vil sige, at den kan fjernes af skraldemanden, hvorefter den konverterede pointer pTbliver ugyldig. Dette kan ske til enhver tid, både umiddelbart efter afslutningen af operationen, og efter mange timers programdrift, således at fejlen kommer til udtryk i tilfældige programnedbrud, hvis årsag vil være ekstremt svær at identificere. Og når du bruger en affaldsopsamler i bevægelse [* 1] , kan markøren konverteret til et tal blive irrelevant, selv når objektet endnu ikke er blevet fjernet fra hukommelsen.
Da Go-specifikationen ikke giver præcise indikationer af, i hvilket omfang en programmør kan forvente at holde en pointer konverteret til et tal ajour, er der en anbefaling: at holde sådanne konverteringer på et minimum og organisere dem, så konverteringen af den originale pointer, dens ændringer og tilbagekonvertering er inden for en sproginstruktion, og når du kalder biblioteksfunktioner, der returnerer en adresse i form af uintptr, skal du straks konvertere deres resultat til unsafe.Pointerfor at bevare garantien for, at markøren ikke går tabt.
Pakken unsafebruges sjældent direkte i applikationsprogrammering, men den bruges aktivt i pakkerne reflect, os, syscall, context, netog nogle andre.
Der er adskillige eksterne værktøjer, der giver udenlandske funktionsgrænseflader (FFI) til Go-programmer. Cgo -værktøjet kan bruges til at interagere med ekstern C -kode (eller have en C-kompatibel grænseflade) . Det kaldes automatisk, når compileren behandler et korrekt skrevet Go-modul, og sikrer oprettelsen af en midlertidig Go-indpakningspakke, der indeholder erklæringer af alle nødvendige typer og funktioner. I C-funktionsopkald er du ofte nødt til at ty til pakkefaciliteter , hovedsageligt ved hjælp af . Et mere kraftfuldt værktøj er SWIG [16] , som giver mere avancerede funktioner, såsom integration med C++ klasser . unsafeunsafe.Pointer
Go-standardbiblioteket understøtter opbygning af konsolapplikationer og webbaserede serverapplikationer , men der er ingen standardværktøjer til at bygge GUI'er i klientapplikationer. Dette hul udfyldes af tredjeparts-indpakninger til populære UI- rammer såsom GTK+ og Qt , under Windows kan du bruge de grafiske WinAPI -værktøjer ved at få adgang til dem gennem pakken syscall, men alle disse metoder er ret besværlige. Der er også flere udviklinger af UI-rammer i selve Go, men ingen af disse projekter har nået niveauet for industriel anvendelighed. I 2015 på GopherCon-konferencen i Denver besvarede en af skaberne af sproget, Robert Grismer, spørgsmål, var enig i, at Go har brug for en UI-pakke, men bemærkede, at en sådan pakke skulle være universel, kraftfuld og multi-platform, hvilket gør dens udvikling lang og vanskelig proces. Spørgsmålet om implementering af en klient-GUI i Go er stadig åbent.
På grund af sprogets ungdom er dets kritik hovedsageligt koncentreret i internetartikler, anmeldelser og fora.
Meget af kritikken af sproget fokuserer på dets mangel på visse populære funktioner fra andre sprog. Blandt dem [17] [18] [19] [20] :
Som nævnt ovenfor skyldes manglen på en række funktioner tilgængelige på andre populære sprog udviklernes bevidste valg, som mener, at sådanne funktioner enten hindrer effektiv kompilering eller provokerer programmøren til at lave fejl eller skabe ineffektive eller "dårligt" med hensyn til kodevedligeholdelse, eller have andre uønskede bivirkninger.
Kritikere påpeger, at nogle funktioner i Go er implementeret i form af den enkleste eller mest effektive implementering, men ikke opfylder " princippet om mindste overraskelse ": deres adfærd adskiller sig fra, hvad programmøren ville forvente baseret på intuition og tidligere erfaringer. Sådanne funktioner kræver øget opmærksomhed fra programmøren, gør det vanskeligt at lære og skifte fra andre sprog.
Ofte kritiseres mekanismen for automatiske semikoloner, på grund af hvilke nogle former for skriveudsagn, funktionskald og lister bliver forkerte. I en kommentar til denne beslutning bemærker sprogets forfattere [9] , at det sammen med tilstedeværelsen af en kodeformater i det officielle værktøjssæt gofmtførte til fikseringen af en ret stiv standard for kodning i Go. Det er næppe muligt at skabe en standard for at skrive kode, der passer til alle; introduktionen til sproget af en funktion, der i sig selv sætter en sådan standard, forener programmernes udseende og eliminerer principløse konflikter på grund af formatering, hvilket er en positiv faktor for gruppeudvikling og vedligeholdelse af software.
Gos popularitet er vokset i de seneste år: Fra 2014 til 2020 er Go steget fra 65. pladsen til 11. pladsen på TIOBE- ranglisten, ratingværdien for august 2020 er 1,43 %. Ifølge resultaterne af en dou.ua-undersøgelse [22] blev Go-sproget i 2018 det niende på listen over de mest brugte og det sjette på listen over sprog, som udviklere giver personlig præference til.
Siden den første offentlige udgivelse i 2012 har brugen af sproget været støt stigende. Listen over virksomheder, der bruger sproget i industriel udvikling, offentliggjort på Go-projektets hjemmeside, omfatter flere dusin navne. Et stort udvalg af biblioteker til forskellige formål er blevet akkumuleret. Udgivelsen af version 2.0 var planlagt til 2019, men arbejdet blev forsinket og er stadig i gang i anden halvdel af 2022. En række nye funktioner forventes at dukke op, herunder generiske og speciel syntaks for at forenkle fejlhåndtering, hvis fravær er en af de mest almindelige klager fra kritikere af sproget .
RoadRunner (Application Server) webserveren blev udviklet i Golang , som gør det muligt for webapplikationer at opnå en anmodning-svar-hastighed på 10-20 ms i stedet for de traditionelle 200 ms. Denne webservice er planlagt til at blive inkluderet i populære rammer såsom Yii .
Sammen med C ++ bruges Golang til at udvikle mikrotjenester, som giver dig mulighed for at "indlæse" multi-processor platforme med arbejde. Du kan interagere med en mikrotjeneste ved hjælp af REST , og PHP -sproget er fantastisk til dette.
Spiral Framework blev udviklet ved hjælp af PHP og Golang. [23]
Der er kun én større version af selve Go-sproget, version 1. Versioner af Go-udviklingsmiljøet (compiler, værktøjer og standardbiblioteker) er nummereret enten tocifret ("<sprogversion>.<større udgivelse>") eller trecifret ("<sprogversion>.< større udgivelse>.<mindre udgivelse>") til systemet. Udgivelsen af en ny "tocifret" version afslutter automatisk understøttelsen af den tidligere "tocifrede" version. "Trecifrede" versioner frigives for at rette rapporterede fejl og sikkerhedsproblemer; sikkerhedsrettelser i sådanne versioner kan påvirke de sidste to "to-cifrede" versioner [24] .
Forfatterne erklærede [25] ønsket om så vidt muligt at bevare bagudkompatibilitet inden for hovedversionen af sproget. Det betyder, at før udgivelsen af Go 2 vil næsten ethvert program, der er oprettet i Go 1-miljøet, kompileres korrekt i enhver efterfølgende version af Go 1.x og køre uden fejl. Undtagelser er mulige, men de er få. Binær kompatibilitet mellem udgivelser er dog ikke garanteret, så et program skal være fuldstændigt omkompileret, når man flytter til en senere udgivelse af Go.
Siden marts 2012, hvor Go 1 blev introduceret, er følgende hovedversioner blevet frigivet:
Udviklingsfremskridt Siden 2017 har forberedelserne været i gang til udgivelsen af den næste basisversion af sproget, som har symbolet "Go 2.0" [26] . Indsamlingen af kommentarer til den aktuelle version og forslag til transformationer, samlet på projektets wiki-side [27] . Oprindeligt blev det antaget, at forberedelsesprocessen ville tage "ca. to år", og nogle af de nye elementer i sproget ville blive inkluderet i de næste udgivelser af Go 1-versionen (selvfølgelig kun dem, der ikke krænker bagudkompatibilitet ). [26] Fra april 2021 er version 2.0 endnu ikke klar, nogle af de planlagte ændringer er i design- og implementeringsfasen. Ifølge planerne skitseret i projektbloggen [28] vil arbejdet med implementeringen af de planlagte ændringer fortsætte i mindst 2021. Foreslåede innovationer Blandt de grundlæggende innovationer er eksplicit erklærede konstante værdier, en ny fejlhåndteringsmekanisme og generiske programmeringsværktøjer. Innovationsprojekter er tilgængelige online. Den 28. august 2018 blev en video tidligere præsenteret på Gophercon 2018-konferencen offentliggjort på den officielle udviklerblog , som demonstrerer udkast til versioner af det nye fejlhåndteringsdesign og generiske funktionsmekanisme. Der er også planlagt mange mindre bemærkelsesværdige, men meget væsentlige ændringer, [29] såsom at udvide reglerne for tilladeligheden af tegn for identifikatorer i ikke-latinske alfabeter, tillade skiftoperationer for fortegnede heltal, brug af understregningen som en adskillelse af grupper af tusinder i tal, binære bogstaver . De fleste af dem er allerede implementeret og tilgængelige i de nyeste versioner af Go 1. Fejl ved behandling Flere muligheder for at ændre fejlhåndteringsmekanismen blev overvejet, især et design med en separat fejlbehandler (" Error Handling - Draft Design "). Den sidste variant for juli 2019 er beskrevet i artiklen " Forslag: En indbygget Go-fejlkontrolfunktion, prøv ". Denne mulighed er den mest minimalistiske og involverer kun tilføjelse af én indbygget funktion try(), der behandler resultatet af et funktionskald. Dets brug er illustreret af pseudokoden nedenfor. func f ( … )( r1 type_1 , … , rn type_n , err error ) { // Testet funktion // Returnerer n+1 resultater: r1... rn, err of type error. } func g ( … )( … , err error ) { // Kald funktion f() med fejlkontrol: … x1 , x2 , … xn = try ( f ( … )) // Brug indbygget try: // if f( ) returnerede ikke-nul i det sidste resultat, så vil g() automatisk afslutte, // returnerer den samme værdi i ITS sidste resultat. … } func t ( … )( … , err error ) { // Svarer til g() uden at bruge den nye syntaks: t1 , t2 , … tn , te := f ( … ) // Kald f() med resultater gemt i midlertidig variabler. if te != nil { // Tjek returkoden for lighed nil err = te // Hvis returkoden ikke er nul, så skrives den til det sidste resultat af t(), returnerer // hvorefter t() afsluttes straks. } // Hvis der ikke var nogen fejl, x1 , x2 , … xn = t1 , t2 , … tn // … variabler x1…xn får deres værdier // og udførelsen af t() fortsætter. … } Det vil sige, try()at den blot giver et fejltjek i opkaldet til den funktion, der kontrolleres, og en øjeblikkelig retur fra den aktuelle funktion med samme fejl. Du kan bruge mekanismen til at håndtere en fejl, før du vender tilbage fra den aktuelle funktion defer. Brugen try()kræver, at både den funktion, der kontrolleres, og den funktion, den kaldes i, skal have den sidste returværdi af type error. Derfor kan du for eksempel ikke main()bruge try(); på øverste niveau skal alle fejl håndteres eksplicit. Denne fejlhåndteringsmekanisme skulle være inkluderet i Go 1.14 , men dette blev ikke gjort. Implementeringsdatoer er ikke angivet. Generisk kode I slutningen af 2018 blev et udkast til implementering af generiske typer og funktioner i Go præsenteret [30] . Den 9. september 2020 blev et revideret design offentliggjort [31] , hvor funktioner, typer og funktionsparametre kan parameteriseres af parametertyper , som igen er styret af begrænsninger . // Stringer er en begrænsningsgrænseflade, der kræver typen for at implementere // en strengmetode, der returnerer en strengværdi. type Stringer interface { String () string } // Funktionen modtager som input et array af værdier af enhver type, der implementerer String-metoden, og returnerer // den tilsvarende array af strenge opnået ved at kalde String-metoden for hvert element i input-arrayet. func Stringify [ T Stringer ] ( s [] T ) [] string { // typeparameteren T, underlagt Stringer-begrænsningen, // er værditypen for matrixparameteren s. ret = lav ([] streng , len ( s )) for i , v := område s { ret [ i ] = v . String () } return ret } ... v := make ([] MyType ) ... // For at kalde en generisk funktion skal du angive en specifik type s := Stringify [ String ]( v ) Her Stringifyindeholder funktionen en typeparameter T, som bruges i beskrivelsen af en almindelig parameter s. For at kalde en sådan funktion, som vist i eksemplet, skal du i opkaldet angive den specifikke type, den kaldes for. Stringeri denne beskrivelse er det en begrænsning , der kræver, at MyType implementerer en Stringparameterløs metode, der returnerer en strengværdi. Dette gør det muligt for compileren at " " behandle udtrykket korrekt v.String(). Implementeringen af den generiske kode annonceres i version 1.18, der er planlagt til august 2021. [28]
Der er i øjeblikket to hoved Go-compilere:
Der er også projekter:
Go-udviklingsmiljøet indeholder flere kommandolinjeværktøjer: go-værktøjet, som leverer kompilering, test og pakkehåndtering, og hjælpeværktøjerne godoc og gofmt, designet til henholdsvis at dokumentere programmer og formatere kildekode i henhold til standardregler. For at få vist en komplet liste over værktøjer skal du kalde værktøjet go uden at angive argumenter. gdb-debuggeren kan bruges til at fejlsøge programmer. Uafhængige udviklere leverer et stort antal værktøjer og biblioteker designet til at understøtte udviklingsprocessen, primært for at lette kodeanalyse, test og fejlfinding.
I øjeblikket er to IDE'er tilgængelige, som oprindeligt er fokuseret på Go-sproget - dette er det proprietære GoLand [1] (udviklet af JetBrains på IntelliJ-platformen) og den gratis LiteIDE [2] (tidligere hed projektet GoLangIDE). LiteIDE er en lille shell skrevet i C++ ved hjælp af Qt . Giver dig mulighed for at kompilere, fejlrette, formatere kode, køre værktøjer. Editoren understøtter syntaksfremhævning og autofuldførelse.
Go understøttes også af plugins i de universelle IDE'er Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus og andre. Automatisk fremhævning, autofuldførelse af Go-kode og kørsel af kompilering og kodebehandlingsværktøjer er implementeret som plugins til mere end to dusin almindelige teksteditorer til forskellige platforme, herunder Emacs, Vim, Notepad++, jEdit.
Nedenfor er et eksempel på "Hej, verden!" på Go-sproget.
hovedpakke _ importer "fmt" func main () { fmt . println ( "Hej, verden!" ) }Et eksempel på implementering af Unix echo kommandoen :
hovedpakke _ import ( "os" "flag" // kommandolinjeparser ) var omitNewLine = flag . Bool ( "n" , falsk , "udskriv ikke ny linje" ) const ( Mellemrum = " " NewLine = "\n" ) func main () { flag . Parse () // Scan argumentlisten og sæt flag var s string for i := 0 ; i < flag . Narg (); i ++ { if i > 0 { s += Mellemrum } s += flag . Arg ( i ) } if ! * udeladNewLine { s += NewLine } os . Stdout . WriteString ( s ) } ![]() | |
---|---|
Tematiske steder | |
I bibliografiske kataloger |
Programmeringssprog | |
---|---|
|