gå | |
---|---|
Språkklass | flertrådad , imperativ , strukturerad , objektorienterad [1] [2] |
Utförandetyp | sammanställt |
Framträdde i | 10 november 2009 |
Författare | Robert Grismer , Rob Pike och Ken Thompson |
Utvecklaren | Google , Rob Pike , Ken Thompson , The Go Authors [d] och Robert Grismer [d] |
Filtillägg _ | .go |
Släpp |
|
Typ system | strikt , statisk , med typinferens |
Blivit påverkad | 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 och Occam |
Licens | BSD |
Hemsida | go.dev _ |
OS | DragonFly BSD , FreeBSD , Linux , macOS , NetBSD , OpenBSD , Plan 9 , Solaris , Microsoft Windows , iOS , Android , AIX och Illumos |
Mediafiler på Wikimedia Commons |
Go (ofta även golang ) är ett kompilerat flertrådigt programmeringsspråk som utvecklats internt av Google [8] . Utvecklingen av Go började i september 2007, med Robert Grismer , Rob Pike och Ken Thompson [9] , som tidigare arbetat på Infernos operativsystemutvecklingsprojekt, direkt involverade i dess design . Språket introducerades officiellt i november 2009 . För närvarande tillhandahålls stöd för den officiella kompilatorn som utvecklats av skaparna av språket för operativsystem FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX . [10] . Go stöds också av gcc- kompilatoruppsättningen , och det finns flera oberoende implementeringar. En andra version av språket håller på att utvecklas.
Namnet på språket som Google valt är nästan detsamma som namnet på programmeringsspråket Go! , skapad av F. Gee. McCabe och C. L. Clark 2003 [11] . Namnet diskuteras på Go-sidan [11] .
På språkets hemsida och i allmänhet i Internetpublikationer används ofta det alternativa namnet "golang".
Go-språket utvecklades som ett programmeringsspråk för att skapa högeffektiva program som körs på moderna distribuerade system och flerkärniga processorer. Det kan ses som ett försök att skapa en ersättning för C- och C++-språken , med hänsyn till de förändrade datorteknikerna och den samlade erfarenheten av utvecklingen av stora system [12] . Med orden av Rob Pike [12] , "Go designades för att lösa verkliga mjukvaruutvecklingsproblem hos Google." Han listar följande som huvudproblem:
Huvudkraven för språket var [13] :
Go skapades med förväntningen att program på den skulle översättas till objektkod och exekveras direkt utan att kräva en virtuell maskin , så ett av kriterierna för att välja arkitektoniska lösningar var möjligheten att säkerställa snabb kompilering till effektiv objektkod och frånvaron av överdriven krav på dynamiskt stöd.
Resultatet blev ett språk "som inte var ett genombrott, men som ändå var ett utmärkt verktyg för utveckling av stora mjukvaruprojekt" [12] .
Även om en tolk finns tillgänglig för Go finns det praktiskt taget inget stort behov av det, eftersom kompileringshastigheten är tillräckligt snabb för att möjliggöra interaktiv utveckling.
Huvuddragen i Go-språket [9] :
Go innehåller inte många av de populära syntaktiska funktionerna som finns tillgängliga i andra moderna applikationsprogrammeringsspråk. I många fall är detta orsakat av ett medvetet beslut av utvecklarna. Korta motiveringar för de valda designbesluten finns i "Frequently Asked Questions" [9] om språket, mer detaljerat - i artiklarna och diskussionerna publicerade på språkets webbplats, med tanke på olika designalternativ. Särskilt:
Syntaxen för Go-språket liknar den för C- språket , med element lånade från Oberon och skriptspråk .
Go är ett skiftlägeskänsligt språk med fullt Unicode-stöd för strängar och identifierare.
En identifierare kan traditionellt vara vilken som helst icke-tom sekvens av bokstäver, siffror och ett understreck som börjar med en bokstav och inte matchar något av Go-nyckelorden. "Bokstäver" hänvisar till alla Unicode-tecken som faller inom kategorierna "Lu" (versaler), "Ll" (gemener), "Lt" (versaler), "Lm" (modifierande bokstäver) eller "Lo" ( andra bokstäver), under "siffror" - alla tecken från kategorin "Nd" (siffror, decimalsiffror). Inget hindrar alltså att använda kyrilliska i identifierare, till exempel.
Identifierare som endast skiljer sig i skiftläge är distinkta. Språket har ett antal konventioner för användning av stora och små bokstäver. I synnerhet används endast små bokstäver i paketnamn. Alla Go-sökord skrivs med små bokstäver. Variabler som börjar med stora bokstäver kan exporteras (offentliga) och de som börjar med gemener kan inte exporteras (privata).
Strängliteraler kan använda alla Unicode-tecken utan begränsningar. Strängar representeras som sekvenser av UTF-8- tecken .
Alla Go-program innehåller ett eller flera paket. Paketet som en källkodsfil tillhör ges av paketbeskrivningen i början av filen. Paketnamn har samma begränsningar som identifierare, men kan bara innehålla små bokstäver. Goroutinepaketsystemet har en trädstruktur som liknar ett katalogträd. Alla globala objekt (variabler, typer, gränssnitt, funktioner, metoder, element i strukturer och gränssnitt) är tillgängliga utan begränsningar i paketet där de deklareras. Globala objekt vars namn börjar med stor bokstav kan exporteras.
För att använda objekt som exporterats av ett annat paket i en Go-kodfil måste paketet importeras med hjälp av import.
paketets huvud /* Import */ import ( "fmt" // Standardpaket för formaterad utdata "database/sql" // Importera kapslat paket w "os" // Importera med alias . "math" // Importera utan kvalifikation vid användning av _ "gopkg.in/goracle.v2" // Paketet har inga explicita referenser i koden ) func main () { för _ , arg := intervall w . Args { // Åtkomst till Args-arrayen som deklareras i "os"-paketet via fmt- aliaset . Println ( arg ) // Anropar Println()-funktionen som deklarerats i paketet "fmt" med paketnamnet } var db * sql . db = sql . Open ( drivrutin , dataSource ) // Namn från det kapslade paketet kvalificeras // endast av namnet på själva paketet (sql) x := Sin ( 1.0 ) // call math.Sin() - kvalificering av paketnamnet math behövs inte // eftersom det importerades utan ett namn // Det finns ingen referens till paketet "goracle.v2" i koden, men det kommer att importeras. }Den listar sökvägarna till importerade paket från src-katalogen i källträdet, vars position ges av miljövariabeln GOPATH, medan för standardpaket, bara ange namnet. En sträng som identifierar ett paket kan föregås av ett alias, i vilket fall den kommer att användas i kod istället för paketnamnet. Importerade objekt är tillgängliga i filen som importerar dem med en fullständig kvalifikation som " пакет.Объект". Om ett paket importeras med en punkt istället för ett alias, kommer alla namn som det exporterar att vara tillgängliga utan kvalifikationer. Denna funktion används av vissa systemverktyg, men dess användning av programmeraren rekommenderas inte, eftersom explicita kvalifikationer ger skydd mot namnkollisioner och "omärkliga" ändringar i kodbeteende. Det är inte möjligt att importera två paket som exporterar samma namn utan kvalifikationer.
Importen av paket i Go är noggrant kontrollerad: om ett paket importeras av en modul, måste minst ett namn som exporteras av det paketet användas i koden för den modulen. Go-kompilatorn behandlar import av ett oanvänt paket som ett fel; en sådan lösning tvingar utvecklaren att ständigt hålla importlistorna uppdaterade. Detta skapar inga svårigheter, eftersom Go programmeringsstödverktyg (redigerare, IDE) vanligtvis tillhandahåller automatisk kontroll och uppdatering av importlistor.
När ett paket innehåller kod som endast används genom introspektion , finns det ett problem: import av ett sådant paket är nödvändigt för att inkludera det i programmet, men kommer inte att tillåtas av kompilatorn, eftersom det inte är direkt åtkomligt. _Anonym import tillhandahålls för sådana fall: " " (enkelt understreck) anges som ett alias ; ett paket som importeras på detta sätt kommer att kompileras och inkluderas i programmet om det inte uttryckligen hänvisas till i koden. Ett sådant paket kan dock inte användas explicit; detta förhindrar importkontroll från att förbigås genom att importera alla paket som anonyma.
Ett körbart Go-program måste innehålla ett paket med namnet main, som måste innehålla en funktion main()utan parametrar och ett returvärde. Funktionen main.main()är "programmets kropp" - dess kod körs när programmet startar. Vilket paket som helst kan innehålla en funktion init() - det kommer att köras när programmet laddas, innan det börjar köras, innan någon funktion i detta paket anropas och i något paket som importerar detta. Huvudpaketet initieras alltid sist, och alla initieringar görs innan funktionen börjar köras main.main().
Go-paketeringssystemet designades med antagandet att hela utvecklingsekosystemet existerar som ett enda filträd som innehåller uppdaterade versioner av alla paket, och när nya versioner dyker upp är det helt omkompilerat. För applikationsprogrammering med hjälp av tredjepartsbibliotek är detta en ganska stark begränsning. I verkligheten finns det ofta begränsningar för versionerna av paket som används av en eller annan kod, såväl som situationer när olika versioner (grenar) av ett projekt använder olika versioner av bibliotekspaket.
Sedan version 1.11 har Go stöd för så kallade moduler . En modul är ett speciellt beskrivet paket som innehåller information om dess version. När en modul importeras är versionen som användes fixad. Detta tillåter byggsystemet att kontrollera om alla beroenden är uppfyllda, automatiskt uppdatera importerade moduler när författaren gör kompatibla ändringar i dem och blockera uppdateringar av icke-bakåtkompatibla versioner. Moduler är tänkta att vara en lösning (eller en mycket enklare lösning) på problemet med beroendehantering.
Go använder båda typerna av kommentarer i C-stil: infogade kommentarer (som börjar med // ...) och blockkommentarer (/* ... */). En radkommentar behandlas av kompilatorn som en nyrad. Block, beläget på en linje - som ett mellanslag, på flera linjer - som en nylinje.
Semikolonet i Go används som en obligatorisk separator i vissa operationer (om, för, switch). Formellt bör det också avsluta varje kommando, men i praktiken finns det inget behov av att sätta ett sådant semikolon i slutet av raden, eftersom kompilatorn själv lägger till semikolon i slutet av varje rad, exklusive tomma tecken, i slutet av raden. identifierare, tal, en bokstavlig bokstav, en sträng, brytningen, fortsätt, fallthrough, returnera nyckelord, ett inkrement- eller minskningskommando (++ eller --), eller en avslutande parentes, fyrkant eller krulklammer (ett viktigt undantag är att ett kommatecken ingår inte i listan ovan). Två saker följer av detta:
Språket innehåller en ganska standarduppsättning av enkla inbyggda datatyper: heltal, flyttal, tecken, strängar, booleaner och några specialtyper.
HeltalDet finns 11 heltalstyper:
Skaparna av språket rekommenderar att man endast använder standardtypen för att arbeta med siffror i programmet int. Typer med fasta storlekar är utformade för att fungera med data som tas emot från eller skickas till externa källor, när det är viktigt för kodens korrekthet att specificera en specifik storlek av typen. Typerna är synonymer byteoch runeär designade för att fungera med binära data respektive symboler. Typen uintptrbehövs endast för interaktion med extern kod, till exempel i C.
FlyttalFlyttalsnummer representeras av två typer, float32och float64. Deras storlek är 32 respektive 64 bitar, implementeringen överensstämmer med IEEE 754- standarden . Värdeintervallet kan erhållas från standardpaketet math.
Numeriska typer med obegränsad precisionGo-standardbiblioteket innehåller också paketet big, som tillhandahåller tre typer med obegränsad precision: big.Int, big.Ratoch big.Float, som representerar heltal, rational respektive flyttal; storleken på dessa nummer kan vara vad som helst och begränsas endast av mängden tillgängligt minne. Eftersom operatörer i Go inte är överbelastade, implementeras beräkningsoperationer på tal med obegränsad precision som vanliga metoder. Prestandan för beräkningar med stora siffror är naturligtvis betydligt sämre än de inbyggda numeriska typerna, men när man löser vissa typer av beräkningsproblem bigkan det vara att föredra att använda ett paket framför att manuellt optimera en matematisk algoritm.
Komplexa talSpråket har också två inbyggda typer för komplexa tal, complex64och complex128. Varje värde av dessa typer innehåller ett par verkliga och imaginära delar med typer, respektive, float32och float64. Du kan skapa ett värde av en komplex typ i kod på ett av två sätt: antingen genom att använda en inbyggd funktion complex()eller genom att använda en imaginär bokstavlig i ett uttryck. Du kan få de verkliga och imaginära delarna av ett komplext tal med hjälp av funktionerna real()och imag().
var x komplex128 = komplex ( 1 , 2 ) // 1 + 2i y := 3 + 4i // 3 + 4i, 4 är ett tal följt av ett i-suffix // är en imaginär fmt- literal . Println ( x * y ) // skriver ut "(-5+10i)" fmt . Println ( real ( x * y )) // skriver ut "-5" fmt . Println ( bild ( x * y )) // skriver ut "10" Booleska värdenDen booleska typen boolär ganska vanlig - den inkluderar de fördefinierade värdena trueoch falsebetecknar sant respektive falskt. Till skillnad från C är booleaner i Go inte numeriska och kan inte direkt omvandlas till siffror.
SträngarSträngtypsvärden stringär oföränderliga byte-arrayer som innehåller UTF-8. Detta orsakar ett antal specifika egenskaper hos strängar (till exempel i det allmänna fallet är längden på en sträng inte lika med längden på den matris som representerar den, dvs. antalet tecken som finns i den är inte lika med antalet av byte i motsvarande array). För de flesta applikationer som bearbetar hela strängar är denna specificitet inte viktig, men i de fall då programmet direkt måste bearbeta specifika runor (Unicode-tecken) krävs ett paket som unicode/utf8innehåller hjälpverktyg för att arbeta med Unicode-strängar.
För alla datatyper, inklusive inbyggda, kan nya analoga typer deklareras som upprepar alla egenskaper hos originalen, men som är inkompatibla med dem. Dessa nya typer kan också valfritt deklarera metoder. Användardefinierade datatyper i Go är pekare (deklareras med symbolen *), arrayer (deklareras med hakparenteser), strukturer ( struct), funktioner ( func), gränssnitt ( interface), mappningar ( map) och kanaler ( chan). Deklarationerna av dessa typer anger typerna och eventuellt identifierarna för deras element. Nya typer deklareras med nyckelordet type:
typ PostString string // Skriv "string", liknande inbyggd typ StringArray [] sträng // Arraytyp med strängtypselement typ Person struct { // Struct type name string // fält för standardsträngtypen post PostString // fält för den tidigare deklarerade anpassade strängtypen bdate time . Tid // fält av typen Tid, importerat från paketets tid edate time . Tidschef * Person // pekarfält infer [ ]( * Person ) // matrisfält } typ InOutString chan sträng // kanaltyp för att skicka strängar typ CompareFunc func ( a , b gränssnitt {}) int // funktionstyp.Sedan version Go 1.9 är deklaration av typalias (alias) också tillgänglig:
typ TitleString = sträng // "TitleString" är ett alias för den inbyggda typen strängtyp Integer = int64 // "Integer" är ett alias för den inbyggda 64-bitars heltalstypenEtt alias kan deklareras för antingen en systemtyp eller vilken användardefinierad typ som helst. Den grundläggande skillnaden mellan alias och vanliga typdeklarationer är att deklarationen skapar en ny typ som inte är kompatibel med originalet, även om inga ändringar läggs till originaltypen i deklarationen. Ett alias är bara ett annat namn för samma typ, vilket betyder att aliaset och originaltypen är helt utbytbara.
Strukturfält kan ha taggar i beskrivningen - godtyckliga sekvenser av tecken omslutna av bakre citattecken:
// Struktur med fälttaggar typ XMLInvoices struct { XMLName xml . Namn `xml:"INVOICES"` Version int `xml:"version,attr"` Faktura [] * XMLInvoice `xml:"INVOICE"` }Taggar ignoreras av kompilatorn, men information om dem placeras i koden och kan läsas med funktionerna i paketet som reflectingår i standardbiblioteket. Typiskt används taggar för att tillhandahålla typrangering för att lagra och återställa data på externa media eller interagera med externa system som tar emot eller sänder data i sina egna format. Exemplet ovan använder taggar som bearbetas av standardbiblioteket för att läsa och skriva data i XML-format.
Syntaxen för att deklarera variabler är huvudsakligen löst i Pascals anda: deklarationen börjar med nyckelordet var, följt av variabelnamnet genom separatorn, sedan, genom separatorn, dess typ.
gå | C++ |
---|---|
var v1 int const v2 sträng var v3 [ 10 ] int var v4 [ ] int var v5 struct { f int } var v6 * int /* pekarritmetik stöds inte */ var v7 map [ sträng ] int var v8 func ( a int ) int | int v1 ; const std :: stringv2 ; _ /* handla om */ intv3 [ 10 ] ; int * v4 ; /* handla om */ struct { int f ; } v5 ; int * v6 ; std :: unordered_map v7 ; /* handla om */ int ( * v8 )( int a ); |
Variabel deklaration kan kombineras 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 } = { f int } 50 } var v6 * int = & v1 var v7 map [ string ] int = { "one" : 1 , "two" : 2 , "three" : 3 } var v8 func ( a int ) int = func ( a int ) int { returnera a + 1 }Om en variabel inte explicit initieras när den deklareras , initieras den automatiskt till "nullvärde" för den givna typen. Nullvärdet för alla numeriska typer är 0, för en typ string är det den tomma strängen, för pekare är det nil. Strukturer initieras som standard med uppsättningar av nollvärden för vart och ett av fälten som ingår i dem, arrayelement initieras med nollvärden av den typ som anges i arraydefinitionen.
Annonser kan grupperas:
var ( i int m flyta )Go-språket stöder också automatisk typinferens . Om en variabel initieras när den deklareras kan dess typ utelämnas - typen av uttryck som tilldelas den blir variabelns typ. För bokstaver (siffror, tecken, strängar) definierar språkstandarden specifika inbyggda typer som varje sådant värde tillhör. För att initiera en variabel av en annan typ måste en explicit typkonvertering tillämpas på den bokstavliga.
var p1 = 20 // p1 int - heltalsliteralen 20 är av typen int. var p2 = uint ( 20 ) // p2 uint - värde cast explicit till uint. var v1 = & p1 // v1 *int är en pekare till p1, för vilken int-typen härleds. var v2 = & p2 // v2 *uint är en pekare till p2, som uttryckligen initieras som ett heltal utan tecken.För lokala variabler finns det en förkortad form av deklaration i kombination med initiering med typinferens:
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 använder symbolen som en tilldelningsoperator =:
a = b // Sätt variabel a till bSom nämnts ovan finns det en form för att definiera en variabel med automatisk typinferens kombinerad med initiering, som utåt liknar tilldelning i Pascal :
v1 := v2 // liknande var v1 = v2Go-kompilatorn håller strikt reda på definitioner och tilldelningar och skiljer den ena från den andra. Eftersom omdefiniering av en variabel med samma namn är förbjuden inom ett område, inom ett kodblock, kan en variabel endast visas till vänster om tecknet :=en gång:
a := 10 // Deklarera och initiera en heltalsvariabel a. b := 20 // Deklarera och initiera en heltalsvariabel b. ... a := b // FEL! Försök att omdefiniera a.Go tillåter att flera uppdrag kan utföras parallellt:
i , j = j , i // Byt i- och j-värden.I det här fallet måste antalet variabler till vänster om tilldelningstecknet exakt matcha antalet uttryck till höger om tilldelningstecknet.
Parallelltilldelning är också möjligt när du använder :=. Dess egenhet är att bland de variabler som listas till vänster om tecknet :=kan det finnas redan existerande. I det här fallet kommer nya variabler att skapas, befintliga kommer att återanvändas. Denna syntax används ofta för felhantering:
x , err := SomeFunction () // Funktionen returnerar två värden (se nedan), // två variabler deklareras och initieras. if ( fel != noll ) { returnera noll } y , err := SomeOtherFunction () // Endast y deklareras här, err tilldelas helt enkelt ett värde.På den sista raden i exemplet tilldelas det första värdet som returneras av funktionen till den nya variabeln y, det andra till den redan existerande variabeln err, som används genom hela koden för att placera det sista felet som returneras av de anropade funktionerna. Om inte denna funktion hos operatorn :=, skulle man i det andra fallet behöva deklarera en ny variabel (till exempel err2) eller separat deklarera yoch sedan använda den vanliga parallella tilldelningen.
Go implementerar "copy-on-assignment" semantik, vilket innebär att en uppgift resulterar i att man gör en kopia av värdet på den ursprungliga variabeln och placerar den kopian i en annan variabel, varefter värdena på variablerna är olika och ändrar en av de förändrar inte den andra. Detta gäller dock endast för inbyggda skalära typer, strukturer och arrayer med en given längd (det vill säga typer vars värden är allokerade på stacken). Matriser av obestämd längd och mappningar allokeras på heapen , variabler av dessa typer innehåller faktiskt referenser till objekt, när de tilldelas kopieras endast referensen, men inte själva objektet. Ibland kan detta leda till oväntade effekter. Tänk på två nästan identiska exempel:
typ vektor [ 2 ] float64 // Längden på arrayen är explicit inställd v1 := vektor { 10 , 15.5 } // Initialisering - v1 innehåller själva arrayen v2 := v1 // Array v1 kopieras till array v2 v2 [ 0 ] = 25.3 // Ändrade bara v2 fmt- arrayen . Println ( v1 ) // Skriver ut "[10 15.5]" - den ursprungliga arrayen har inte ändrats. fmt . Println ( v2 ) // Skriver ut "[25.3 15.5]"vectorHär definieras typen som en matris med två tal. Att tilldela sådana arrayer fungerar på samma sätt som att tilldela nummer och strukturer.
Och i följande exempel skiljer sig koden med exakt ett tecken: typen vectordefinieras som en array med en obestämd storlek. Men den här koden beter sig helt annorlunda:
typ vektor [] float64 // Array med odefinierad längd v1 := vektor { 10 , 15.5 } // Initialisering - v1 innehåller arrayreferens v2 := v1 // Arrayreferens kopieras från v1 till v2 v2 [ 0 ] = 25.3 / / Kan ses som att endast ändra v2 fmt- arrayen . Println ( v1 ) // Skriver ut "[25.3 15.5]" - den ursprungliga arrayen har ÄNDRATS! fmt . Println ( v2 ) // Skriver ut "[25.3 15.5]"På samma sätt som i det andra exemplet beter sig mappningar och gränssnitt. Dessutom, om strukturen har ett fält av en referens- eller gränssnittstyp, eller ett fält är en dimensionslös array eller mappning, då kommer endast referensen också att kopieras när man tilldelar en sådan struktur, det vill säga fälten för olika strukturer börjar att peka på samma objekt i minnet.
För att undvika denna effekt måste du uttryckligen använda en systemfunktion copy()som garanterar skapandet av en andra instans av objektet.
deklareras så här:
func f ( i , j , k int , s , t sträng ) sträng { }Typerna av sådana värden är omgivna inom parentes:
func f ( a , b int ) ( int , sträng ) { return a + b , "addition" }Funktionsresultat kan också namnges:
func incTwo ( a , b int ) ( c , d int ) { c = a + 1 d = b + 1 retur }Namngivna resultat anses deklarerade omedelbart efter funktionshuvudet med noll initiala värden. Return-satsen i en sådan funktion kan användas utan parametrar, i vilket fall, efter att ha återvänt från funktionen, kommer resultaten att ha de värden som tilldelades dem under dess exekvering. Så i exemplet ovan kommer funktionen att returnera ett par heltalsvärden, ett större än dess parametrar.
Flera värden som returneras av funktioner tilldelas variabler genom att lista dem separerade med kommatecken, medan antalet variabler som resultatet av funktionsanropet tilldelas måste exakt matcha antalet värden som returneras av funktionen:
första , andra := incTwo ( 1 , 2 ) // första = 2, andra = 3 första := incTwo ( 1 , 2 ) // FEL - ingen variabel tilldelad det andra resultatetTill skillnad från Pascal och C, där att deklarera en lokal variabel utan att använda den senare, eller att förlora värdet på en lokal variabel (när värdet som tilldelats variabeln sedan inte läses någonstans) bara kan orsaka en kompilatorvarning, i Go anses denna situation vara ett språkfel och leder till omöjlighet att kompilera programmet. Detta innebär i synnerhet att programmeraren inte kan ignorera värdet (eller ett av värdena) som returneras av funktionen, helt enkelt genom att tilldela den till någon variabel och vägra att använda den vidare. Om det blir nödvändigt att ignorera ett av värdena som returneras av ett funktionsanrop, används en fördefinierad pseudovariabel med namnet "_" (ett understreck). Den kan anges var som helst där det ska finnas en variabel som tar ett värde. Motsvarande värde kommer inte att tilldelas någon variabel och kommer helt enkelt att gå förlorad. Meningen med ett sådant arkitektoniskt beslut är att i kompileringsstadiet identifiera en möjlig förlust av beräkningsresultat: en oavsiktlig utelämnande av värdebearbetning kommer att upptäckas av kompilatorn, och användningen av "_" pseudovariabeln kommer att indikera att programmeraren medvetet ignorerade resultaten. I följande exempel, om bara ett av de två värdena som returneras av incTwo-funktionen behövs, ska "_" anges istället för den andra variabeln:
första := incTwo ( 1 , 2 ) // INVALID first , _ := incTwo ( 1 , 2 ) // TRUE, andra resultatet används inteVariabeln "_" kan anges i uppdragslistan hur många gånger som helst. Alla funktionsresultat som matchar "_" kommer att ignoreras.
Det uppskjutna samtalet ersätter flera syntaktiska funktioner samtidigt, i synnerhet undantagshanterare och garanterade slutförandeblock. Ett funktionsanrop som föregås av nyckelordet defer parametriseras vid den punkt i programmet där det placeras och exekveras omedelbart innan programmet lämnar scopet där det deklarerades, oavsett hur och av vilken anledning denna utgång sker. Om en enskild funktion innehåller flera defer-deklarationer, exekveras motsvarande anrop sekventiellt efter att funktionen avslutas, i omvänd ordning. Nedan är ett exempel på hur man använder defer som ett garanterat slutförandeblock [15] :
// Funktion som kopierar filen func CopyFile ( dstName , srcName string ) ( skriven int64 , err error ) { src , err := os . Öppna ( srcName ) // Öppna källfilen om err != noll { // Kontrollera retur // Om det misslyckas, returnera med ett fel } // Om du kom hit, öppnades källfilen framgångsrikt defer src . Stäng () // Fördröjt samtal: src.Close() kommer att anropas när CopyFile är klar dst , err := os . Skapa ( dstName ) // Öppna destinationsfilen om err != noll { // Kontrollera och returnera vid felretur } defer dst . Close () // Fördröjt samtal: dst.Close() kommer att anropas när CopyFile är klar returnio . _ Kopiera ( dst , src ) // Kopiera data och returnera från funktion // Efter alla operationer kommer att anropas: först dst.Close(), sedan src.Close() }Till skillnad från de flesta språk med C-liknande syntax, har Go inte parenteser för villkorskonstruktioner for, if, switch:
om i >= 0 && i < len ( arr ) { println ( arr [ i ]) } ... för i := 0 ; i < 10 ; i ++ { } }Go använder en loopkonstruktion för att organisera alla typer av loopar for.
för i < 10 { // loop med förutsättning, liknande medan i C } för i := 0 ; i < 10 ; i ++ { // slinga med en räknare, liknande för i C } för { // infinite loop // Att lämna loopen måste hanteras manuellt, // vanligtvis med retur eller break } för { // loop med postcondition ... // loop body if i >= 10 { // exit condition break } } för i , v := range arr { // slinga genom samlingen (array, segment, display) arr // i - index (eller nyckel) för det aktuella elementet // v - kopia av värdet på det aktuella arrayelementet } för i := range arr { // loop genom samlingen används endast indexet } för _ , v := range arr { // loop through collection, använd endast elementvärden } för område arr { // Slinga genom samlingen utan variabler (samlingen används // endast som en iterationsräknare). } för v := range c { // slinga genom kanalen: // v kommer att läsa värden från kanal c, // tills kanalen stängs av en samtidig // goroutine }Syntaxen för flervalsoperatören switchhar ett antal funktioner. Först och främst, till skillnad från C, krävs inte användningen av operatören break: efter att den valda grenen har bearbetats avslutas exekveringen av operatören. Om du tvärtom vill att nästa gren ska fortsätta bearbeta efter den valda grenen, måste du använda operatorn fallthrough:
switch value { fall 1 : fmt . Println ( "One" ) fallthrough // Därefter kommer "case 0:"-grenen att exekveras case 0 : fmt . println ( "Noll" ) }Här, när value==1två rader kommer att visas, "En" och "Noll".
Valuttrycket och, följaktligen, alternativen i switch-satsen kan vara av vilken typ som helst, det är möjligt att räkna upp flera alternativ i en gren:
byta tecken [ kod ]. kategori { case "Lu" , "Ll" , "Lt" , "Lm" , "Lo" : ... case "Nd" : ... default : ... }Frånvaron av ett valuttryck är tillåtet, i vilket fall logiska villkor måste skrivas i alternativen. Den första grenen exekveras, vars villkor är sant:
switch { case '0' <= c && c <= '9' : return c - '0' case 'a' <= c && c <= 'f' : return c - 'a' + 10 case 'A' <= c && c <= 'F' : returnera c - 'A' + 10 }En viktig detalj: om en av grenarna med villkoret slutar med operatören fallthrough, kommer nästa gren att behandlas efter denna gren, oavsett om dess villkor är uppfyllt . Om du vill att nästa gren ska bearbetas endast om dess villkor är uppfyllt, måste du använda sekventiella konstruktioner if.
Go-språket stöder inte den strukturerade syntaxen för undantagshantering som är typisk för de flesta moderna språk , vilket innebär att man kastar undantag med ett speciellt kommando (vanligtvis throweller raise) och hanterar dem i ett block try-catch. Istället rekommenderas det att använda felretur som ett av funktionens resultat (vilket är praktiskt eftersom en funktion i Go kan returnera mer än ett värde):
Många kritiker av språket anser att denna ideologi är värre än undantagshantering, eftersom många kontroller rör ihop koden och inte tillåter all felhantering att koncentreras i block catch. Språkets skapare anser inte att detta är ett allvarligt problem. Ett antal felhanteringsmönster i Go beskrivs (se t.ex. Rob Pikes artikel på den officiella Go-bloggen , rysk översättning ), vilket kan minska mängden felhanteringskod.
När fatala fel uppstår som gör ytterligare programkörning omöjlig (till exempel division med noll eller åtkomst av arraygränser), uppstår ett paniktillstånd , vilket som standard leder till att programmet kraschar med ett felmeddelande och en anropsstackspårning. Panik kan fångas och hanteras med den uppskjutna exekveringskonstruktionen som deferbeskrivs ovan. Funktionsanropet som anges i defergörs innan det aktuella omfånget lämnas, inklusive vid panik. Inuti funktionen som kallas in deferkan du anropa en standardfunktion recover() - den stoppar systembearbetningen av en panik och returnerar dess orsak i form av ett objekt errorsom kan behandlas som ett normalt fel. Men programmeraren kan också återuppta en tidigare fången panik genom att anropa standarden panic(err error).
// Programmet utför en heltalsdelning // av sin första parameter med den andra // och skriver ut resultatet. func main () { defer func () { err := recover () if v , ok := err .( error ); ok { // Hanterar en panik som motsvarar gränssnittsfel fmt . Fprintf ( os . Stderr , "Fel %v \"%s\"\n" , err , v . Error ()) } else if err != noll { panic ( err ) // Hantera oväntade fel - höj på nytt panik. } }() a , err := strconv . ParseInt ( os . Args [ 1 ], 10 , 64 ) om err != noll { panic ( err ) } b , err := strconv . ParseInt ( os . Args [ 2 ], 10 , 64 ) om err != noll { panic ( err ) } fmt . fprintf ( os . Stdout , "%d / %d = %d\n" , a , b , a / b ) }I exemplet ovan kan fel uppstå när programargumenten konverteras till heltal av funktionen strconv.ParseInt(). Det är också möjligt att få panik när man kommer åt os.Args-arrayen med ett otillräckligt antal argument, eller när man dividerar med noll om den andra parametern är noll. För alla felsituationer genereras en panik, som bearbetas i anropet defer:
> dividera 10 5 10/5 = 2 > dividera 10 0 Error runtime.errorString "runtime error: heltal dividera med noll" > dividera 10,5 2 Fel *strconv.NumError "strconv.ParseInt: tolkar "10.5": ogiltig syntax" > dividera 10 Fel runtime.errorString "runtime error: index out of range"En panik kan inte utlösas i en parallellkörande goroutin (se nedan) utan hanteras i en annan. Det rekommenderas inte heller att "passera" panik över en paketgräns.
Gos trådningsmodell ärvdes från Active Oberon -språket baserat på Tony Hoares CSP med hjälp av idéer från Occam- och Limbo -språken [9] , men funktioner som pi-calculus och kanalisering finns också.
Go låter dig skapa en ny tråd för programkörning med hjälp av nyckelordet go , som kör en anonym eller namngiven funktion i en nyskapad goroutine (Gos term för coroutines ). Alla goroutiner inom samma process använder ett gemensamt adressutrymme, som körs på OS-trådar , men utan hård bindning till den senare, vilket gör att en löpande goroutin kan lämna en tråd med en blockerad goroutin (väntar till exempel på att skicka eller ta emot ett meddelande från en kanal) och fortsätt vidare. Runtime-biblioteket inkluderar en multiplexer för att dela det tillgängliga antalet systemkärnor mellan goroutiner. Det är möjligt att begränsa det maximala antalet fysiska processorkärnor som programmet kommer att köras på. Go-runtime-bibliotekets självstöd för goroutiner gör det enkelt att använda ett stort antal goroutiner i program, vilket vida överskrider gränsen för antalet trådar som stöds av systemet.
func server ( i int ) { för { print ( i ) time . Sleep ( 10 ) } } go server ( 1 ) go server ( 2 )Förslutningar kan användas i ett go -uttryck .
var g int go func ( i int ) { s := 0 för j := 0 ; j < i ; j ++ { s += j } g = s }( 1000 )För kommunikation mellan goroutiner används kanaler (den inbyggda typen chan ), genom vilka vilket värde som helst kan skickas. En kanal skapas av den inbyggda funktionen make(), som överförs kanalens typ och (valfritt) volym. Som standard är kanalvolymen noll. Sådana kanaler är obuffrade . Du kan ställa in valfri positiv heltalsvolym för kanalen, sedan skapas en buffrad kanal.
En obuffrad pipe synkroniserar läsaren och skribentens trådar som använder den. När en författartråd skriver något till ett rör, pausar den och väntar tills värdet har lästs. När en läsartråd försöker läsa något från en pipe som redan har skrivits till, läser den värdet och båda trådarna kan fortsätta att köras. Om inget värde ännu har skrivits till kanalen pausar läsartråden och väntar på att någon ska skriva till kanalen. Det vill säga, obuffrade pipes i Go beter sig på samma sätt som pipes i Occam eller rendezvous- mekanismen på Ada-språket .
En buffrad kanal har en värdebuffert vars storlek är lika med kanalens storlek. När man skriver till ett sådant rör placeras värdet i rörets buffert, och skrivtråden fortsätter utan paus, om inte rörets buffert är full vid skrivningstillfället. Om bufferten är full avbryts skrivtråden tills minst ett värde har lästs från kanalen. Läsartråden läser också ett värde från ett buffrat rör utan att pausa om det finns olästa värden i rörets buffert; om kanalbufferten är tom pausar tråden och väntar tills någon annan tråd skriver ett värde till den.
När den är klar kan kanalen stängas med den inbyggda funktionen close(). Ett försök att skriva till en privat kanal resulterar i panik, läsning från en privat kanal sker alltid utan paus och läser standardvärdet. Om kanalen är buffrad och vid tidpunkten för stängning innehåller N tidigare skrivna värden i bufferten, kommer de första N läsoperationerna att utföras som om kanalen fortfarande var öppen och läsa värdena från bufferten, och först efter det kommer läsningen från kanalen att returnera standardvärdena.
Operationen används för att skicka ett värde till och från en kanal <-. När du skriver till en kanal används den som en binär operator, vid läsning - som en unär operator:
in := make ( chan string , 0 ) // Skapa en obuffrad kanal in out := make ( chan int , 10 ) // Skapa en buffrad kanal ut ... in <- arg // Skriv ett värde till kanalen in ... r1 := <- ut // läser från kanalen ut ... r2 , ok := <- ut // läser med att kontrollera om kanalen är stängd om ok { // om ok == sant - kanalen är öppen ... } else { // om kanalen är stängd, gör något annat ... }Operationen att läsa från en kanal har två alternativ: utan kontroll och med kontroll för att stänga kanalen. Det första alternativet (läser r1 i exemplet ovan) läser helt enkelt nästa värde i variabeln; om kanalen är stängd kommer standardvärdet att läsas in i r1. Det andra alternativet (läser r2) läser, förutom värdet, ett booleskt värde - ok kanalstatusflaggan, vilket kommer att vara sant om data som placeras där av någon ström har lästs från kanalen, och falskt om kanalen är stängd och dess buffert är tom. Med denna operation kan läsartråden avgöra när ingångskanalen är stängd.
Avläsning från ett rör stöds också med konstruktionen för slingor för intervall:
// Funktionen startar parallellläsning från ingångskanalen i heltal och skriver // till utgångskanalen endast de heltal som är positiva. // Returnerar utgångskanalen. func positives ( in <- chan int64 ) <- chan int64 { out := make ( chan int64 ) go func () { // Slingan fortsätter tills kanal in stängs för nästa := range in { if next > 0 { ut <- nästa } } stäng ( ut ) }() returnera ut }Förutom CSP eller i kombination med kanalmekanismen, låter Go dig också använda den vanliga modellen för synkroniserad interaktion av trådar genom delat minne, med hjälp av typiska åtkomstsynkroniseringsverktyg som mutexes . Samtidigt varnar språkspecifikationen dock för alla försök till osynkroniserad interaktion av parallella trådar genom delat minne, eftersom kompilatorn i avsaknad av explicit synkronisering optimerar dataåtkomstkoden utan att ta hänsyn till möjligheten till samtidig åtkomst från olika trådar, vilket kan leda till oväntade fel. Till exempel, att skriva värden till globala variabler i en tråd kanske inte är synliga eller synliga i fel ordning från en parallell tråd.
Tänk till exempel på programmet nedan. Funktionskoden är main()skriven på antagandet att funktionen som lanseras i goroutinen setup()kommer att skapa en struktur av typen T, initiera den med strängen "hello, world", och sedan tilldela en referens till den initierade strukturen till den globala variabeln g. B main()startar en tom loop och väntar på att ett gvärde som inte är noll ska visas. Så snart den dyker upp, main()skrivs en sträng från strukturen som pekade gut, förutsatt att strukturen redan har initierats.
typ T struct { msg string } varg * T _ func setup () { t : = new ( T ) t . msg = "hej världen" g = t } func main () { go setup () för g == noll { // FUNKAR INTE!!! } print ( g . msg ) }I verkligheten är ett av två fel möjligt.
Det enda korrekta sättet att organisera dataöverföring genom delat minne är att använda bibliotekssynkroniseringsverktyg, som garanterar att all data som skrivits av en av de synkroniserade strömmarna före synkroniseringspunkten garanteras vara tillgänglig i en annan synkroniserad ström efter synkroniseringspunkten.
En egenskap med multithreading i Go är att en goroutine inte identifieras på något sätt och inte är ett språkobjekt som man kan referera till när man anropar funktioner eller som kan placeras i en container. Följaktligen finns det inga sätt som tillåter dig att direkt påverka utförandet av en koroutin utifrån den, såsom att avbryta och sedan starta den, ändra prioritet, vänta på att en koroutin ska slutföras i en annan och att tvångsavbryta utförandet. Alla åtgärder på en goroutine (förutom att avsluta huvudprogrammet, som automatiskt avslutar alla goroutiner) kan endast göras genom rör eller andra synkroniseringsmekanismer. Följande är exempelkod som startar flera goroutiner och väntar på att de ska slutföras med hjälp av WaitGroup-synkroniseringsobjektet från synkroniseringssystempaketet. Detta objekt innehåller en räknare, initialt noll, som kan öka och minska, och en Wait()-metod, som gör att den aktuella tråden pausar och väntar tills räknaren återställs till noll.
func main () { var wg sync . WaitGroup // Skapa en väntegrupp. Räknarens initiala värde är 0 logger := log . New ( os . Stdout , "" , 0 ) // log.Logger är en trådsäker utdatatyp för _ , arg := range os . Args { // Gå igenom alla kommandoradsargument wg . Add ( 1 ) // Öka väntegruppsräknaren med en // Kör en goroutin för att bearbeta arg-parametern go func ( ordsträng ) { // Fördröjd minskning av väntegruppsräknaren med ett . // Händer när funktionen avslutas. skjuta upp wg . Klar () logger . Println ( prepareWord ( word )) // Utför bearbetning och skriv ut resultatet }( arg ) } wg . Vänta () // Vänta tills räknaren i väntegruppen wg är noll. }Här, innan skapandet av varje ny goroutin, ökas räknaren för objektet wg med en, och efter avslutad goroutine, minskas den med en. Som ett resultat, i slingan som startar bearbetningen av argument, kommer lika många enheter att läggas till i räknaren som det lanseras goroutiner. När slingan slutar kommer anropet av wg.Wait() att göra att huvudprogrammet pausas. När var och en av goroutinerna slutförs minskar den wg-räknaren med ett, så huvudprogrammets väntan kommer att sluta när lika många gooutiner som det kördes har slutförts. Utan den sista raden skulle huvudprogrammet, efter att ha kört alla goroutiner, avslutas omedelbart och avbryta exekveringen av de som inte hade tid att köra.
Trots förekomsten av multithreading inbyggd i språket är inte alla standardspråkobjekt trådsäkra. Så standardkarttypen (visning) är inte trådsäker. Skaparna av språket förklarade detta beslut med effektivitetsöverväganden, eftersom att säkerställa säkerhet för alla sådana objekt skulle leda till ytterligare overhead, vilket är långt ifrån alltid nödvändigt (samma operationer med mappningar kan vara en del av större operationer som redan är synkroniserade av programmeraren , och då kommer den extra synkroniseringen bara att komplicera och sakta ner programmet). Från och med version 1.9 har synkroniseringsbibliotekspaketet, som innehåller stöd för parallell bearbetning, lagt till den trådsäkra typen sync.Map, som kan användas vid behov. Du kan också vara uppmärksam på den trådsäkra typen som används i exemplet för att visa resultat log.Logger; det används istället för standardpaketet fmt, vars funktioner (Printf, Println, och så vidare) inte är trådsäkra och skulle kräva ytterligare synkronisering.
Det finns inget speciellt nyckelord för att deklarera en klass i Go, men metoder kan definieras för vilken typ som helst, inklusive strukturer och bastyper som int , så i OOP-bemärkelsen är alla sådana typer klasser.
skriv newInt intMetoddefinitionssyntaxen är lånad från Oberon-2- språket och skiljer sig från den vanliga funktionsdefinitionen genom att efter nyckelordet func deklareras den så kallade "receiver" ( engelsk mottagare ) inom parentes , det vill säga det objekt för vilket metod kallas, och den typ, till vilken metoden hör. Medan mottagaren i traditionella objektspråk är underförstådd och har ett standardnamn (i C++ eller Java är det "detta", i ObjectPascal är det "själv", etc.), i Go specificeras det explicit och dess namn kan vara någon giltig Go-identifierare.
typ myType struct { i int } // Här är p mottagaren i metoder av typen myType. func ( p * myType ) get () int { retur p . i } func ( p * myType ) set ( i int ) { p . jag = jag }Det finns inget formellt arv av klasser (strukturer) i Go, men det finns en tekniskt nära inbäddningsmekanism . I beskrivningen av strukturen kan du använda det så kallade anonyma fältet - ett fält för vilket ett namn inte anges, utan endast en typ. Som ett resultat av en sådan beskrivning kommer alla element i den inbäddade strukturen att bli samma namngivna element i den inbäddade strukturen.
// Ny struct typ typ myType2 struct { myType // Anonymt fält ger inbäddning av typen myType. // Nu innehåller myType2 i-fältet och metoderna get() och set(int). k int }Till skillnad från klassisk nedärvning involverar inte inlining polymorft beteende (ett objekt i en inbäddningsklass kan inte fungera som ett objekt i en inbäddningsbar klass utan explicit typkonvertering).
Det är inte möjligt att uttryckligen deklarera metoder för en icke namngiven typ (syntaxen tillåter dig helt enkelt inte att ange typen av mottagare i metoden), men denna begränsning kan enkelt kringgås genom att infoga den namngivna typen med nödvändiga metoder.
Klasspolymorfism tillhandahålls i Go genom mekanismen för gränssnitt (liknande helt abstrakta klasser i C++ ). Ett gränssnitt beskrivs med nyckelordet gränssnitt; inuti (till skillnad från klasstypsdeklarationer) deklarerar beskrivningar metoderna som tillhandahålls av gränssnittet.
typ myInterface interface { get () int set ( i int ) }I Go finns det inget behov av att uttryckligen ange att en typ implementerar ett visst gränssnitt. Istället är regeln att varje typ som tillhandahåller metoder definierade i ett gränssnitt kan användas som en implementering av det gränssnittet. Typen som deklareras ovan myTypeimplementerar gränssnittet myInterface, även om detta inte uttryckligen anges någonstans, eftersom det innehåller metoder get()och set(), vars signaturer matchar de som beskrivs i myInterface.
Liksom klasser kan gränssnitt infogas:
typ mySecondInterface interface { myInterface // samma som explicit deklarera get() int; set(i int) change ( i int ) int }Här ärver mySecondInterface-gränssnittet myInterface-gränssnittet (det vill säga, det förklarar att det exponerar metoderna som ingår i myInterface) och deklarerar dessutom en inbyggd metod change().
Även om det i princip är möjligt att bygga in ett Go-program i en hierarki av gränssnitt, som görs i andra objektspråk, och till och med att simulera arv, anses detta som dålig praxis. Språket dikterar inte ett hierarkiskt, utan ett sammansättningssätt för systemet av klasser och gränssnitt. Strukturklasser med detta tillvägagångssätt kan i allmänhet förbli formellt oberoende, och gränssnitt kombineras inte till en enda hierarki, utan skapas för specifika applikationer, om nödvändigt, inbäddade befintliga. Den implicita implementeringen av gränssnitt i Go ger dessa mekanismer extrem flexibilitet och ett minimum av tekniska svårigheter vid användningen.
Detta förhållningssätt till arv är i linje med några praktiska trender inom modern programmering. Så i den berömda boken "gäng av fyra" ( Erich Gamma och andra) om designmönster , i synnerhet, står det skrivet:
Implementeringsberoende kan orsaka problem när man försöker återanvända en underklass. Om ens en aspekt av den äldre implementeringen är olämplig för den nya domänen, måste den överordnade klassen skrivas om eller ersättas med något mer lämpligt. Detta beroende begränsar flexibiliteten och återanvändbarheten. Problemet kan lösas genom att bara ärva från abstrakta klasser, eftersom de vanligtvis inte har någon eller minimal implementering.
Det finns inget koncept med en virtuell funktion i Go . Polymorfism tillhandahålls av gränssnitt. Om en variabel av en vanlig typ används för att anropa en metod, så är ett sådant anrop statiskt bundet, det vill säga metoden som definieras för denna speciella typ anropas alltid. Om metoden anropas för en variabel av typen "gränssnitt", är ett sådant anrop dynamiskt bundet, och vid tidpunkten för exekvering, den metodvariant som definieras för den typ av objekt som faktiskt tilldelats vid anropstillfället. variabel väljs för lansering.
Dynamiskt stöd för objektorienterad programmering för Go tillhandahålls av GOOP- projektet .
Möjligheten att introspektera vid körning, det vill säga åtkomst och bearbetning av värden av vilken typ som helst och dynamisk justering av de typer av data som bearbetas, implementeras i Go med hjälp av systempaketet reflect. Detta paket låter dig:
Paketet innehåller också reflectmånga hjälpverktyg för att utföra operationer beroende på programmets dynamiska tillstånd.
Minnesåtkomstfaciliteter på låg nivå är koncentrerade i systempaketet unsafe. Dess egenhet är att även om det ser ut som ett vanligt Go-paket, implementeras det faktiskt av kompilatorn själv. Paketet unsafeger tillgång till den interna representationen av data och till "riktiga" minnespekare. Den har funktioner:
Paketet tillhandahåller också en typ unsafe.Pointersom kan konverteras till vilken pekare som helst och kan konverteras till en pekare av vilken typ som helst, såväl som en standardtyp uintptr , ett osignerat heltalsvärde som är tillräckligt stort för att lagra en fullständig adress på den aktuella plattformen. Genom att konvertera pekaren till unsafe.Pointeroch sedan till uintptrkan du få adressen som ett heltal, på vilket du kan tillämpa aritmetiska operationer. Genom att sedan omvandla värdet tillbaka till unsafe.Pointeroch till en pekare till någon speciell typ kan du komma åt nästan var som helst i adressutrymmet på detta sätt.
De beskrivna omvandlingarna kan vara osäkra, så de rekommenderas att undvikas om möjligt. För det första finns det uppenbara problem förknippade med felaktig åtkomst till fel minnesområde. En mer subtil poäng är att trots användningen av paketet unsafefortsätter Go-objekt att hanteras av minneshanteraren och sophämtaren. Att konvertera en pekare till ett nummer gör att pekaren är utom kontroll, och programmeraren kan inte förvänta sig att en sådan konverterad pekare förblir relevant på obestämd tid. Om du till exempel försöker lagra en pekare till ett nytt typobjekt så Тhär:
pT := uintptr ( osäker . Pekare ( ny ( T ))) // FEL!kommer att göra att objektet skapas, pekaren till det konverteras till ett nummer (som kommer att tilldelas till pT). Den pThar dock en heltalstyp och anses inte av sopsamlaren vara en pekare till ett skapat objekt, så efter att operationen är klar kommer minneshanteringssystemet att betrakta detta objekt som oanvänt. Det vill säga att den kan tas bort av sopsamlaren, varefter den konverterade pekaren pTblir ogiltig. Detta kan hända när som helst, både omedelbart efter avslutad operation och efter många timmars programdrift, så att felet kommer att uttryckas i slumpmässiga programkrascher, vars orsak kommer att vara extremt svår att identifiera. Och när du använder en rörlig sopsamlare [* 1] kan pekaren som konverteras till ett nummer bli irrelevant även när objektet ännu inte har tagits bort från minnet.
Eftersom Go-specifikationen inte ger exakta indikationer på i vilken utsträckning en programmerare kan förvänta sig att hålla en pekare omvandlad till ett nummer uppdaterad, finns det en rekommendation: att hålla sådana omvandlingar till ett minimum och organisera dem så att konverteringen av den ursprungliga pekaren, dess ändringar och bakåtkonvertering finns inom en språkinstruktion, och när du anropar biblioteksfunktioner som returnerar en adress i form av uintptr, konvertera omedelbart resultatet till unsafe.Pointerför att bevara garantin att pekaren inte kommer att gå förlorad.
Paketet unsafeanvänds sällan direkt i applikationsprogrammering, men det används aktivt i paketen reflect, os, syscall, context, netoch några andra.
Det finns flera externa verktyg som tillhandahåller främmande funktionsgränssnitt (FFI) för Go-program. Cgo- verktyget kan användas för att interagera med extern C -kod (eller ha ett C-kompatibelt gränssnitt) . Det anropas automatiskt när kompilatorn bearbetar en korrekt skriven Go-modul, och säkerställer att ett tillfälligt Go-omslagspaket skapas som innehåller deklarationer av alla nödvändiga typer och funktioner. I C-funktionsanrop måste du ofta tillgripa paketfaciliteter , främst med hjälp av . Ett kraftfullare verktyg är SWIG [16] , som ger mer avancerade funktioner, såsom integration med C++- klasser . unsafeunsafe.Pointer
Go-standardbiblioteket stöder byggkonsolapplikationer och webbaserade serverapplikationer , men det finns inga standardverktyg för att bygga GUI:er i klientapplikationer. Denna lucka fylls av tredje parts wrappers för populära UI- ramverk som GTK+ och Qt , under Windows kan du använda WinAPI- grafiska verktyg genom att komma åt dem genom paketet syscall, men alla dessa metoder är ganska besvärliga. Det finns också flera utvecklingar av UI-ramverk i själva Go, men inget av dessa projekt har nått nivån av industriell tillämpbarhet. 2015 på GopherCon-konferensen i Denver , svarade en av skaparna av språket, Robert Grismer, på frågor, höll med om att Go behöver ett UI-paket, men noterade att ett sådant paket borde vara universellt, kraftfullt och multi-plattform, vilket gör dess utveckling lång och svår process. Frågan om att implementera ett klient-GUI i Go är fortfarande öppen.
På grund av språkets ungdom koncentreras kritiken främst till internetartiklar, recensioner och forum.
Mycket av kritiken mot språket fokuserar på dess brist på vissa populära egenskaper som tillhandahålls av andra språk. Bland dem [17] [18] [19] [20] :
Som nämnts ovan, bristen på ett antal funktioner tillgängliga på andra populära språk på utvecklarnas medvetna val, som tror att sådana funktioner antingen hindrar effektiv kompilering eller provocerar programmeraren att göra misstag eller skapa ineffektiva eller "dåligt" när det gäller kodunderhåll, eller har andra oönskade biverkningar.
Kritiker påpekar att vissa funktioner i Go implementeras i termer av den enklaste eller mest effektiva implementeringen, men inte uppfyller " principen om minsta överraskning ": deras beteende skiljer sig från vad programmeraren skulle förvänta sig baserat på intuition och tidigare erfarenheter. Sådana funktioner kräver ökad uppmärksamhet av programmeraren, gör det svårt att lära sig och byter från andra språk.
Ofta kritiseras mekanismen för automatiska semikolon, på grund av vilka vissa former av skrivpåståenden, funktionsanrop och listor blir felaktiga. När de kommenterar detta beslut, noterar författarna till språket [9] att det tillsammans med närvaron av en kodformaterare i den officiella verktygslådan gofmtledde till fixeringen av en ganska stel standard för kodning i Go. Det är knappast möjligt att skapa en standard för att skriva kod som skulle passa alla; introduktionen i språket av en funktion som i sig sätter en sådan standard, förenar utseendet på program och eliminerar principlösa konflikter på grund av formatering, vilket är en positiv faktor för grupputveckling och underhåll av mjukvara.
Gos popularitet har vuxit de senaste åren: från 2014 till 2020 har Go stigit från 65:e plats till 11:e plats i TIOBE- rankingen, betygsvärdet för augusti 2020 är 1,43 %. Enligt resultaten från en dou.ua-undersökning [22] blev Go-språket 2018 det nionde i listan över de mest använda och det sjätte i listan över språk som utvecklare ger personlig preferens till.
Sedan den första offentliga releasen 2012 har användningen av språket växt stadigt. Listan över företag som använder språket i industriell utveckling som publiceras på Go-projektets webbplats innehåller flera dussin namn. Ett stort utbud av bibliotek för olika ändamål har samlats. Releasen av version 2.0 var planerad till 2019, men arbetet var försenat och pågår fortfarande under andra halvan av 2022. Ett antal nya funktioner förväntas dyka upp, inklusive generika och speciell syntax för att förenkla felhantering, vars frånvaro är ett av de vanligaste klagomålen från kritiker av språket .
Webbservern RoadRunner (Application Server) utvecklades i Golang , vilket gör att webbapplikationer kan uppnå en begäran-svarshastighet på 10-20 ms istället för de traditionella 200 ms. Denna webbtjänst är planerad att ingå i populära ramverk som Yii .
Tillsammans med C ++ används Golang för att utveckla mikrotjänster, som låter dig "ladda" flerprocessorplattformar med arbete. Du kan interagera med en mikrotjänst med REST , och PHP- språket är bra för detta.
Spiral Framework utvecklades med hjälp av PHP och Golang. [23]
Det finns bara en huvudversion av själva Go-språket, version 1. Versioner av Go-utvecklingsmiljön (kompilator, verktyg och standardbibliotek) är antingen tvåsiffriga ("<språkversion>.<huvudversion>") eller tresiffrig ("<språkversion>.< större utgåva>.<mindre utgåva>") till systemet. Utgivningen av en ny "tvåsiffrig" version avslutar automatiskt stödet för den tidigare "tvåsiffriga" versionen. "Tresiffriga" versioner släpps för att fixa rapporterade buggar och säkerhetsproblem; säkerhetskorrigeringar i sådana versioner kan påverka de två sista "tvåsiffriga" versionerna [24] .
Författarna förklarade [25] önskan att så långt som möjligt bevara bakåtkompatibiliteten inom huvudversionen av språket. Detta innebär att före lanseringen av Go 2 kommer nästan alla program som skapats i Go 1-miljön att kompileras korrekt i alla efterföljande versioner av Go 1.x och köras utan fel. Undantag är möjliga, men de är få. Binär kompatibilitet mellan utgåvor är dock inte garanterad, så ett program måste vara helt omkompilerat när man flyttar till en senare version av Go.
Sedan mars 2012, när Go 1 introducerades, har följande större versioner släppts:
Utvecklingsframsteg Sedan 2017 har förberedelser pågått för släppet av nästa grundläggande version av språket, som har symbolen "Go 2.0" [26] . Samlingen av kommentarer till den aktuella versionen och förslag till omvandlingar, samlad på projektets wikisida [27] . Inledningsvis antogs det att förberedelseprocessen skulle ta "ungefär två år", och några av de nya delarna av språket skulle inkluderas i nästa utgåvor av Go 1-versionen (naturligtvis bara de som inte bryter mot bakåtkompatibiliteten ). [26] Från och med april 2021 är version 2.0 ännu inte klar, några av de planerade ändringarna är i design- och implementeringsstadiet. Enligt de planer som skisseras i projektbloggen [28] kommer arbetet med genomförandet av de planerade förändringarna att fortsätta åtminstone till 2021. Föreslagna innovationer Bland de grundläggande innovationerna finns uttryckligen deklarerade konstanta värden, en ny felhanteringsmekanism och generiska programmeringsverktyg. Innovationsprojekt finns tillgängliga online. Den 28 augusti 2018 publicerades en video som tidigare presenterats på Gophercon 2018-konferensen på den officiella utvecklarbloggen , som visar utkast till versioner av den nya felhanteringsdesignen och mekanismen för generiska funktioner. Det finns också många mindre märkbara men mycket betydelsefulla förändringar planerade, [29] som att utöka reglerna för tillåtligheten av tecken för identifierare i icke-latinska alfabet, tillåta skiftoperationer för tecken med heltal, använda understrecket som en separator av grupper om tusentals i tal, binära bokstaver . De flesta av dem är redan implementerade och tillgängliga i de senaste versionerna av Go 1. Fel vid bearbetning Flera alternativ för att ändra felhanteringsmekanismen övervägdes, i synnerhet en design med en separat felhanterare (" Error Handling - Draft Design "). Den sista varianten för juli 2019 beskrivs i artikeln " Förslag: En inbyggd Go-felkontrollfunktion, försök ". Det här alternativet är det mest minimalistiska och innebär att man bara lägger till en inbyggd funktion try()som bearbetar resultatet av ett funktionsanrop. Dess användning illustreras av pseudokoden nedan. func f ( … )( r1 typ_1 , … , rn typ_n , felfel ) { // Testad funktion // Returnerar n+1 resultat: r1... rn, fel av typfel. } func g ( … )( … , err error ) { // Anropa funktion f() med felkontroll: … x1 , x2 , … xn = try ( f ( … )) // Använd inbyggt försök: // if f( ) returnerade icke-noll i det senaste resultatet, då avslutas g() automatiskt, // returnerar samma värde i ITS senaste resultat. … } func t ( … )( … , err error ) { // Liknar g() utan att använda den nya syntaxen: t1 , t2 , … tn , te := f ( … ) // Anrop f() med resultat lagrade i temporärt variabler. if te != nil { // Kontrollera returkoden för likhet nil err = te // Om returkoden inte är noll, så skrivs den till det sista resultatet av t(), returnerar // varefter t() avslutas omedelbart. } // Om det inte fanns något fel, x1 , x2 , … xn = t1 , t2 , … tn // … variabler x1…xn får sina värden // och exekveringen av t() fortsätter. … } Det vill säga, try()det ger helt enkelt en felkontroll i anropet till funktionen som kontrolleras, och en omedelbar retur från den aktuella funktionen med samma fel. Du kan använda mekanismen för att hantera ett fel innan du återgår från den aktuella funktionen defer. Användningen try()kräver att både funktionen som kontrolleras och funktionen som den anropas i måste ha det sista returvärdet av typ error. Därför kan du till exempel inte main()använda try(); på toppnivå måste alla fel hanteras explicit. Denna felhanteringsmekanism var tänkt att inkluderas i Go 1.14 , men detta gjordes inte. Implementeringsdatum är inte specificerade. Generisk kod I slutet av 2018 presenterades ett utkast till implementering av generiska typer och funktioner i Go [30] . Den 9 september 2020 publicerades en reviderad design [31] där funktioner, typer och funktionsparametrar kan parametriseras av parametertyper , som i sin tur styrs av begränsningar . // Stringer är ett begränsningsgränssnitt som kräver typen för att implementera // en strängmetod som returnerar ett strängvärde. typ Stringer -gränssnitt { String () string } // Funktionen tar emot som indata en array av värden av vilken typ som helst som implementerar String-metoden, och returnerar // motsvarande array av strängar som erhålls genom att anropa String-metoden för varje element i inmatningsmatrisen. func Stringify [ T Stringer ] ( s [] T ) [] string { // typparametern T, med förbehåll för Stringer-begränsningen, // är värdetypen för arrayparametern s. ret = gör ([] sträng , len ( s )) för i , v := område s { ret [ i ] = v . String () } return ret } ... v := make ([] MyType ) ... // För att anropa en generisk funktion måste du ange en specifik typ s := Stringify [ String ]( v ) Här innehåller funktionen Stringifyen typparameter T, som används i beskrivningen av en vanlig parameter s. För att anropa en sådan funktion, som visas i exemplet, måste du ange i anropet den specifika typ för vilken den anropas. Stringeri den här beskrivningen är det en begränsning som kräver att MyType implementerar en Stringparameterlös metod som returnerar ett strängvärde. Detta tillåter kompilatorn att korrekt bearbeta uttrycket " " v.String(). Implementeringen av den generiska koden tillkännages i version 1.18, planerad till augusti 2021. [28]
Det finns för närvarande två huvudsakliga Go-kompilatorer:
Det finns även projekt:
Go-utvecklingsmiljön innehåller flera kommandoradsverktyg: verktyget go, som tillhandahåller kompilering, testning och pakethantering, och hjälpverktygen godoc och gofmt, designade för att dokumentera program och formatera källkod enligt standardregler. För att visa en komplett lista med verktyg måste du anropa go-verktyget utan att ange argument. gdb debugger kan användas för att felsöka program. Oberoende utvecklare tillhandahåller ett stort antal verktyg och bibliotek utformade för att stödja utvecklingsprocessen, främst för att underlätta kodanalys, testning och felsökning.
För närvarande finns två IDE:er tillgängliga som ursprungligen är fokuserade på Go-språket - detta är det proprietära GoLand [1] (utvecklat av JetBrains på IntelliJ-plattformen) och det gratis LiteIDE [2] (tidigare kallades projektet GoLangIDE). LiteIDE är ett litet skal skrivet i C++ med Qt . Låter dig kompilera, felsöka, formatera kod, köra verktyg. Redaktören stöder syntaxmarkering och autokomplettering.
Go stöds också av plugins i de universella IDE:erna Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus och andra. Automatisk framhävning, automatisk komplettering av Go-kod och körande kompilering och kodbehandlingsverktyg implementeras som plugins för mer än två dussin vanliga textredigerare för olika plattformar, inklusive Emacs, Vim, Notepad++, jEdit.
Nedan är ett exempel på "Hej världen!" på Go-språket.
huvudpaket _ importera "fmt" func main () { fmt . println ( "Hej världen!" ) }Ett exempel på implementering av Unix -ekokommandot :
huvudpaket _ import ( "os" "flagga" // kommandoradstolkare ) var omitNewLine = flagga . Bool ( "n" , falskt , "skriv inte ut nyrad" ) const ( Mellanslag = " " NewLine = "\n" ) func main () { flagga . Parse () // Skanna argumentlistan och ställ in flaggor var s sträng för i := 0 ; i < flagga . Narg (); i ++ { if i > 0 { s += Mellanslag } s += flagga . Arg ( i ) } if ! * utelämnaNewLine { s += NewLine } os . Stdout . WriteString ( s ) }I sociala nätverk | |
---|---|
Tematiska platser | |
I bibliografiska kataloger |
Programmeringsspråk | |
---|---|
|