Megy | |
---|---|
Nyelvóra | többszálú , imperatív , strukturált , objektumorientált [1] [2] |
A végrehajtás típusa | összeállított |
Megjelent | 2009. november 10 |
Szerző | Robert Grismer , Rob Pike és Ken Thompson |
Fejlesztő | Google , Rob Pike , Ken Thompson , The Go Authors [d] és Robert Grismer [d] |
Fájlkiterjesztés _ | .go |
Kiadás |
|
Típusrendszer | szigorú , statikus , típuskövetkeztetéssel |
Befolyásolva | 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 és Occam |
Engedély | BSD |
Weboldal | go.dev _ |
OS | DragonFly BSD , FreeBSD , Linux , macOS , NetBSD , OpenBSD , Plan 9 , Solaris , Microsoft Windows , iOS , Android , AIX és Illumos |
Médiafájlok a Wikimedia Commons oldalon |
A Go (gyakran a golang is ) a Google által belsőleg kifejlesztett többszálú , lefordított programozási nyelv [8] . A Go fejlesztése 2007 szeptemberében kezdődött, és Robert Grismer , Rob Pike és Ken Thompson [9] , aki korábban az Inferno operációs rendszer fejlesztési projektjén dolgozott, közvetlenül részt vett annak tervezésében . A nyelvet hivatalosan 2009 novemberében vezették be . Jelenleg a nyelv készítői által kifejlesztett hivatalos fordítóprogram támogatása a FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX operációs rendszerekhez biztosított . [10] . A Go-t a gcc fordítókészlet is támogatja , és számos független megvalósítás létezik. A nyelv második változata fejlesztés alatt áll.
A Google által választott nyelv neve majdnem megegyezik a Go! programozási nyelv nevével! , készítette F. Gee. McCabe és C. L. Clark 2003-ban [11] . A nevet a Go oldalon tárgyaljuk [11] .
A nyelv honlapján és általában az internetes kiadványokban gyakran használják a "golang" alternatív nevet.
A Go nyelvet programozási nyelvként fejlesztették ki rendkívül hatékony programok létrehozására, amelyek modern elosztott rendszereken és többmagos processzorokon futnak. Kísérletként fogható fel a C és C++ nyelvek helyettesítésének megteremtésére , figyelembe véve a megváltozott számítástechnikai technológiákat és a nagy rendszerek fejlesztésében felhalmozott tapasztalatokat [12] . Rob Pike [12] szavaival élve: "A Go-t arra tervezték, hogy megoldja a valós szoftverfejlesztési problémákat a Google-nál." A fő problémákként a következőket sorolja fel:
A nyelvvel szemben támasztott fő követelmények a következők voltak [13] :
A Go-t azzal az elvárással hozták létre, hogy a rajta lévő programokat objektumkódba fordítsák le, és közvetlenül, virtuális gép nélkül hajtsák végre , így az architekturális megoldások kiválasztásának egyik kritériuma a hatékony objektumkódba való gyors fordítás és a túlzott mennyiség hiánya volt. dinamikus támogatás követelményei.
Az eredmény egy nyelv, "ami nem volt áttörés, de ennek ellenére kiváló eszköz volt nagy szoftverprojektek fejlesztéséhez" [12] .
A Go-hoz ugyan elérhető interpreter , de gyakorlatilag nincs rá nagy szükség, hiszen a fordítási sebesség elég gyors ahhoz, hogy interaktív fejlesztést tegyen lehetővé.
A Go nyelv főbb jellemzői [9] :
A Go nem tartalmazza a többi modern alkalmazásprogramozási nyelvben elérhető népszerű szintaktikai funkciókat. Ezt sok esetben a fejlesztők tudatos döntése okozza. A választott tervezési döntések rövid indoklása a nyelvről szóló "Gyakran Ismételt Kérdésekben" [9] található, részletesebben - a nyelv honlapján megjelent cikkekben és vitákban, különféle tervezési lehetőségeket mérlegelve. Különösen:
A Go nyelv szintaxisa hasonló a C nyelvéhez, az Oberontól és a szkriptnyelvektől kölcsönzött elemekkel .
A Go egy kis- és nagybetűket megkülönböztető nyelv, amely teljes Unicode-támogatást nyújt a karakterláncokhoz és azonosítókhoz.
Az azonosító hagyományosan bármilyen nem üres betűből, számból és aláhúzásból álló sorozat lehet, amely betűvel kezdődik, és nem egyezik a Go kulcsszavak egyikével sem. A "betűk" az összes Unicode karakterre utalnak, amelyek a "Lu" (nagybetűk), "Ll" (kisbetűk), "Lt" (nagybetűk), "Lm" (módosító betűk) vagy "Lo" ( egyéb betűk), a "számok" alatt - az "Nd" kategória összes karaktere (számok, tizedesjegyek). Így semmi sem akadályozza meg például a cirill betű használatát az azonosítókban.
Azok az azonosítók, amelyek csak kis- és nagybetűben különböznek egymástól, különböznek egymástól. A nyelvnek számos konvenciója van a kis- és nagybetűk használatára vonatkozóan. A csomagnevekben különösen csak kisbetűket használnak. Minden Go kulcsszó kisbetűvel van írva. A nagybetűvel kezdődő változók exportálhatók (nyilvános), a kisbetűkkel kezdődő változók pedig nem exportálhatók (privát).
A string literálok korlátozás nélkül használhatják az összes Unicode karaktert . A karakterláncok UTF-8 karaktersorozatokként vannak ábrázolva .
Bármely Go program egy vagy több csomagot tartalmaz. A csomagot, amelyhez a forráskód fájl tartozik, a fájl elején található csomagleírás adja meg. A csomagnevekre ugyanazok a korlátozások vonatkoznak, mint az azonosítókra, de csak kisbetűket tartalmazhatnak. A gorutin csomagrendszernek a könyvtárfához hasonló fastruktúrája van. Bármely globális objektum (változók, típusok, interfészek, függvények, metódusok, struktúrák és interfészek elemei) korlátozás nélkül elérhető abban a csomagban, amelyben deklarálták. A nagybetűvel kezdődő globális objektumok exportálhatók.
Egy másik csomag által exportált objektumok Go kódfájlban való használatához a csomagot a következővel kell importálni import.
csomag fő /* Import */ import ( "fmt" // Szabványos csomag a formázott kimenethez "database/sql" // Beágyazott csomag importálása w "os" // Importálás aliassal . "math" // Importálás minősítés nélkül _ használatakor "gopkg.in/goracle.v2" // A csomag kódjában nincs kifejezett hivatkozás ) func main () { for _ , arg := range w . Args { // Az "os" csomagban deklarált Args tömb elérése az fmt álnéven keresztül . Println ( arg ) // Az "fmt" csomagban deklarált Println() függvény meghívása } var db * sql csomagnévvel . db = sql . Megnyitás ( driver , dataSource ) // A beágyazott csomagból származó nevek minősítése // csak a csomag neve (sql) x := Sin ( 1.0 ) // a math.Sin() hívása - minősítés a csomagnév alapján math nem szükséges // mert név nélkül importált // A kódban nincs utalás a "goracle.v2" csomagra, de be lesz importálva. }Felsorolja a forrásfa src könyvtárából importált csomagok elérési útját, amelyek pozícióját a környezeti változó adja meg GOPATH, míg a szabványos csomagok esetében csak a nevet adja meg. A csomagot azonosító karakterláncot egy álnév előzheti meg, ebben az esetben a csomag neve helyett kódban kerül felhasználásra. Az importált objektumok teljes minősítéssel (például " ") állnak rendelkezésre az őket importáló fájlban пакет.Объект. Ha egy csomagot álnév helyett ponttal importálnak, akkor az általa exportált összes név minősítés nélkül elérhető lesz. Ezt a funkciót egyes rendszersegédprogramok használják, de a programozó általi használata nem javasolt, mivel az explicit minősítés védelmet nyújt a névütközések és a kód viselkedésének "észrevehetetlen" változásai ellen. Nem lehet minősítés nélkül importálni két azonos nevű csomagot.
A csomagok importálása a Go-ban szigorúan ellenőrzött: ha egy csomagot egy modul importál, akkor legalább egy, a csomag által exportált nevet fel kell használni a modul kódjában. A Go fordító egy nem használt csomag importálását hibaként kezeli; egy ilyen megoldás arra kényszeríti a fejlesztőt, hogy folyamatosan frissítse az import listákat. Ez nem okoz nehézséget, mivel a Go programozást támogató eszközök (szerkesztők, IDE-k) általában az importlisták automatikus ellenőrzését és frissítését biztosítják.
Ha egy csomag olyan kódot tartalmaz, amelyet csak introspekcióval használunk , akkor egy probléma adódik: egy ilyen csomag importálása szükséges ahhoz, hogy szerepeljen a programban, de a fordító nem engedélyezi, mivel közvetlenül nem érhető el. _Az ilyen esetekben a névtelen importálás biztosított: „ ” (egy aláhúzás) álnévként van megadva ; egy ilyen módon importált csomag akkor kerül lefordításra és bekerül a programba, ha a kódban kifejezetten nem hivatkozik rá. Egy ilyen csomag azonban nem használható kifejezetten; ez megakadályozza az importvezérlés megkerülését az összes csomag névtelen importálásával.
A futtatható Go programnak tartalmaznia kell egy main nevű csomagot, aminek tartalmaznia kell egy main()paraméter nélküli függvényt és egy visszatérési értéket. A függvény main.main()a "program törzse" - kódja a program indulásakor fut le. Bármely csomag tartalmazhat függvényt init() – lefut a program betöltésekor, a végrehajtás megkezdése előtt, mielőtt bármelyik függvényt meghívnák ebben a csomagban és minden olyan csomagban, amely ezt importálja. A fő csomag mindig utoljára inicializálódik, és minden inicializálás megtörténik a függvény végrehajtása előtt main.main().
A Go csomagolási rendszert azzal a feltételezéssel tervezték, hogy a teljes fejlesztési ökoszisztéma egyetlen fájlfaként létezik, amely tartalmazza az összes csomag legfrissebb verzióit, és amikor új verziók jelennek meg, teljesen újrafordítják. A harmadik féltől származó könyvtárakat használó alkalmazásprogramozásnál ez meglehetősen erős korlátozás. A valóságban gyakran vannak korlátozások az egyik vagy másik kód által használt csomagok verzióira, valamint olyan helyzetekre, amikor egy projekt különböző verziói (ágai) a könyvtári csomagok különböző verzióit használják.
Az 1.11-es verzió óta a Go támogatja az úgynevezett modulokat . A modul egy speciálisan leírt csomag, amely információkat tartalmaz a verziójáról. A modul importálásakor a használt verzió rögzítve van. Ez lehetővé teszi a build rendszer számára, hogy szabályozza, hogy minden függőség teljesül-e, automatikusan frissítse az importált modulokat, amikor a szerző kompatibilis módosításokat hajt végre rajtuk, és blokkolja a nem visszafelé kompatibilis verziók frissítését. A modulok állítólag megoldást (vagy sokkal könnyebb megoldást) jelentenek a függőségkezelés problémájára.
A Go mindkét típusú C-stílusú megjegyzést használja: a soron belüli megjegyzéseket (a // ... karakterrel kezdődően) és a blokkolt megjegyzéseket (/* ... */). A soros megjegyzést a fordító újsorként kezeli. Blokk, amely egy sorban található - szóközként, több sorban - újsorként.
A pontosvessző a Go-ban kötelező elválasztóként használatos bizonyos műveleteknél (ha, for, switch). Formálisan is be kell fejeznie az egyes parancsokat, de a gyakorlatban nem szükséges ilyen pontosvesszőt a sor végére tenni, mivel a fordító maga ad pontosvesszőt minden sor végére, az üres karakterek kivételével a sor végére. azonosító, szám, karakter literál, karakterlánc, törés, folytatás, átfutás, visszatérési kulcsszavak, növelő vagy csökkentő parancs (++ vagy --), vagy záró zárójel, négyzet vagy kapcsos zárójel (fontos kivétel, hogy a vessző nem szerepel a fenti listán). Ebből két dolog következik:
A nyelv egyszerű beépített adattípusok meglehetősen szabványos halmazát tartalmazza: egész számokat, lebegőpontos számokat, karaktereket, karakterláncokat, logikai értékeket és néhány speciális típust.
Egész számok11 egész számtípus létezik:
A nyelv készítői a programon belüli számokkal való munkavégzéshez csak a szabványos típus használatát javasolják int. A rögzített méretű típusokat úgy tervezték, hogy a külső forrásokból származó vagy átadott adatokkal dolgozzanak, amikor a kód helyessége érdekében fontos a típus egy adott méretének megadása. A típusok szinonimák byte, és runebináris adatokkal, illetve szimbólumokkal való együttműködésre készültek. A típus uintptrcsak külső kóddal való interakcióhoz szükséges, például C-ben.
Lebegőpontos számokA lebegőpontos számokat kétféle, float32és float64. Méretük 32, illetve 64 bites, a megvalósítás megfelel az IEEE 754 szabványnak . Az értéktartomány a standard csomagból szerezhető be math.
Numerikus típusok korlátlan pontossággalA Go standard könyvtára tartalmazza a csomagot is big, amely három típust biztosít korlátlan pontossággal: big.Int, big.Ratés big.Float, amelyek rendre egész számokat, racionális számokat és lebegőpontos számokat jelentenek; ezeknek a számoknak a mérete bármi lehet, és csak a rendelkezésre álló memória mennyisége korlátozza. Mivel a Go operátorai nincsenek túlterhelve, a számokkal végzett korlátlan pontosságú számítási műveletek szokásos módszerekként valósulnak meg. A nagy számokkal végzett számítások teljesítménye természetesen lényegesen gyengébb a beépített numerikus típusokéhoz képest, de bizonyos típusú számítási feladatok megoldásánál a csomag bighasználata előnyösebb lehet, mint egy matematikai algoritmus kézi optimalizálása.
Komplex számokA nyelv két beépített típust is biztosít a komplex számokhoz, complex64és complex128. Ezen típusok mindegyik értéke egy valós és képzeletbeli részpárt tartalmaz, amelyek típusai, float32illetve float64. Kétféleképpen hozhat létre összetett típusú értéket a kódban: vagy egy beépített függvény complex()használatával, vagy egy képzeletbeli literál használatával egy kifejezésben. Egy komplex szám valós és imaginárius részeit a real()és függvényekkel kaphatja meg imag().
var x complex128 = complex ( 1 , 2 ) // 1 + 2i y := 3 + 4i // 3 + 4i , ahol a 4 egy szám , amelyet egy i utótag követ // egy képzeletbeli fmt literál . Println ( x * y ) // kiírja a "(-5+10i)" fmt-t . Println ( valós ( x * y )) // kiírja a "-5" fmt . Println ( imag ( x * y )) // "10"-et nyomtat Logikai értékekA logikai típus boolmeglehetősen gyakori - magában foglalja az előre meghatározott értékeket , valamint az igaz trueés falsehamis jelölést. A C-vel ellentétben a Go logikai értékei nem numerikusak, és nem konvertálhatók közvetlenül számokká.
StringsA karakterlánc típusú értékek stringmegváltoztathatatlan bájttömbök, amelyek UTF-8. Ez a karakterláncok számos sajátos jellemzőjét okozza (például általános esetben egy karakterlánc hossza nem egyenlő az őt reprezentáló tömb hosszával, azaz a benne lévő karakterek száma nem egyenlő a számmal bájtok száma a megfelelő tömbben). A legtöbb teljes karakterláncot feldolgozó alkalmazásnál ez a specifikum nem fontos, de olyan esetekben, amikor a programnak közvetlenül meghatározott rúnákat (Unicode karaktereket) kell feldolgoznia, szükség van egy csomagra unicode/utf8, amely a Unicode karakterláncokkal való munkavégzéshez szükséges segédeszközöket tartalmazza.
Bármilyen adattípushoz, beleértve a beépítetteket is, új analóg típusok deklarálhatók, amelyek megismétlik az eredetiek összes tulajdonságát, de nem kompatibilisek velük. Ezek az új típusok opcionálisan deklarálhatnak metódusokat is. A felhasználó által definiált adattípusok a Go-ban: mutatók (szimbólummal deklarálva *), tömbök (szögletes zárójelben), struktúrák ( struct), függvények ( func), interfészek ( interface), leképezések ( map) és csatornák ( chan). E típusok deklarációi megadják elemeik típusát, esetleg azonosítóit. Új típusok deklarálása a következő kulcsszóval történik type:
type PostString string // Írja be a "string"-et, hasonlóan a beépítetthez type StringArray [] string // Tömbtípus string típusú elemekkel type Person struct { // Struktúratípus neve string // a szabványos karakterlánc típus mezője post PostString // a korábban deklarált egyéni karakterlánc típus mezője bdate time . Time // Time típusú mező, a csomagból importálva time edate time . Időfőnök * Személy // mutatómező következtetés [ ]( * Személy ) // tömb mező } típus InOutString chan string // csatorna típusa karakterláncok átadásához típus CompareFunc func ( a , b interfész {}) int // függvénytípus.A Go 1.9-es verziója óta a típusálnevek (aliasok) deklarálása is elérhető:
type TitleString = string // A "TitleString" a beépített karakterlánc típusú álneve Integer = int64 // Az "Integer" a beépített 64 bites egész típus álneveAz álnév deklarálható egy rendszertípushoz vagy bármely felhasználó által meghatározott típushoz. Az alapvető különbség az álnevek és a közönséges típusdeklarációk között, hogy a deklaráció egy új típust hoz létre, amely nem kompatibilis az eredetivel, még akkor is, ha a deklarációban nincs módosítás az eredeti típushoz. Az álnév ugyanannak a típusnak csak egy másik neve, ami azt jelenti, hogy az álnév és az eredeti típus teljesen felcserélhető.
A szerkezeti mezők leírásában lehetnek címkék – tetszőleges karaktersorozatok hátsó idézőjelbe zárva:
// Struktúra mezőcímkékkel típusú XMLInvoices struct { XMLName xml . Név `xml:"INVOICES"` Verzió int `xml:"version,attr"` Számla [] * XMLInvoice `xml:"SZÁMLA"` }reflectA címkéket a fordító figyelmen kívül hagyja, de a róluk szóló információkat elhelyezi a kódban, és a szabványos könyvtárban található csomag funkcióival kiolvasható . A címkéket jellemzően típusfelosztás biztosítására használják adatok külső adathordozón való tárolására és visszaállítására, vagy olyan külső rendszerekkel való interakcióra, amelyek saját formátumukban fogadják vagy továbbítják az adatokat. A fenti példa a szabványos könyvtár által feldolgozott címkéket használ az adatok XML formátumban történő olvasására és írására.
A változók deklarálásának szintaxisát elsősorban a Pascal szellemében oldják meg: a deklaráció a var kulcsszóval kezdődik, ezt követi az elválasztón keresztül a változó neve, majd az elválasztón keresztül a típusa.
Megy | C++ |
---|---|
var v1 int const v2 string var v3 [ 10 ] int var v4 [ ] int var v5 struct { f int } var v6 * int /* mutató aritmetika nem támogatott */ var v7 map [ string ] int var v8 func ( a int ) int | int v1 ; const std :: stringv2 ; _ /* ról ről */ intv3 [ 10 ] ; int * v4 ; /* ról ről */ struct { int f ; } v5 ; int * v6 ; std :: rendezetlen_térkép v7 ; /* ról ről */ int ( * v8 )( int a ); |
A változó deklaráció kombinálható inicializálással:
var v1 int = 100 var v2 string = "Szia!" 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 = { "egy" : 1 , "kettő" : 2 , "három" : 3 } var v8 func ( a int ) int = func ( a int ) int { return a + 1 }Ha egy változót nem inicializálunk kifejezetten a deklaráláskor , akkor az automatikusan "null értékre" inicializálódik az adott típushoz. A null értéke minden számtípusnál 0, típusnál ez string az üres karakterlánc, mutatóknál pedig nil. A struktúrák alapértelmezés szerint nulla értékkészlettel vannak inicializálva az egyes mezőkhöz, a tömbelemek pedig a tömbdefinícióban megadott típusú nulla értékekkel.
A hirdetések csoportosíthatók:
var ( i int m float )A Go nyelv az automatikus típuskövetkeztetést is támogatja . Ha egy változót a deklaráláskor inicializálunk, akkor a típusa elhagyható - a hozzárendelt kifejezés típusa a változó típusává válik. A literálok (számok, karakterek, karakterláncok) esetében a nyelvi szabvány meghatározott beépített típusokat határoz meg, amelyekhez minden ilyen érték tartozik. Más típusú változó inicializálásához explicit típuskonverziót kell alkalmazni a literálra.
var p1 = 20 // p1 int - a 20 egész literál int típusú. var p2 = uint ( 20 ) // p2 uint - explicit módon uintra öntött érték. var v1 = & p1 // A v1 *int a p1-re mutató mutató, amelyhez az int típust vezetjük le. var v2 = & p2 // v2 *uint egy mutató a p2-re, amely kifejezetten előjel nélküli egész számként van inicializálva.A helyi változók esetében létezik egy rövidített deklarációs forma, amelyet típuskövetkeztetést használó inicializálással kombinálnak:
v1 := 100 v2 := "Szia!" v3 := [ 10 ] int { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } v4 := [] int { 1000 , 2000 , 12334 } v5 := }{ struct { f int 50 } v6 := és v1A Go a szimbólumot használja hozzárendelési operátorként =:
a = b // Állítsa be az a változót b-reMint fentebb említettük, létezik egy változó definiálása automatikus típuskövetkeztetéssel, inicializálással kombinálva, amely külsőleg a Pascal -beli hozzárendeléshez hasonlít :
v1 := v2 // hasonló a var v1 = v2-hezA Go fordító szigorúan nyomon követi a definíciókat és hozzárendeléseket, és megkülönbözteti őket a másiktól. Mivel az azonos nevű változó újradefiniálása egy hatókörben, egy kódblokkon belül tilos, egy változó :=csak egyszer jelenhet meg a jeltől balra:
a := 10 // Egész változó deklarálása és inicializálása a. b := 20 // Egész változó deklarálása és inicializálása b. ... a := b // HIBA! Próbálja meg újradefiniálni a.A Go több hozzárendelés párhuzamos végrehajtását teszi lehetővé:
i , j = j , i // i és j értékek felcserélése.Ebben az esetben a hozzárendelési jeltől balra lévő változók számának pontosan meg kell egyeznie a hozzárendelési jeltől jobbra lévő kifejezések számával.
A párhuzamos hozzárendelés is lehetséges a :=. Különlegessége, hogy a jeltől balra felsorolt változók :=között előfordulhatnak már létező változók. Ebben az esetben új változók jönnek létre, a meglévők újra felhasználásra kerülnek. Ezt a szintaxist gyakran használják hibakezelésre:
x , err := SomeFunction () // A függvény két értéket ad vissza (lásd lent), // két változót deklarál és inicializál. if ( err != nil ) { return nil } y , err := SomeOtherFunction () // Itt csak y van deklarálva, az err egyszerűen hozzárendel egy értéket.A példa utolsó sorában a függvény által visszaadott első érték az új y változóhoz, a második a már meglévő változóhoz van hozzárendelve err, amely a kódban a meghívott függvények által visszaadott utolsó hiba elhelyezésére szolgál. Ha nem ez a tulajdonsága az operátornak :=, akkor a második esetben egy új változót (például err2) kell deklarálni, vagy külön deklarálni y, majd a szokásos párhuzamos hozzárendelést kell használni.
A Go "hozzárendelésre másolás" szemantikát valósít meg, ami azt jelenti, hogy a hozzárendelés az eredeti változó értékének másolatát, majd a másolatot egy másik változóba helyezi, ami után a változók értékei eltérnek, és megváltoztatják az egyik változót. nem változtatják meg a másikat. Ez azonban csak az adott hosszúságú beépített skaláris típusokra, struktúrákra és tömbökre igaz (vagyis azokra a típusokra, amelyek értékei a veremben vannak lefoglalva). A halomban határozatlan hosszúságú tömbök és leképezések vannak lefoglalva, az ilyen típusú változók tulajdonképpen objektumokra való hivatkozásokat tartalmaznak, hozzárendelésükkor csak a hivatkozást másolja át, magát az objektumot nem. Néha ez nem várt hatásokhoz vezethet. Tekintsünk két szinte egyforma példát:
type vector [ 2 ] float64 // A tömb hossza kifejezetten be van állítva v1 := vector { 10 , 15.5 } // Inicializálás - a v1 magát a tömböt tartalmazza v2 := v1 // A v1 tömb a v2 v2 tömbbe van másolva [ 0 ] = 25.3 // Csak a v2 fmt tömb változott . Println ( v1 ) // "[10 15.5]" kinyomtatása – az eredeti tömb nem változott. fmt . Println ( v2 ) // "[25.3 15.5]" nyomtatásaItt a típust vectorkét számból álló tömbként határozzuk meg. Az ilyen tömbök hozzárendelése ugyanúgy működik, mint a számok és struktúrák hozzárendelése.
A következő példában pedig a kód pontosan egy karakterrel különbözik: a típus vectoregy határozatlan méretű tömbként van definiálva. De ez a kód teljesen másként viselkedik:
type vector [] float64 // Meghatározatlan hosszúságú tömb v1 := vector { 10 , 15.5 } // Inicializálás - v1 tömbhivatkozást tartalmaz v2 := v1 // A tömbhivatkozás a v1-ből a v2-be másolódik v2 [ 0 ] = 25.3 / / Úgy képzelhető el, hogy csak a v2 fmt tömb változott . Println ( v1 ) // "[25.3 15.5]" kinyomtatása - az eredeti tömb VÁLTOZOTT! fmt . Println ( v2 ) // "[25.3 15.5]" nyomtatásaA leképezések és interfészek ugyanúgy viselkednek, mint a második példában. Sőt, ha a struktúrának van referencia vagy interfész típusú mezője, vagy egy mező dimenzió nélküli tömb vagy leképezés, akkor egy ilyen struktúra hozzárendelésekor csak a hivatkozás kerül másolásra, vagyis a különböző struktúrák mezői kezdődnek. hogy ugyanazokra a tárgyakra mutasson a memóriában.
E hatás elkerülése érdekében kifejezetten olyan rendszerfüggvényt kell használnia, copy()amely garantálja az objektum második példányának létrehozását.
így deklarálják:
func f ( i , j , k int , s , t string ) string { }Az ilyen értékek típusai zárójelben vannak:
func f ( a , b int ) ( int , string ) { return a + b , "kiegészítés" }A függvény eredményeit el is nevezhetjük:
func incTwo ( a , b int ) ( c , d int ) { c = a + 1 d = b + 1 return }A megnevezett eredményeket közvetlenül a függvényfejléc után deklaráltnak tekintjük nulla kezdeti értékkel. Az ilyen függvényben lévő return utasítás paraméterek nélkül használható, ebben az esetben a függvényből való visszatérés után az eredmények a végrehajtás során hozzárendelt értékekkel rendelkeznek. Tehát a fenti példában a függvény a paramétereinél eggyel nagyobb egész értékpárt ad vissza.
A függvények által visszaadott több értéket vesszővel elválasztva kell a változókhoz hozzárendelni, míg a függvényhívás eredményeként hozzárendelt változók számának pontosan meg kell egyeznie a függvény által visszaadott értékek számával:
first , second := incTwo ( 1 , 2 ) // first = 2, second = 3 first := incTwo ( 1 , 2 ) // ROSSZ - nincs változó hozzárendelve a második eredményhezEllentétben a Pascal-lal és a C-vel, ahol a helyi változó későbbi felhasználása nélküli deklarálása, vagy a helyi változó értékének elvesztése (amikor a változóhoz rendelt érték nem olvasható sehol) csak fordítói figyelmeztetést okozhat, Go-ban ez a helyzet figyelembe vehető. nyelvi hiba, és a program fordításának ellehetetlenüléséhez vezet. Ez különösen azt jelenti, hogy a programozó nem hagyhatja figyelmen kívül a függvény által visszaadott értéket (vagy valamelyik értéket), egyszerűen csak hozzárendeli azt valamilyen változóhoz, és megtagadja a további használatát. Ha szükségessé válik a függvényhívás által visszaadott értékek egyikének figyelmen kívül hagyása, egy előre definiált "_" nevű pszeudováltozó (egy aláhúzás) kerül felhasználásra. Bárhol megadható, ahol legyen olyan változó, amely értéket vesz fel. A megfelelő érték nem lesz hozzárendelve egyetlen változóhoz sem, és egyszerűen elvész. Egy ilyen építészeti döntésnek az a célja, hogy a fordítási szakaszban azonosítsa a számítási eredmények esetleges elvesztését: az értékfeldolgozás véletlen kihagyását észleli a fordító, és a „_” pszeudováltozó használata azt jelzi, hogy a programozó szándékosan figyelmen kívül hagyta az eredményeket. A következő példában, ha az incTwo függvény által visszaadott két érték közül csak az egyikre van szükség, a második változó helyett "_"-t kell megadni:
first := incTwo ( 1 , 2 ) // INVALID first , _ := incTwo ( 1 , 2 ) // TRUE, a második eredmény nincs felhasználvaA "_" változó tetszőleges számú alkalommal megadható a hozzárendelési listában. A „_”-nak megfelelő függvényeredmények figyelmen kívül maradnak.
A halasztott hívás egyszerre több szintaktikai jellemzőt helyettesít, különösen a kivételkezelőket és a garantált befejezési blokkokat. A defer kulcsszó előtti függvényhívás paraméterezése a program azon a pontján történik, ahová elhelyezték, és közvetlenül azelőtt fut le, hogy a program kilép a hatókörből, ahol deklarálták, függetlenül attól, hogy ez a kilépés hogyan és milyen okból történik. Ha egy függvény több halasztási deklarációt tartalmaz, a megfelelő hívások egymás után, a függvény lejárta után, fordított sorrendben kerülnek végrehajtásra. Az alábbiakban egy példa látható a halasztás garantált befejezési blokkként való használatára [15] :
// A fájlt másoló függvény func CopyFile ( dstName , srcName string ) ( int64 írás , err hiba ) { src , err : = os . Megnyitás ( srcName ) // Nyissa meg a forrásfájlt, ha err != nil { // Ellenőrizze a return -t // Ha nem sikerül, térjen vissza hibával } // Ha ide jutott, akkor a forrásfájl megnyitása sikeres volt defer src . Close () // Késleltetett hívás: src.A Close() akkor kerül meghívásra, amikor a CopyFile befejeződik dst , err := os . Create ( dstName ) // Célfájl megnyitása , ha err != nil { // Ellenőrzés és visszatérés hiba esetén return } defer dst . Close () // Késleltetett hívás: A dst.Close() meghívásra kerül, amikor a CopyFile befejeződik returnio . _ Másolás ( dst , src ) // Adatok másolása és visszatérés a függvényből // Miután minden művelet meg lesz hívva: először dst.Close(), majd src.Close() }A legtöbb C-szerű szintaxisú nyelvtől eltérően a Go nem rendelkezik zárójelekkel a feltételes konstrukciókhoz for, if, switch:
if i >= 0 && i < len ( arr ) { println ( arr [ i ]) } ... for i := 0 ; i < 10 ; i ++ { } }A Go egy hurokkonstrukciót használ mindenféle hurok megszervezéséhez for.
i < 10 { // hurok esetén előfeltétellel, hasonlóan a C- ben leírtakhoz for i := 0 ; i < 10 ; i ++ { // ciklus számlálóval, hasonló a for-hoz a C-ben } for { // végtelen ciklus // A ciklusból való kilépést manuálisan kell kezelni, // általában return vagy break } for { // ciklus utófeltétellel ... // ciklustörzs if i >= 10 { // kilépési feltétel törése } } for i , v := range arr { // hurok a gyűjteményben (tömb, szelet, megjelenítés) arr // i - az aktuális elem indexe (vagy kulcsa) // v - az aktuális tömbelem értékének másolata } for i := range arr { // hurok a gyűjteményben, csak az index kerül felhasználásra } _ , v := tartomány arr { // cikluson keresztül a gyűjtésen keresztül, csak elemértékeket használjon } for range arr { // Végigpörgetés a gyűjteményben változók nélkül (a gyűjtemény // csak iterációs számlálóként használható). } v := c tartomány esetén { // hurok a csatornán keresztül: // v a c csatorna értékeit olvassa ki, // amíg a csatornát be nem zárja egy párhuzamos // gorutin }A feleletválasztós operátor szintaxisának switchszámos jellemzője van. Először is, a C-vel ellentétben nem szükséges az operátor használata break: a kiválasztott ág feldolgozása után az operátor végrehajtása véget ér. Ha éppen ellenkezőleg, azt szeretné, hogy a következő ág a kiválasztott elágazás után folytassa a feldolgozást, akkor a következő operátort kell használnia fallthrough:
kapcsoló értéke { 1. eset : fmt . Println ( "One" ) fallthrough // Ezután a "case 0:" ág kerül végrehajtásra case 0 : fmt . println ( "nulla" ) }Itt, amikor value==1két sor jelenik meg, az "Egy" és a "Zero".
A switch utasításban szereplő választási kifejezés és ennek megfelelően az alternatívák tetszőleges típusúak lehetnek, egy ágban több opció is felsorolható:
karaktereket váltani [ kód ]. category { case "Lu" , "Ll" , "Lt" , "Lm" , "Lo" : ... case "Nd" : ... default : ... }Választási kifejezés hiánya megengedett, ebben az esetben logikai feltételeket kell beírni az alternatívákba. Az első ág végrehajtásra kerül, amelynek feltétele igaz:
switch { case '0' <= c && c <= '9' : return c - '0' case 'a' <= c && c <= 'f' : return c - 'a' + 10 case 'A' <= c && c <= 'F' : return c - 'A' + 10 }Egy fontos részlet: ha az egyik feltételű ág az operátorral végződik fallthrough, akkor ez után az ág után a következő kerül feldolgozásra, függetlenül attól, hogy a feltétele teljesül-e . Ha azt szeretné, hogy a következő ág csak akkor kerüljön feldolgozásra, ha a feltétele teljesül, akkor szekvenciális konstrukciókat kell használnia if.
A Go nyelv nem támogatja a legtöbb modern nyelvre jellemző strukturált kivételkezelési szintaxist , amely egy speciális paranccsal (általában throwvagy raise) kivételeket dob, és blokkban kezeli őket try-catch. Ehelyett ajánlatos a hibavisszaadást használni a függvény egyik eredményeként (ami praktikus, mivel a Go-ban egy függvény egynél több értéket is visszaadhat):
A nyelv számos kritikusa úgy véli, hogy ez az ideológia rosszabb, mint a kivételkezelés, mivel számos ellenőrzés összezavarja a kódot, és nem teszi lehetővé, hogy minden hibakezelés blokkokban összpontosuljon catch. A nyelv alkotói ezt nem tartják komoly problémának. Számos hibakezelési mintát írnak le a Go programban (lásd például Rob Pike cikkét a hivatalos Go blogon , orosz fordítás ), amelyek csökkenthetik a hibakezelési kód mennyiségét.
Amikor végzetes hibák fordulnak elő, amelyek lehetetlenné teszik a további programvégrehajtást (például nullával való osztás vagy tömbhatárok elérése), pánikállapot lép fel , ami alapértelmezés szerint a program összeomlásához vezet egy hibaüzenettel és egy hívási verem nyomkövetésével. deferA pánikokat a fent leírt késleltetett végrehajtási konstrukció segítségével lehet elkapni és kezelni . A -ban megadott függvényhívás deferaz aktuális hatókör elhagyása előtt történik, beleértve a pánikhelyzetet is. A behívott függvényen belül deferhívhatunk egy szabványos függvényt recover() - leállítja a pánik rendszer feldolgozását és visszaadja annak okát erroregy normál hibaként feldolgozható objektum formájában. De a programozó egy korábban elkapott pánikot is folytathat a szabvány hívásával panic(err error).
// A program elvégzi az első paraméterének egész számmal történő osztását // a másodikkal // és kiírja az eredményt. func main () { defer func () { err := recovery () if v , ok := err .( error ); ok { // Az fmt interfész hibájának megfelelő pánik kezelése . Fprintf ( os . Stderr , "Error %v \"%s\"\n" , err , v . Error ()) } else if err != nil { pánik ( err ) // Váratlan hibák kezelése - emelje újra a pánik. } }() a , err := strconv . ParseInt ( os . Args [ 1 ], 10 , 64 ) if err != nil { pánik ( 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 ) }A fenti példában hibák fordulhatnak elő, amikor a függvény a program argumentumait egész számokká konvertálja strconv.ParseInt(). Pánikba eshet az os.Args tömb elégtelen számú argumentumával történő elérésekor is, vagy nullával való osztásakor, ha a második paraméter nulla. Bármilyen hibahelyzet esetén pánik keletkezik, amelyet a hívás feldolgoz defer:
> oszd el 10 5-tel 10/5 = 2 > oszd el a 10 0-t Hiba runtime.errorString "futásidejű hiba: egész szám osztása nullával" > oszd el 10,5 2-vel Hiba *strconv.NumError "strconv.ParseInt: "10.5" elemzés: érvénytelen szintaxis > oszd el a 10-et Hiba runtime.errorString "futásidejű hiba: az index tartományon kívül van"A pánik nem váltható ki az egyik párhuzamosan végrehajtott gorutinban (lásd alább), de kezelhető egy másikban. Szintén nem ajánlott a pánikot a csomaghatáron átlépni.
A Go szálfűzési modelljét az Active Oberon nyelvtől örökölték, amely Tony Hoare CSP -jén alapul, az Occam és a Limbo nyelv ötleteit felhasználva [9] , de olyan funkciók is jelen vannak, mint a pi-calculus és a csatornázás.
A Go segítségével új programvégrehajtási szálat hozhat létre a go kulcsszó használatával , amely névtelen vagy elnevezett függvényt futtat egy újonnan létrehozott gorutinban (a Go kifejezés a coroutines -okra ). Ugyanazon a folyamaton belül minden gorutin közös címteret használ, az operációs rendszer szálain fut, de az utóbbihoz való kemény kötődés nélkül, ami lehetővé teszi a futó gorutin számára, hogy blokkolt gorutinnal rendelkező szálat hagyjon (például várjon üzenet küldésére vagy fogadására). csatornából ) és folytassa tovább. A futásidejű könyvtár tartalmaz egy multiplexert, amely megosztja a rendelkezésre álló számú rendszermagot a gorutinok között. Lehetőség van korlátozni azon fizikai processzormagok maximális számát, amelyeken a program végrehajtásra kerül. A gorutinok Go futásidejű könyvtár általi öntámogatása megkönnyíti a hatalmas számú gorutin használatát a programokban, ami messze meghaladja a rendszer által támogatott szálak számának korlátját.
func server ( i int ) { for { print ( i ) time . Sleep ( 10 ) } } go szerver ( 1 ) go szerver ( 2 )A lezárások használhatók egy go kifejezésben .
var g int go func ( i int ) { s := 0 j := 0 esetén ; j < i ; j ++ { s += j } g = s } ( 1000 )A gorutinok közötti kommunikációhoz csatornákat használnak (a beépített chan típus ), amelyeken keresztül tetszőleges érték továbbítható. Egy csatornát a beépített függvény hoz létre make(), amely átadja a csatorna típusát és (opcionálisan) hangerejét. Alapértelmezés szerint a csatorna hangereje nulla. Az ilyen csatornák puffermentesek . A csatorna tetszőleges pozitív egész térfogatát beállíthatja, ekkor egy pufferelt csatorna jön létre.
Egy puffereletlen cső szorosan szinkronizálja az olvasó és író szálakat a segítségével. Amikor egy írószál ír valamit egy csőbe, az megáll, és megvárja, amíg az érték beolvasásra kerül. Amikor egy olvasószál megpróbál beolvasni valamit egy olyan csőből, amelyre már írtak, beolvassa az értéket, és mindkét szál folytathatja a végrehajtást. Ha még nincs érték írva a csatornára, az olvasószál szünetel, és várja, hogy valaki írjon a csatornára. Vagyis a nem pufferolt csövek a Go nyelvben ugyanúgy viselkednek, mint az Occam vagy az Ada nyelvben a randevúzási mechanizmus .
A pufferelt csatornának van egy értékpuffere, amelynek mérete megegyezik a csatorna méretével. Amikor egy ilyen pipere ír, az érték a cső pufferébe kerül, és az író szál szünet nélkül folytatódik, kivéve, ha a cső puffere megtelt az írás idején. Ha a puffer megtelt, akkor az író szál felfüggesztésre kerül mindaddig, amíg legalább egy értéket ki nem olvas a csatornából. Az olvasószál egy pufferelt csőből is kiolvas egy értéket szünet nélkül, ha olvasatlan értékek vannak a cső pufferében; Ha a csatornapuffer üres, akkor a szál szünetel, és megvárja, amíg valamelyik másik szál értéket ír neki.
Ha kész, a csatorna bezárható a beépített funkcióval close(). A privát csatornára való írási kísérlet pánikot okoz, a privát csatornáról történő olvasás mindig szünet nélkül történik, és az alapértelmezett értéket olvassa be. Ha a csatorna pufferelt, és a bezáráskor N korábban írt értéket tartalmaz a pufferben, akkor az első N olvasási művelet úgy történik, mintha a csatorna még nyitva lenne, és kiolvassa az értékeket a pufferből, és csak ezután a csatorna kiolvasása adja vissza az alapértelmezett értékeket.
A művelet egy érték átadására szolgál egy csatornának és egy csatornáról <-. Csatornára írásakor bináris operátorként, olvasáskor unáris operátorként használják:
in := make ( chan string , 0 ) // Puffer nélküli csatorna létrehozása in out := make ( chan int , 10 ) // Pufferelt csatorna létrehozása out ... in <- arg // Írjon egy értéket a csatornának ... r1 := <- ki // kiolvasás a csatornából ... r2 , ok := <- ki // olvasás a csatorna zárásának ellenőrzésével ha ok { // ha ok == igaz - a csatorna nyitva van ... } else { // ha a csatorna be van zárva, csinálj valami mást ... }A csatornáról történő olvasás műveletének két lehetősége van: ellenőrzés nélkül és a csatorna bezárásának ellenőrzésével. Az első opció (a fenti példában r1 olvasása) egyszerűen beolvassa a következő értéket a változóba; ha a csatorna zárva van, akkor az alapértelmezett érték lesz beolvasva az r1-be. A második opció (r2 olvasása) az érték mellett egy logikai értéket is beolvas - az ok csatorna állapotjelző jelzőt, amely akkor lesz igaz, ha a csatornából kiolvasták a stream által odahelyezett adatokat, és hamis, ha a csatorna zárva és a puffere üres. Ezzel a művelettel az olvasószál meg tudja határozni, hogy a bemeneti csatorna mikor van zárva.
A csőből történő olvasás a for-range hurokkonstrukcióval is támogatott:
// A függvény a bemeneti csatornából párhuzamosan egész számokkal kezdi a olvasást és a kimeneti csatornára csak azokat az egészeket // írja, amelyek pozitívak. // Visszaadja a kimeneti csatornát. func pozitívok ( in <- chan int64 ) <- chan int64 { out := make ( chan int64 ) go func ( ) { // A hurok addig folytatódik , amíg be nem záródik a következő := range in { if next > 0 { ki <- következő } } bezár ( ki ) }( ) visszatér }A CSP mellett vagy a csatornázási mechanizmussal együtt a Go azt is lehetővé teszi, hogy a szálak szinkronizált interakciójának szokásos modelljét használja a megosztott memórián keresztül, tipikus hozzáférési szinkronizálási eszközökkel, például mutexekkel . Ugyanakkor a nyelvi specifikáció óva int a párhuzamos szálak osztott memórián keresztüli szinkronizálatlan interakciójának minden kísérletétől, mivel kifejezett szinkronizálás hiányában a fordító optimalizálja az adathozzáférési kódot anélkül, hogy figyelembe venné az egyidejű hozzáférés lehetőségét a különböző forrásokból. szálakat, ami váratlan hibákhoz vezethet. Például előfordulhat, hogy egy szálban a globális változók értékeinek írása nem vagy rossz sorrendben látható egy párhuzamos szálból.
Vegyük például az alábbi programot. A függvény kódja main()abból a feltételezésből indul ki, hogy a gorutinban elindított függvény setup()létrehoz egy típusú struktúrát T, inicializálja a "hello, world" karakterlánccal, majd hivatkozást rendel az inicializált szerkezetre a globális változóhoz g. B main()elindít egy üres ciklust, és vár egy gnem nulla érték megjelenésére. Amint megjelenik, main()kiír egy karakterláncot a mutatott struktúrából g, feltételezve, hogy a szerkezetet már inicializálták.
írja be a T struct { msg string } varg * T _ func setup () { t : = new ( T ) t . msg = "helló, világ" g = t } func main () { go setup () for g == nil { // NEM MŰKÖDIK!!! } nyomtatás ( g . üzenet ) }A valóságban két hiba egyike lehetséges.
Az osztott memórián keresztüli adatátvitel megszervezésének egyetlen helyes módja a könyvtári szinkronizálási eszközök használata, amelyek garantálják, hogy az egyik szinkronizált adatfolyam által a szinkronizálási pont előtt írt összes adat garantáltan elérhető lesz egy másik szinkronizált adatfolyamban a szinkronizálási pont után.
A Go-ban a többszálú kezelés egyik jellemzője, hogy a gorutin semmilyen módon nem azonosítható, és nem olyan nyelvi objektum, amelyre függvények hívásakor hivatkozni lehet, vagy amely konténerbe helyezhető. Ennek megfelelően nincsenek olyan eszközök, amelyek lehetővé tennék a korutin végrehajtásának azon kívülről történő közvetlen befolyásolását, mint például a felfüggesztés, majd az indítás, a prioritás megváltoztatása, az egyik korutin befejezésének megvárása a másikban, illetve a végrehajtás erőszakos megszakítása. Bármilyen művelet a gorutinon (a főprogram leállításán kívül, amely automatikusan leállítja az összes gorutint) csak csöveken vagy más szinkronizálási mechanizmusokon keresztül hajtható végre. A következő mintakód több gorutint indít el, és várja, hogy azok befejeződjenek a szinkronizálási rendszercsomag WaitGroup szinkronizálási objektumával. Ez az objektum tartalmaz egy számlálót, kezdetben nullát, amely növelheti és csökkentheti, és egy Wait() metódust, amely az aktuális szál szüneteltetését és megvárását, amíg a számláló nullára áll.
func main () { var wg sync . WaitGroup // Várakozócsoport létrehozása. A számláló kezdeti értéke 0 logger := log . New ( os . Stdout , "" , 0 ) // log.Logger egy szálbiztos kimeneti típus a _ , arg := tartomány os számára . Args { // Végigfut az összes parancssori argumentum wg . Add ( 1 ) // Növelje eggyel a várakozási csoport számlálóját // Futtasson le egy gorutint az arg paraméter feldolgozásához go func ( word string ) { // A várakozási csoport számlálójának késleltetett csökkentése eggyel. // Akkor történik, amikor a függvény véget ér. defer wg . Kész () logger . Println ( előkészítiWord ( szó )) // Feldolgozás végrehajtása és az eredmény kinyomtatása }( arg ) } wg . Várjon () // Várjon, amíg a wg várócsoport számlálója nulla lesz. }Itt minden új gorutin létrehozása előtt a wg objektum számlálója eggyel növekszik, a gorutin befejezése után pedig eggyel csökken. Ennek eredményeként az argumentumok feldolgozását indító ciklusban annyi egység kerül a számlálóba, ahány gorutin indul. Amikor a ciklus véget ér, a wg.Wait() meghívása a főprogram szüneteltetését okozza. Ahogy mindegyik gorutin befejeződik, eggyel csökkenti a wg számlálót, így a fő program várakozása akkor ér véget, amikor annyi gorutin befejeződik, amennyi futott. Az utolsó sor nélkül a főprogram az összes gorutin futtatása után azonnal kilép, megszakítva azok végrehajtását, amelyeknek nem volt ideje végrehajtani.
A nyelvbe épített többszálú feldolgozás ellenére nem minden szabványos nyelvi objektum szálbiztos. Tehát a szabványos térképtípus (megjelenítés) nem szálbiztos. A nyelv megalkotói ezt a döntést hatékonysági megfontolásokkal magyarázták, mivel az összes ilyen objektum biztonságának biztosítása plusz többletköltséggel járna, ami messze nem mindig szükséges (ugyanezek a leképezésekkel végzett műveletek részei lehetnek nagyobb, a programozó által már szinkronizált műveleteknek is. , és akkor a további szinkronizálás csak bonyolítja és lelassítja a programot). Az 1.9-es verziótól kezdődően a párhuzamos feldolgozást támogató szinkronkönyvtár-csomag hozzáadta a szálbiztos sync.Map típust, amely szükség esetén használható. Figyelembe veheti a példában használt szálbiztos típust is az eredmények megjelenítéséhez log.Logger; a szabványos fmt csomag helyett használatos, amelynek funkciói (Printf, Println és így tovább) nem szálbiztosak, és további szinkronizálást igényelnének.
A Go-ban nincs speciális kulcsszó az osztály deklarálására, de a metódusok bármely elnevezett típushoz definiálhatók, beleértve a struktúrákat és az alaptípusokat, például az int , tehát OOP értelemben minden ilyen típus osztály.
írja be a newInt intA metódusdefiníciós szintaxis az Oberon-2 nyelvből kölcsönzött, és abban különbözik a szokásos függvénydefiníciótól, hogy a func kulcsszó után zárójelben van deklarálva az úgynevezett „receiver” ( angolul Receiver ) , vagyis az az objektum, amelyre a metódus hívódik meg, és a típus, amelyhez a metódus tartozik. Míg a hagyományos objektumnyelvekben a vevő hallgatólagos és szabványos névvel rendelkezik (C++-ban vagy Java-ban "ez", ObjectPascalban "self" stb.), a Go nyelvben kifejezetten meg van adva, és a neve is megadható. bármely érvényes Go azonosító .
type myType struct { i int } // Itt p a vevő a myType típusú metódusokban. func ( p * myType ) get () int { return p . i } func ( p * myType ) set ( i int ) { p . én = én }A Go-ban nincs formális osztályok (struktúrák) öröklődése, de van egy technikailag szoros beágyazási mechanizmus . A szerkezet leírásában használhatjuk az úgynevezett anonim mezőt - olyan mezőt, amelyhez nincs megadva név, csak típus. Egy ilyen leírás eredményeként a beágyazott szerkezet minden eleme a beágyazott szerkezet azonos nevű elemévé válik.
// Új struktúra típus típusa myType2 struct { myType // Az Anonymous mező a myType típusú beágyazását biztosítja. // Most a myType2 tartalmazza az i mezőt és a get() és set(int) metódusokat. k int }A klasszikus öröklődéstől eltérően a beágyazás nem jár polimorf viselkedéssel (egy beágyazó osztály objektuma nem működhet egy beágyazható osztály objektumaként kifejezett típuskonverzió nélkül).
Egy névtelen típushoz nem lehet kifejezetten metódusokat deklarálni (a szintaxis egyszerűen nem teszi lehetővé a vevő típusának megadását a metódusban), de ez a korlátozás könnyen megkerülhető, ha a megnevezett típust beillesztjük a szükséges metódusokkal.
Az osztálypolimorfizmust a Go-ban az interfészek mechanizmusa biztosítja (hasonlóan a C++ teljesen absztrakt osztályaihoz ). Egy interfész leírása az interfész kulcsszó használatával történik, a belső leírások (az osztály típusú deklarációkkal ellentétben) deklarálják az interfész által biztosított metódusokat.
írja be a myInterface interface { get () int set ( i int ) }A Go-ban nincs szükség kifejezetten kijelenteni, hogy egy típus egy adott interfészt valósít meg. Ehelyett a szabály az, hogy minden típus, amely egy interfészben definiált metódusokat biztosít, használható az adott interfész megvalósításaként. A fent deklarált típus myTypeaz interfészt valósítja meg myInterface, bár ez sehol nincs kifejezetten feltüntetve, mert olyan get()és metódusokat tartalmaz set(), amelyek aláírása megegyezik a -ban leírtakkal myInterface.
Az osztályokhoz hasonlóan az interfészek is beépíthetők:
írja be a mySecondInterface interface { myInterface // ugyanaz, mint explicit deklarálja get() int; set(i int) change ( i int ) int }Itt a mySecondInterface interfész örökli a myInterface interfészt (azaz deklarálja, hogy felfedi a myInterface-ben szereplő metódusokat), és emellett deklarál egy natív metódust change().
Noha elvileg lehetséges a Go programot az interfészek hierarchiájába építeni, ahogyan más objektumnyelveknél teszik, és még az öröklődést is szimulálni, ez rossz gyakorlatnak minősül. A nyelv nem hierarchikus, hanem kompozíciós megközelítést diktál az osztályok és interfészek rendszeréhez. A struktúraosztályok ezzel a megközelítéssel általában formálisan függetlenek maradhatnak, és az interfészeket nem egyesítik egyetlen hierarchiába, hanem adott alkalmazásokhoz hozzák létre, szükség esetén a meglévőket beágyazva. Az interfészek implicit megvalósítása a Go-ban rendkívüli rugalmasságot és minimális technikai nehézséget biztosít ezeknek a mechanizmusoknak.
Az öröklődésnek ez a megközelítése összhangban van a modern programozás néhány gyakorlati irányzatával. Tehát a híres "négyes banda" ( Erich Gamma és mások) című könyvben a tervezési mintákról különösen ez van írva:
A megvalósítási függőség problémákat okozhat egy alosztály újrafelhasználásakor. Ha a legacy implementáció egyetlen aspektusa is alkalmatlan az új tartományhoz, akkor a szülőosztályt át kell írni vagy valami megfelelőbbre kell cserélni. Ez a függőség korlátozza a rugalmasságot és az újrafelhasználhatóságot. A probléma csak absztrakt osztályokból való örökléssel oldható meg, mivel ezeknek általában nincs vagy csak minimális implementációjuk van.
A Go-ban nincs virtuális függvény fogalma . A polimorfizmust interfészek biztosítják. Ha egy metódus meghívására egy közönséges típusú változót használunk, akkor az ilyen hívás statikusan kötött, vagyis mindig az adott típushoz meghatározott metódus hívódik meg. Ha a metódust egy „interfész” típusú változóhoz hívjuk, akkor az ilyen hívás dinamikusan kötődik, és a végrehajtáskor az a metódusváltozat, amely a hívásakor ténylegesen hozzárendelt objektum típusához van definiálva. változó van kiválasztva indításhoz.
A Go objektumorientált programozásának dinamikus támogatását a GOOP projekt biztosítja .
A futás közbeni introspektív képesség, azaz bármilyen típusú érték elérése és feldolgozása, valamint a feldolgozott adattípusok dinamikus igazítása a Go rendszercsomag segítségével valósul meg reflect. Ez a csomag lehetővé teszi, hogy:
A csomag reflectszámos segédeszközt is tartalmaz a program dinamikus állapotától függő műveletek végrehajtásához.
Az alacsony szintű memória-hozzáférési lehetőségek a rendszercsomagban koncentrálódnak unsafe. Különlegessége, hogy bár úgy néz ki, mint egy normál Go csomag, valójában maga a fordító implementálja. A csomag unsafehozzáférést biztosít az adatok belső reprezentációjához és a "valódi" memóriamutatókhoz. A következő tulajdonságokkal rendelkezik:
A csomag tartalmaz egy olyan típust is unsafe.Pointer, amely bármilyen mutatóvá konvertálható, és bármilyen típusú mutatóvá konvertálható, valamint egy szabványos típust is uintptr , egy előjel nélküli egész értéket, amely elég nagy ahhoz, hogy egy teljes címet tároljon az aktuális platformon. A mutatót -ra, unsafe.Pointermajd -ra konvertálva uintptregész számként kaphatjuk meg a címet, amelyre aritmetikai műveleteket alkalmazhatunk. Ha ezután visszakonvertálja az értéket unsafe.Pointeregy adott típusú mutatóvá, akkor szinte bárhol elérheti a címteret ilyen módon.
A leírt átalakítások nem biztonságosak lehetnek, ezért lehetőleg kerülni kell őket. Először is nyilvánvaló problémák vannak a rossz memóriaterülethez való hibás hozzáféréssel kapcsolatban. Egy finomabb dolog az, hogy a csomag használata ellenére a unsafeGo objektumokat továbbra is a memóriakezelő és a szemétgyűjtő kezeli. Ha egy mutatót számmá alakítunk, a mutató kikerül az ellenőrzés alól, és a programozó nem számíthat arra, hogy egy ilyen átalakított mutató a végtelenségig releváns marad. Például megpróbál egy mutatót tárolni egy új típusú objektumra, például Т:
pT := uintptr ( nem biztonságos . Mutató ( new ( T ))) // HIBA!létrehozza az objektumot, a mutatót pedig számmá alakítja (amely a következőhöz lesz hozzárendelve pT). Ennek azonban pTegész típusa van, és a szemétgyűjtő nem tekinti egy létrehozott objektum mutatójának, így a művelet befejezése után a memóriakezelő rendszer ezt az objektumot nem használja fel. Vagyis a szemétgyűjtő eltávolíthatja, utána az átalakított mutató pTérvénytelenné válik. Ez bármikor megtörténhet, mind közvetlenül a művelet befejezése után, mind a program több órás működése után, így a hiba véletlenszerű programösszeomlásokban fejeződik ki, amelyek okát rendkívül nehéz lesz azonosítani. Mozgó szemétgyűjtő [* 1] használatakor pedig a számmá konvertált mutató irrelevánssá válhat, még akkor is, ha az objektumot még nem távolították el a memóriából.
Mivel a Go specifikáció nem ad pontos jelzéseket arra vonatkozóan, hogy a programozó milyen mértékben számíthat arra, hogy egy számmá konvertált mutatót naprakészen tartson, van egy javaslat: az ilyen átalakításokat minimálisra kell csökkenteni, és úgy kell megszervezni őket, hogy az eredeti mutató, annak módosításai és visszakonverziója egy nyelvi utasításon belül van, és ha olyan könyvtári függvényeket hívunk meg, amelyek formátumban adnak vissza címet uintptr, azonnal konvertálják az eredményüket a mutatóba, unsafe.Pointerhogy megőrizzék a garancia elvesztését.
A csomagot unsaferitkán használják közvetlenül az alkalmazásprogramozásban, de aktívan használják a reflect, os, syscall, context, netés néhány más csomagban.
Számos külső eszköz létezik, amelyek idegen funkciójú interfészt (FFI) biztosítanak a Go programok számára. A cgo segédprogram használható külső C kóddal való interakcióra (vagy C-kompatibilis interfésszel) . Ez automatikusan meghívódik, amikor a fordító feldolgoz egy megfelelően megírt Go modult, és biztosítja egy ideiglenes Go wrapper csomag létrehozását, amely tartalmazza az összes szükséges típus és funkció deklarációit. A C függvényhívásoknál gyakran csomaglehetőségeket kell igénybe vennie , főleg a . Erősebb eszköz a SWIG [16] , amely fejlettebb funkciókat biztosít, mint például a C++ osztályokkal való integráció . unsafeunsafe.Pointer
A Go szabványos könyvtára támogatja a konzolalkalmazások és a webalapú szerveralkalmazások építését , de nincsenek szabványos eszközök a grafikus felhasználói felületek kliens alkalmazásokban történő létrehozásához. Ezt a hiányt a népszerű felhasználói felületi keretrendszerek , például a GTK+ és a Qt harmadik féltől származó burkolói pótolják , Windows alatt a WinAPI grafikus eszközöket is használhatja , ha a csomagon keresztül éri el őket syscall, de mindezek a módszerek meglehetősen körülményesek. Magában a Go-ban is számos UI keretrendszer fejlesztés létezik, de egyik projekt sem érte el az ipari alkalmazhatóság szintjét. 2015-ben a denveri GopherCon konferencián a nyelv egyik megalkotója, Robert Grismer válaszolt a kérdésekre, egyetértett azzal, hogy a Go-nak szüksége van felhasználói felületi csomagra, de megjegyezte, hogy egy ilyen csomagnak univerzálisnak, erőteljesnek és többplatformosnak kell lennie. fejlesztés hosszú és nehéz folyamat. A kliens GUI megvalósításának kérdése továbbra is nyitott.
A nyelv fiatalsága miatt kritikája elsősorban internetes cikkekben, ismertetőkben, fórumokon összpontosul.
A nyelvvel kapcsolatos kritikák nagy része arra irányul, hogy hiányoznak a más nyelvek által biztosított népszerű jellemzők. Köztük [17] [18] [19] [20] :
Mint fentebb említettük, számos más népszerű nyelven elérhető funkció hiánya a fejlesztők tudatos választásának köszönhető, akik úgy vélik, hogy az ilyen szolgáltatások vagy akadályozzák a hatékony fordítást, vagy hibázásra késztetik a programozót, vagy nem hatékony, ill. "rossz" a kód karbantartása szempontjából, vagy egyéb nemkívánatos mellékhatásai vannak.
A kritikusok rámutatnak arra, hogy a Go egyes funkcióit a legegyszerűbb vagy leghatékonyabb implementáció szerint valósítják meg, de nem felelnek meg a " legkisebb meglepetés elvének ": viselkedésük eltér attól, amit a programozó az intuíció és a múltbeli tapasztalatok alapján elvárna. Az ilyen funkciók fokozott figyelmet igényelnek a programozótól, megnehezítik a tanulást és a más nyelvekről való váltást.
Gyakran kritizálják az automatikus pontosvessző mechanizmusát, amely miatt az utasítások, függvényhívások és listák írásának egyes formái hibássá válnak. Ezt a döntést kommentálva a nyelvi szöveg szerzői megjegyzik [9] , hogy a hivatalos eszköztárban egy kódformázó jelenlétével együtt gofmtegy meglehetősen merev kódolási szabvány rögzítéséhez vezetett a Go-ban. Aligha lehet olyan szabványt alkotni a kódíráshoz, amely mindenkinek megfelelne; egy olyan szolgáltatás nyelvi bevezetése, amely önmagában is ilyen mércét állít fel, egységesíti a programok megjelenését és kiküszöböli a formázásból adódó elvtelen konfliktusokat, ami pozitív tényező a szoftverek csoportos fejlesztése és karbantartása szempontjából.
A Go népszerűsége az elmúlt években nőtt: 2014-ről 2020-ra a Go a 65. helyről a 11. helyre emelkedett a TIOBE -rangsorban, a 2020. augusztusi értékelési érték 1,43%. Egy dou.ua felmérés [22] eredményei szerint a Go nyelv 2018-ban a kilencedik lett a leggyakrabban használt nyelvek listáján és a hatodik azon nyelvek listáján, amelyeket a fejlesztők személyesen preferálnak.
A 2012-es első nyilvános kiadás óta a nyelv használata folyamatosan növekszik. A Go projekt honlapján közzétett, az ipari fejlesztésben nyelvet használó cégek listája több tucat nevet tartalmaz. A különféle célokra szolgáló könyvtárak nagy skálája halmozódott fel. A 2.0-s verzió megjelenését 2019-re tervezték, de a munka késett, és 2022 második felében még folyamatban van. megjelenése várható , beleértve a generikus kifejezéseket és a hibakezelést egyszerűsítő speciális szintaxist, amelyek hiánya az egyik leggyakoribb panasz a nyelv kritikusaitól .
Golangban fejlesztették ki a RoadRunner (Application server) webszervert , amely lehetővé teszi a webes alkalmazások számára, hogy a hagyományos 200 ms helyett 10-20 ms-os kérés-válasz sebességet érjenek el. Ezt a webszolgáltatást a tervek szerint olyan népszerű keretrendszerekbe építik be, mint például a Yii .
A C ++ mellett a Golangot mikroszolgáltatások fejlesztésére használják, ami lehetővé teszi a többprocesszoros platformok munkával való „betöltését”. A REST segítségével kommunikálhat egy mikroszolgáltatással , és a PHP nyelv kiválóan alkalmas erre.
A Spiral Framework fejlesztése PHP és Golang segítségével történt. [23]
Magának a Go nyelvnek csak egy fő verziója létezik, az 1. verzió. A Go fejlesztői környezet verziói (fordító, eszközök és szabványos könyvtárak) vagy kétjegyű számozást kapnak ("<nyelvi verzió>.<főkiadás>"), vagy három számjegyű ("<nyelvi verzió>.< fő kiadás>. <kisebb kiadás>") a rendszerhez. Az új "kétjegyű" verzió kiadása automatikusan megszünteti az előző "kétjegyű" verzió támogatását. „Háromjegyű” verziók jelentek meg a jelentett hibák és biztonsági problémák javítására; az ilyen verziókban található biztonsági javítások hatással lehetnek az utolsó két „kétjegyű” verzióra [24] .
A szerzők kinyilvánították [25] azt a törekvést, hogy amennyire csak lehetséges, megőrizzék a visszafelé kompatibilitást a nyelv fő változatán belül. Ez azt jelenti, hogy a Go 2 megjelenése előtt szinte minden, a Go 1 környezetben létrehozott program megfelelően lefordítja a Go 1.x következő verzióit, és hiba nélkül fut. Kivételek lehetségesek, de kevés. A kiadások közötti bináris kompatibilitás azonban nem garantált, ezért a programot teljesen újra kell fordítani, amikor a Go egy későbbi kiadására váltunk.
2012 márciusa óta, a Go 1 bemutatása óta a következő főbb verziók jelentek meg:
Fejlesztési haladás 2017 óta folynak az előkészületek a nyelv következő alapváltozatának kiadására, amely a „Go 2.0” szimbólumot viseli [26] . A projekt wiki oldalán felhalmozott, az aktuális verzióhoz fűzött megjegyzések és átalakítási javaslatok gyűjteménye [27] . Kezdetben azt feltételezték, hogy az előkészítési folyamat "körülbelül két évig" tart majd, és a nyelv néhány új eleme bekerül a Go 1 verzió következő kiadásaiba (természetesen csak azok, amelyek nem sértik a visszafelé kompatibilitást ). [26] 2021 áprilisára a 2.0-s verzió még nem készült el, a tervezett változtatások egy része a tervezési és megvalósítási szakaszban van. A projektblogban [28] vázolt tervek szerint legalább 2021-ig folytatódik a munka a tervezett változtatások megvalósításán. Javasolt újítások Az alapvető újítások közé tartoznak a kifejezetten deklarált konstans értékek, az új hibakezelési mechanizmus és az általános programozási eszközök. Az innovációs projektek online elérhetők. 2018. augusztus 28-án a hivatalos fejlesztői blogon megjelent egy korábban a Gophercon 2018 konferencián bemutatott videó , amely bemutatja az új hibakezelési tervezés és az általános funkciók mechanizmusának vázlatos változatait. Sok kevésbé észrevehető, de nagyon jelentős változtatást is terveznek [29] , mint például a karakterek megengedettségére vonatkozó szabályok kiterjesztése a nem latin ábécé azonosítóihoz, lehetővé válik az előjeles egész számok eltolása, az aláhúzás használata az ezres csoportok elválasztójaként. számokban, bináris literálokban . A legtöbbjük már implementálva van, és elérhető a Go 1 legújabb verziójában. Hiba a feldolgozásban A hibakezelési mechanizmus módosítására több lehetőséget is mérlegeltek, különösen egy külön hibakezelővel rendelkező tervezést (" Hibakezelés - Tervezettervezet "). A 2019. júliusi utolsó változatot az „ Javaslat: Beépített Go hibaellenőrző funkció, próbálkozzon ” című cikk ismerteti. Ez a lehetőség a legminimálisabb, és csak egy olyan beépített függvény hozzáadását jelenti, try()amely feldolgozza a függvényhívás eredményét. Használatát az alábbi pszeudokód szemlélteti. func f ( … )( r1 type_1 , … , rn type_n , err error ) { // Tesztelt függvény // n+1 eredményt ad vissza: r1... rn, err típusú hiba. } func g ( … )( … , err error ) { // Az f() függvény hívása hibaellenőrzéssel: … x1 , x2 , … xn = try ( f ( … )) // A beépített try használata: // if f( ) nem nullát adott vissza az utolsó eredményben, akkor a g() automatikusan befejeződik, // ugyanazt az értéket adja vissza az ITS utolsó eredményében. … } func t ( … )( … , err error ) { // Hasonló a g()-hez az új szintaxis használata nélkül: t1 , t2 , … tn , te := f ( … ) // Az f() meghívása ideiglenesen tárolt eredményekkel változók. if te != nil { // Ellenőrizze a visszatérési kódot a nil egyenlőséghez azonnal. } // Ha nem volt hiba, akkor x1 , x2 , … xn = t1 , t2 , … tn // … x1…xn változók megkapják az értéket // és a t() végrehajtása folytatódik. … } Vagyis try()egyszerűen hibaellenőrzést biztosít az ellenőrzött függvény hívásában, és azonnali visszatérést az aktuális függvényből ugyanazzal a hibával. A mechanizmus segítségével kezelheti a hibát, mielőtt visszatérne az aktuális függvényből defer. A használat try()megköveteli, hogy mind az ellenőrzött, mind a meghívott függvénynek az utolsó típusú visszatérési értékkel kell rendelkeznie error. Ezért például nem main()használhatja a try(); a legfelső szinten minden hibát kifejezetten kezelni kell. Ennek a hibakezelési mechanizmusnak a Go 1.14 -ben kellett volna szerepelnie , de ez nem történt meg. A megvalósítás dátumai nincsenek megadva. Általános kód 2018 végén bemutatták az általános típusok és funkciók Go-ban való megvalósításának tervezetét [30] . 2020. szeptember 9-én megjelent egy átdolgozott terv [31] , amelyben a függvények, típusok és függvényparaméterek paramétertípusokkal paraméterezhetők , amelyeket viszont megszorítások vezérelnek . // A Stringer egy megszorító interfész, amelyhez a típus szükséges // egy String metódus megvalósításához, amely egy karakterlánc értéket ad vissza. type Stringer interface { String () string } // A függvény bemenetként kap egy tetszőleges típusú értéktömböt, amely megvalósítja a String metódust, és // visszaadja a megfelelő karakterlánc-tömböt, amelyet a String metódus meghívásával kapunk a bemeneti tömb minden eleméhez. func Stringify [ T Stringer ] ( s [] T ) [] string { // a T típusparaméter, a Stringer kényszertől függően, // az s tömbparaméter értéktípusa. ret = make ([] string , len ( s )) for i , v := range s { ret [ i ] = v . String () } return ret } ... v := make ([] MyType ) ... // Általános függvény hívásához meg kell adni egy konkrét típust s := Stringify [ String ]( v ) Itt a függvény Stringifyegy típusparamétert tartalmaz T, amelyet egy normál paraméter leírásában használunk s. Egy ilyen függvény meghívásához, amint az a példában is látható, meg kell adni a hívásban azt a konkrét típust, amelyre hívva van. StringerEbben a leírásban ez egy olyan megszorítás , amely megköveteli a MyType-tól, hogy olyan Stringparaméter nélküli metódust valósítson meg, amely karakterlánc-értéket ad vissza. Ez lehetővé teszi a fordító számára a " " kifejezés helyes feldolgozását v.String(). Az általános kód bevezetését a 2021 augusztusára tervezett 1.18-as verzióban jelentették be. [28]
Jelenleg két fő Go fordító létezik:
Vannak projektek is:
A Go fejlesztői környezet számos parancssori eszközt tartalmaz: a fordítást, tesztelést és csomagkezelést biztosító go segédprogramot, valamint a godoc és gofmt segédprogramokat, amelyek a programok dokumentálására és a forráskód szabványos szabályok szerinti formázására szolgálnak. Az eszközök teljes listájának megjelenítéséhez argumentumok megadása nélkül kell meghívnia a go segédprogramot. A gdb hibakereső használható programok hibakeresésére. A független fejlesztők számos eszközt és könyvtárat kínálnak a fejlesztési folyamat támogatására, főként a kódelemzés, tesztelés és hibakeresés megkönnyítésére.
Jelenleg két IDE érhető el, amelyek eredetileg a Go nyelvre összpontosítanak – ez a szabadalmaztatott GoLand [1] (amelyet a JetBrains fejlesztett ki az IntelliJ platformon) és az ingyenes LiteIDE [2] (korábban a projekt neve GoLangIDE). A LiteIDE egy kis shell, amelyet C++ nyelven írnak Qt használatával . Lehetővé teszi a fordítást, a hibakeresést, a kód formázását és az eszközök futtatását. A szerkesztő támogatja a szintaxis kiemelését és az automatikus kiegészítést.
A Go-t az univerzális Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus és mások bővítményei is támogatják. Az automatikus kiemelés, a Go kód automatikus kiegészítése, valamint a fordító és kódfeldolgozó segédprogramok futtatása több mint kéttucatnyi általános szövegszerkesztő bővítményeként valósul meg különféle platformokon, köztük az Emacs, Vim, Notepad++, jEdit.
Az alábbiakban egy példa látható a "Hello, World!" a Go nyelven.
csomag fő import "fmt" func main () { fmt . println ( "Helló, világ!" ) }Egy példa a Unix echo parancs megvalósítására :
csomag fő import ( "os" "flag" // parancssori elemző ) var omitNewLine = flag . Bool ( "n" , false , "ne nyomtatjon újsort" ) const ( Space = " " NewLine = "\n" ) func main () { flag . Elemzés () // Vizsgálja meg az argumentumlistát, és állítsa be a flags var s stringet i : = 0 esetén ; i < zászló . Narg (); i ++ { if i > 0 { s += Space } s += flag . Arg ( i ) } ha ! * omitNewLine { s += NewLine } os . Stdout . WriteString ( ek ) } ![]() | |
---|---|
Tematikus oldalak | |
Bibliográfiai katalógusokban |
Programozási nyelvek | |
---|---|
|