The Go Programming Language Specification — Deutsche Übersetzung
- Das Original:
-
https://golang.org/ref/spec
Version of August 9, 2021 (go1.17) - Diese Übersetzung:
-
https://bitloeffel.de/DOC/golang/go_spec_20210817_de.html
Stand: 17.08.2021
© 2012-21 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Attribution 3.0"-Lizenz erlaubt.
Für Fachbegriffe und ähnliches gibt es hier noch eine Wörterliste. - Achtung:
- Dieses Dokument ist nicht die offizielle Sprachreferenz. Es ist eine Übersetzung, und deshalb — wie jede Übersetzung — nur "quasi dasselbe mit anderen Worten". Maßgebend ist das Original.
Go-Sprachbeschreibung
Einleitung
Dies ist ein Referenzhandbuch für die Programmiersprache Go [aber: siehe Bemerkung im Kasten oben, A.d.Ü.]. Weitere Informationen und Dokumente finden Sie unter https://golang.org.
Go ist eine universelle Programmiersprache, die ursprünglich als Sprache für Systementwicklung entwickelt wurde. Sie hat strenge Typprüfung sowie eine Automatische Speicherbereinigung [englisch: garbage collector, A.d.Ü], und unterstützt explizit das Programmieren von Nebenläufigkeit. Programme werden aus Paketen zusammengebaut, die so konstruiert sind, dass Abhängigkeiten effizient gehandhabt werden können.
Die Grammatik ist kompakt und leicht zu analysieren, so dass sie von automatischen Werkzeugen, wie z.B. Integrierten Entwicklungsumgebungen, leicht zu analysieren ist.
Schreibweise
Die Syntax wird in der Erweiterten Backus-Naur Form (EBNF) beschrieben:
Definition = definiens_name "=" [ Ausdruck ] "." . Ausdruck = Alternative { "|" Alternative } . Alternative = Term { Term } . Term = definiens_name | vokabel [ "…" vokabel ] | Gruppierung | Option | Wiederholung . Gruppierung = "(" Ausdruck ")" . Option = "[" Ausdruck "]" . Wiederholung = "{" Ausdruck "}" .
Definitionen werden also aus Termen und den folgenden Operatoren gebildet (aufsteigend sortiert nach Vorrang):
| Auswahl () Gruppierung [] Option (0 oder 1 mal) {} Wiederholung (0 bis n mal)
Kleingeschriebene definiens_namen stehen für lexikalische Sinneinheiten.
Nicht-Terminale dagegen werden mit Binnenmajuskeln ("CamelCase") geschrieben.
Lexikalische Sinneinheiten selbst werden durch Anführungszeichen ""
oder Gravis-Zeichen ``
eingeschlossen.
Die Schreibweise a … b
steht für die Menge der Zeichen von
a
bis b
, aus der ausgewählt werden kann.
Das Auslassungszeichen …
wird auch an anderer Stelle dieser
Sprachbeschreibung benutzt, um Aufzählungen oder nicht weiter beschriebene
Kode-Schnipsel zu markieren. Das Zeichen …
ist kein Bestandteil
der Sprache Go. (Anders ist das mit der Drei-Zeichen-Folge ...
)
Quellkode
Quellkode ist UTF-8-kodierter Unicode-Text. Der Text ist nicht normalisiert, was bedeutet, dass eine einzelne Kodenummer für einen Akzent-Buchstaben sich unterscheidet von demselben Akzent-Buchstaben, der aus einem Akzent und einem Buchstaben kombiniert wurde; diese werden als zwei Kodenummern behandelt. Wir benutzen in diesem Dokument den ungenauen Begriff Zeichen und meinen damit eine Unicode-Kodenummer im Quelltext.
Jede Kodenummer ist eindeutig; zum Beispiel sind Klein- und Großbuchstaben voneinander verschiedene Zeichen.
Go-Implementierungseinschränkung: Um mit anderen Werkzeugen kompatibel zu bleiben, darf der Compiler das Zeichen NUL (U+0000) im Quellkode verbieten.
Go-Implementierungseinschränkung: Um mit anderen Werkzeugen kompatibel zu bleiben, darf der Compiler die UTF-8-kodierte Bytereihenfolgemarke (U+FEFF) im Quellkode ignorieren, wenn sie die erste Unicode-Kodenummer im Quelltext ist. Die Bytereihenfolgemarke kann für den gesamten Quellkode verboten werden.
Zeichen
Die folgenden Terme stehen für bestimmte Klassen von Unicode-Zeichen:
newline = /* die Unicode-Kodenummer U+000A */ . unicode_char = /* jede beliebige Unicode-Kodenummer ausgenommen newline */ . unicode_letter = /* eine Unicode-Kodenummer der Kategorie "Letter" */ . unicode_digit = /* eine Unicode-Kodenummer der Kategorie "Number, decimal digit" */ .
"The Unicode Standard 8.0", Section 4.5 "General Category" definiert eine Reihe von Zeichen-Kategorien. Go behandelt alle Zeichen der Buchstaben-Kategorien Lu, Ll, Lt, Lm und Lo als Unicode-Buchstaben, und die der Zahlen-Kategorie Nd als Unicode-Ziffern.
Buchstaben und Ziffern
Der Unterstrich _
(U+005F) wird als Buchstabe angesehen,
letter = unicode_letter | "_" . decimal_digit = "0" … "9" . binary_digit = "0" | "1" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
Lexikalische Bestandteile
Kommentare
Kommentare dienen der Programmdokumentation. Es gibt zwei Formen davon:
-
Zeilenkommentare beginnen mit der Zeichenfolge
//
und enden am Ende der Zeile. -
Allgemeine Kommentare beginnen mit der Zeichenfolge
/*
und enden mit dem ersten Auftreten der Zeichenfolge*/
.
Ein Kommentar kann nicht innerhalb einer Rune oder eines String-Literals oder eines Kommentars beginnen. Ein allgemeiner Kommentar ohne Zeilenvorschub wirkt wie ein Leerzeichen. Jeder andere Kommentar wirkt wie ein Zeilenvorschub.
Vokabeln
Vokabeln [englisch: tokens, A.d.Ü.] bilden das Vokabular der Sprache Go. Es gibt vier Klassen: Bezeichner, Schlüsselwörter, Operatoren und Satzzeichen, und Literale. Leerraum [englisch: white space, A.d.Ü.] kann bestehen aus Leerzeichen (U+0020), Tabulatorzeichen (U+0009), Wagenrückläufen (U+000D) und Zeilenvorschüben (U+000A), und wird ignoriert. Er dient nur als Trennzeichen zwischen Vokabeln, die sonst als eine kombinierte Vokabel gelesen würden. Darüberhinaus können Zeilenvorschub oder Dateiende das Einfügen eines Semikolons auslösen. Beim Aufbrechen einer Eingabe in Vokabeln ist die längste Zeichenfolge, die eine gültige Vokabel darstellt, die nächste Vokabel.
Semikolon
Die formale Grammatik benutzt Semikolons ";
" als Endezeichen
für eine Reihe von Definitionen. Go-Programme können die meisten dieser
Semikolons weglassen, da die beiden folgenden Regeln angewandt werden:
-
Beim Aufbrechen des Eingabestroms in Vokabeln wird unmittelbar nach der letzten Vokabel der Zeile automatisch ein Semikolon in den Vokabel-Strom eingefügt, wenn die letzte Vokabel der Zeile
- ein Bezeichner,
- ein Ganzzahl-, Gleitkomma-, Imaginär-, Runen- oder String-Literal,
- eines der Schlüsselwörter
break
,continue
,fallthrough
, oderreturn
, - oder einer der Operatoren und Begrenzer
++
,--
,)
,]
oder}
ist.
-
Damit komplexe Anweisungen in einer einzelnen Zeile möglich werden,
kann ein Semikolon vor einem schließenden "
)
" or "}
" weggelassen werden.
Um typischen Kode zu zeigen, werden in den Beispielen dieses Dokuments Semikolons gemäß den genannten Regeln weggelassen.
Bezeichner
Bezeichner benennen Programmbestandteile wie etwa Variablen oder Typen. Ein Bezeichner ist eine Folge von einem oder mehreren Buchstaben und Ziffern. Das erste Zeichen eines Bezeichners muss ein Buchstabe sein.
identifier = letter { letter | unicode_digit } .
a _x9 DieseVariableWirdExportiert αβ
Einige Bezeichner sind vordeklariert.
Schlüsselwörter
Die folgenden Schlüsselwörter sind reserviert, und dürfen nicht als Bezeichner verwendet werden:
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
Operatoren und Satzzeichen
Die folgenden Zeichenfolgen stehen für Operatoren, (den Zuweisungsoperatot mit eingeschlossen) und Satzzeichen:
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^=
Ganzzahlliterale
Ein Ganzzahlliteral ist eine Folge von mehreren Ziffern, die für eine
Ganzzahlkonstante stehen. Ein optionaler Präfix
legt eine nicht-dezimale Basiszahl fest:
0b
oder 0B
für Binärzahlen,
0
, 0o
oder 0O
für Oktalzahlen,
0x
oder 0X
für
Hexadezimalzahlen. Eine alleinstehende 0
wird als
dezimale Null angesehen.
In Hexadezimalliteralen stehen die Ziffern a
bis f
und A
bis F
für die Werte 10 bis 15.
Zur Verbesserung der Lesbarkeit darf ein Unterstrich _
nach dem Präfix und/oder zwischen aufeinanderfolgenden Ziffern stehen;
solche Unterstriche verändern nicht den Wert des Literals.
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . decimal_digits = decimal_digit { [ "_" ] decimal_digit } . binary_digits = binary_digit { [ "_" ] binary_digit } . octal_digits = octal_digit { [ "_" ] octal_digit } . hex_digits = hex_digit { [ "_" ] hex_digit } .
42 4_2 0600 0_600 0o600 0O600 // das zweite Zeichen ist der Großbuchstabe 'O' 0xBadFace 0xBad_Face 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // ist ein Bezeichner, kein Ganzzahlliteral 42_ // verboten: _ muss aufeinanderfolgende Ziffern trennen 4__2 // verboten: nur einzelne _ erlaubt 0_xBadFace // verboten: _ muss aufeinanderfolgende Ziffern trennen
Gleitkommaliterale
Ein Gleitkommaliteral ist eine dezimale oder hexadezimale Darstellung einer Gleitkommakonstanten.
Ein Gleitkommaliteral in dezimaler Darstellung besteht aus einem
ganzzahligen Anteil (Dezimalziffern), einem Dezimalpunkt, dem
gebrochenen Anteil (Dezimalziffern) und einem Exponententeil
(e
or E
gefolgt von einem optionalen
Vorzeichen und dezimalen Ziffern).
Entweder der ganzzahlige oder der gebrochene Anteil darf fehlen;
entweder der Dezimalpunkt oder der Exponent darf fehlen.
Ein Exponentenwert exp vergrößert die Mantisse (den ganzzahligen und
den gebrochenen Anteil) um 10exp.
Ein Gleitkommaliteral in hexadezimaler Darstellung besteht aus einem
Präfix 0x
oder 0X
, einem ganzzahligen Anteil
(Hexadezimalziffern), dem Radixpunkt, einem gebrochenen Anteil
(Hexadezimalziffern) und einem Exponententeil
(p
or P
gefolgt von einem optionalen
Vorzeichen und dezimalen Ziffern).
Entweder der ganzzahlige oder der gebrochene Anteil darf fehlen;
auch der Radixpunkt darf ebenfalls fehlen, aber der Exponententeil
muss vorhanden sein. (Das entspricht der in IEEE 754-2008 §5.12.3
beschriebenen Syntax.)
Ein Exponentenwert exp vergrößert die Mantisse (den ganzzahligen und
den gebrochenen Anteil) um 2exp.
Zur Verbesserung der Lesbarkeit darf ein Unterstrich _
nach dem Präfix und/oder zwischen aufeinanderfolgenden Ziffern stehen;
solche Unterstriche verändern nicht den Wert des Literals.
float_lit = decimal_float_lit | hex_float_lit . decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] | decimal_digits decimal_exponent | "." decimal_digits [ decimal_exponent ] . decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | [ "_" ] hex_digits | "." hex_digits . hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (Ganzzahlsubtraktion) 0x.p1 // verboten: Mantisse ohne Ziffer 1p-2 // verboten: p-Exponent verlangt eine hexadezimale Mantisse 0x1.5e-2 // verboten: hexadezimale Mantisse verlangt einen p-Exponenten 1_.5 // verboten: _ muss aufeinanderfolgende Ziffern trennen 1._5 // verboten: _ muss aufeinanderfolgende Ziffern trennen 1.5_e1 // verboten: _ muss aufeinanderfolgende Ziffern trennen 1.5e_1 // verboten: _ muss aufeinanderfolgende Ziffern trennen 1.5e1_ // verboten: _ muss aufeinanderfolgende Ziffern trennen
Imaginärliterale
Ein Imaginärliteral steht für den imaginären Teil einer
Komplexkonstanten. Es besteht aus einem
Ganzzahl- oder Gleitkommal-Literal, gefolgt vom
kleingeschriebenen Buchstaben i
. Der Wert des
Imaginärliterals ist der Wert des Ganzzahl- oder Gleitkommaliterals
multipliziert mit der imaginären Einheit i.
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
Um rückwärtskompatibel zu bleiben wird der Ganzzahlteil eines
Imaginärliterals, der ganz aus Dezimalziffern (und eventuell
Unterstrichen) besteht, als dezimale Ganzzahl angesehen, selbst wenn
er mit 0
beginnt.
0i 0123i // == 123i wegen Rückwärtskompatibilität 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25i
Runenliterale
Ein Runenliteral steht für eine Runenkonstante;
das ist ein Ganzzahlwert, der für eine Unicode-Kodenummer steht.
Ein Runenliteral wird dargestellt durch ein oder mehrerer Zeichen eingeschlossen
in Hochkommata, wie in 'x'
oder '\n'
.
Innerhalb der Hochkommata sind alle Zeichen erlaubt außer Zeilenvorschub oder
Hochkomma ohne Fluchtsymbol. Ein einzelnes Zeichen in Hochkommata
steht für den Unicode-Wert des Zeichens selbst, wohingegen Zeichenfolgen,
die mit Rückschräger beginnen, Werte in ganz verschiedenen Formaten darstellen.
Die einfachste Form ist ein einzelnes Zeichen in Hochkommata;
da Go-Quellkode aus UTF-8-kodierten Unicode-Zeichen besteht, können mehrere
UTF-8-kodierte Bytes für einen Ganzzahlwert stehen. Zum Beispiel enthält
das Literal 'a'
ein Byte, das für das Zeichen a
oder Unicode U+0061 oder den Wert 0x61
steht, wohingegen
'ä'
2 Bytes enthält (0xc3
0xa4
), die
für den Umlaut ä
oder U+00E4 oder den Wert 0xe4
stehen.
Verschiedene Formate mit dem Rückschräger als Fluchtsymbol
erlauben es, beliebige Werte als ASCII-Text zu kodieren.
Ganzzahlige Werte können auf 4 verschiedene Weisen als numerische Konstanten dargestellt
werden: durch \x
gefolgt von genau zwei hexadezimalen Ziffern,
durch \u
gefolgt von genau vier hexadezimalen Ziffern,
durch \U
gefolgt von genau acht hexadezimalen Ziffern und
durch einen einfachen Rückschräger \
gefolgt von genau drei Oktalziffern.
In all diesen Fällen ist der Wert des Literals der Wert, für den die Ziffern
im jeweiligen Zahlensystem stehen.
Obwohl jede dieser Darstellungen zu einer Ganzzahl führt, haben sie verschiedene
Gültigkeitsbereiche. Die Oktaldarstellung muss für einen Wert zwischen 0 und 255
(inklusive) stehen. Die Darstellungen mit \u
und \U
stehen für Unicode-Kodenummern, für die einige Werte nicht erlaubt sind,
insbesondere jene oberhalb von 0x10FFFF
sowie die Hälften von
Ersatzkodierungen.
Einige wenige Zeichen alleine hinter einem Rückschräger stehen für besondere Werte:
\a U+0007 Alarm \b U+0008 Rückwärtslöschen \f U+000C Seitenvorschub \n U+000A Zeilenvorschub \r U+000D Wagenrücklauf \t U+0009 horizontaler Tabulator \v U+000B vertikaler Tabulator \\ U+005C Rückschräger \' U+0027 Hochkomma (nur gültig innerhalb eines Runenliterals) \" U+0022 Anführungszeichen (nur gültig innerhalb eines String-Literals)
Alle anderen 2-Zeichen-Folgen sind innerhalb von Runenliteralen nicht erlaubt.
rune_lit = "'" ( unicode_value | byte_value ) "'" . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\` octal_digit octal_digit octal_digit . hex_byte_value = `\` "x" hex_digit hex_digit . little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit . big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a' 'ä' '本' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // Runenliteral mit einem einzelnen Hochkomma 'aa' // verboten: zu viele Zeichen '\xa' // verboten: zu wenige Hexadezimalziffern '\0' // verboten: zu wenige Oktalziffern '\uDFFF' // verboten: Hälfte einer Ersatzkodierung '\U00110000' // verboten: ungültige Unicode-Kodenummer
String-Literale
Ein String-Literal steht für eine String-Konstante, die man erhält, wenn man eine Folge von Zeichen verkettet. Es gibt 2 Formen: rohe und interpretierte String-Literale.
Rohe String-Literale sind Zeichenfolgen zwischen Gravis-Zeichen, wie in
`foo`
. Innerhalb dieser ist jedes Zeichen erlaubt
— außer dem Gravis-Zeichen.
Der Wert eines rohen String-Literals ist die Verkettung der uninterpretierten
(implizit UTF-8-kodierte) Zeichen zwischen den Gravis-Zeichen; insbesondere haben
Rückschräger hier keine besondere Bedeutung und der String darf Zeilenvorschübe
enthalten. Wagenrücklaufzeichen ('\r') innerhalb roher String-Literale werden
verworfen.
Interpretierte String-Literale sind Zeichenfolgen zwischen Anführungszeichen,
wie in "bar"
. Inerhalb dieser ist jedes Zeichen erlaubt
— außer Zeilenvorschub und Anführungszeichen ohne Fluchtsymbol.
Der Text zwischen diesen Zeichen bildet den Wert des Literals, wobei
Sequenzen mit dem Rückschräger als Fluchtsymbol so interpretiert werden,
wie bei den Runenliteralen auch, mit denselben
Einschränkungen.
(Ausnahme: \'
ist nicht erlaubt, \
" dagegen schon.)
Die drei-ziffrige Oktalform (\
nnn) und die zwei-ziffrige
Hexadezimalform (\x
nn) stehen für einzelne Bytes;
die anderen Formen stehen für die (möglicherweise Multi-Byte-) UTF-8-Kodierung
eines einzelnen Zeichens.
Also stehen innerhalb eines interpretierten String-Literals
\377
und \xFF
für ein einzelnes Byte mit dem Wert
0xFF
=255, während ÿ
, \u00FF
,
\U000000FF
und \xc3\xbf
für die zwei Bytes
0xc3
0xbf
der UTF-8-Kodierung des Zeichens U+00FF stehen.
string_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // gleich "abc" `\n \n` // gleich "\\n\n\\n" "\n" "\"" // gleich `"` "Hallo, Welt!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // verboten: Hälfte einer Ersatzkodierung "\U00110000" // verboten: ungültige Unicode-Kodenummer
Die folgenden Beispiele stehen alle für den gleichen String:
"日本語" // UTF-8-Eingabetext `日本語` // UTF-8-Eingabetext als rohes Literal "\u65e5\u672c\u8a9e" // die expliziten Unicode-Kodenummern "\U000065e5\U0000672c\U00008a9e" // die expliziten Unicode-Kodenummern "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // die expliziten UTF-8-Bytes
Wenn der Quellkode ein Zeichen mit zwei Kodenummern enthält, sagen wir ein kombiniertes Zeichen aus Akzent und Buchstabe, dann führt es zu einem Fehler, wenn das in einem Runenliteral versucht wird, und es ergibt zwei Zeichen in einem String-Literal.
Konstanten
Es gibt Boole'sche Konstanten, Runenkonstanten, Ganzzahlkonstanten, Gleitkommakonstanten, Komplexkonstanten und String-Konstanten. Runen-, Ganzzahl-, Gleitkomma- und Komplexkonstanten nennt man numerische Konstanten.
Der Wert einer Konstanten wird dargestellt durch
ein Runen-,
ein Ganzzahl-,
ein Gleitkomma-,
ein Imaginär- oder
ein String-Literal,
einen Bezeichner, der eine Konstante benennt,
einen konstanten Ausdruck,
eine Konversion mit einer Konstanten als Ergebnis, sowie
das Ergebnis der Anwendung einer Standardfunktion
wie unsafe.Sizeof
auf einen beliebigen Wert,
wie cap
oder len
auf bestimmte Ausdrücke,
wie real
und imag
auf eine Komplexkonstante
und wie complex
auf numerische Konstanten.
Die Boole'schen Wahrheitswerte werden repräsentiert durch die vordeklarierten
Konstanten true
und false
.
Der vordeklarierte Bezeichner iota
benennt
eine Ganzzahlkonstante.
Komplexkonstanten sind eine Form von konstanten Ausdrücken und werden im Abschnitt mit dieser Überschrift abgehandelt.
Numerische Konstanten stehen für exakte Werte beliebiger Genauigkeit und kennen keinen Überlauf. Folgerichtig gibt es keine Konstanten für die durch IEEE-754 definierten negative Null, unendlich oder NaN (keine Zahl).
Konstanten können typbehaftet oder typfrei sein.
Konstanten in Form von Literalen sowie true
, false
,
iota
, und bestimmte konstante Ausdrücke,
die nur typfreie konstante Operanden enthalten, sind typfrei.
Einer Konstanten kann explizit ein Typ zugeordnet werden durch eine Konstantendeklaration oder eine Konversion, implizit innerhalb einer Variablendeklaration oder in einer Zuweisung oder als einem Operanden in einem Ausdruck.
Es ist ein Fehler, wenn der Wert einer Konstanten nicht als Wert des zugewiesenen Typs dargestellt werden kann.
Beispielsweise kann der Konstanten 3.0
jeder Ganzzahl-
und jeder Gleitkommatyp zugeordnet werden. Dagegen kann der Konstanten
2147483648.0
(gleich 1<<31
) zwar der Typ
float32
, float64
oder uint32
zugeordnet
werden, aber nicht int32
oder string
.
Zu einer typfreien Konstanten gehört ein Standardtyp, nämlich der Typ,
in den die Konstante implizit konvertiert wird, sobald ein typbehafteter Wert
gebraucht wird, zum Beispiel in einer
Variablenkurzdeklaration wie
i := 0
, die keinen expliziten Typ enthält. Die Standardtypen für
typfreie Konstanten sind bool
, rune
,
int
, float64
, complex128
oder
string
, abhängig davon, ob es sich um Boole'sche, Runen-, Ganzzahl-,
Gleitkomma-, Komplex- oder String-Konstante handelt.
Go-Implementierungseinschränkung: Wenn auch die Sprache numerische Konstanten beliebiger Genauigkeit vorsieht, so mag ein Compiler sie intern mit eingeschränkter Genauigkeit implementieren. Doch jede Go-Implementierung muss:
- Ganzzahlkonstanten mit mindestens 256 Bit darstellen,
- Gleitkommakonstanten, incl. der Bestandteile von Komplexkonstanten, mit einer mindestens 256 Bit großen Mantisse und einem mindestens 16 Bit großen Binärexponenten mit Vorzeichen, darstellen können,
- einen Fehler melden, wenn eine Ganzzahlkonstante nicht korrekt dargestellt werden kann,
- einen Fehler melden, wenn eine Gleitkomma- oder Komplexkonstante wegen Überlauf nicht korrekt dargestellt werden kann,
- zur nächsten darstellbaren Konstanten runden, wenn eine Gleitkomma- oder Komplexkonstante wegen Begrenzung der Genauigkeit nicht korrekt dargestellt werden kann.
Diese Bedingungen gelten sowohl für Konstantenliterale als auch für Ergebnisse der Auswertung von konstanten Ausdrücken.
Variablen
Eine Variable ist ein Speicherort für einen Wert. Die Menge seiner erlaubten Werte wird festgelegt durch den Typ der Variablen.
Eine Variablendeklaration
oder, im Fall von Funktionsparametern und Rückgabewerten, die Signatur
einer Funktionsdeklaration
oder eines Funktionsliterals
reserviert Speicher für eine namensbehaftete Variable.
Aufruf der Standardfunktion new
oder das Abgreifen der Adresse eines
Verbundliterals teilt zur Laufzeit
Speicher für diese Variable zu. Solch eine anonyme Variable wird durch eine
(möglicherweise implizite) Zeigerauflösung
angesprochen.
Strukturierte Variablen vom Typ Array, Slice oder Struct haben Elemente oder Felder, die einzeln angesprochen werden können. Ein solches Element verhält sich wie eine Variable.
Der statische Typ (oder nur Typ) einer Variablen ist der Typ, der
in der Deklaration oder beim Aufruf von new
oder im Verbundliteral
angegeben wurde, oder der Typ des Elements einer strukturierten Variablen.
Variablen eines Interface-Typs haben außerdem einen davon unterschiedenen
dynamischen Typ, welcher der aktuelle Typ des Wertes zur Laufzeit ist
(solange der Wert nicht der vordeklarierte Bezeichner nil
ist,
der typfrei ist).
Der dynamische Typ kann sich während der Ausführung ändern, doch die Werte,
die in Interface-Variablen gespeichert sind, sind immer dem statischen Typ
der Variablen zuweisbar.
var x interface{} // x ist nil und hat den statischen Typ interface{} var v *T // v hat den Wert nil und den statischen Typ *T x = 42 // x hat den Wert 42 und den dynamischen Typ int x = v // x hat den Wert (*T)(nil) und den dynamischen Typ *T
Den Wert einer Variablen erhält man, indem man sie in einem Ausdruck anspricht; es ist dann der zuletzt der Variablen zugewiesene Wert. Wenn der variablen bis dahin noch kein Wert zugewiesen worden ist, ist ihr Wert der Nullwert ihres Typs.
Typen
Ein Typ legt einen Menge von Werten und Operationen auf diese Werte fest. Ein Typ kann durch einen Typnamen angesprochen werden, wenn er einen hat, oder er wird festgelegt durch ein Typliteral, das einen neuen Typ aus bereits bestehenden zusammensetzt.
Type = TypeName | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
Die Sprache selbst hat bestimmte Typnamen vor deklariert. Andere werden mit Typdeklarationen eingeführt. Zusammengesetzte Typen — Array-, Struktur-, Zeiger-, Funktions-, Interface-, Slice-, Map- und Kanaltypen — können mithilfe von Typliteralen gebildet werden.
Jeder Typ T
hat einen Ursprungstyp:
Wenn T
einer der vordeklarierten Boole'schen, numerischen
oder String-Typen oder ein Typliteral ist,
so ist der zugehörige Ursprungstyp T
selbst.
Sonst ist der Ursprungstyp von T
der Typ, auf den
T
in seiner Typdeklaration verweist.
type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 )
Der Ursprungstyp von string
, A1
, A2
,
B1
und B2
ist string
.
Der Ursprungstyp von []B1
, B3
und B4
ist []B1
.
Methodenmenge
Einem Typ ist eine (möglicherweise leere)
Methodenmenge zugeordnet.
Die Methodenmenge eines Interface-Typs
ist seine Schnittstelle.
Die Methodenmenge jedes anderen namenbehafteten Typs T
besteht aus
allen mit Empfängertyp T
deklarierten Methoden .
Die Methodenmenge des korrespondierenden Zeigertyps *T
besteht aus allen mit Empfängertyp *T
oder T
deklarierten Methoden.
(Das heißt, sie enthält auch die Methodenmenge von T
.)
Es gibt weitere Regeln, die Strukturen mit eingebetteten Feldern betreffen,
und die im Abschnitt Struktur-Typen beschrieben sind.
Alle anderen Typen besitzen eine leere Methodenmenge.
In einer Methodenmenge muss jede Methode einen
eindeutigen
Nicht-Leeren Bezeichner
Methodennamen haben.
Die Methodenmenge eines Typs entscheidet über die Interfaces, die ein Typ implementiert; die Methoden können mit einem Empfänger dieses Typs gerufen werden.
Boole'sche Typen
Ein Boole'scher Typ vertritt die Menge der Boole'schen Wahrheitswerte,
die durch die vordeklarierten Konstanten true
und false
benannt werden. Der vordeklarierte Boole'sche Typ ist bool
,
ein definierter Typ.
Numerische Typen
Ein numerischer Typ vertritt Mengen von Ganzzahl- und Gleitkommawerten. Die vordeklarierten architekturunabhängigen numerischen Typen sind:
uint8 die Menge aller 8-Bit Ganzzahlen ohne Vorzeichen (0 bis 255) uint16 die Menge aller 16-Bit Ganzzahlen ohne Vorzeichen (0 bis 65535) uint32 die Menge aller 32-Bit Ganzzahlen ohne Vorzeichen (0 bis 4294967295) uint64 die Menge aller 64-Bit Ganzzahlen ohne Vorzeichen (0 bis 18446744073709551615) int8 die Menge aller 8-Bit Ganzzahlen mit Vorzeichen (-128 bis 127) int16 die Menge aller 16-Bit Ganzzahlen mit Vorzeichen (-32768 bis 32767) int32 die Menge aller 32-Bit Ganzzahlen mit Vorzeichen (-2147483648 bis 2147483647) int64 die Menge aller 64-Bit Ganzzahlen mit Vorzeichen (-9223372036854775808 bis 9223372036854775807) float32 die Menge aller IEEE-754 32-Bit Gleitkommazahlen float64 die Menge aller IEEE-754 64-Bit Gleitkommazahlen complex64 die Menge aller Komplexzahlen mit float32 Real- und Imaginäranteil complex128 die Menge aller Komplexzahlen mit float64 Real- und Imaginäranteil byte Alias für uint8 rune Alias für int32
Der Wert einer n-Bit-Ganzzahl ist n Bits groß und wird mithilfe der Zweierkomplement-Arithmetik dargestellt.
Es gibt auch mehrere vordeklarierter numerischer Typen, deren Größe von der Go-Implementierung abhängt:
uint entweder 32 oder 64 Bit int genauso groß wie uint uintptr Ganzzahl ohne Vorzeichen, groß genug, um die uninterpretierten Bits eines Zeigerwerts aufzunehmen
Um portabel zu bleiben, sind alle numerischen Typen
definierte Typen und
unterscheiden sich deshalb voneinander,
mit den Ausnahmen byte
, welches ein
Alias für uint8
ist,
und rune
, welches ein Alias ist für int32
.
Wenn verschiedene Typen gleichzeitig in einem Ausdruck oder einer Zuweisung
vorkommen, dann ist explizites Konvertieren nötig. Zum Beispiel sind int32
und
int
nicht derselbe Typ, selbst wenn sie auf einer bestimmten
Architektur dieselbe Länge haben.
String-Typen
Ein String-Typ vertritt die Menge der String-Werte.
Ein String-Wert ist eine (möglicherweise leere) Folge von Bytes.
Die Anzahl der Bytes nennt man Länge des Strings; sie ist niemals negativ.
Strings sind unveränderlich: einmal erzeugt, ist es nicht
mehr möglich, den Inhalt eines Strings zu verändern.
Der vordeklarierte String-Typ ist string
,
ein definierter Typ.
Die Länge eines Strings s
kann mit der
Standardfunktion len
ermittelt werden. Ist der String eine Konstante, so ist die Länge eine
Konstante zum Umwandlungszeitpunkt. Auf die Bytes eines Strings kann mit
den Indices 0
bis
len(s)-1
zugegriffen werden.
Es ist nicht erlaubt, die Adresse eines solchen Elements
abzugreifen: ist s[i]
das i
-te Byte des Strings,
so ist &s[i]
ungültig.
Array-Typen
Ein Array ist eine nummerierte Folge von Elementen eines einzigen Typs, der Elementtyp genannt wird. Die Anzahl der Elemente nennt man Länge des Array; sie ist niemals negativ.
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
Die Länge ist Bestandteil des Array-Typs; sie muss eine nicht negative
Konstante sein, die als Wert vom Typ int
darstellbar ist.
Die Länge eines Arrays a
kann mit der Standardfunktion
len
ermittelt werden.
Die Elemente können durch die Ganzzahl-Indices
0 bis len(a)-1
angesprochen werden.
Arrays sind immer eindimensional, können aber zu mehrdimensionale Typen
geschachtelt werden.
[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // dasgleiche wie [2]([2]([2]float64))
Slice-Typen
Ein Slice [deutsch: Abschnitt,Scheibe,Schnitte, A.d.Ü.]
beschreibt ein zusammenhängendes Teilstück eines zugrundeliegenden Arrays
und ermöglicht den
Zugang zu einer nummerierte Folge von Elementen dieses Arrays.
Ein Slice-Typ bezeichnet die Menge aller möglichen Array-Teilstücke seines Elementtyps.
Die Anzahl der Elemente nennt man Länge des Slice; sie ist niemals negativ.
Der Wert eines nicht-initialisierten Slice ist nil
.
SliceType = "[" "]" ElementType .
Die Länge eines Slice s
kann mit der Standardfunktion
len
ermittelt werden;
anders als bei Arrays kann sie sich während der Laufzeit ändern.
Die Elemente können durch die Ganzzahl-Indices
0 bis len(s)-1
angesprochen werden.
Der Index eines Elements eines Slice kann kleiner sein als der Index
desselben Elements im zugrundeliegenden Array.
Ein einmal initialisiertes Slice ist immer an ein zugrundeliegendes Array gebunden, das die Elemente enthält. Auf diese Weise teilt sich ein Slice Speicherplatz mit seinem Array und anderen Slices zum selben Array; im Gegensatz dazu stehen voneinander verschiedene Arrays für voneinander verschiedene Speicherplätze.
Das einem Slice zugrundeliegende Array kann sich über das Ende des Slice hinaus ausdehnen.
Das Maß für diese Ausdehnung ist die Kapazität: sie ist die Summe
aus der Länge des Slice und der Restlänge des Arrays, die an das Slice
anschließt.
Ein Slice der Länge bis hin zu dieser Kapazität kann erzeugt werden, indem man
vom Original-Slice neu aufschneidet.
Die Kapazität eines Slice s
kann mit der Standardfunktion
cap(s)
ermittelt werden.
Man erzeugt einen neue initialisierten Slice-Wert für einen Elementtyp
T
mit der Standardfunktion
make
mit einem
Slice-Typ und einem Parameter für die Länge und optional einem für die Kapazität.
Ein mit make
erzeugtes Slice reserviert ein neues verstecktes Array,
auf das sich der zurückgegebene Slice-Wert bezieht. Das heißt, die Ausführung von:
make([]T, length, capacity)
erzeugt das gleiche Slice, wie die Anforderung eines Arrays mit anschließendem Aufschneiden; d.h. diese beiden Ausdrücke sind gleichbedeutend:
make([]int, 50, 100) new([100]int)[0:50]
Genau wie Arrays sind Slices immer eindimensional, können aber zu mehrdimensionale Objekten geschachtelt werden. Bei Arrays aus Arrays haben die inneren Arrays, durch die Konstruktion, immer die gleichen Längen, hingegen können sich bei Slices aus Slices (oder auch Arrays aus Slices) die inneren Längen dynamisch ändern. Darüberhinaus müssen dort die inneren Slices einzeln initialisiert werden.
Struktur-Typen
Ein Struktur ist eine Folge von namensbehafteten Elementen, die Felder genannt werden, die wiederum jeweils einen Namen und einen Typ besitzen. Feldnamen können explizit (Bezeichnerliste) oder implizit (eingebettetes Feld) festgelegt werden. Innerhalb einer Struktur müssen nicht-leere Feldnamen eindeutig sein.
StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName . Tag = string_lit .
// Eine leere Struktur. struct {} // Eine Struktur mit 6 Feldern. struct { x, y int u float32 _ float32 // Reserve A *[]int F func() }
Ein Feld, das mit einem Typ, aber nicht mit einem expliziten Feldnamen deklariert
wird, heißt eingebettetes Feld,
Ein eingebettetes Feld muss als ein Typname T
oder
als ein Zeiger auf einen Nicht-Interface-Typ *T
festgelegt sein, und
T
selbst darf kein Zeigertyp sein. Der unqualifizierte Typname
gilt dann als Feldname.
// Eine Struktur mit vier eingebetteten Feldern vom Typ T1, *T2, P.T3 und *P.T4 struct { T1 // Feldname ist T1 *T2 // Feldname ist T2 P.T3 // Feldname ist T3 *P.T4 // Feldname ist T4 x, y int // Feldnamen sind x und y }
Die folgende Deklaration ist ungültig, weil Feldnamen in einem Struktur-Typ eindeutig sein müssen:
struct { T // kollidiert mit den eingebetteten Feldern *T und *P.T *T // kollidiert mit den eingebetteten Feldern T und *P.T *P.T // kollidiert mit den eingebetteten Feldern T und *T }
Ein Feld oder eine Methode f
eines eingebetteten Feldss einer Struktur x
nennt man befördert
[englisch: promoted, A.d.Ü.],
wenn x.f
ein gültiger Selektor ist,
der das Feld oder die Methode f
benennt.
Beförderte Felder verhalten sich wie die gewöhnlichen Felder einer Struktur, nur, dass sie nicht als Feldnamen in einem Verbundliterals der Struktur benutzt werden können.
Zu einem gegebenen Struktur-Typ S
und einem
definierten Typ T
werden beförderte Methoden in der Methodenmenge der Struktur wie folgt aufgenommen:
-
Wenn
S
ein eingebettes FeldT
enthält, so enthält sowohl die Methodenmenge vonS
als auch die von*S
die beförderten Methoden des EmpfängertypsT
. Die Methodenmenge von*S
enthält außerdem die beförderten Methoden des Empfängertyps*T
. -
Wenn
S
ein eingebettes Feld*T
enthält, so enthält sowohl die Methodenmenge vonS
als auch von*S
die beförderten Methoden der EmpfängertypenT
und*T
.
Auf eine Felddeklaration darf optional eine Markierung [englisch: tag, A.d.Ü.] folgen, das für alle Felder der zugehörigen Felddeklaration zum Attribut wird. Ein leerer Markierungs-String ist äquivalent zu einer fehlenden Markierung. Markierungen können mit einem Reflexions-Interface sichtbar gemacht werden und gehören zur Typidentität von Strukturen; ansonsten werden sie ignoriert.
struct { x, y float64 "" // ein leerer Markierungs-String ist wie kein String; // "ein jeglicher String darf Markierung sein" _ [4]byte "ceci n'est pas un champ de structure" } // Eine Struktur, die dem "TimeStamp protocol buffer" entspricht. // Die Markierungs-Strings definieren die "protocol buffer"-Feldnummern; // Sie richten sich nach der Konvention, die im reflect-Paket beschrieben ist. struct { microsec uint64 `protobuf:"1"` serverIP6 uint64 `protobuf:"2"` }
Zeigertypen
Ein Zeigertyp bezeichnet die Menge aller Zeiger zu
Variablen eines vorgegebenen
Typs, der Basistyp des Zeigers genannt wird.
Der Wert eines nicht-initialisierten Zeigers ist nil
.
PointerType = "*" BaseType . BaseType = Type .
*Point *[4]int
Funktionstypen
Ein Funktionstyp bezeichnet die Menge aller Funktionen mit den gleichen
Parameter- und Ergebnistypen. Der Wert einer nicht-initialisierten
Variablen eines Funktionstyps ist nil
.
FunctionType = "func" Signature . Signature = Parameters [ Result ] . Result = Parameters | Type . Parameters = "(" [ ParameterList [ "," ] ] ")" . ParameterList = ParameterDecl { "," ParameterDecl } . ParameterDecl = [ IdentifierList ] [ "..." ] Type .
Innerhalb einer Parameter- oder Ergebnisliste müssen die Bezeichner (IdentifierList) entweder alle vorhanden oder alle abwesend sein. Wenn vorhanden, vertritt jeder Name ein Element (Parameter oder Ergebnis) des bezeichneten Typs und alle nicht-leeren Namen in der Signatur müssen eindeutig sein. Wenn nicht vorhanden, vertritt jeder Typ ein Element dieses Typs. Parameter- und Ergebnislisten stehen immer in Klammern — mit einer Ausnahme: wenn es genau ein Ergebnis ohne Namen gibt, dann darf der Ergebnistyp ohne Klammern geschrieben werden.
Der letzte Input-Parameter in der Signatur einer Funktion kann ein Typ
mit dem Präfix ...
sein.
Eine Funktion mit einem solchen Parameter nennt man variadisch,
und sie kann mit 0 oder mehr Argumenten für diesen Parameter
aufgerufen werden.
func() func(x int) int func(a, _ int, z float32) bool func(a, b int, z float32) (bool) func(prefix string, values ...int) func(a, b int, z float64, opt ...interface{}) (success bool) func(int, int, float64) (float64, *[]int) func(n int) func(p *T)
Interface-Typen
Ein Interface-Typ legt eine Methodenmenge fest,
die man seine Schnittstelle nennt. Eine Variable vom Typ Interface
kann den Wert all der Typen speichern, deren Methodenmenge eine Obermenge
dieser Schnittstelle ist.
Man sagt, solch ein Typ implementiert das Interface.
Der Wert einer nicht-initialisierten Variablen vom Typ Interface ist nil
.
InterfaceType = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" . MethodSpec = MethodName Signature . MethodName = identifier . InterfaceTypeName = TypeName .
Ein Interface-Typ kann Methoden explizit spezifizieren oder er kann Methoden anderer Interfaces mithilfe von Interface-Typnamen einbetten.
// Ein einfaches Datei-Interface. interface { Read([]byte) (int, error) Write([]byte) (int, error) Close() error }
Der Name jeder explizit spezifizierten Methode muss eindeutig und darf auch nicht der Leere Bezeichner sein.
interface { String() string String() string // Verboten: String nicht eindeutig _(x int) // Verboten: Methode brauct einen nicht-leeren Namen }
Ein Interface kann durch mehr als einen Typ implementiert werden. Wenn zum Beispiel
die beiden Typen S1
und S2
die folgenden Methoden besitzen:
func (p T) Read(p []byte) (n int, err error) func (p T) Write(p []byte) (n int, err error) func (p T) Close() error
(wobei T
sowohl für S1
als auch S2
steht),
dann wird das Datei-Interface sowohl durch S1
als auch durch S2
implementiert, egal was S1
oder S2
sonst noch an Methoden besitzen
oder teilen.
Ein Typ implementiert jedes Interface, das aus einer beliebigen Untermenge seiner Methoden besteht und kann deshalb mehrere verschiedene Interfaces implementieren. Zum Beispiel implementieren alle Typen das Leere Interface:
interface{}
Oder nehmen wir diese Interface-Beschreibung, die in einer
Typdeklaration steht, um ein Interface namens
Locker
zu definieren:
type Locker interface { Lock() Unlock() }
Wenn also S1
und S2
außerdem noch folgendes implementieren:
func (p T) Lock() { … } func (p T) Unlock() { … }
, dann implementieren sie das Locker
-Interface genauso wie das
Datei-Interface.
Ein Interface T
kann anstelle von Methodenbeschreibungen einen
(möglicherweise qualifizierten) Interface-Typnamen E
benutzen.
Dies nennt man Einbetten des Interfaces E
in T
Die Methodenmenge
von T
ist die Vereinigungsmenge
der Menge aller von T
explizit
deklarierten Methoden und den Methodenmengen aller
in T
eingebetteten Interfaces.
type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // Methoden von ReadWriter sind Read, Write und Close. type ReadWriter interface { Reader // ergänzt Methodenmenge von ReadWriter mit den Methoden von Reader Writer // ergänzt Methodenmenge von ReadWriter mit den Methoden von Writer }
Eine Vereinigngmenge von Methodenmengen enthält die (exportierten wie nicht-exporierten) Methoden jeder Methodenmenge genau einmal; Methoden mit demselben Namen müssen identische Signaturen haben.
type ReadCloser interface { Reader // ergänzt Methodenmenge von ReadCloser mit den Methoden von Reader Close() // verboten: Signaturen von Reader.Close und Close sind verschiedenare different }
Ein Interface-Typ T
darf sich nicht selbst einbetten, auch nicht
indirekt über einen anderen Interface-Typ, der T
einbettet.
// verboten: Bad darf sich selbst nicht einbetten type Bad interface { Bad } // verboten: Bad1 darf sich selbst nicht über Bad2 einbetten type Bad1 interface { Bad2 } type Bad2 interface { Bad1 }
Map-Typen
Eine Map ist eine ungeordnete Menge von Elementen eines Typs,
genannt Elementtyp, die durch eine Menge von eindeutigen
Schlüsseln eines anderen Typs, genannt Schlüsseltyp,
indiziert werden. Der Wert einer nicht-initialisierten Map ist
nil
.
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
Für Schlüsseltypen müssen die Vergleichsoperatoren
==
und !=
komplett definiert sein;
darum sind Funktionen, Maps oder Slices keine Schlüsseltypen.
Ist der Schlüsseltyp ein Interface, so müssen diese Vergleichsoperatoren
für die dynamischen Schlüsselwerte definiert sein; Nichterfüllung
führt zu einem Laufzeitfehler.
map[string]int map[*T]struct{ x, y float64 } map[string]interface{}
Die Anzahl der Map-Elemente nennt man Länge.
Für eine Map m
kann man sie mit der Standardfunktion
len
ermitteln,
sie kann sich zur Laufzeit ändern. Man kann Elemente mittels
Zuweisungen hinzufügen, man kann sie
mit Indexausdrücken abrufen, und man kann sie
mit der Standardfunktion
delete
entfernen.
Eine neue, leere Map erzeugt man mit der Standardfunktion
make
,
der man den Map-Typ und wahlweise auch einen Kapazitätshinweis
als Argumente mitgibt:
make(map[string]int) make(map[string]int, 100)
Die Anfangskapazität beschränkt die Größe nicht: Maps wachsen mit der
Anzahl der in ihnen gespeicherten Elemente, mit der Ausnahme von
nil
-Maps. Eine nil
-Map entspricht einer
leeren Map, nur dass keine Elemente hinzugefügt werden können.
Kanaltypen
Ein Kanal bietet einen Mechanismus, mit dem
nebenläufige Funktionen kommunizieren können
indem sie den Wert eines festgelegten Elementtyps
senden und empfangen.
Der Wert eines nicht-initialisierten Kanals ist nil
.
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Der optionale Operator <-
legt die Kanal-Richtung fest,
entweder fürs Senden oder fürs Empfangen. Ist keine Richtung vorgegeben,
so ist der Kanal bidirektional.
Ein Kanal kann auf nur Senden oder nur Empfangen durch
Zuweisung oder explizite
Konversion eingeschränkt werden.
chan T // kann zum Senden und Empfangen von Werten vom Typ T benutzt werden chan<- float64 // kann nur zum Senden von float64 benutzt werden <-chan int // kann nur zum Empfangen von int benutzt werden
Der Operator <-
gehört zum jeweils linkest möglichen chan
:
chan<- chan int // gleichwertig zu chan<- (chan int) chan<- <-chan int // gleichwertig zu chan<- (<-chan int) <-chan <-chan int // gleichwertig zu <-chan (<-chan int) chan (<-chan int)
Einen neuen, initialisierten Kanal erzeugt man mit der Standardfunktion
make
,
der man den Kanaltyp und wahlweise auch die Kapazität
als Argumente mitgibt:
make(chan int, 100)
Die Kapazität, angegeben in Anzahl von Elementen, legt die Größe des Kanalpuffers fest.
Ist die Kapazität nicht angegeben oder null, so handelt es sich um einen
ungepufferten Kanal und Kommunikationsoperationen gelingen nur,
wenn sowohl Sender als auch Empfänger bereit sind.
Sonst handelt es sich um einen gepufferten Kanal und Kommunikationsoperationen
gelingen ohne Blockieren, solange der Puffer
nicht voll ist (beim Senden) oder nicht leer (beim Empfangen).
Ein nil
-Kanal ist niemals bereit.
Ein Kanal kann mit der Standardfunktion
close
geschlossen werden.
In Form der Mehrfachzuweisung meldet der
Empfangsoperator auch,
ob ein empfangener Wert gesendet wurde, bevor der Kanal geschlossen wurde.
Ein einzelner Kanal kann aus beliebig vielen Goroutinen heraus benutzt werden in
Sendeanweisungen und
Empfangsanweisungen sowie in Aufrufen
der Standardfunktionen
cap
und
len
, und zwar ohne
zusätzliche Synchronisation. Kanäle arbeiten wie FIFO-Warteschlangen
(first in first out). Sendet zum Beispiel eine Goroutine Werte über einen
Kanal und eine zweite empfängt sie, so werden sie in derselben Reihenfolge
empfangen wie gesendet.
Eigenschaften von Typen und Werten
Typidentität
Zwei Typen sind entweder identisch oder verschieden.
Ein definierter Typ ist von jedem anderen Typ verschieden. Zwei andere Typen sind dann identisch, wenn ihre zugrundeliegenden Typliterale in Ihrer Struktur gleichwertig sind, das heißt, wenn sie dieselbe Literalstruktur haben und die entsprechenden Komponenten vom selben Typ sind. Im Einzelnen:
- Zwei Array-Typen sind identisch, wenn sie identische Elementtypen und die gleiche Länge haben.
- Zwei Slice-Typen sind identisch, wenn sie identische Elementtypen haben.
- Zwei Struktur-Typen sind identisch, wenn sie die gleiche Abfolge von Feldern haben, und wenn sich entsprechende Felder jeweils den gleichen Namen, identischen Typ und die gleiche Markierung haben. Nichtexportierte Feldnamen aus verschiedenen Paketen sind immer verschieden.
- Zwei Zeigertypen sind identisch, wenn ihre Basistypen identisch sind.
- Zwei Funktionstypen sind identisch, wenn sie die gleiche Anzahl von Parameter- und Ergebnistypen haben, wenn die sich entsprechenden Parameter- und Ergebnistypen identisch sind, und wenn beide Funktionen variadisch oder beide nicht-variadisch sind. Die Namen von sich entsprechenden Parametern oder Ergebnissen müssen nicht gleich sein.
- Zwei Interface-Typen sind identisch, wenn sie die gleiche Methodenmenge mit gleichen Namen und identischen Funktionstypen haben. Nichtexportierte Methodennamen aus verschiedenen Paketen sind immer verschieden. Die Reihenfolge der Methoden hat keine Bedeutung.
- Zwei Maps sind identisch, wenn sie sowohl den gleichen Schlüssel- als auch den gleichen Elementtyp haben.
- Zwei Kanaltypen sind identisch, wenn sie den gleichen Elementtyp und die gleiche Richtung haben.
Geht man von diesen Deklarationen aus:
type ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string ) type ( B0 A0 B1 []string B2 struct{ a, b int } B3 struct{ a, c int } B4 func(int, float64) *B0 B5 func(x int, y float64) *A1 ) type C0 = B0
dann sind folgende Typen identisch:
A0, A1 und []string A2 und struct{ a, b int } A3 und int A4, func(int, float64) *[]string, und A5 B0 und C0 []int und []int struct{ a, b *T5 } und struct{ a, b *T5 } func(x int, y float64) *[]string, func(int, float64) (result *[]string), und A5
B0
und B1
sind verschieden, weil sie neue Typen
mit unterschiedlichen Typdefinitionens sind;
func(int, float64) *B0
und
func(x int, y float64) *[]string
sind verschieden, weil B0
von []string
verschieden ist.
Zuweisbarkeit
Ein Wert x
ist einer Variablen T
zuweisbar ("x
ist zuweisbar zu T
"), wenn:
-
der Typ von
x
mitT
identisch ist, -
der Typ
V
vonx
undT
identische Ursprungstypen haben und mindestens einer der beiden TypenV
oderT
ein definierter Typ ist, -
T
vom Typ Interface ist undx
T
implementiert, -
x
ein bidirektionaler Kanalwert ist,T
ein Kanaltyp ist,T
und der TypV
vonx
von identischem Elementtyp sind, und mindestens einer der beiden TypenV
oderT
kein definierter Typ ist, -
x
der vordeklarierte Bezeichnernil
ist undT
vom Typ Zeiger, Funktion, Slice, Map, Kanal, oder Interface ist, oder -
x
eine typfreie Konstante ist, die durch einen Wert vom TypT
darstellbar ist.
Darstellbarkeit
Eine Konstante x
ist
durch einen Wert vom Typ T
darstellbar,
wenn eine der folgenden Bedingungen erfüllt ist:
-
x
gehört zur Menge der Werte, die durchT
festgelegt ist. -
T
ist ein Gleitkommatyp undx
kann ohne Überlauf auf die Genauigkeit vonT
gerundet werden. Der Rundungsvorgang genügt den IEEE-754-Rundungsregeln (round-to-even), nur dass danach noch eine Minus Null zu einer Null ohne Vorzeichen vereinfacht wir. Beachten Sie, dass ein konstanter Wert niemals zu einer IEEE-Minus-Null, zu einem NaN oder zu Unendlich werden kann. -
T
ist ein Komplextyp und die Komponenten vonx
, nämlichreal(x)
undimag(x)
, sind darstellbar als Werte vonT
's Komponententyp (float32
oderfloat64
).
x T x ist durch einen Wert vom Typ T darstellbar, weil: 'a' byte 97 zur Menge der Byte-Werte gehört 97 rune rune ein Alias für int32 ist, und 97 zur Menge der 32-Bit-Ganzzahlen gehört "foo" string "foo" zur Menge der String-Werte gehört 1024 int16 1024 zur Menge der 16-Bit-Ganzzahlen gehört 42.0 byte 42 zur Menge der 8-Bit-Ganzzahlen gehört 1e10 uint64 10000000000 zur Menge der 64-Bit-Ganzzahlen ohne Vorzeichen gehört 2.718281828459045 float32 2.718281828459045 zu 2.7182817 gerundet wird, was zur Menge der float32-Werte gehört -1e-1000 float64 -1e-1000 zu IEEE -0.0 gerundet und dann zu 0.0 vereinfacht wird 0i int 0 ein Ganzzahlwert ist (42 + 0i) float32 42.0 (mit dem Imaginärteil 0) zur Menge der float32-Werte gehört
x T x ist nicht durch einen Wert vom Typ T darstellbar, weil: 0 bool 0 nicht zur Menge der Boole'schen Werte gehört 'a' string 'a' eine Rune ist; es gehört nicht zur Menge der String-Werte 1024 byte 1024 nicht zur Menge der 8-Bit-Ganzzahlen ohne Vorzeichen gehört -1 uint16 -1 nicht zur Menge der 16-Bit-Ganzzahlen ohne Vorzeichen gehört 1.1 int 1.1 kein ganzzahliger Wert ist 42i float32 (0 + 42i)nicht zur Menge der float32-Werte gehört 1e1000 float64 1e1000 nach dem Runden zu IEEE +Inf überläuft
Blöcke
Ein Block ist eine eventuell leere Folge von Deklarationen und Anweisungen, eingeschlossen in geschweiften Klammern.
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
Außer den expliziten Blöcken im Quellkode gibt es noch die impliziten:
- Der All-Block enthält den gesamten Go-Quelltext.
- Jedes Paket besitzt einen Paketblock, der den gesamten Go-Quelltext dieses Pakets enthält.
- Jede Datei besitzt einen Dateiblock, der den gesamten Go-Quelltext dieser Datei enthält.
- Für jede If-, For- und Switch-Anweisung wird angenommen, dass sie sich in ihrem eigenen impliziten Block befindet.
- Jede Klausel in einer Switch- oder Select-Anweisung stellt einen impliziten Block dar.
Blöcke können geschachtelt werden. Sie beeinflussen Reichweiten.
Deklarationen und Reichweiten
Eine Deklaration bindet jeden Bezeichner, ausgenommen den Leeren Bezeichner, an eine Konstante, einen Typ, eine Variable, eine Funktion, eine Sprungmarke, oder an ein Paket. Jeder Bezeichner in einem Programm muss deklariert werden. Kein Bezeichner kann zweimal im gleichen Block deklariert werden; das gilt auch für Datei- und Paketblock.
Der Leere Bezeichner kann wie jeder andere
Bezeichner in einer Deklaration benutzt werden, aber das führt zu keiner neuen
Bindung, er ist also nicht deklariert.
Im Paketblock darf der Bezeichner init
nur in
init
-Funktions-Deklarationen
verwendet werden; auch dieser, wie der Leere Bezeichner, führt zu keiner neuen
Bindung.
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
Die Reichweite [englisch: scope, A.d.Ü.] eines deklarierten Bezeichners ist der Bereich des Quellkodes, in dem der Bezeichner die in der Deklaration bestimmte Konstante, den Typ, die Variable, die Funktion, die Sprungmarke oder das Paket bezeichnet.
In Go werden Reichweiten lexikalisch durch Blöcke festgelegt.
- Die Reichweite eines vordeklarierten Bezeichners ist der All-Block.
- Die Reichweite eines Bezeichners einer Konstanten, eines Typs, einer Variablen oder einer Funktion (aber nicht einer Methode), welcher auf oberster Ebene (also nicht innerhalb einer Funktion) deklariert ist, ist der Paketblock.
- Die Reichweite des Paketnamens eines importierten Pakets ist der Dateiblock der Datei, welche die Importdeklaration enthält.
- Die Reichweite des Bezeichners eines Methodenempfängers, eines Funktionsparameters oder einer Ergebnisvariablen ist der Funktionsrumpf.
- Die Reichweite eines Konstanten- oder Variablen-Bezeichners, welcher innerhalb einer Funktion deklariert ist, beginnt am Ende der Konstantenfestlegung (ConstSpec), der Variablenfestlegung (VarSpec) oder der Variablen-Kurzdeklaration (ShortVarDecl), und endet am Ende des innersten Blocks, der diese Deklaration enthält.
- Die Reichweite eines Typ-Bezeichners, welcher innerhalb einer Funktion deklariert ist, beginnt beim Bezeichner in der Typfestlegung (TypSpec) und endet am Ende des innersten Blocks, der diese Deklaration enthält.
Ein Bezeichner, welcher in einem Block deklariert ist, kann in einem Block innerhalb dieses Blocks erneut deklariert werden. Solange der Bezeichner in der Reichweite der inneren Deklaration auftaucht, bezeichnet er die Einheit, die dort deklariert ist.
Die Paket-Klausel ist keine Deklaration; der Paketname hat keine Reichweite. Seine Aufgabe ist es, die Dateien zu kennzeichnen, die zum selben Paket gehören, und den Standard-Paketnamen fürs Importieren festzulegen.
Reichweite von Sprungmarken
Sprungmarken werden durch markierte Anweisungen deklariert, und werden benutzt in den Break-, Continue- und Goto-Anweisungen. Es ist nicht erlaubt, eine Sprungmarke zu deklarieren, die nie benutzt wird. Anders als andere Bezeichner sind Sprungmarken nicht an Blöcke gebunden und kollidieren auch nicht mit anderen Bezeichnern, die keine Sprungmarken sind. Die Reichweite einer Sprungmarke ist der Rumpf der Funktion, in der sie deklariert ist, ohne die Funktionsrümpfe der darin enthaltenen Funktionen.
Der Leere Bezeichner
Der Leere Bezeichner wird dargestellt durch den Unterstrich _
.
Er dient als namenloser Platzhalter anstelle eines regulären Bezeichners und hat
spezielle Bedeutung in Deklarationen und
Zuweisungen.
Vordeklarierte Bezeichner
Die folgenden Bezeichner sind implizit im All-Block deklariert:
Typen: bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Konstanten: true false iota Nullwert: nil Funktionen: append cap close complex copy delete imag len make new panic print println real recover
Exportierte Bezeichner
Ein Bezeichner kann exportiert werden, um anderen Paketen darauf Zugriff zu gewähren. Ein Bezeichner wird exportiert, wenn:
- das erste Zeichen des Bezeichnernamens ein Unicode-Großbuchstabe (Unicode-Klasse "Lu") ist, und
- der Bezeichner im Paketblock deklariert ist, oder ein Feldname oder ein Methodenname ist.
Alle anderen Bezeichner werden nicht exportiert.
Eindeutigkeit von Bezeichnern
In einer gegebenen Menge von Bezeichnern wird ein Bezeichner eindeutig genannt, wenn er sich von allen anderen in der Menge unterscheidet. Zwei Bezeichner unterscheiden sich voneinander, wenn sie verschieden geschrieben werden oder wenn sie in verschiedenen Paketen erscheinen aber nicht exportiert wurden. Sonst sind sie gleich.
Konstantendeklarationen
Eine Konstantendeklaration bindet eine Liste von Bezeichnern (die Konstantennamen) an die Werte einer Liste von Konstantenausdrücken. Die Anzahl der Bezeichner muss mit der Anzahl der Ausdrücke übereinstimmen. Der n-te Bezeichner auf der linken Seite wird an den n-ten Ausdruck auf der rechten Seite gebunden.
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } .
Ist ein Typ angegeben, so sind alle Konstanten von diesem Typ, und die Ausdrücke müssen diesem Typ zuweisbar sein. Ist kein Typ angegeben, so sind die Konstanten vom jeweiligen Typ der zugehörigen Ausdrücke. Ist der Wert eines Ausdrucks eine typfreie Konstante, so bleibt auch die deklarierte Konstante typfrei und der Konstantenbezeichner benennt den Konstantenwert. Wenn beispielsweise der Ausdruck ein Gleitkommaliteral ist, dann benennt der Konstantenbezeichner eine Gleitkommakonstante, selbst wenn der gebrochene Anteil des Literals null ist.
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // typfreie Gleitkommakonstante const ( size int64 = 1024 eof = -1 // typfreie Ganzzahlkonstante ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", typfreie Ganzzahl- und Stringkonstanten const u, v float32 = 0, 3 // u = 0.0, v = 3.0
Innerhalb einer geklammerten const
-Deklarationsliste
kann die Ausdrückeliste weggelassen werden, außer beim ersten ConstSpec.
Eine solche leere Liste entspricht der letzten vorhergehenden nicht-leeren
Ausdrückeliste und dessen Typen, soweit vorhanden. Das Weglassen ist also
gleichbedeutend mit dem Wiederholen der vorhergehenden Liste.
Die Anzahl der Bezeichner muss gleich der Anzahl der Ausdrücke der
vorhergehenden Liste sein. Zusammen mit dem
iota
-Konstantengenerator ergibt das
eine einfache Deklaration von aufeinanderfolgenden Werten:
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // diese Konstante wird nicht exportiert )
Iota
Innerhalb einer Konstantendeklaration
steht der vordeklarierte Bezeichner iota
für aufeinanderfolgende
typfreie Ganzzahlkonstanten.
Ihr Wert ist die Nummer des zugehörigen
ConstSpec dieser Konstantendeklaration
und beginnt bei 0.
Damit kann man eine Menge zusammengehöriger Konstanten konstruieren:
const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 b = 1 << iota // b == 2 c = 3 // c == 3 d = 1 << iota // d == 8 ) const ( u = iota * 42 // u == 0 (typfreie Ganzzahlkonstante) v float64 = iota * 42 // v == 42.0 (Gleitkommakonstante) w = iota * 42 // w == 84 (typfreie Ganzzahlkonstante) ) const x = iota // x == 0 const y = iota // y == 0
Definitionsgemäß haben alle in demselben ConstSpec benutzten
iota
denselben Wert:
const ( bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 iota == 0 bit1, mask1 // bit1 == 2, mask1 == 1 iota == 1 _, _ // iota == 2 (nicht benutzt) bit3, mask3 // bit3 == 8, mask3 == 7 iota == 3 )
Dieses letzte Beispiel macht Gebrauch von der impliziten Wiederholung der letzten nicht-leeren Ausdrückeliste.
Typdeklarationen
Eine Typdeklaration bindet einen Bezeichner, den Typnamen, an einen Typ. Es gibt zwei Arten von Typdeklarationen: Aliasdeklarationen und Typdefinitionen.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . TypeSpec = AliasDecl | TypeDef .
Aliasdeklarationen
Eine Aliasdeklaration bindet einen Bezeichner an einen vorgegebenen Typ.
AliasDecl = identifier "=" Type .
Innerhalb der Reichweite des Bezeichners dient sie als Alias für den Typ.
type ( nodeList = []*Node // nodeList und []*Node sind identische Typen Polar = polar // Polar und polar bezeichnen identische Typen )
Typdefinitionen
Eine Typdefinition erzeugt einen neuen einzigartigen Typ mit demselben darunterligenden Typ und denselben Operationen wie der vorgegebene Typ, und bindet daran einen Bezeichner.
TypeDef = identifier Type .
Den neuen Typ nennt man einen definierten Typ. Er unterscheidet sich von allen anderen Typen, wozu auch der Typ gehört, aus dem er erzeugt wurde.
type ( Point struct{ x, y float64 } // Point and struct{ x, y float64 } sind verschiedene Typen polar Point // polar and Point bezeichnen verschiedene Typen ) type TreeNode struct { left, right *TreeNode value *Comparable } type Block interface { BlockSize() int Encrypt(src, dst []byte) Decrypt(src, dst []byte) }
Der definierte Typ kann Methoden haben, die mit ihm verbunden sind. Er erbt keine Methoden, die an den vorgegebenen Typ gebunden sind; die Methodenmenge eines Interface-Typs und der Elemente eines zusammengesetzten Typs bleibt jedoch erhalten:
// Ein Mutex ist ein Datentyp mit zwei Methoden, Lock und Unlock. type Mutex struct { /* Mutex-Felder */ } func (m *Mutex) Lock() { /* Lock-Implementierung */ } func (m *Mutex) Unlock() { /* Unlock-Implementierung */ } // NewMutex hat denselben Aufbau wie Mutex aber seine Methodenmenge ist leer. type NewMutex Mutex // Die Methodenmenge des dem PtrMutex zugrundeliegenden Typs *Mutex bleibt erhalten, // aber die Methodenmenge von PtrMutex ist leer. type PtrMutex *Mutex // Die Methodenmenge von *PrintableMutex enthält die Methoden // Lock und Unlock, die an das eingebettete Feld Mutex gebunden sind. type PrintableMutex struct { Mutex } // MyBlock ist ein Interface-Typ mit derselben Methodenmenge wie Block. type MyBlock Block
Mit Typdedefinitionen kann man neue Boole'sche, numerische oder String-Typen definieren und ihnen Methoden zuweisen:
type TimeZone int const ( EST TimeZone = -(5 + iota) CST MST PST ) func (tz TimeZone) String() string { return fmt.Sprintf("GMT+%dh", tz) }
Variablendeklarationen
Eine Variablendeklaration erzeugt eine oder mehrere Variablen, bindet daran die zugehörigen Bezeichner und gibt jeder einen Typ und einen Initialwert.
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int var U, V, W float64 var k = 0 var x, y float32 = -1, -2 var ( i int u, v, s = 2.0, 3.0, "bar" ) var re, im = complexSqrt(-1) var _, found = entries[name] // "Nachschlagen" in der Map; nur "found" interessiert
Wenn eine Liste von Ausdrücken angegeben ist, dann werden die Variablen entsprechend den Zuweisungsregeln initialisiert, Sonst wird jede Variable mit ihrem Nullwert initialisiert.
Ist der Typ angegeben, so wird jeder Variablen dieser Typ zugeordnet.
Sonst wird jeder Variablen der Typ des entsprechenden Initialwerts zugeordnet.
Ist der Wert eine typfreie Konstante, so wird er zuerst implizit
zu seinem Standardtyp
konvertiert;
wenn sie also ein typfreier Boole'scher Wert ist, wird sie
zunächst implizit zum Typ bool
konvertiert.
Der vordeklarierte Wert nil
kann
nicht zum Initialisieren einer Variable ohne expliziten Typ benutzt werden.
var d = math.Sin(0.5) // d ist float64 var i = 42 // i ist int var t, ok = x.(T) // t ist T, ok ist bool var n = nil // verboten
Go-Implementierungseinschränkung: Ein Compiler kann es verbieten, eine Variable innerhalb eines Funktionsrumpfs zu deklarieren, wenn die Variable nie benutzt wird.
Variablenkurzdeklarationen
Eine Variablenkurzdeklaration schreibt man so:
ShortVarDecl = IdentifierList ":=" ExpressionList .
Es ist die Kurzform einer Variablendeklaration mit Ausdrücken fürs Initialisieren aber ohne Angabe eines Typs.
"var" IdentifierList = ExpressionList .
i, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w, _ := os.Pipe() // os.Pipe() gibt ein Paar verbundener Dateien zurück, // sowie einen error-Wert _, y, _ := coord(p) // coord() gibt drei Werte zurück; nur die y-Koordinate interessiert
Anders als die normale Variablendeklaration darf die Variablenkurzdeklaration Variablen redeklarieren, wenn sie vorher im selben Block (oder in den Parameterlisten, wenn der Funktionsrumpf der Block ist) mit demselben Typ deklariert worden sind und mindestens eine der nicht-leeren Variablen neu ist. Daraus folgt, dass eine erneute Deklaration nur in Mehrfach-Variablenkurzdeklaration vorkommen kann. Eine erneute Deklaration führt keine neue Variable ein; sie weist der ursprünglichen Variablen nur einen neuen Wert zu.
field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // deklariert offset erneut a, a := 1, 2 // verboten: entweder Doppeldeklaration von a, oder keine neue Variable, wenn bereits vorher schon deklariert
Variablenkurzdeklarationen dürfen nur innerhalb von Funktionen erscheinen. In einigen Fällen, wie etwa als Startschritt in If-, For oder Switch-Anweisungen können sie benutzt werden, um lokale temporäre Variablen zu deklarieren.
Funktionsdeklarationen
Eine Funktionsdeklaration bindet einen Bezeichner, den Funktionsnamen, an eine Funktion.
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block .
Wenn die Signatur einer Funktion Ergebnisparameter deklariert, dann muss die Anweisungsliste des Funktionsrumpfs mit einer Terminierenden Anweisung enden.
func IndexRune(s string, r rune) int { for i, c := range s { if c == r { return i } } // verboten: return fehlt }
Eine Funktionsdeklaration darf den Funktionsrumpf weglassen. Solcherart liefert sie die Signatur für eine außerhalb von Go implementierte Funktion, etwa einer Assembler-Routine.
func min(x int, y int) int { if x < y { return x } return y } func flushICache(begin, end uintptr) // extern implementiert
Methodendeklarationen
Eine Methode ist eine Funktion mit einem Empfänger. Eine Methodendeklaration bindet einen Bezeichner, den Methodennamen, an eine Methode und verknüpft die Methode mit dem Basistyp des Empfängers.
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
Der Empfänger wird durch einen eigenen Parameterabschnitt vor dem Methodennamen
festgelegt. Dieser Abschnitt muss einen einzigen
(nicht-variadischen) Parameter, nämlich den Empfänger, deklarieren.
Sein Typ muss ein definierter Typ
T
oder ein Zeiger auf einen definierten Typ
T
sein.
T
nennt man Basistyp des Empfängers;
er darf kein Zeiger- oder Interface-Typ sein und er muss im selben
Paket wie die Methode definiert sein.
Man sagt, die Methode sei an den Empfänger-Basistyp gebunden,
und der Methodenname ist nur innerhalb von
Selektoren für die Typen T
oder *T
sichtbar.
Ein nicht-leerer Bezeichner für einen Empfänger in der Methodensignatur muss eindeutig sein. Wird auf den Empfängerwert im Methodenrumpf nicht zugegriffen, so kann sein Bezeichner in der Deklaration weggelassen werden. Dasselbe gilt auch ganz allgemein für Parameter von Funktionen und Methoden.
Für einen Basistyp müssen die Bezeichner, ohne den leeren, eindeutig sein. Ist der Basistyp ein Struktur-Typ, so müssen sich die Methoden- und die nicht-leeren Feldnamen unterscheiden.
Für einen gegebenen definierten Typ Point
bindet die Deklaration:
func (p *Point) Length() float64 { return math.Sqrt(p.x * p.x + p.y * p.y) } func (p *Point) Scale(factor float64) { p.x *= factor p.y *= factor }
die Methoden Length
und Scale
mit dem Empfängertyp
*Point
an den Basistyp Point
.
Der Typ einer Methode ist der Typ einer Funktion mit dem Empfänger als
erstem Argument. Zum Beispiel hat die Methode Scale
den Typ:
func(p *Point, factor float64)
Allerdings ist eine so deklarierte Funktion keine Methode.
Ausdrücke
Ausdruck bezeichnet die Berechnung eines Wertes durch die Anwendung von Operatoren und Funktionen auf Operanden.
Operanden
Operanden bezeichnen die einzelnen Werte in einem Ausdruck. Ein Operand kann ein Literal sein, ein (möglicherweise qualifizierter) Nicht-leerer Bezeichner für eine Konstante, Variable oder Funktion, oder ein Klammerausdruck.
Der Leere Bezeichner darf nur auf der linken Seite einer Zuweisung erscheinen.
Operand = Literal | OperandName | "(" Expression ")" . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent.
Qualifizierte Bezeichner
Ein qualifizierter Bezeichner ist ein Bezeichner, der mit einen Paketnamen als Präfix qualifiziert wird. Weder der Paketname noch der Bezeichner dürfen leer sein.
QualifiedIdent = [ PackageName "." ] identifier .
Ein qualifizierter Bezeichner greift auf einen Bezeichner in einem anderen Paket durch, welches importiert worden sein muss. Dieser Bezeichner wiederum muss exportiert und im Paket-Block dieses Pakets deklariert sein.
math.Sin // bezeichnet die Funktion Sin im Paket math
Verbundliterale
Verbundliterale bilden Werte für Strukturen, Arrays, Slices und Maps und erzeugen einen neuen Wert jedesmal dann, wenn sie ausgewertet werden. Sie bestehen aus dem Typ des Literals, gefolgt von einer Liste aus Verbundelementen. Jedem dieser Elemente kann ein zugehöriger Schlüssel vorausgehen.
CompositeLit = LiteralType LiteralValue . LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | SliceType | MapType | TypeName . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue .
Der Ursprungstyp des Literals muss ein Struktur-, Array-, Slice- oder ein Map-Typ sein (Die Grammatik verlangt diese Einschränkung, solange der Typ nicht in Form des Typnamens gegeben wird). Die Typen der Schlüssel und der Elemente müssen zu den jeweiligen Feld-, Element- und Schlüsseltypen zuweisbar sein; es erfolgt keine Konversion. Der Schlüssel wird als Feldname für Struktur-Literale, als Index für Array- und Slice-Literale sowie als Schlüssel für Map-Literale interpretiert. Alle Elemente eines Map-Literals müssen einen Schlüssel haben. Es ist ein Fehler, wenn mehrere Elemente mit demselben Feldnamen oder demselben konstanten Schlüsselwert festgelegt werden. Zu nicht-konstanten Map-Schlüsseln steht etwas im Abschnitt Auswertungsreihenfolge.
Für Struktur-Literale gelten die folgenden Regeln:
- Ein Schlüssel muss ein Feldname sein, der im Struktur-Typ deklariert ist.
- Eine Elementliste, die keine Schlüssel enthält, muss Elemente für jedes Feld der Struktur auflisten, und zwar in der Reihenfolge, in der die Felder deklariert wurden.
- Sobald ein Element einen Schlüssel hat, müssen alle Elemente einen Schlüssel haben.
- Eine Elementliste mit Schlüsseln braucht nicht für jedes Feld der Struktur ein Element. Weggelassene Felder bekommen ihren Nullwert zugewiesen.
- Ein Literal kann ohne Elementliste auskommen; solch ein Literal bekommt den Nullwert für seinen Typ.
- Es ist ein Fehler, ein Element für ein nicht exportiertes Feld eines anderen Pakets anzugeben.
Sind diese Deklarationen gegeben:
type Point3D struct { x, y, z float64 } type Line struct { p, q Point3D }
, so ist folgendes möglich:
origin := Point3D{} // Nullwert für Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // Nullwert für line.q.x
Für Array- und Slice-Literale gelten folgende Regeln:
- Jedem Element ist eine Ganzzahl zugeordnet, die seine Position innerhalb des Arrays markiert.
-
Ein Element mit einem Schlüssel benutzt diesen Schlüssel als Index; der Schlüssel
muss eine nicht-negative Konstante sein, die durch einen Wert vom Typ
int
darstellbar ist; wenn sie typbehaftet ist, muss der Typ ein Ganzzahltyp sein. - Ein Element ohne Schlüssel, benutzt den Index des Vorgängerelements plus eins. Hat das erste Element keinen Schlüssel, dann ist sein Index null.
Abgreifen der Adresse eines Verbundliterals erzeugt einen Zeiger auf eine eindeutige Variable, die mit dem Literalwert initialisiert wird.
var pointer *Point3D = &Point3D{y: 1000}
Beachten Sie, dass der Nullwert für einen Slice- oder Maptyp nicht dasselbe ist, wie ein initialisierter aber leerer Wert desselben Typs. Entsprechend hat das Abgreifen der Adresse eines leeren Slice- oder Mapliterals nicht dieselbe Wirkung wie das Allozieren eines neuen Slice- oder Mapwerts mit new.
p1 := &[]int{} // p1 zeigt auf ein initialisiertes, leeres Slice mit dem Wert []int{} und der Länge 0 p2 := new([]int) // p2 zeigt auf ein uninitialisiertes Slice mit dem Wert nil und der Länge 0
Die Länge eines Array-Literals ist die im Literaltyp angegebene.
Wenn durch das Literal weniger Elemente angegeben werden, als die Länge vorgibt,
so werden die fehlenden Elemente auf den Nullwert ihres Typs gesetzt.
Es ist ein Fehler, Elemente mit Indexwerten außerhalb des Indexbereichs anzugeben.
Die Schreibweise ...
setzt die Arraylänge auf den höchsten Elementindex
plus eins.
buffer := [10]string{} // len(buffer) == 10 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6 days := [...]string{"Sat", "Sun"} // len(days) == 2
Ein Slice-Literal beschreibt das gesamte zugrundeliegende Array-Literal. Also sind sowohl Länge als auch Kapazität des Slice-Literals gleich dem höchsten Elementindex plus eins. Ein Slice-Literal hat die Form:
[]T{x1, x2, … xn}
und ist eine verkürzte Schreibweise für das Aufschneiden [englisch: to slice, A.d.Ü.] eines Arrays:
tmp := [n]T{x1, x2, … xn} tmp[0 : n]
Innerhalb eines Verbundliterals vom Array-, Slice- oder Map-Typ T
dürfen Elemente oder Schlüssel von Maps, die wiederum Verbundliterale sind,
den Literaltyp weglassen, wenn er gleich dem Element- oder Schlüsseltyp
T
ist.
Ähnlich dürfen Elemente oder Schlüssel, die Adressen von Verbundliteralen sind,
&T
weglassen, wenn der Element- oder Schlüsseltyp
*T
ist.
[...]Point{{1.5, -3.5}, {0, 0}} // gleich [...]Point{Point{1.5, -3.5}, Point{0, 0}} [][]int{{1, 2, 3}, {4, 5}} // gleich [][]int{[]int{1, 2, 3}, []int{4, 5}} [][]Point{{{0, 1}, {1, 2}}} // gleich [][]Point{[]Point{Point{0, 1}, Point{1, 2}}} map[string]Point{"orig": {0, 0}} // gleich map[string]Point{"orig": Point{0, 0}} map[Point]string{{0, 0}: "orig"} // gleich map[Point]string{Point{0, 0}: "orig"} type PPoint *Point [2]*Point{{1.5, -3.5}, {}} // gleich [2]*Point{&Point{1.5, -3.5}, &Point{}} [2]PPoint{{1.5, -3.5}, {}} // gleich [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}
Eine Mehrdeutigkeit entsteht, wenn ein Verbundliteral, in der Typnamenschreibweise, als Operand zwischen dem Schlüsselwort und der öffnenden geschweiften Klammer einer If-, For- oder Switch-Anweisung auftaucht, und nicht in runde, eckige oder geschweifte Klammern eingeschlossen ist. In diesem seltenen Fall würde die öffnende geschweifte Klammer des Literals fälschlicherweise als Einleitung eines Anweisungsblocks interpretiert werden. Um die Mehrdeutigkeit aufzuheben, muss das Verbundliteral in runden Klammern stehen:
if x == (T{a,b,c}[i]) { … } // oder if (x == T{a,b,c}[i]) { … }
Es folgen Beispiele für gültige Array-, Slice- und Map-Literale:
// Liste aus Primzahlen primes := []int{2, 3, 5, 7, 9, 2147483647} // vowels[ch] ist true, wenn ch ein Selbstlaut ist vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true} // Das Array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1} filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1} // Frequenzen in Hz für die wohltemperierte Skala (A4 = 440Hz) noteFrequency := map[string]float32{ "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83, "G0": 24.50, "A0": 27.50, "B0": 30.87, }
Funktionsliterale
Ein Funktionsliteral steht für eine anonyme Funktion.
FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }
Ein Funktionsliteral kann einer Variablen zugewiesen oder direkt aufgerufen werden.
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK }(replyChan)
Funktionsliterale sind Funktionsabschlüsse [englisch: closure, A.d.Ü.]: sie können auf Variablen verweisen, die in einer sie umschließenden Funktion definiert sind. Die umschließende Funktion und das Funktionsliteral teilen sich die Variablen; diese leben solange sie erreichbar sind.
Primäre Ausdrücke
Primäre Ausdrücke sind die Operanden für unäre und binäre Ausdrücke.
PrimaryExpr = Operand | Conversion | MethodExpr | PrimaryExpr Selector | PrimaryExpr Index | PrimaryExpr Slice | PrimaryExpr TypeAssertion | PrimaryExpr Arguments . Selector = "." identifier . Index = "[" Expression "]" . Slice = "[" [ Expression ] ":" [ Expression ] "]" | "[" [ Expression ] ":" Expression ":" Expression "]" . TypeAssertion = "." "(" Type ")" . Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x 2 (s + ".txt") f(3.1415, true) Point{1, 2} m["foo"] s[i : j + 1] obj.color f.p[i].x()
Selektoren
Für einen primären Ausdruck
x
, der kein Paketname ist,
bezeichnet der Selektorausdruck:
x.f
das Feld oder die Methode f
des Werts, der mit
x
(oder manchmal mit *x
; siehe unten) bezeichnet ist.
Der Bezeichner f
wird (Feld- oder Methoden-) Selektor genannt;
er darf nicht der Leere Bezeichner sein.
Der Typ des Ausdrucks ist der Typ von f
.
Für den Fall, dass x
ein Paketname ist, siehe: Abschnitt
"Qualifizierte Bezeichner".
Ein Selektor f
kann ein Feld oder eine Methode f
eines
Typs T
bezeichnen, oder ein Feld oder Methode f
eines
in T
enthaltenen eingebetteten Felds.
Man nennt die Anzahl eingebetteter Felder, die bis zum Erreichen von f
zu
durchschreiten ist, seine Tiefe in T
.
Die Tiefe eines in T
deklarierten Feldes/einer Methode f
ist null.
Die Tiefe eines in einem eingebetteten Feld A
in T
deklarierten Feldes/einer Methode f
ist die Tiefe von f
in A
plus Eins.
Die folgenden Regeln gelten für Selektoren.
-
Wenn
T
kein Zeiger- oder Interface-Typ ist, dann bezeichnetx.f
für einen Wertx
vom TypT
oder*T
ein Feld oder eine Methode in der geringsten Tiefe, in der ein solchesf
auftritt. Wenn es nicht genau einf
mit der geringsten Tiefe gibt, dann ist der Selektorausdruck nicht erlaubt. -
Wenn ein Wert
x
vom TypI
ist, undI
ein Interface-Typ, dann bezeichnetx.f
die Methode mit dem Namenf
des dynamischen Werts vonx
. Enthält die Methodenmenge vonI
keine Methode mit dem Namenf
, so istx.f
nicht erlaubt. -
Ausnahme: Wenn der Typ von
x
ein definierter Zeigertyp und(*x).f
ein gültiger Selektorausdruck ist, der ein Feld, nicht eine Methode) bezeichnet, dann istx.f
die Kurzform für(*x).f
. -
In allen anderen Fällen ist
x.f
nicht erlaubt. -
Wenn
x
von einem Zeiger-Typ ist, den Wertnil
hat, undx.f
das Feld einer Struktur bezeichnet, dann führen Zuweisungen oder Auswertungen vonx.f
zu einem Laufzeitfehler. -
Wenn
x
von einem Interface-Typ ist und den Wertnil
hat, so führt der Aufruf oder das Auswerten der Methodex.f
zu einem Laufzeitfehler.
Sind zum Beispiel folgende Deklarationen gegeben:
type T0 struct { x int } func (*T0) M0() type T1 struct { y int } func (T1) M1() type T2 struct { z int T1 *T0 } func (*T2) M2() type Q *T2 var t T2 // mit t.T0 != nil var p *T2 // mit p != nil und (*p).T0 != nil var q Q = p
so kann man schreiben:
t.z // t.z t.y // t.T1.y t.x // (*t.TO).x p.z // (*p).z p.y // (*p).T1.y p.x // (*(*p).T0).x q.x // (*(*q).T0).x (*q).x ist ein gültiger Feldselektor p.M0() // ((*p).T0).M0() M0 erwartet *T0 Empfänger p.M1() // ((*p).T1).M1() M1 erwartet T1 Empfänger p.M2() // p.M2() M2 erwartet *T2 Empfänger t.M2() // (&t).M2() M2 erwartet *T2 Empfänger, siehe Abschnitt "Funktionsaufrufe"
Methodenausdrücke
Wenn M
Teil der Methodenmenge
eines Typs T
ist, dann ist T.M
eine Funktion,
die wie eine reguläre Funktion mit denselben Argumenten wie M
aufgerufen werden kann, mit einem zusätzlichen Argument vorneweg, welches den
Empfänger der Methode angibt.
MethodExpr = ReceiverType "." MethodName . ReceiverType = Type .
Gegeben sei ein Struktur-Typ T
mit zwei Methoden, nämlich
Mv
mit einem Empfänger vom Typ T
und
Mp
mit einem Empfänger vom Typ *T
:
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // Empfänger ist ein Wert func (tp *T) Mp(f float32) float32 { return 1 } // Empfänger ist ein Zeiger var t T
Der Ausdruck
T.Mv
ergibt eine Funktion gleichwertig zu Mv
, nur mit einem expliziten
Empfänger als erstem Argument; ihre Signatur ist:
func(tv T, a int) int
Diese Funktion kann ganz normal, mit einem expliziten Empfänger, gerufen werden; diese fünf Aufrufe sind also gleichwertig:
t.Mv(7) T.Mv(t, 7) (T).Mv(t, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7)
Analog ergibt der Ausdruck:
(*T).Mp
einen Funktionswert, der für Mp
steht, mit der Signatur:
func(tp *T, f float32) float32
Aus einer Methode mit einem Wert als Empfänger lässt sich eine Funktion mit einem Zeiger als Empfänger ableiten, so dass:
(*T).Mv
einen Funktionswert ergibt, der für Mv
steht, mit der Signatur:
func(tv *T, a int) int
Eine solche Funktion löst den Zeiger auf, um daraus den Wert zu ermitteln, der als Empfänger an die zugrundeliegende Methode weitergereicht wird; der Wert, dessen Adresse beim Funktionsaufruf übergeben wird, wird durch die Methode nicht überschrieben.
Übrig bleibt der Fall einer Wert empfangenden Funktion anstelle einer Zeiger empfangenden Methode: das ist nicht erlaubt, da Zeiger empfangende Methoden nicht Teil der Methodenmenge des Werttyps sind.
Funktionswerte, die von Methoden abgeleitet sind, werden wie Funktionen gerufen;
der Empfänger wird dem Aufruf als erstes Argument mitgegeben.
Für ein gegebenes f := T.Mv
wird also f
durch
f(t, 7)
und nicht durch t.f(7)
gerufen.
Um eine Funktion zu konstruieren, die den Empfänger einbindet, benutzt man
ein Funktionsliteral oder einen
Methodenwert.
Es ist erlaubt, einen Funktionswert von der Methode eines Interface-Typs abzuleiten. Die sich ergebende Funktion bekommt explizit einen Empfänger dieses Interface-Typs.
Methodenwerte
Wenn der Ausdruck x
vom statischen Typ T
ist und
M
zur Methodenmenge von T
gehört,
so nennt man x.M
einen Methodenwert.
Der Methodenwert x.M
ist ein Funktionswert, der mit denselben
Argumenten wie der Methodenaufruf x.M
aufrufbar ist.
Der Ausdruck x
wird während der Auswertung des Methodenwerts
ausgewertet und gesichert; die gesicherte Kopie wird dann als Empfänger aller
Aufrufe benutzt, die eventuell später ausgeführt werden.
Der Typ T
kann ein Interface- oder ein anderer Typ sein.
Stellen Sie sich — wie oben in der Erörterung der
Methoden-Ausdrücke — einen Struktur-Typ vor,
mit zwei Methoden, nämlich Mv
mit dem Empfänger vom Typ
T
und Mp
mit dem Empfänger vom Typ *T
:
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // Wert ist Empfänger func (tp *T) Mp(f float32) float32 { return 1 } // Zeiger ist Empfänger var t T var pt *T func makeT() T
Der Ausdruck
t.Mv
ergibt einen Funktionswert vom Typ
func(int) int
Diese beiden Aufrufe sind gleichwertig:
t.Mv(7) f := t.Mv; f(7)
Ähnlich, ergibt der Ausdruck
pt.Mp
einen Funktionswert vom Typ
func(float32) float32
Wie bei Selektoren wird beim Benutzen einer Methode,
die einen Wert als Empfänger hat und keine Interface-Methode ist, ein Zeiger als
Empfänger automatisch aufgelöst [dereferenziert, A.d.Ü.]:
pt.Mv
ist gleichwertig zu (*pt).Mv
.
Wie bei Methodenaufrufen wird beim Benutzen einer Methode,
die einen Zeiger als Empfänger hat und keine Interface-Methode ist, von einem
adressierbaren Wert automatisch die Adresse genommen [referenziert, A.d.Ü.]:
t.Mp
ist gleichwertig zu (&t).Mp
.
f := t.Mv; f(7) // wie t.Mv(7) f := pt.Mp; f(7) // wie pt.Mp(7) f := pt.Mv; f(7) // wie (*pt).Mv(7) f := t.Mp; f(7) // wie (&t).Mp(7) f := makeT().Mp // verboten: Ergebnis von makeT() ist nicht adressierbar
Über diese Beispiele ohne Interfaces-Typen hinaus, ist es erlaubt, einen Methodenwert von dem Wert eines Interface-Typs zu erzeugen:
var i interface { M(int) } = myVal f := i.M; f(7) // like i.M(7)
Indexausdrücke
Ein primärer Ausdruck der Form:
a[x]
bezeichnet dasjenige Element von Array, Array-Zeiger, Slice, String oder Map
a
, das durch x
indiziert wird. Der Wert x
wird
Index oder Map-Schlüssel genannt, je nachdem.
Es gelten die folgenden Regeln:
Wenn a
keine Map ist, git:
-
Es muss der Index
x
ein Ganzzahlwert oder eine typfreie Konstante sein. -
Ein konstanter Index darf nicht negativ sein und
darstellbar durch einen Wert vom Typ
int
-
Einem konstanten, typfreien Index wird der Typ
int
zugewiesen. -
Der Index
x
ist bereichszugehörig, wenn0 <= x < len(a)
, sonst ist er bereichsfremd.
Für a
vom Array-Typ A
gilt:
- Ein konstanter Index muss bereichszugehörig sein.
-
Wenn zur Laufzeit
x
bereichsfremd, dann führt das zu einem Laufzeitfehler. -
Es ist
a[x]
das Arrayelement zum Indexx
und der Typ vona[x]
ist der Elementtyp vonA
.
Wenn a
Zeiger auf einen Array-Typ ist, gilt:
-
a[x]
ist die Abkürzung von(*a)[x]
.
Für a
vom Slice-Typ S
gilt:
-
Wenn zur Laufzeit
x
bereichsfremd, dann führt das zu einem Laufzeitfehler. -
a[x]
ist das Slice-Element zum Indexx
unda[x]
ist vom Elementtyp vonS
.
Wenn a
ein String-Typ ist, gilt:
-
Ein konstanter Index muss bereichszugehörig sein,
wenn auch der String
a
konstant ist. -
Wenn zur Laufzeit
x
bereichsfremd ist, dann führt das zu einem Laufzeitfehler. -
Es ist
a[x]
der nicht-konstante Bytewert zum Indexx
und der Typ vona[x]
istbyte
. -
Dem
a[x]
darf nichts zugewiesen werden.
Wenn a
vom Map-Typ M
ist, gilt:
-
Der Typ von
x
muss dem Schlüsseltyp vonM
zuweisbar sein. -
Enthält die Map einen Eintrag mit Schlüssel
x
, so ista[x]
das Element der Map zum Schlüsselx
und der Typ vona[x]
ist der Elementtyp vonM
. -
Ist die Map
nil
oder enthält keinen Eintrag mit Schlüsselx
, dann ista[x]
der Nullwert für den Elementtyp vonM
.
Andere a[x]
sind nicht erlaubt.
Ein Indexausdruck einer Map a
vom Typ map[K]V
,
der in einer Zuweisung oder Initialisierung
in der besonderen Form:
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
benutzt wird, erzeugt einen zusätzlichen typfreien Boole'schen Wert.
Der Wert von ok
ist true
, wenn der Schlüssel
x
in der Map existiert, andernfalls false
.
Zuweisen zu einem Element einer nil
-Map führt zu einem
Laufzeitfehler.
Slice-Ausdrücke
Slice-Ausdrücke erzeugen eine Teilkette [englisch: substring, A.d.Ü.] oder ein Slice aus einem String, Array, Zeiger auf ein Array oder Slice. Es gibt zwei Varianten: die einfache Form, die eine untere und eine obere Grenze festlegen, und die vollständige Form, die außerdem eine Kapazitätsgrenze angibt.
Einfache Slice-Ausdrücke
Für einen String, ein Array, einen Zeiger auf ein Array oder ein Slice
a
erzeugt:
a[low : high]
eine Teilkette oder ein Slice. Die Indices
low
und high
bestimmen welche Elemente des Operanden
a
zum Ergebnis gehören. Der Index des Ergebnisses beginnt bei 0,
die Länge ist high
- low
.
Nach dem Aufschneiden [englisch: slicing, A.d.Ü.] des Arrays a
:
a := [5]int{1, 2, 3, 4, 5} s := a[1:4]
hat das Slice s
den Typ []int
, die Länge 3,
die Kapazität 4 und die Elemente:
s[0] == 2 s[1] == 3 s[2] == 4
Für mehr Komfort ist es erlaubt, Indices wegzulassen. Fehlt low
,
dann wird 0, fehlt high
, dann wird die Länge des aufgeschnittenen
Operanden angenommen:
a[2:] // gleich a[2 : len(a)] a[:3] // gleich a[0 : 3] a[:] // gleich a[0 : len(a)]
Wenn a
ein Zeiger auf ein Array ist, dann ist a[low : high]
die Abkürzung für (*a)[low : high]
.
Für Arrays oder Strings sind die Indices bereichszugehörig, wenn
0 <= low <= high <= len(a)
, andernfalls sind sie bereichsfremd.
Für Slices ist die Obergrenze für den Index die Kapazität cap(a)
anstelle der Länge.
Ein konstanter Index muss nicht-negativ und
durch einen Wert vom Typ int
darstellbar sein; für Arrays und
konstante Strings müssen konstante Indices ebenfalls bereichszugehörig sein.
Wenn beide Indices konstant sind, muss gelten: low <= high
.
Wenn zur Laufzeit die Indices bereichsfremd sind, dann führt das zu einem
Laufzeitfehler.
Typfreie Strings ausgenommen — wenn der aufgeschnittene Operand ein String oder Slice ist, dann ist das Ergebnis des Aufschneidens ein nicht-konstanter Wert vom gleichen Typ. Bei typfreien String-Operanden ist das Ergebnis ein nicht-konstanter vom Typ String. Ist der aufgeschnittene Operand ein Array, dann muss er adressierbar sein, und das Ergebnis des Aufschneidens ist ein Slice mit dem gleichen Elementtyp wie das Array.
Ist der aufgeschnittene Operand eines gültigen Slice-Ausdrucks ein nil
-Slice,
so ist auch das Ergebnis ein nil
-Slice. Ansonsten,
wenn das Ergebnis ein Slice ist, teilen sich Ergebnis und Operand
das zugrundeliegende Array.
var a [10]int s1 := a[3:7] // zugrundeliegendes Array von s1 ist das Array a; &s1[2] == &a[5] s2 := s1[1:4] // zugrundeliegendes Array von s2 ist das zugrundeliegende Array von s1, also a; &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; alle beziehen sich auf dasselbe zugrundeliegende Arrayelement
Vollständige Slice-Ausdrücke
Für ein Array, einen Zeiger auf ein Array oder ein Slice a
(aber nicht für
einen String) erzeugt:
a[low : high : max]
ein Slice desselben Typs, der gleichen Länge und den gleichen Elementen wie der einfache
Slice-Ausdruck a[low : high]
. Zusätzlich legt es die Kapazität des sich
ergebenden Slice als max - low
fest. Nur der erste Index darf weggelassen
werden; es wird dann 0 angenommen.
Nach dem Aufschneiden des Arrays a
:
a := [5]int{1, 2, 3, 4, 5} t := a[1:3:5]
ist das Slice t
vom Typ []int
, hat die Länge 2, die Kapazität 4
und die Elemente:
t[0] == 2 t[1] == 3
Ähnlich wie bei einfachen Slice-Ausdrücken gilt: Wenn a
ein Zeiger auf ein
Array ist, dann ist a[low : high : max]
die Abkürzung für
(*a)[low : high : max]
. Ist der aufgeschnittene Operand ein Array,
dann muss er adressierbar sein.
Die Indices sind bereichszugehörig,
wenn 0 <= low <= high <= max <= cap(a)
,
sonst sind sie bereichsfremd. Ein konstanter Index
muss nicht-negativ sein und darstellbar als Wert vom Typ int
; für Arrays und
konstante Strings müssen konstante Indices ebenfalls bereichszugehörig sein.
Wenn mehrere Indices konstant sind, müssen diese im Verhältnis zueinander bereichszugehörig
sein. Wenn zur Laufzeit die Indices bereichsfremd sind, dann führt das zu einem
Laufzeitfehler.
Typzusicherungen
Für einen Ausdruck x
, der ein
Interface-Typ ist, und einen Typ T
sichert der primäre Ausdruck:
x.(T)
zu, dass x
nicht nil
ist und dass der Wert, der in
x
gespeichert ist, vom Typ T
ist.
Die Schreibweise x.(T)
nennt man eine Typzusicherung
[englisch: type assertion, A.d.Ü.].
Genauer gesagt, sichert x.(T)
für ein T
, das kein
Interface-Typ ist, zu, dass der dynamische Typ von x
gleich
T
ist.
In dem Fall muss T
den (Interface-)Typ von x
implementieren; andernfalls wäre die Zusicherung
ungültig, da es für x
nicht möglich ist, einen Wert vom Typ
T
aufzunehmen.
Wenn T
ein Interface-Typ ist, dann sichert x.(T)
zu,
dass der dynamische Typ von x
das Interface T
implementiert.
Wenn die Typzusicherung zutrifft, dann ist der Wert des Ausdrucks der Wert,
der in x
gespeichert ist und sein Typ ist T
.
Wenn die Typzusicherung nicht zutrifft, tritt ein
Laufzeitfehler auf.
Anders gesagt, obwohl der dynamische Typ von x
erst zur
Laufzeit bekannt ist, weiß man, dass der Typ von x.(T)
in einem korrekten Programm T
ist.
var x interface{} = 7 // x hat den dynamischen Typ int und den Wert 7 i := x.(int) // i ist vom Typ int und hat den Wert 7 type I interface { m() } func f(y I) { s := y.(string) // verboten: string implementiert nicht I (Methode m fehlt) r := y.(io.Reader) // r hat Typ io.Reader und der dynamische Typ von y muss sowohl // I als auch io.Reader implementieren … }
Wird eine Typzusicherung in einer Zuweisung oder Initialisierung in der Form:
v, ok = x.(T) v, ok := x.(T) var v, ok = x.(T) var v, ok interface{} = x.(T) // dynamische Typen von v und ok sind T und bool
benutzt, so erzeugt das einen zusätzlichen typfreien Boole'schen Wert. Der Wert von
ok
ist true
, wenn die Zusicherung zutrifft.
Andernfalls ist er false
, und der Wert von v
ist der
Nullwert des Typs T
.
In diesem Fall gibt es keinen Laufzeitfehler.
Funktionsaufrufe
Ist ein Ausdruck f
eines Funktionstyps F
vorgegeben,
dann ruft:
f(a1, a2, … an)
die Funktion f
mit den Argumenten a1, a2, … an
auf.
Mit einer speziellen Ausnahme müssen Argumente einwertige Ausdrücke sein,
die den Parametertypen von F
zuweisbar sein müssen, und die vor dem Funktionsaufruf
ausgewertet werden. Der Typ des Ausdrucks ist der Ergebnistyp von F
.
Ein Methodenaufruf ist ähnlich, nur dass die Methode als Selektor eines Werts
vom Empfängertyp der Methode angegeben wird:
math.Atan2(x, y) // Funktionsaufruf var pt *Point pt.Scale(3.5) // Methodenaufruf mit Empfänger pt
Bei einem Funktionsaufruf werden der Funktionswert und die Argumente in der üblichen Reihenfolge ausgewertet. Nach dieser Auswertung werden die Aufrufparameter als Werte [englisch: by value, A.d.Ü.] an die Funktion übergeben, und die Funktion beginnt zu arbeiten. Die Ergebnisparameter der Funktion werden bei der Rückkehr als Werte an den Rufer zurückgegeben.
Ein nil
-Funktionswert führt zu einem
Laufzeitfehler.
Als ein Spezialfall kann ein Aufruf f(g(parameters_of_g))
die Ergebniswerte von g
der Reihe nach an die Parameter von
f
binden, wenn die Ergebniswerte der Funktion oder Methode
g
in Anzahl und Zuweisbarkeit den Parametern der Funktion oder
Methode f
entsprechen.
Der Aufruf von f
darf keine anderen Parameter als den Aufruf
von g
enthalten und g
muss mindestens einen Ergebniswert
haben. Wenn f
als letzten Parameter
...
hat, dann werden dem alle Ergebniswerte von g
zugewiesen, die nach der Zuweisung zu den regulären Parametern übrig bleiben.
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Panic("fehlgeschlagen") }
Ein Methodenaufruf x.m()
ist gültig, wenn m
in der
Methodenmenge des Typs von x
enthalten
ist und die Argumentliste der Parameterliste von m
zugewiesen
werden kann. Wenn x
adressierbar ist
und die Methodenmenge von &x
m
enthält, dann ist
x.m()
die Abkürzung für (&x).m()
:
var p Point p.Scale(3.5)
Es gibt keinen eigenen Methodentyp und es gibt keine Methodenliterale.
Argumentübergabe an den
...
-Parameter
Ist f
eine variadische Funktion
mit einem Parameter p
vom Typ ...T
am Ende,
so ist innerhalb von f
der Typ von p
gleichwertig zum
Typ []T
.
Wird f
ohne Argumente für p
aufgerufen, dann wird
nil
als Wert für p
übergeben.
Andernfalls ist der übergebene Wert ein neues Slice vom Typ []T
,
dessen aufeinanderfolgende Elemente die eigentlichen Argumente sind, und die alle
dem Typ T
zuweisbar sein müssen.
Länge und Kapazität des Slice ist also die Anzahl der Argumente, die an den letzten
Parameter gebunden sind, und die kann für jeden Aufruf verschieden sein.
Für die folgende Funktion und ihre Aufrufe:
func Greeting(prefix string, who ...string) Greeting("niemand") Greeting("Hallo:", "Joe", "Anna", "Eileen")
wird innerhalb von Greeting
das who
den Wert
nil
im ersten Aufruf, und
[]string{"Joe", "Anna", "Eileen"}
im zweiten Aufruf haben.
Ist das letzte Argument einem Slice-Typ []T
zuweisbar und wenn dem Argument ein ...
folgt, dann wird es unverändert als ein Wert für
...T
übergeben.
In diesem Fall wird kein neues Slice erzeugt.
Für das Slice s
und den folgenden Funktionsaufruf:
s := []string{"James", "Jasmine"} Greeting("Tschüs:", s...)
wird innerhalb von Greeting
das who
denselben Wert
wie s
mit demselben zugrundeliegenden Array haben.
Operatoren
Operatoren verbinden Operanden zu Ausdrücken.
Expression = UnaryExpr | Expression binary_op Expression . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = "||" | "&&" | rel_op | add_op | mul_op . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" . mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
Die Vergleiche werden woanders behandelt. Bei den übrigen binären Operationen müssen die Typen der Operanden identisch sein, außer wenn es sich um Schiebe-Operationen [englisch: shifts, A.d.Ü.] handelt oder typfreie Konstanten beteiligt sind. Operationen, an denen nur Konstanten beteiligt sind, sind im Abschnitt "Konstantenausdrücke" beschrieben.
Außer bei Schiebe-Operationen wird eine Konstante zu dem Typ des jeweils anderen Operanden implizit konvertiert, wenn sie eine typfreie Konstante ist und die jeweils andere nicht.
The right operand in a shift expression must have unsigned integer type or be an untyped constant representable by a value of typeuint
.
Der rechte Operand in einem Schiebe-Ausdruck muss eine Ganzzahl sein,
oder eine typfreie Konstante, die durch einen Wert vom Typ uint
darstellbar ist.
Ist der linke Operand eines nicht-konstanten Schiebe-Ausdrucks
eine typfreie Konstante, so wird er zuerst implizit zu dem Typ
konvertiert, den er annehmen würde, wenn der Schiebe-Ausdruck
nur durch seinen linken Operanden ersetzt würde.
var a [1024]byte var s uint = 33 // Die Ergebnisse folgender Beispiele gelten für 64-Bit ints. var i = 1<<s // 1 ist vom Typ int var j int32 = 1<<s // 1 ist vom Typ int32; j == 0 var k = uint64(1<<s) // 1 ist vom Typ uint64; k == 1<<33 var m int = 1.0<<s // 1.0 ist vom Typ int; m == 1<<33 var n = 1.0<<s == j // 1.0 ist vom Typ int; n == true var o = 1<<s == 2<<s // 1 and 2 sind vom Typ int; o == false var p = 1<<s == 1<<33 // 1 ist vom Typ int; p == true var u = 1.0<<s // verboten: 1.0 ist vom Typ float64, kann nicht geschoben werden var u1 = 1.0<<s != 0 // verboten: 1.0 ist vom Typ float64, kann nicht geschoben werden var u2 = 1<<s != 1.0 // verboten: 1 ist vom Typ float64, kann nicht geschoben werden var v float32 = 1<<s // verboten: 1 ist vom Typ float32, , kann nicht geschoben werden var w int64 = 1.0<<33 // 1.0<<33 ist ein konstanter Schiebe-Ausdruck; w == 1<<33 var x = a[1.0<<s] // Laufzeitfehler: 1.0 ist vom Typ int aber 1<<33 überschreitet Array-Grenzen var b = make([]byte, 1.0<<s) // 1.0 ist vom Typ int; len(b) == 1<<33 // Die Ergebnisse folgender Beispiele gelten für 32-Bit ints, // wodurch Schiebe-Ausdrücke zum Überlaufen führen var mm int = 1.0<<s // 1.0 ist vom Typ int; mm == 0 var oo = 1<<s == 2<<s // 1 und 2 sind vom Typ int; oo == true var pp = 1<<s == 1<<33 // verboten: 1 ist vom Typ int aber 1<<33 läuft über var xx = a[1.0<<s] // 1.0 ist vom Typ int; xx == a[0] var bb = make([]byte, 1.0<<s) // 1.0 ist vom Typ int; len(bb) == 0
Rangfolge der Operatoren
Unäre Operatoren haben die höchste Priorität.
Da die Operatoren ++
und --
Anweisungen und keine
Ausdrücke bilden, fallen sie aus der Operatorhierarchie heraus. Deshalb ist
*p++
das gleiche wie (*p)++
.
Es gibt fünf Prioritätsstufen. Die Multiplikationsoperatoren binden am stärksten,
gefolgt von den Additionsoperatoren, den Vergleichsoperatoren,
dem &&
(logisches UND), und zuletzt dem ||
(logisches ODER):
Priorität Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
Binäre Operatoren der gleichen Priorität werden von links nach rechts verbunden.
Zum Beispiel ist x / y * z
dasgleiche wie (x / y) * z
.
+x 23 + 3*x[i] x <= f() ^a >> b f() || g() x == y+1 && <-chanInt > 0
Arithmetische Operatoren
Arithmetische Operatoren werden auf numerische Werte angewendet und führen zu
einem Ergebnis desselben Typs wie dem des ersten Operanden. Die vier üblichen
arithmetischen Operatoren (+
, -
, *
und
/
) werden auf die Typen der Ganz-, Gleitkomma- und
der Komplexzahlen angewendet; +
kann auch auf Strings
angewendet werden. Die bitweise logischen und die Schiebe-Operatoren
können nur auf Ganzzahlen angewendet werden.
+ Summe Ganz-, Gleitkomma-, Komplexzahlen, Strings - Differenz Ganz-, Gleitkomma-, Komplexzahlen * Produkt Ganz-, Gleitkomma-, Komplexzahlen / Quotient Ganz-, Gleitkomma-, Komplexzahlen % Rest Ganzzahlen & Bitweise UND Ganzzahlen | Bitweise ODER Ganzzahlen ^ Bitweise XODER Ganzzahlen &^ Bit löschen (UND NICHT) Ganzzahlen << nach links schieben Ganzzahl << Ganzzahl >= 0 >> nach rechst schieben Ganzzahl >> Ganzzahl >= 0
Ganzzahloperatoren
Für zwei Ganzzahlwerte x
und y
stehen der ganzzahlige
Quotient q = x / y
und der Rest r = x % y
in folgender
Beziehung zueinander:
x = q*y + r und |r| < |y|
wobei x / y
nach dem Komma abgeschnitten wird
("truncated division").
x y x / y x % y 5 3 1 2 -5 3 -1 -2 5 -3 -1 2 -5 -3 1 -2
Es gibt eine Ausnahme zu dieser Regel: Wenn der Dividend x
der negativst mögliche Wert des Ganzzahltyps von x
ist, dann
ist der Quotient q = x / -1
gleich x
(und r = 0
), und zwar wegen des
Ganzzahlüberlaufs des Zweierkomplements:
x, q int8 -128 int16 -32768 int32 -2147483648 int64 -9223372036854775808
Ist der Divisor eine Konstante, dann darf er nicht null sein. Ist der Divisor zur Laufzeit null ist, dann tritt ein Laufzeitfehler auf. Ist der Dividend nicht-negativ und der Divisor eine konstante Potenz von 2, so darf die Division durch Nach-rechts-Schieben und das Bestimmen des Rest durch Bitweise-UND ersetzt werden.
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
Die Schiebeoperatoren verschieben den linken Operanden um die
Schiebeanzahl, die durch den rechten Operanden angegeben ist,
welcher nicht-negativ sein muss.
Ist die Schiebeanzahl zur Laufzeit negativ, so tritt ein
Laufzeitfehler auf.
Die Schiebeoperatoren implementieren arithmetisches Schieben,
wenn der linke Operand eine positive Ganzzahl mit Vorzeichen ist,
und ein logisches Schieben, wenn er eine Ganzzahl ohne Vorzeichen ist.
Es gibt keine Obergrenze für die Schiebeanzahl.
Verschieben um die Schiebeanzahl n
verhält
sich als ob der linke Operand n
-mal um 1 verschoben wird.
Das Ergebnis von x << 1
ist dasselbe wie von x*2
,
das von x >> 1
dasselbe wie x/2
abgeschnitten
in Richtung Minus-Unendlich.
Für Ganzzahloperanden sind die unären Operatoren
+
, -
und ^
wie folgt definiert:
+x wie 0 + x -x Negation wie 0 - x ^x Bit-Komplement wie m ^ x wobei: m "hat alle Bits auf 1" für x ohne Vorzeichen oder m == -1 für x mit Vorzeichen
Ganzzahlüberlauf
Für Ganzzahlwerte ohne Vorzeichen werden die Operationen +
,
-
, *
und <<
modulo
2n berechnet, wobei n die Länge (in Bit) des
Ganzzahltyps ohne Vorzeichen ist.
Vereinfacht gesagt verwerfen diese Ganzzahloperationen ohne Vorzeichen die
überlaufenden hohen Bits; Programme dürfen darauf zählen, dass die Null wieder
durchlaufen wird.
+
,
-
, *
, /
und <<
überlaufen; es gibt
ein Ergebnis, und das ist eindeutig definiert durch die
Darstellung als
Ganzzahl mit Vorzeichen, durch die Operation und durch die
Operanden. Überlauf erzeugt keinen
Laufzeitfehler.
Ein Compiler darf nicht unter der Prämisse optimieren, es gäbe
keinen Überlauf. Beispielsweise wäre es falsch
anzunehmen, dass x < x + 1
immer wahr sei.
Gleitkommaoperatoren
Für Gleitkomma- und komplexe Zahlen ist +x
dasgleiche wie
x
, während -x
die Negation von x
ist.
Das Ergebnis einer Gleitkomma- oder Komplexdivision durch Null ist nicht über den Standard IEEE-745 hinaus festgelegt; ob ein
Laufzeitfehler auftritt,
hängt von der Go-Implementierung ab.
Eine Implementierung darf mehrere Gleitkommaoperationen, auch über mehrere Anweisungen hinweg, in einer einzigen Operation fusionieren, und dabei ein Ergebnis produzieren, das von dem Wert abweicht, der durch Einzeloperationen mit jeweils anschließendem Runden erreicht würde. Eine explizite Konversion eines Gleitkommatyps rundet auf die Präzision des Zieltyps und verhindert damit eine Fusion, die solches Runden verwerfen würde.
Beispielsweise bietet manche Prozessorarchitektur eine "fusionierte Multiplizier
und Addier"-Anweisung (FMA), die x*y + z
ohne Runden des Zwischenergebnisses
x*y
berechnet.
Folgende Beispiele zeigen, wann eine Go-Implementierung diese Anweisung benutzen kann.
// FMA zum Berechnen von r erlaubt, weil x*y nicht explizit gerundet wird: r = x*y + z r = z; r += x*y t = x*y; r = t + z *p = x*y; r = *p + z r = x*y + float64(z) // FMA zum Berechnen von r nicht erlaubt, weil das das Runden von x*y ausließe: r = float64(x*y) + z r = z; r += float64(x*y) t = float64(x*y); r = t + z
String-Verkettung
Strings können mit dem Operator +
oder mit dem
Zuweisungsoperator +=
verkettet werden:
s := "Hallo" + string(c) s += " und tschüs"
String-"Addition" erzeugt einen neuen String, indem die Operanden verkettet werden.
Vergleichsoperatoren
Vergleichsoperatoren vergleichen zwei Operanden und ergeben einen typfreien Boole'schen Wert.
== gleich != ungleich < kleiner <= kleiner oder gleich > größer >= größer oder gleich
Bei jedem Vergleich muss der erste Operand dem Typ des zweiten Operanden zuweisbar sein, oder umgekehrt.
Die Gleichheitsoperatoren ==
und !=
können auf
Operanden angewandt werden, die vergleichbar sind.
Die ordnenden Operatoren <
, <=
, >
und >=
können auf Operanden angewandt werden, die
geordnet sind.
Diese Begriffe sowie die Vergleichsergebnisse sind wie folgt definiert:
-
Boole'sche Werte sind vergleichbar. Zwei Boole'sche Werte sind gleich, wenn
beide entweder
true
oder beidefalse
sind. - Ganzzahlwerte sind vergleichbar und geordnet, wie üblich.
- Gleitkommawerte sind vergleichbar und geordnet, wie im IEEE-754- Standard definiert.
-
Komplexwerte sind vergleichbar. Zwei Komplexwerte
u
undv
sind gleich, wenn sowohlreal(u) == real(v)
als auchimag(u) == imag(v)
gilt. - String-Werte sind vergleichbar und lexikalisch byteweise geordnet.
-
Zeiger-Werte sind vergleichbar. Zwei Zeiger-Werte sind gleich, wenn sie auf
dieselbe Variable zeigen oder wenn beide den Wert
nil
haben. Zeiger auf verschiedene Variablen der Länge null können gleich sein, oder auch nicht. -
Kanalwerte sind vergleichbar. Zwei Kanalwerte sind gleich, wenn sie durch
denselben Aufruf von
make
erzeugt wurden oder wenn beide den Wertnil
haben. -
Interface-Werte sind vergleichbar. Zwei Interface-Werte sind gleich, wenn
sie von identischem dynamischen Typ sind und
den gleichen dynamischen Wert oder beide den Wert
nil
haben. -
Ein Wert
x
vom TypX
, der kein Interface-Typ ist, und ein Wertt
vom Interface-TypT
sind vergleichbar, wenn die Werte vom TypX
vergleichbar sind undX
T
implementiert. Sie sind gleich, wenn der dynamische Wert vont
gleichx
ist. - Struktur-Werte sind vergleichbar, wenn alle ihre Felder vergleichbar sind. Zwei Struktur-Werte sind gleich, wenn die entsprechenden Felder, ohne die leeren, gleich sind.
- Array-Werte sind vergleichbar, wenn die Werte des Elementtyps vergleichbar sind. Zwei Array-Werte sind gleich, wenn die entsprechenden Elemente gleich sind.
Der Vergleich zweier Interface-Werte von identischem dynamischen Typ führt dann zu einem Laufzeitfehler, wenn Werte dieses Typs nicht vergleichbar sind. Das gilt auch, wenn nicht Interface-Werte direkt verglichen werden, sondern Arrays aus Interface-Werten oder Strukturen mit Feldern mit Interface-Werten.
Slice-, Map- oder Funktions-Werte sind nicht vergleichbar. Allerdings dürfen
— als Spezialfall — Slice-, Map- oder Funktions-Werte mit dem
vordeklarierten Bezeichner nil
verglichen werden. Der erlaubte
Vergleich von Zeiger- Kanal- und Interface-Werten mit nil
folgt aus den oben aufgeführten Regeln.
const c = 3 < 4 // c ist die typfreie Boole'sche Konstante true type MyBool bool var x, y int var ( // Das Ergebnis eines Vergleichs ist eine typfreies Boole'sche Variable. // Es gelten die üblichen Regeln für Zuweisungen. b3 = x == y // b3 ist vom Typ bool b4 bool = x == y // b4 ist vom Typ bool b5 MyBool = x == y // b5 ist vom Typ MyBool )
Logische Operatoren
Logische Operatoren werden auf Boole'sche Werte angewendet und ergeben ein Ergebnis vom gleichen Typ wie die Operanden. Der rechte Operand wird nur bedingt ausgewertet.
&& bedingtes UND p && q wird zu "wenn p dann q sonst false" || bedingtes ODER p || q wird zu "wenn p dann true sonst q" ! NICHT !p wird zu "nicht p"
Addressoperatoren
Für einen Operanden x
vom Typ T
erzeugt die
Adressieroperation &x
einen Zeiger vom Typ
*T
auf x
. Der Operand muss adressierbar sein,
d.h. entweder eine Variable, eine Zeigerauflösung, eine Sliceindizierung,
ein Feldselektor eines adressierbaren Struktur-Operanden oder
eine Arrayindizierung auf ein adressierbares Array.
Ausnahme zur geforderten Adressierbarkeit: x
darf ein (möglicherweise
geklammertes) Verbundliteral sein.
Wenn die Auswertung von x
zu einem Laufzeitfehler
führt, dann auch die Auswertung von of &x
.
Für einen Operanden x
vom Zeigertyp *T
bezeichnet
*x
die Variable vom Typ T
,
auf die x
zeigt.
Wenn x
nil
ist, dann führt *x
zu einem
Laufzeitfehler.
&x &a[f(2)] &Point{2, 3} *p *pf(x) var x *int = nil *x // verursacht einen Laufzeitfehler &*x // verursacht einen Laufzeitfehler
Empfangsoperator
Für einen Operanden ch
eines Kanaltyps
ist der Wert der Empfangsoperation <-ch
der Wert, der über den
Kanal ch
empfangen wird. Die Kanalrichtung muss Empfangsoperationen
zulassen, und der Typ der Empfangsoperation ist der Elementtyp des Kanals.
Der Ausdruck blockiert, bis ein Wert zur Verfügung steht.
Empfängt er von einem nil
-Kanal, so blockiert er für immer.
Eine Empfangsoperation auf einen geschlossenen Kanal wird sofort
durchgeführt und dabei der Nullwert des Elementtyps
zurückgegeben, nachdem zuvor alle vorher gesendeten Werte empfangen worden sind.
v1 := <-ch v2 = <-ch f(<-ch) <-strobe // warte auf Taktsignal & ignoriere den empfangenen Wert
Ein Empfangsausdruck in einer Zuweisung oder Initialisierung in der speziellen Form:
x, ok = <-ch x, ok := <-ch var x, ok = <-ch var x, ok T = <-ch
liefert eine zusätzliches typfreies Boole'sches Ergebnis, welches anzeigt,
ob die Kommunikation erfolgreich war. Der Wert von ok
ist
true
, wenn der empfangene Wert durch eine erfolgreiche
Sendeoperation in den Kanal geliefert wurde; er ist false
,
wenn ein Nullwert erzeugt wurde, weil der Kanal geschlossen und leer war.
Konversionen
Eine Konversion ändert den Typ eines Ausdrucks hin zu dem Typ, der für die Konversion angegeben ist. Eine Konversion kann explizit im Quellkode erscheinen, oder sie kann durch den Kontext, in dem der Ausdruck erscheint, impliziert sein.
Eine explicite Konversionen ist ein Ausdruck der Form
T(x)
, in denen T
ein Typ ist und x
ein Ausdruck, der nach
T
konvertiert werden kann.
Conversion = Type "(" Expression [ "," ] ")" .
Wenn der Typ mit einem Operator *
oder <-
oder
dem Schlüsselwort func
beginnt, dann muss er in Klammern gesetzt werden:
*Point(p) // gleich *(Point(p)) (*Point)(p) // p wird nach *Point konvertiert <-chan int(c) // gleich <-(chan int(c)) (<-chan int)(c) // c wird nach <-chan int konvertiert func()(x) // Funktionssignatur func() x (func())(x) // x wird nach func() konvertiert (func() int)(x) // x wird nach func() int konvertiert func() int(x) // x wird nach func() int konvertiert (eindeutig)
Ein konstanter Wert x
kann nach
T
konvertiert werden,
wenn x
durch einen Wert von T
darstellbar ist.
Als Sonderfall kann auch eine Ganzzahlkonstante x
explizit zu einem String-Typ
konvertiert werden,
indem dieselbe Regel
wie für nicht-konstante x
verwendet wird.
Die Konversion einer Konstanten ergibt eine typbehaftete Konstante.
uint(iota) // Jota-Wert vom Typ uint float32(2.718281828) // 2.718281828 vom Typ float32 complex128(1) // 1.0 + 0.0i vom Typ complex128 float32(0.49999999) // 0.5 vom Typ float32 float64(-1e-1000) // 0.0 vom Typ float64 string('x') // "x" vom Typ string string(0x266c) // "♬" vom Typ string MeinString("foo" + "bar") // "foobar" vom Typ MeinString string([]byte{'a'}) // keine Konstante: []byte{'a'} ist keine Konstante (*int)(nil) // keine Konstante: nil ist keine Konstante, *int ist weder Boole'scher noch numerischer noch String-Typ int(1.2) // verboten: 1.2 kann nicht als Ganzzahl repräsentiert werden string(65.0) // verboten: 65.0 ist keine Ganzzahlkonstante
Ein nicht-konstanter Wert x
kann nach
T
in den folgenden Fällen konvertiert werden:
-
x
istT
zuweisbar. -
T
und der Typ vonx
haben identische Ursprungstypen, dabei werden Markierungen ignoriert (siehe unten). -
T
und der Typ vonx
sind Zeigertypen, die keine definierten Typen sind, und ihre Zeigerbasistypen sind Zeiger auf identische Ursprungstypen, dabei werden Markierungen ignoriert (siehe unten). -
Sowohl
T
als auch der Typ vonx
sind Ganzzahl- oder Gleitkommatypen. -
Sowohl
T
als auch der Typ vonx
sind Komplextypen. -
x
ist eine Ganzzahl oder ein Slice aus Bytes oder Runen undT
ist ein String-Typ. -
x
ist ein String undT
ist ein Slice aus Bytes oder Runen. -
x
ist ein Slice,T
ist ein Zeiger auf ein Array, und der Slice- und der Array-Typ haben denselben Elementtyp.
Damit Konversionen möglich bleiben, werden beim Vergleich von Strukturtypen die Markierungen ignoriert.
type Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"` } var person = (*Person)(data) // von Markierungen abgesehen sind die // darunterliegenden Typen identisch.
Für die (nicht-konstanten) Konversionen zwischen numerischen Typen und
von und nach String-Typen gibt es besondere Regeln.
Solche Konversionen können die Repräsentation von x
ändern
und kosten Laufzeit.
Alle anderen Konversionen ändern nur den Typ aber nicht die Repräsentation
von x
.
Es gibt kein Sprachkonstrukt, mit dem zwischen Zeigern und Ganzzahlen
konvertiert werden könnte. Eine solche Funktionalität bietet in eingeschränktem
Maße das Paket unsafe
.
Konversion zwischen numerischen Typen
Für die Konversion nicht-konstanter Ganzzahlwerte gelten die folgenden Regeln:
-
Ist bei Konversion zwischen Ganzzahltypen der Wert eine Ganzzahl mit Vorzeichen,
so wird "vorzeichenerweitert", mit implizit unendlicher Genauigkeit.
Im anderen Fall wird mit Null erweitert.
Danach wird auf die Länge des Ergebnistyps abgeschnitten.
Ist beispielsweise
v := uint16(0x10F0)
, dann istuint32(int8(v)) == 0xFFFFFFF0
. Konversion ergibt immer einen gültigen Wert; es gibt keine Darstellung des Überlaufs. - Wird eine Gleitkommazahl in einen Ganzzahltyp konvertiert, so wird der Bruchanteil verworfen (abgeschnitten).
-
Wird eine Ganz- oder Gleitkommazahl in einen Gleitkommatyp konvertiert,
oder eine Komplexzahl in einen anderen Komplextyp, so wird der Wert des
Ergebnisses entsprechend dem Ergebnistyp gerundet.
Beispielsweise kann der Wert einer Variablen
x
vom Typfloat32
mit größerer Genauigkeit als eine "IEEE-754"-32-Bit-Zahl gespeichert sein, dochfloat32(x)
repräsentiert den auf 32-Bit-Genauigkeit gerundeten Wert vonx
. Genauso kannx + 0.1
mehr als 32 Bit genau sein, aberfloat32(x + 0.1)
nicht.
Alle nicht-konstante Konversionen, an denen Gleitkomma- oder Komplexwerte beteiligt sind, enden auch dann erfolgreich, wenn der Ergebnistyp das Ergebnis nicht fassen kann, aber der Ergebniswert ist abhängig von der Go-Implementierung.
Konversion von und nach String-Typen
-
Die Konversion einer Ganzzahl mit oder ohne Vorzeichen in einen String-Typ
ergibt einen String, der die UTF-8-Darstellung der Ganzzahl enthält.
Werte, die keine gültigen Unicode-Kodenummern sind, werden nach
"
\uFFFD
" konvertiert.string('a') // "a" string(-1) // "\ufffd" == "\xef\xbf\xbd" string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8" type MeinString string MeinString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
-
Die Konversion eines Byte-Slice in einen String-Typ ergibt einen String, dessen
aufeinanderfolgende Bytes die Slice-Elemente sind.
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type MeineBytes []byte string(MeineBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
-
Die Konversion eines Rune-Slice in einen String-Typ ergibt einen String, der
eine Verkettung der zu Strings konvertierten einzelnen Rune-Werten ist.
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type MeineRunen []rune string(MeineRunen{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
-
Die Konversion des Wertes eines String-Typs in ein Byte-Typ-Slice ergibt ein Slice,
dessen aufeinanderfolgende Elemente die Bytes des Strings sind.
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} MeineBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
-
Die Konversion des Wertes eines String-Typs in ein Rune-Typ-Slice ergibt ein Slice,
welches die einzelnen Unicode-Kodenummern des Strings enthält.
[]rune(MeinString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} MeineRunen("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
Konversionen von Slices zu Array-Zeigern
Die Konversion eines Slice zu einem Array-Zeiger ergibt einen Zeiger auf das dem Slice zugrundliegende Array. Ist die Länge des Slice kleiner als die Länge des Arrays, so tritt ein Laufzeitfehler auf.
s := make([]byte, 2, 4) s0 := (*[0]byte)(s) // s0 != nil s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1] s2 := (*[2]byte)(s) // &s2[0] == &s[0] s4 := (*[4]byte)(s) // Panik: len([4]byte) > len(s) var t []string t0 := (*[0]string)(t) // t0 == nil t1 := (*[1]string)(t) // Panik: len([1]string) > len(t) u := make([]byte, 0) u0 = (*[0]byte)(u) // u0 != nil
Konstante Ausdrücke
Konstante Ausdrücke dürfen nur konstante Operanden enthalten und werden zum Umwandlungszeitpunkt ausgewertet.
Typfreie Boole'sche, numerische oder String-Konstanten können überall dort als Operanden benutzt werden, wo Operanden von entsprechendem Boole'schem, numerischen oder String-Typ erlaubt sind.
Ein konstanter Vergleich ergibt immer eine typfreie Boole'sche Konstante. Ist der linke Operand eines konstanten Schiebe-Ausdrucks eine typfreie Konstante, so ist das Ergebnis eine Ganzzahlkonstante; im anderen Fall ist es eine Konstante desselben Typs wie der linke Operand, welcher von einem Ganzzahltyp sein muss.
Alle anderen Operationen auf typfreie Konstanten führen zu typfreien Konstanten der gleichen Art, das heißt zu einer Boole'schen, Ganzzahl-, Gleitkomma-, Komplex- oder String-Konstante. Sind die typfreien Operanden einer binären Operation (ohne die Schiebeoperationen) von verschiedener Art, so ist das Ergebnis von der Art des Operanden, der in der folgenden Liste später erscheint: Ganzzahl, Rune, Gleitkommazahl, Komplexzahl. Zum Beispiel ergibt eine typfreie Ganzzahl-Konstante dividiert durch eine typfreie Komplex-Konstante eine typfreie Komplex-Konstante.
const a = 2 + 3.0 // a == 5.0 (typfreie Gleitkommakonstante) const b = 15 / 4 // b == 3 (typfreie Ganzzahlkonstante) const c = 15 / 4.0 // c == 3.75 (typfreie Gleitkommakonstante) const Θ float64 = 3/2 // Θ == 1.5 (Typ float64, 3/2 ist eine Ganzzahldivision) const Π float64 = 3/2. // Π == 1.5 (Typ float64, 3/2. ist eine Gleitkommadivision) const d = 1 << 3.0 // d == 8 (typfreie Ganzzahlkonstante) const e = 1.0 << 3 // e == 8 (typfreie Ganzzahlkonstante) const f = int32(1) << 33 // verboten (Konstante 8589934592 passt nicht in int32) const g = float64(2) >> 1 // verboten (float64(2) ist eine typbehaftete Gleitkommakonstante) const h = "foo" > "bar" // h == true (typfreie Boole'sche Konstante) const j = true // j == true (typfreie Boole'sche Konstante) const k = 'w' + 1 // k == 'x' (typfreie Runenkonstante) const l = "hi" // l == "hi" (typfreie String-Konstante) const m = string(k) // m == "x" (Typ string) const Σ = 1 - 0.707i // (typfreie Komplexkonstante) const Δ = Σ + 2.0e-4 // (typfreie Komplexkonstante) const Φ = iota*1i - 1/1i // (typfreie Komplexkonstante)
Anwendung der Standardfunktion complex
auf eine typfreie
Ganzzahl-, Runen- oder Gleitkommakonstante ergibt eine typfreie Komplexkonstante.
const ic = complex(0, c) // ic == 3.75i (typfreie Komplexkonstante) const iΘ = complex(0, Θ) // iΘ == 1i (Typ complex128)
Konstante Ausdrücke werden immer exakt ausgewertet; Zwischenergebnisse und die Konstanten selbst können deutlich größere Genauigkeit erfordern, als die in der Sprache vordeklarierten Typen unterstützen. Die folgenden Deklarationen sind erlaubt:
const Riesig = 1 << 100 // Riesig == 1267650600228229401496703205376 (typfreie Ganzzahlkonstante) const Vier int8 = Riesig >> 98 // Vier == 4 (Typ int8)
Der Divisor in einer konstanten Divisions- oder Divisionsrestoperation darf nicht null sein:
3.14 / 0.0 // verboten: Division durch Null
Die Werte typbehafteter Konstanten müssen immer exakt als Werte des Konstantentyps darstellbar sein. Die folgenden konstanten Ausdrücke sind nicht erlaubt:
uint(-1) // -1 nicht als uint darstellbar int(3.14) // 3.14 nicht als int darstellbar int64(Riesig) // 1267650600228229401496703205376 nicht als int64 darstellbar Vier * 300 // Operand 300 nicht als int8 (Typ von Vier) darstellbar Vier * 100 // Operand 400 nicht als int8 (Typ von Vier) darstellbar
Die Maske, die vom unären Bitweise-Komplement-Operator ^
benutzt wird,
erfüllt die Bedingung für Nicht-Konstanten: die Maske besteht aus lauter
Einsen für Konstanten ohne Vorzeichen und ist -1 für Konstanten mit Vorzeichen
und für typfreie Konstanten.
^1 // typfreie Ganzzahlkonstante, gleich -2 uint8(^1) // verboten: gleich uint8(-2), bereichsfremd ^uint8(1) // Typbehaftete Konstante (uint8), gleich 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // gleich int8(-2) ^int8(1) // gleich -1 ^ int8(1) = -2
Go-Implementierungseinschränkung: Ein Compiler darf bei der Berechnung typfreier Gleitkomma- oder Komplexausdrücke runden; siehe auch die Implementierungseinschränkung im Abschnitt Konstanten. Solches Runden kann dazu führen, dass ein Gleitkommaausdruck in einem Ganzzahlkontext ungültig wird, selbst wenn er bei unendlicher Genauigkeit gegen eine Ganzzahl tendiert, und umgekehrt. [?, A.d.Ü.]
Auswertungsreihenfolge
Auf Paketebene bestimmen Initialisierungsabhängigkeiten die Auswertungsreihenfolge der einzelnen Initialisierungsausdrücke in den Variablendeklarationen. Ansonsten werden beim Auswerten der Operanden eines Ausdrucks, einer Zuweisung oder einer Return-Anweisung alle Funktionsaufrufe, Methodenaufrufe und Kommunikationsoperationen lexikalisch von links nach rechts ausgewertet.
Zum Beispiel erfolgen in der (funktionslokalen) Zuweisung
y[f()], ok = g(h(), i()+x[j()], <-c), k()
die Funktionsaufrufe und die Kommunikation in der folgenden Reihenfolge:
f()
, h()
, i()
, j()
,
<-c
, g()
und k()
.
Dagegen ist die Reihenfolge dieser Ereignisse gegenüber der Auswertung
und Indizierung von x
oder gegenüber der Auswertung von
y
nicht festgelegt.
a := 1 f := func() int { a++; return a } x := []int{a, f()} // x kann [1, 2] oder [2, 2] sein: // Auswertungsreihenfolge von a und f() ist nicht festgelegt m := map[int]int{a: 1, a: 2} // m kann {2: 1} oder {2: 2} sein: // Auswertungsreihenfolge der beiden Map-Zuweisungen ist nicht festgelegt n := map[int]int{a: f()} // n kann {2: 3} oder {3: 3} sein: // Auswertungsreihenfolge von Schlüssel und Wert ist nicht festgelegt
Auf Paketebene heben Initialisierungsabhängigkeiten die Von-links-nach-rechts-Regel für einzelne Initialisierungsausdrücke auf, aber nicht für Operanden innerhalb eines solchen Ausdrucks:
var a, b, c = f() + v(), g(), sqr(u()) + v() func f() int { return c } func g() int { return a } func sqr(x int) int { return x*x } // Funktionen u und v sind unabhängig von allen anderen Variablen und Funktionen
Die Funktionen werden in folgender Reihenfolge aufgerufen:
u()
, sqr()
, v()
,
f()
, v()
, g()
.
Die Auswertung von Gleitkommaoperationen richtet sich nach der Assoziativität
der Operatoren. Klammersetzung hebt die Standard-Assoziativität auf.
Im Ausdruck x + (y + z)
wird die Addition y + z
vor dem Addieren von x
durchgeführt.
Anweisungen
Anweisungen steuern die Ausführung.
Statement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
Terminierende Anweisungen
Eine terminierende Anweisung verhindert die Ausführung aller Anweisungen, die sich danach im selben Block befinden. Folgende Anweisungen sind terminierend:
- eine Return- oder eine Goto-Anweisung,
-
ein Aufruf der Standardfunktion
panic
, - ein Block, deren Anweisungsliste mit einer Terminierenden Anweisung endet,
-
eine If-Anweisung, bei der:
- der Else-Zweig vorhanden ist und
- beide Zweige Terminierende Anweisungen sind,
-
eine For-Anweisung, bei der:
- keine Break-Anweisung sich auf die For-Anweisung bezieht und
- die ohne Schleifenbedingung ist,
-
eine Switch-Anweisung, bei der:
- keine Break-Anweisung sich auf die Switch-Anweisung bezieht,
- es einen Default-Fall gibt und
- die Anweisungsliste jedes Falles, inklusive des Default-Falls, mit einer Terminierenden Anweisung oder einer Fallthrough-Anweisung, eventuell mit Sprungmarke, endet,
-
eine Select-Anweisung bei der:
- keine Break-Anweisung sich auf die Select-Anweisung bezieht und
- die Anweisungsliste jedes Falles, inklusive des Default-Falls, falls es ihn gibt, mit einer Terminierenden Anweisung endet, oder
- eine Terminierende Anweisung mitSprungmarke.
Alle anderen Anweisungen sind nicht-terminierend
Eine Anweisungsliste endet mit einer Terminierenden Anweisung, wenn die Liste nicht leer ist und die letzte nicht leere Anweisung eine terminierende ist.
Leere Anweisungen
Die leere Anweisung tut nichts.
EmptyStmt = .
Markierte Anweisungen
Eine markierte Anweisung kann Ziel einer goto
-, break
-
oder continue
-Anweisung sein.
LabeledStmt = Label ":" Statement . Label = identifier .
Error: log.Panic("Fehler aufgetreten")
Ausdruck-Anweisungen
Mit Ausnahme bestimmter Standardfunktionen können Funktionsaufrufe und Methodenaufrufe und Empfangsoperationen im Kontext einer Anweisung erscheinen. Solche Anweisungen können in Klammern stehen.
ExpressionStmt = Expression .
Die folgenden Standardfunktionen sind in einem Anweisungskontext nicht erlaubt:
append cap complex imag len make new real unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice
h(x+y) f.Close() <-ch (<-ch) len("foo") // verboten, wenn len die Standardfunktion ist
Sendeanweisungen
Eine Sendeanweisung schickt einen Wert in einen Kanal. Der Kanalausdruck muss von einem Kanaltyp sein, die Kanalrichtung muss Sendeoperationen erlauben und der Typ des zu sendenden Werts muss dem Elementtyp des Kanals zuweisbar sein.
SendStmt = Channel "<-" Expression . Channel = Expression .
Sowohl der Kanal- als auch der Wertausdruck werden ausgewertet, bevor der
Nachrichtenaustausch beginnt. Die Ausführung blockiert bis die Sendeoperation
möglich ist.
Eine Sendeoperation in einen ungepufferten Kanal ist möglich,
wenn der Empfänger bereit ist.
Eine Sendeoperation in einen gepufferten Kanal ist möglich,
wenn noch Platz im Puffer ist.
Eine Sendeoperation in einen geschlossenen Kanal ist möglich
und führt zu einem Laufzeitfehler.
Eine Sendeoperation in einen nil
-Kanal blockiert für immer.
Kanäle sind Warteschlangen [englisch: first-in-first-out queues, A.d.Ü.]. Wenn also beispielsweise eine einzelne Goroutine Werte in einen Kanal sendet, aus dem von nur einer Goroutine empfangen wird, dann werden die Werte in derselben Reihenfolge empfangen wie sie gesendet wurden.
Ein einzelner Kanal kann von beliebig vielen Goroutinen für Sende- und Empfangsoperationen
sowie für Aufrufe der Standardfunktionen
cap
und
len
benutzt werden.
ch <- 3
Inkrement/Dekrement-Anweisungen
Die Anweisungen "++
" und "--
" erhöhen oder erniedrigen
ihren jeweiligen Operanden um die typfreie Konstante
1
. Wie bei einer Zuweisung muss der Operand
adressierbar
oder der Indexausdruck einer Map sein.
IncDecStmt = Expression ( "++" | "--" ) .
Die folgenden Anweisungen/Zuweisungen bedeuten jeweils dasselbe:
InkDek-Anweisung Zuweisung x++ x += 1 x-- x -= 1
Zuweisungen
Assignment = ExpressionList assign_op ExpressionList . assign_op = [ add_op | mul_op ] "=" .
Jeder linksseitige Operand muss adressierbar,
der Indexausdruck einer Map oder, das aber nur in =
-Zuweisungen,
der Leere Bezeichner
sein. Operanden dürfen in Klammern stehen.
x = 1 *p = f() a[i] = 23 (k) = <-ch // gleichwertig zu k = <-ch
Eine Zuweisung x
op= y
, wobei op
ein binärer arithmetischer Operator ist,
ist gleichbedeutend mit
x = x
op (y)
, wertet x
aber nur
einmal aus. Das Konstrukt op=
ist eine Sinneinheit.
In solchen Zuweisungen müssen sowohl die links- als auch die rechtsseitige
Ausdrückeliste genau einen Ein-Wert-Ausdruck enthalten und der linksseitige Ausdruck
darf nicht der Leere Bezeichner sein.
a[i] <<= 2 i &^= 1<<n
Eine Mehrfachzuweisung weist die einzelnen Elemente einer Operation mit
Mehrfachergebnis einer Liste von Variablen zu. Es gibt zwei Formen.
In der ersten Form ist der rechtsseitige Operand ein einzelner
Mehrfachergebnis-Ausdruck, also ein Funktionsaufruf, eine
Kanal- oder eine
Map-Operation oder eine
Typzusicherungen.
Die Anzahl der Operanden auf der linken Seite muss gleich der Anzahl der
Ergebniswerte sein.
Wenn zum Beispiel f
eine Funktion ist, die zwei Werte zurückgibt:
x, y = f()
, dann wird der erste Wert dem x
und der zweite dem y
zugewiesen. In der zweiten Form der Mehrfachzuweisung muss die Anzahl der Operanden links
gleich der Anzahl der Ausdrücke rechts sein, wobei jeder Ausdruck genau einen
Wert zurückgeben muss. Dann wird der Wert des n-ten Ausdrucks dem
n-ten Operanden auf der linken Seite zugewiesen:
one, two, three = '一', '二', '三'
Der Leere Bezeichner ermöglicht, rechtsseitige Ergebniswerte eines Mehrfachergebnis-Ausdrucks zu ignorieren:
_ = x // wertet x aus, aber ignoriert den Wert x, _ = f() // wertet f() aus, aber ignoriert den zweiten Ergebniswert
Die Zuweisung geschieht in zwei Phasen. Zunächst werden linksseitig die Operanden von Indexausdrücken und Zeigerauflösungen (inklusive der impliziten Zeigerauflösungen in Selektoren) sowie die rechtsseitigen Ausdrücke in der üblichen Reihenfolge ausgewertet. Dann werden die Zuweisungen von links nach rechts vorgenommen.
a, b = b, a // tausche a und b x := []int{1, 2, 3} i := 0 i, x[i] = 1, 2 // setze i = 1 und x[0] = 2 i = 0 x[i], i = 2, 1 // setze x[0] = 2 und i = 1 x[0], x[0] = 1, 2 // setze x[0] = 1 und dann x[0] = 2 (so dass schließlich x[0] == 2 ist) x[1], x[3] = 4, 5 // setze x[1] = 4 und dann: Panik beim Setzen von x[3] = 5. type Point struct { x, y int } var p *Point x[2], p.x = 6, 7 // setze x[2] = 6 und dann: Panik beim Setzen p.x = 7 i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // setze i, x[2] = 0, x[0] break } // nach dieser Schleife: i == 0 und x == []int{3, 5, 3}
Bei Zuweisungen muss jeder Wert dem Typ des Operanden, dem er zugewiesen wird, auch zuweisbar sein. Es gibt folgende Spezialfälle:
- Jeder typbehaftete Wert darf dem Leeren Bezeichner zugewiesen werden.
- Wird eine typfreie Konstante einer Variablen eines Interface-Typs oder dem Leeren Bezeichner zugewiesen, so wird die Konstante zunächst implizit zu ihrem Standardtyp konvertiert.
-
Wird ein typfreier Boole'scher Wert einer Variablen eines
Interface-Typs oder dem Leeren Bezeichner zugewiesen, so wird er
zunächst implizit zum
Typ
bool
konvertiert.
If-Anweisungen
If-Anweisungen legen die bedingte Ausführung zweier Verarbeitungszweige
gemäß dem Wert eines Boole'schen Ausdrucks fest. Wenn der Ausdruck
true
ergibt, wird der If-Zweig ausgeführt, ansonsten,
wenn vorhanden, der Else-Zweig.
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max { x = max }
Vor dem Boole'schen Ausdruck darf eine einfache Anweisung stehen, die ausgeführt wird, bevor der Ausdruck ausgewertet wird:
if x := f(); x < y { return x } else if x > z { return z } else { return y }
Switch-Anweisungen
Switch-Anweisungen ermöglichen die Auswahl aus vielen Verarbeitungszweigen. Ein Ausdruck oder ein Typ wird mit Fallwerten verglichen, um zu entscheiden, welcher Zweig zu verarbeiten ist.
"Switch" statements provide multi-way execution. An expression or type is compared to the "cases" inside the "switch" to determine which branch to execute.
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
Es gibt zwei Formen: den Ausdruck-Switch und den Typ-Switch. Bei einem Ausdruck-Switch enthalten die Case-Klauseln Ausdrücke, die mit dem Wert des Switch-Ausdrucks verglichen werden. Bei einem Typ-Switch enthalten die Case-Klauseln Typen, die mit dem Typ des dafür besonders formulierten Switch-Ausdrucks verglichen werden. In einer Switch-Anweisung wird der Switch-Ausdruck genau einmal ausgewertet.
Ausdruck-Switch
Bei einem Ausdruck-Switch wird der Switch-Ausdruck ausgewertet, und die
Case-Ausdrücke, die keine Konstanten zu sein brauchen, werden nacheinander
von links nach rechts und von oben nach unten ausgewertet; die erste Übereinstimmung
löst die Ausführung der Anweisungen aus, die zum entsprechenden Fall gehören;
die restlichen Case-Klauseln werden übergangen.
Gibt es keine Übereinstimmung, aber eine Default-Klausel, so werden deren
Anweisungen ausgeführt.
Es darf maximal eine Default-Klausel geben, die irgendwo in der Switch-Anweisung
stehen darf.
Ein fehlender Switch-Ausdruck ist gleichbedeutend mit dem Boole'schen Wert
true
.
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .
Wird der Switch-Ausdruck zu einer typfreien Konstanten ausgewertet,
so wird er zuerst implizit zu seinem
Vorgabetyp
konvertiert.
Der vordeklarierte typfreie Wert nil
kann
nicht als Switch-Ausdruck benutzt werden.
Der Switch-Ausdruck muss
vergleichbar sein.
Wenn der Case-Ausdruck typfrei ist, wird er zuerst implizit zu
dem Typ des Switch-Ausdrucks
konvertiert.
Für jeden Case-Ausdruck x
und den Wert t
des Switch-Ausdrucks muss x == t
ein gültiger
Vergleich sein.
Anders ausgedrückt wird ein Switch-Ausdruck so behandelt, als ob er zum
Deklarieren und Initialisieren einer temporären Variablen t
ohne expliziten Typ benutzt würde; gegen diesen Wert von t
wird jeder Case-Ausdruck x
auf Gleichheit geprüft.
Nur die jeweils letzte nicht-leere Anweisung einer Case- oder Default-Klausel darf eine Fallthrough-Anweisung (eventuell mit Sprungmarke) sein, und zeigt an, dass es danach mit der ersten Anweisung der nächsten Klausel weitergeht. Ohne Fallthrough geht's am Ende der Switch-Anweisung weiter. Eine Fallthrough-Anweisung darf letzte Anweisung jeder Klausel eines Ausdruck-Switch sein, außer der letzten.
Vor dem Switch-Ausdruck darf eine einfache Anweisung stehen, die ausgeführt wird, bevor der Ausdruck ausgewertet wird.
switch tag { default: s3() case 0, 1, 2, 3: s1() case 4, 5, 6, 7: s2() } switch x := f(); { // Switch-Ausdruck fehlt == "true" case x < 0: return -x default: return x } switch { case x < y: f1() case x < z: f2() case x == 4: f3() }
Go-Implementierungseinschränkung: Ein Compiler darf verbieten, dass mehrere Case-Ausdrücke zur gleichen Konstanten ausgewertet würden. Zum Beispiel verbieten die aktuellen Compiler doppelte Ganzzahl-, Gleitkomma- oder String-Konstanten in Case-Ausdrücken.
Typ-Switch
Ein Typ-Switch vergleicht Typen, nicht Werte. Davon abgesehen ist er dem
Ausdruck-Switch ähnlich. Kennzeichnend ist ein besonderer Switch-Ausdruck
in Form einer Typzusicherung, wobei anstelle eines
echten Typs das Schlüsselwort type
benutzt wird:
switch x.(type) { // Case-Klauseln }
Die Case-Klauseln vergleichen dann die jeweiligen Typen T
mit dem
dynamischen Typ des Ausdrucks x
. Wie bei Typzusicherungen muss
x
von einem Interface-Typ sein und
jeder Nicht-Interface-Typ T
in einer Case-Klausel muss den Typ
von x
implementieren.
Die in den Case-Klauseln aufgeführten Typen müssen sich alle voneinander
unterscheiden.
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" . TypeList = Type { "," Type } .
Die Wächterklausel (TypeSwitchGuard) kann eine Variablenkurzdeklaration enthalten. Wird diese Kurzform benutzt, so wird die Variable am Ende des TypeSwitchCase im impliziten Blocks jeder Klausel deklariert. Nennt die Case-Klausel genau einen Typ, so bekommt die Variable diesen Typ; andernfalls bekommt die Variable den Typ des Ausdrucks in der Wächterklausel.
Anstelle eines Typs kann die Case-Klausel den vordefinierten Bezeichner
nil
benutzen;
sein;
dieser Fall tritt ein, wenn der Ausdruck in der Wächterklausel ein
nil
-Interface-Wert ist.
Es darf nur einen nil
-Fall geben.
Für einen vorgegebenen Ausdruck x
vom Typ interface{}
könnte man den folgenden Typ-Switch:
switch i := x.(type) { case nil: printString("x ist nil") // Typ von i ist Typ von x (interface{}) case int: printInt(i) // Typ von i ist int case float64: printFloat64(i) // Typ von i ist float64 case func(int) float64: printFunction(i) // Typ von i ist func(int) float64 case bool, string: printString("Typ ist bool oder string") // Typ von i ist Typ von x (interface{}) default: printString("unbekannter Typ") // Typ von i ist Typ von x (interface{}) }
auch so schreiben:
v := x // x wird genau einmal ausgewertet if v == nil { i := v // Typ von i ist Typ von x (interface{}) printString("x ist nil") } else if i, isInt := v.(int); isInt { printInt(i) // Typ von i ist int } else if i, isFloat64 := v.(float64); isFloat64 { printFloat64(i) // Typ von i ist float64 } else if i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // Typ von i ist func(int) float64 } else { _, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // Typ von i ist Typ von x (interface{}) printString("Typ ist bool oder string") } else { i := v // Typ von i ist Typ von x (interface{}) printString("unbekannter Typ") } }
Vor der Wächterklausel darf eine einfache Anweisung stehen, die ausgeführt wird, bevor die Klausel ausgewertet wird.
Die Anweisung fallthrough
ist in einem Typ-Switch nicht erlaubt.
For-Anweisungen
Eine For-Anweisung legt die wiederholte Ausführung eines Blocks fest. Drei Formen davon gibt es: Diese Wiederholung (iteration) kann durch eine einzelne Bedingung, eine For-Klausel oder eine Range-Klausel kontrolliert werden.
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
For-Anweisungen mit einer einzelnen Bedingung
In ihrer einfachsten Form legt eine For-Anweisung die wiederholte Ausführung
eines Blocks fest, solange ein Boole'scher Ausdruck true
ist.
Die Bedingung wird vor jeder Iteration ausgewertet. Fehlt die Bedingung,
so ist das gleichbedeutend mit dem Boole'schen Wert true
.
for a < b { a *= 2 }
For-Anweisungen mit einer for
-Klausel
Eine For-Anweisung mit For-Klausel wird ebenfalls durch eine Bedingung kontrolliert, kann aber zusätzlich einen Startschritt (InitStmt) und einen Zählschritt (PostStmt) festlegen, z.B. in Form einer Zuweisung, oder einer Inkrement- oder Dekrement-Anweisung. Der Startschritt darf eine Variablenkurzdeklaration sein, der Zählschritt nicht. Variablen, die im Startschritt deklariert wurden, werden bei jeder Iteration wiederverwendet.
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt .
for i := 0; i < 10; i++ { f(i) }
Eine nicht-leerer Startschritt wird genau einmal ausgeführt, bevor die
Bedingung für die erste Iteration ausgewertet wird, der Zählschritt
nach jeder Ausführung des Blocks ausgeführt, und nur dann.
Jedes Element der For-Klausel darf leer sein, die
Semikolons jedoch sind zwingend, außer, wenn
nur die Bedingung angegeben ist.
Fehlt die Bedingung, so ist sie gleichbedeutend dem Boole'schen Wert
true
.
for cond { S() } das gleiche wie: for ; cond ; { S() } for { S() } das gleiche wie: for true { S() }
For-Anweisungen mit einer range
-Klausel
Eine For-Anweisung mit einer Range-Klausel iteriert über alle Elemente eines Arrays, eines Slices, eines Strings oder einer Map, oder den Werten, die über einen Kanal empfangen werden. Für jedes Element weist es die Iterations-Werte den zugehörigen Iterations-Variablen, soweit vorhanden, zu und führt dann den Block aus.
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
Der Ausdruck ganz rechts in der Range-Klausel heißt Range-Ausdruck, und er kann ein Array, ein Zeiger auf ein Array, ein Slice, ein String, eine Map oder ein Empfangsoperationen erlaubender Kanal sein. Wie bei einer Zuweisung müssen die Operanden auf der linken Seite, wenn vorhanden, adressierbar oder Map-Indexausdrücke sein; sie bezeichnen die Iterations-Variablen. Ist der Range-Ausdruck ein Kanal, so ist maximal eine Iterations-Variable erlaubt; sonst sind bis zu zwei. Wenn die letzte Iterations-Variable der Leere Bezeichner ist, dann ist diese Range-Klausel gleichbedeutend mit einer Range-Klausel ohne diesen Bezeichner.
Der Range-Ausdruck x
wird genau einmal vor Ausführung
der Schleife ausgewertet, mit einer Ausnahme:
Wenn es höchstens eine Iterationsvariable gibt und
len(x)
konstant ist,
dann wird der Range-Ausdruck gar nicht ausgewertet.
Funktionsaufrufe auf der linken Seite werden einmal pro Iteration ausgewertet. Bei jeder Iteration werden die Iterations-Werte wie folgt erzeugt (soweit die entsprechenden Iterationsvariablen vorhanden sind):
Range-Ausdruck 1. Wert 2. Wert Array oder Slice a [n]E, *[n]E, or []E Index i int a[i] E String s string type Index i int siehe unten rune Map m map[K]V Schlüssel k K m[k] V Kanal c chan E, <-chan E Element e E
-
Für einen Array-, Zeiger-auf-Array- oder Slice-Wert
a
werden die Indexwerte in aufsteigender Reihenfolge erzeugt, beginnend mit dem Elementindex 0. Wenn maximal eine Iterationsvariable angegeben ist, produziert die Range-Schleife Indexwerte von 0 bislen(a)-1
, ohne auf das Array oder Slice zuzugreifen. Für einnil
-Slice ist die Anzahl der Iterationen 0. -
Für einen String-Wert iteriert die Range-Klausel über die im String enthaltenen
Unicode-Kodenummern und beginnt beim Byte-Index 0. Bei den folgenden Iterationen
ist der Indexwert dann jeweils der Index des ersten Bytes einer UTF-8-kodierten
Kodenummer im String. Der zweite Wert vom Typ
rune
enthält dann den Wert der zugehörigen Kodenummer. Wenn die Iteration eine ungültige Kodenummer entdeckt, dann wird der zweite Wert zu0xFFFD
(dem Unicode-Ersatzzeichen), und die nächste Iteration schreitet im String um ein Byte voran. -
Die Iterationsreihenfolge über die Elemente von Maps ist weder festgelegt,
noch wird garantiert, dass sie von einer Iteration zur nächsten dieselbe wäre.
Wird ein Map-Element, das während einer Iteration noch nicht erreicht wurde,
entfernt, dann wird der entsprechende Iterationswert nicht produziert.
Wird während einer Iteration ein Element in der Map erzeugt, dann kann dieser
Map-Eintrag von der Iteration erfasst oder auch übergangen werden. Das kann von
einem erzeugten Wert zum nächsten und von einer Iteration zur nächsten anders sein.
Für eine
nil
-Map ist die Anzahl der Iterationen 0. -
Für Kanäle werden als Iterationswerte die über den Kanal gesendeten einander
folgenden Werte produziert; solange bis der Kanal geschlossen
wird.
Für einen
nil
-Kanal blockiert der Range-Ausdruck für immer.
Die Iterationswerte werden den entsprechenden Iterationsvariablen zugewiesen.
Die Iterationsvariablen dürfen in der Range-Klausel mithilfe einer
Variablenkurzdeklaration
(:=
) deklariert werden.
In diesem Fall wird ihr Typ der des jeweiligen Iterationswerts; ihre
Reichweite ist der Block der
For-Anweisung; innerhalb der Iteration werden sie wiederverwendet.
Wurden die Variablen außerhalb der For-Anweisung deklariert, so werden
nach deren Ausführung ihre Werte die aus der letzten Iteration sein.
var testdata *struct { a *[7]int } for i, _ := range testdata.a { // testdata.a wird nie ausgewertet; len(testdata.a) ist konstant // i reicht von 0 bis 6 f(i) } var a [10]string for i, s := range a { // Typ von i ist int // Typ von s ist string // s == a[i] g(i, s) } m := map[string]int{"Mo":1, "Di":2, "Mi":3, "Do":4, "Fr":5, "Sa":6, "So":7} var key string var val interface{} // Elementtyp von m ist val zuweisbar for key, val = range m { h(key, val) } // key == key aus der letzten Iteration // val == m[key] var ch chan Work = producer() for w := range ch { doWork(w) } // Kanal leeren for range ch {}
Go-Anweisungen
Eine Go-Anweisung startet die Ausführung eines Funktionsaufrufs in der Form eines unabhängigen Verarbeitungsstrangs [englisch: control thread, A.d.Ü.], genannt Goroutine, im selben Adressraum.
GoStmt = "go" Expression .
Der Ausdruck muss ein Funktions- oder Methodenaufruf sein; er darf nicht geklammert werden. Aufrufe von Standardfunktionen sind eingeschränkt wie bei Ausdruck-Anweisungen.
Der Funktionswert und die Parameter werden wie üblich von der rufenden Goroutine ausgewertet, aber anders als bei einem üblichen Aufruf wartet das Programm nicht auf das Ende der gerufenen Funktion. Stattdessen beginnt die Ausführung der Funktion unabhängig in einer neuen Goroutine. Wenn die Funktion endet, endet auch ihre Goroutine. Produziert die Funktion Ergebniswerte, so werden diese bei Funktionsende verworfen.
go Server() go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Select-Anweisungen
Eine Select-Anweisung wählt aus einer Menge möglicher Kommunikationsoperationen eine zur Ausführung aus. Sie ähnelt einer Switch-Anweisung, nur dass alle Case-Klauseln sich auf Kommunikationsoperationen beziehen.
SelectStmt = "select" "{" { CommClause } "}" . CommClause = CommCase ":" StatementList . CommCase = "case" ( SendStmt | RecvStmt ) | "default" . RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . RecvExpr = Expression .
Der Empfangsausdruck (RecvExpr) muss eine
Empfangsoperation sein.
Für alle Fälle der Select-Anweisung werden die Kanalausdrücke von oben
nach unten ausgewertet, zusammen mit allen Ausdrücken rechts von Sendeanweisungen.
Ein Kanal darf nil
sein; das ist dann so, als wäre dieser Fall
gar nicht vorhanden, nur, dass bei einer Sendeanweisung dessen Ausdruck ausgewertet
wird.
Wenn Operationen entdeckt werden, die ausgeführt werden können, dann wird eine
davon ausgewählt und die zugehörige Kommunikationsoperation und die zugehörigen
Anweisungen werden ausgeführt; andernfalls, wenn es einen Default-Fall gibt, wird
dieser ausgeführt; gibt es keinen Default-Fall, so blockiert die Select-Anweisung
solange, bis eine der Kommunikationsoperationen ausgeführt werden kann.
Es darf maximal eine Default-Klausel geben, und zwar an beliebiger Stelle in der
Select-Anweisung.
Gibt es keinen Fall mit Nicht-nil
-Kanälen, so wartet die Anweisung
endlos.
Auch wenn die Anweisung warten muss, so werden die Kanal- und die Sendeausdrücke
nur einmal, beim "Eintritt" in die Select-Anweisung, ausgewertet.
Dadurch dass alle Kanal- und alle Sendeausdrücke ausgewertet werden, treten Nebeneffekte, wenn, dann für alle Kommunikationsoperationen der Select-Anweisung auf.
Können mehrere Fälle ausgeführt werden, wird mit einem einheitlichen Pseudo-Zufallsverfahren eine der Kommunikationsoperation zur Ausführung ausgewählt.
Ein Fall mit Empfangsanweisung darf ein oder zwei neue Variablen mithilfe der Variablenkurzdeklaration deklarieren.
var c, c1, c2, c3 chan int var i1, i2 int select { case i1 = <-c1: print(i1, " von c1 empfangen\n") case c2 <- i2: print(i2, " nach c2 gesendet\n") case i3, ok := (<-c3): // gleich i3, ok := <-c3 if ok { print(i3, " von c3 empfangen\n") } else { print("c3 ist geschlossen\n") } default: print("kein Nachrichtenaustausch\n") } for { // Sende eine zufällige Folge von "Bits" nach c select { case c <- 0: // Beachte: Keine Anweisung, kein Fallthrough, keine Zusammenfassung von Cases case c <- 1: } } select {} // Warte auf Godot !
Return-Anweisungen
Eine Return-Anweisung in einer Funktion F
beendet die Ausführung von
F
und stellt optional dem Rufer ein oder mehrere Ergebniswerte zur
Verfügung. Jede Funktion, die von F
zurückgestellt wurde, wird ausgeführt, bevor
F
die Kontrolle an den Aufrufer zurückgibt.
ReturnStmt = "return" [ ExpressionList ] .
In einer Funktion ohne Ergebnistyp darf eine Return-Anweisung keine Ergebniswerte nennen.
func noResult() { return }
Auf drei verschiedene Arten kann man für eine Funktion mit Ergebnistypen die Ergebniswerte zurückgeben:
-
Die Ergebniswerte können explizit in der Return-Anweisung aufgezählt werden.
Jeder Ausdruck darf nur einen Wert ergeben und muss dem entsprechenden
Ergebnistyp der Funktion zuweisbar sein.
func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 }
-
Die Liste der Ausdrücke in der Return-Anweisung kann ein Aufruf einer
Funktion mit mehreren Ergebniswerten sein. Dies verhält sich so, als ob jeder
Ergebniswert der Funktion einer temporären Variablen des entsprechenden Typs
zugeordnet würde und im Anschluss daran eine Return-Anweisung mit einer Liste
dieser Variablen stünde; ab hier greifen dann die Regeln des vorhergehenden Falls.
func complexF2() (re float64, im float64) { return complexF1() }
-
Die Liste der Ausdrücke darf leer sein, wenn der Ergebnistyp der Funktion
Namen für die Ergebnisparameter angibt.
Die Ergebnisparameter funktionieren wie normale lokale Variablen und die Funktion
kann ihnen seiner Aufgabe entsprechend Werte zuweisen.
Die Return-Anweisung gibt dann die Werte dieser Variablen zurück.
func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return } func (devnull) Write(p []byte) (n int, _ error) { n = len(p) return }
Unabhängig davon, wie sie deklariert sind, werden alle Ergebniswerte beim Eintritt in die Funktion mit den Nullwerten ihres Typs initialisiert. Eine Return-Anweisung, die Ergebnisparameter angibt, setzt diese, bevor eventuelle zurückgestellte Funktionen ausgeführt werden.
Go-Implementierungseinschränkung: Ein Compiler kann eine leere Ausdrückeliste in einer Return-Anweisung verbieten, wenn es in Reichweite der Return-Anweisung ein anderes Objekt (Konstante, Typ oder Variable) mit demselben Namen wie dem eines Ergebnisparameters gibt.
func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // fehlerhafte Return-Anweisung: err ist abgeschattet } return }
Break-Anweisungen
Eine Break-Anweisung [ohne Sprungmarke, A.d.Ü.] beendet die Ausführung der innersten For-, Switch- oder Select-Anweisung innerhalb derselben Funktion.
BreakStmt = "break" [ Label ] .
Ist eine Sprungmarke angegeben, so muss sie die einer übergeordneten For-, Switch- oder Select-Anweisung sein, und diese Anweisung wird dann beendet.
OuterLoop: for i = 0; i < n; i++ { for j = 0; j < m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } }
Continue-Anweisungen
Eine Continue-Anweisung [ohne Sprungmarke, A.d.Ü.] beginnt die nächste Iteration der innersten For-Schleife mit deren Zählschritt. Die For-Schleife muss sich in derselben Funktion befinden.
ContinueStmt = "continue" [ Label ] .
Ist eine Sprungmarke angegeben, so muss sie die einer übergeordneten For-Anweisung sein, und diese Anweisung wird fortgeführt.
RowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } }
Goto-Anweisungen
Eine Goto-Anweisung gibt die Kontrolle an die Anweisung mit der entsprechenden Sprungmarke innerhalb derselben Funktion weiter.
GotoStmt = "goto" Label .
goto Error
Die Ausführung der Goto-Anweisung darf nicht dazu führen, dass Variablen in Reichweite geraten, die sich nicht bereits beim Goto selbst in Reichweite befanden. Das folgende Beispiel:
goto L // SCHLECHT v := 3 L:
ist fehlerhaft, weil der Sprung zur Marke L
die Erzeugung von
v
überspringt.
Eine Goto-Anweisung außerhalb eines Blocks kann nicht zu einer Marke in diesem Block springen. Das folgende Beispiel:
if n%2 == 1 { goto L1 // SCHLECHT } for n > 0 { f() n-- L1: f() n-- }
ist fehlerhaft, weil sich die Marke L1
innerhalb des
For-Anweisungsblocks befindet, das Goto
aber nicht.
Fallthrough-Anweisungen
Eine Fallthrough-Anweisung gibt die Kontrolle an die erste Anweisung der nächsten Case-Klausel einer Ausdruck-Switch-Anweisung weiter. Es darf nur als letzte nicht-leere Anweisung in einer solchen Klausel verwendet werden.
FallthroughStmt = "fallthrough" .
Defer-Anweisungen
Eine Defer-Anweisung ruft eine Funktion, deren Ausführung bis zu dem Zeitpunkt zurückgestellt wird, an dem die umschließende Funktion endet. Das, entweder weil die umschließende Funktion eine Return-Anweisung ausführte, oder weil das Ende ihres Funktionsrumpfs erreicht wurde, oder weil die betreffende Goroutine in Panik geriet.
DeferStmt = "defer" Expression .
Der Ausdruck muss der Aufruf einer Funktion oder Methode sein; er darf nicht geklammert sein. Aufrufe von Standardfunktionen sind eingeschränkt wie bei Ausdruck-Anweisungen.
Jedes Mal, wenn eine Defer-Anweisung ausgeführt wird, werden Wert und Parameter
der zu rufenden Funktion wie üblich ausgewertet und gesichert
aber die Funktion selbst wird nicht aufgerufen. Stattdessen werden zurückgestellte
Funktionen in umgekehrter Reihenfolge aufgerufen, direkt bevor
die umschließende Funktion endet.
Soll heißen: Wenn die umschließende Funktion explizit mit einer
Return-Anweisung endet, dann
werden die zurückgestellten Funktionen ausgeführt, nachdem
alle Ergebnisparameter durch diese Return-Anweisung gesetzt wurden
und bevor von der Funktion zum Rufer zurückgekehrt wird.
Wenn der Wert einer zurückgestellten Funktion nil
ist, gibt es einen
Laufzeitfehler, wenn die Funktion aufgerufen wird,
nicht schon bei der Ausführung von "defer".
Ist zum Beispiel die zurückgestellte Funktion ein Funktionsliteral und hat die umschließende Funktion namensbehaftete Ergebnisparameter, die ja im Funktionsliteral erreichbar sind, so kann die zurückgestellte Funktion auf diese Ergebnisparameter zugreifen und sie auch verändern, bevor sie zurückgegeben werden. Hat die zurückgestellte Funktion eigene Ergebniswerte, so werden diese bei Funktionsende verworfen. (Siehe auch den Abschnitt über die Handhabung von Laufzeitfehlern.)
lock(l) defer unlock(l) // Entsperren geschieht, bevor die umschließende Funktion endet // druckt 3 2 1 0, bevor die umschließende Funktion endet for i := 0; i <= 3; i++ { defer fmt.Print(i) } // f gibt 42 zurück func f() (result int) { defer func() { // result wird angesprochen, // nachdem es durch return auf 6 gesetzt wurde result *= 7 }() return 6 }
Standardfunktionen
Standardfunktionen sind vordeklariert. Sie werden genau wie andere Funktionen gerufen; es gibt aber welche, die als ersten Parameter einen Typ statt eines Ausdrucks akzeptieren.
Weil Standardfunktionen keinen Standard-Go-Typ haben, dürfen sie nur in Aufrufen erscheinen; sie können also nicht als Funktionswerte benutzt werden.
Close
Für einen Kanal c
legt close(c)
fest, dass keine
Werte mehr über den Kanal gesendet werden; es ist ein Fehler, wenn c
nur Empfangskanal ist. Senden in einen Kanal oder Schließen eines bereits
geschlossenen Kanals führt zu einem Laufzeitfehler.
Das Schließen eines nil
-Kanals führt ebenfalls zu einem
Laufzeitfehler.
Nach dem Aufruf von close
und nachdem alle vorher gesendeten Werte
empfangen worden sind, blockiert die Empfangsoperation nicht, sondern gibt den
Nullwert des Elementtyps des Kanals zurück.
Empfangsoperationen mit Mehrfach-Ergebniswerten
geben den empfangenen Wert und einen Anzeiger dafür zurück, ob der Kanal
geschlossen ist.
Länge und Kapazität
Die Standardfunktionen len
und cap
akzeptieren
Argumente verschiedenen Typs und geben einen Wert vom Typ int
zurück. Die Go-Implementierung garantiert, dass das Ergebnis immer in ein
int
hineinpasst.
Aufruf Argumenttyp Ergebnis len(s) String-Typ String-Länge in Bytes [n]T, *[n]T Array-Länge (== n) []T Slice-Länge map[K]T Map-Länge (Anzahl definierter Schlüssel) chan T Anzahl von Elementen, die im Kanalpuffer warten cap(s) [n]T, *[n]T Array-Länge (== n) []T Slice-Kapazität chan T Kanalpuffer-Kapazität
Die Kapazität eines Slice ist die Anzahl von Elementen, für die im zugrundeliegenden Array Platz vorhanden ist. Zu jedem Zeitpunkt gilt:
0 <= len(s) <= cap(s)
Die Länge von nil
-Slices, -Maps oder -Kanälen ist 0.
Die Kapazität von nil
-Slices und -Kanälen ist 0.
Der Ausdruck len(s)
ist konstant, wenn
s
eine String-Konstante ist.
Die Ausdrücke len(s)
und cap(s)
sind konstant, wenn
s
vom Typ Array oder Zeiger auf ein Array ist und der Ausdruck
s
weder eine Empfangsoperation
noch einen (nicht-konstante) Funktionsaufruf enthält;
in solchen Fällen wird s
nicht ausgewertet.
In allen anderen Fällen sind Aufrufe von len
und cap
nicht
konstant und s
wird ausgewertet.
const ( c1 = imag(2i) // imag(2i) = 2.0 ist eine Konstante c2 = len([10]float64{2}) // [10]float64{2} enthält keine Funktionsaufrufe c3 = len([10]float64{c1}) // [10]float64{c1} enthält keine Funktionsaufrufe c4 = len([10]float64{imag(2i)}) // imag(2i) ist eine Konstante und kein Funktionsaufruf c5 = len([10]float64{imag(z)}) // verboten: imag(z) ist ein (nicht-konstanter) Funktionsaufruf ) var z complex128
Speicherzuteilung
Die Standardfunktion new
akzeptiert einen Typ T
,
fordert zur Laufzeit Speicher für eine Variable dieses
Typs an und gibt einen Wert vom Typ *T
zurück, der darauf
zeigt.
Der Speicher wird so initialisiert, wie im Abschnitt über
Nullwerte beschrieben.
new(T)
Zum Beispiel fordert
type S struct { a int; b float64 } new(S)
Speicher für eine Variable vom Typ S
, initialisiert ihn
(a=0
, b=0.0
) und gibt einen Wert vom Typ
*S
zurück, der die Speicheradresse enthält.
Make für Slices, Maps und Kanäle
Die Standardfunktion make
akzeptiert einen Typ T
,
der ein Slice-, Map- oder Kanaltyp sein muss; es folgt eine typspezifische
Liste von Ausdrücken.
Sie gibt einen Wert vom Typ T
zurück (nicht *T
).
Speicher wird initialisiert, wie im Abschnitt über
Nullwerte beschrieben.
Aufruf Typ T Ergebnis make(T, n) Slice Slice vom Typ T mit Länge n und Kapazität n make(T, n, m) Slice Slice vom Typ T mit Länge n und Kapazität m make(T) Map Map vom Typ T make(T, n) Map Map vom Typ T anfänglich mit Platz für etwa n Elemente make(T) Kanal ungepufferter Kanal vom Typ T make(T, n) Kanal gepufferter Kanal vom Typ T mit Puffergröße n
Beide Größenargumente n
und m
müssen von
einem Ganzzahltyp oder typfreie Konstanten
sein. Ein konstantes Größenargument
darf nicht negativ und muss als Wert vom Typ int
darstellbar sein; ist es eine typfreie
Konstante, so bekommt es den Typ int
.
Wenn sowohl n
als auch m
angegeben und konstant sind, dann darf n
nicht größer als
m
sein.
Ist zur Laufzeit n
negativ oder größer als m
,
so tritt ein Laufzeitfehler auf.
s := make([]int, 10, 100) // Slice mit len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // Slice mit len(s) == cap(s) == 1000 s := make([]int, 10, 0) // verboten: len(s) > cap(s) s := make([]int, 1<<63) // verboten: len(s) ist nicht als Wert vom Typ int darstellbar c := make(chan int, 10) // Kanal mit Puffergröße 10 m := make(map[string]int, 100) // Map mit anfänglich Platz für etwa 100 Elemente
Der Aufruf von make
mit einem Map-Typ und einem Größenhinweis n
erzeugt eine Map mit einer Anfangsgröße für n
Map-Elemente.
Das genaue Verhalten ist von der Implementierung abhängig.
Append und Copy für Slices
Die Standardfunktionen append
und copy
helfen
bei gängigen Slice-Operationen. Bei beiden Funktionen ist das Ergebnis
unabhängig davon, ob der Speicherplatz der mitgegebenen Argumente
überlappt.
Die variadische Funktion append
fügt einen oder mehrere Werte x
an ein Slice s
eines Slice-Typs S
an, und gibt ein Slice, das ebenfalls vom Typ
S
ist, zurück.
Die Werte x
werden einem Parameter vom Typ ...T
übergeben, wobei T
der Elementtyp von
S
ist und die entsprechenden
Parameterübergaberegeln
angewendet werden.
Als Spezialfall akzeptiert append
auch ein erstes Argument, das
einem Typ []byte
zuweisbar ist zusammen mit einem zweiten Argument
mit einem String-Typ gefolgt von ...
. Diese Form fügt die Bytes
des Strings an.
append(s S, x ...T) S // T ist der Elementtyp von S
Wenn die Kapazität von s
nicht ausreicht, die zusätzlichen Werte
aufzunehmen, dann erzeugt append
ein neues, genügend großes
zugrundeliegendes Array, das sowohl die existierenden Slice-Elemente als auch
die zusätzlichen Werte aufnehmen kann. Ansonsten verwendet append
das bereits existierende zugrundeliegende Array.
s0 := []int{0, 0} s1 := append(s0, 2) // ein Element anhängen s1 == []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // mehrere Elemente anhängen s2 == []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // ein Slice anhängen s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // überlappendes Slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // Inhalt eines Strings anhängen b == []byte{'b', 'a', 'r' }
s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' }
Die Funktion copy
kopiert Slice-Elemente von einer Quelle
src
nach einem Ziel dst
und gibt die Anzahl der
kopierten Elemente zurück.
Beide Argumente müssen vom gleichen Elementtyp
T
und einem Slice vom Typ []T
zuweisbar sein.
Anzahl der kopierten Elemente ist die kleinere der beiden Längen
len(src)
und len(dst)
.
Als Spezialfall akzeptiert copy
ein Zielargument, das dem Typ
[]byte
zuweisbar ist, zusammen mit einem Quellargument
mit einem String-Typ. Diese Form kopiert Bytes aus dem String in ein Byte-Slice.
copy(dst, src []T) int copy(dst []byte, src string) int
Beispiele:
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7} var s = make([]int, 6) var b = make([]byte, 5) n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Hallo, Welt!") // n3 == 5, b == []byte("Hallo")
Delete für Map-Elemente
Die Standardfunktion delete
entfernt das Element mit dem
Schlüssel k
aus einer Map m
.
Der Typ von k
muss dem Schlüsseltyp von m
zuweisbar sein.
delete(m, k) // Entferne Element m[k] aus der Map m
Wenn die Map m
nil
ist oder das Element m[k]
nicht existiert, dann tut delete
nichts.
Manipulieren von komplexen Zahlen
Drei Funktionen erzeugen und zerlegen komplexe Zahlen.
Die Standardfunktion complex
konstruiert einen Komplexwert
aus einem Gleitkommateil für den Real- und einem für den Imaginäranteil.
Die Funktionen real
und imag
extrahieren
den Real- bzw. den Imaginärteil aus einem Komplexwert.
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
Die Typen der Argumente und der Ergebniswerte entsprechen sich.
Im Fall von complex
müssen die beiden Argumente vom selben
Gleitkommatyp sein und der Ergebnistyp ist der Komplextyp, dessen Bestandteile
vom selben Gleitkommatyp sind, nämlich:
complex64
für float32
-Argumente und
complex128
für float64
-Argumente.
Wird eines der Argumente zu einer typfreien Konstanten ausgewertet,
so wird es zuerst implizit zum Typ des anderen Arguments
konvertiert.
Werden beide Argumente zu typfreien Konstanten ausgewertet,
so dürfen sie keine Komplexzahlen sein oder ihr imaginärer Teil
muss Null sein; der Rückgabewert der Funktion ist eine typfreie komplexe
Konstante.
In den Fällen real
und imag
muss das Argument
vom Komplextyp sein, und der Ergebniswert ist vom korrespondierenden
Gleitkommatyp, nämlich:
float32
für ein complex64
-Argument und
float64
für ein complex128
-Argument.
Wird das Argument zu einer typfreien Konstanten ausgewertet, so muss es
eine Zahl sein; der Rückgabewert der Funktion ist eine typfreie
Gleitkommakonstante.
Die Funktionen real
und imag
bilden zusammen
das Inverse von complex
, so dass für einen Wert
z
vom Komplextyp Z
gilt:
z == Z(complex(real(z), imag(z)))
.
Sind die Operanden dieser Funktionen Konstanten, so ist auch der Ergebniswert eine Konstante.
var a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // typfreie Komplexkonstante 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s int = complex(1, 0) // typfreie Komplexkonstante 1 + 0i kann nach int konvertiert werden _ = complex(1, 2<<s) // verboten: 2 gilt als Gleitkommatyp; kann nicht verschoben werden var rl = real(c64) // float32 var im = imag(b) // float64 const c = imag(b) // typfreie Konstante -1.4 _ = imag(3 << s) // verboten: 3 gilt als Komplextyp; kann nicht verschoben werden
Handhabung von Laufzeitfehlern
Die zwei Standardfunktionen panic
und recover
unterstützen beim Melden und Behandeln von
Laufzeitfehlern ebenso wie dem von
programmspezifischen Fehlersituationen.
func panic(interface{}) func recover() interface{}
Ein expliziter Aufruf von panic
oder ein
Laufzeitfehler während der Ausführung von F
stoppt die Ausführung von F
.
Alle Funktionen, deren Ausführung durch F
zurückgestellt wurden, werden wie üblich
abgearbeitet.
Danach werden alle zurückgestellten Funktionen des Aufrufers von F
abgearbeitet, und so weiter bis zu allen zurückgestellten Funktionen der
Top-Level-Funktion der betroffenen Goroutine.
Hier nun endet das Programm, und der Fehlerzustand, inklusive dem
Aufrufargument von panic
, wird gemeldet. Diese Abwicklungssequenz
nennen wir Panik.
panic(42) panic("unerreichbar") panic(Error("parsen nicht möglich"))
Die Funktion recover
erlaubt einem Programm, das Verhalten
einer panischen Goroutine zu steuern.
Nehmen wir den Fall einer Funktion G
, die eine Funktion D
zurückstellt, die wiederum recover
aufruft, und nehmen wir weiter an,
dass in einer Funktion, die in derselben Goroutine arbeitet wie G
eine Panik auftritt.
Wenn nun die Abarbeitung der zurückgestellten Funktionen bei D
ankommt,
wird der Ergebniswert von D
s Aufruf von recover
der sein,
der zuvor dem Aufruf von panic
übergeben wurde.
Wenn D
nun normal endet ohne erneut panic
auszulösen, dann stoppt die Panik-Abwicklungssequenz. Dann wird auch der Status der
Funktionen zwischen G
und dem Aufruf von panic
zurückgesetzt,
und normal weitergearbeitet. Alle Funktionen, die durch G
vor D
zurückgestellt wurden, werden dann abgearbeitet, die Verarbeitung von G
endet mit der Rückkehr zu seinem Rufer.
Der Ergebniswert von recover
ist nil
in folgenden Fällen:
-
Das an
panic
übergebene Argument warnil
, - in der Goroutine tritt keine Panik auf oder
-
recover
wurde nicht direkt durch eine zurückgestellte Funktion gerufen.
Die Funktion protect
im folgenden Beispiel ruft das Argument
g
(eine Funktion) und schützt Aufrufer vor Laufzeitfehlern von
g
:
func protect(g func()) { defer func() { log.Println("Fertig") // Println arbeitet normal, auch bei Panik if x := recover(); x != nil { log.Printf("Panik zur Laufzeit: %v", x) } }() log.Println("Start") g() }
Bootstrapping
Die derzeitigen Go-Implementierungen kennen Standardfunktionen, die bei der Generierung der Go-Umgebung hilfreich sind. Sie werden hier der Vollständigkeit halber dokumentiert; es wird jedoch nicht garantiert, dass sie Teil der Sprache bleiben. Sie haben keine Ergebniswerte.
Funktion Verhalten print druckt alle Argumente; Formatierungen sind abhängig von der Go-Implementierung println wie print, nur mit Leerzeichen zwischen Argumenten und Zeilenvorschub am Ende
Go-Implementierungseinschränkung: print
und println
brauchen nicht beliebige Argumenttypen zu akzeptieren; aber Boole'sche, numerische
und String-Typen müssen unterstützt werden.
Pakete
Go-Programme werden konstruiert, indem Pakete zusammengebunden werden. Ein Paket wiederum wird aus einer oder mehr Quelldateien konstruiert, die die Konstanten, Typen, Variablen und Funktionen deklarieren, die zum Paket gehören und die von allen Dateien desselben Pakets aus erreichbar sind. Solche Bestandteile können auch exportiert und in anderen Paketen benutzt werden.
Aufbau der Quelldateien
Jede Quelldatei enthält eine Paket-Klausel, die festlegt zu welchem Paket sie gehört. Es folgt eine, möglicherweise leere, Menge aus Importdeklarationen, die aussagt, von wo Inhalte genutzt werden sollen. Darauf folgt eine, möglicherweise leere, Menge von Funktions-, Typen-, Variablen- und Konstantendeklarationen.
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
Paket-Klausel
Eine Paket-Klausel steht am Anfang jeder Quelldatei und definiert das Paket, zu dem die Datei gehört.
PackageClause = "package" PackageName . PackageName = identifier .
Der Paketname darf nicht der Leere Bezeichner sein. Beispiel:
package math
Die Menge der Dateien mit demselben Paketnamen bilden die Implementierung des Pakets. Eine Go-Implementierung kann verlangen, dass alle Quelldateien eines Paktes sich im selben Verzeichnis befinden.
Importdeklarationen
Eine Importdeklaration zeigt an, dass die sie enthaltende Quelldatei von Funktionalität des importierten Pakets abhängt (siehe: Abschnitt "Initialisierung und Ausführung"), und ermöglicht den Zugriff auf exportierte Bezeichner dieses Pakets. Der Import nennt einen Bezeichner (Paketname) für die Zugriffe und einen Import-Pfad, der festlegt, welches Paket importiert wird.
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) . ImportSpec = [ "." | PackageName ] ImportPath . ImportPath = string_lit .
Der Paketname wird in qualifizierten Bezeichnern
benutzt, um exportierte Bezeichner eines Pakets innerhalb der importierenden
Quelldatei anzusprechen. Er wird im Dateiblock deklariert.
Wird der Paketname weggelassen, so gilt der Bezeichner aus der
Paket-Klausel des
importierten Pakets. Erscheint explizit ein Punkt (.
) anstelle des
Namens, so werden alle exportierten Bezeichner, die im
Paketblock des importierten Pakets deklariert sind,
im Dateiblock der importierenden Quelldatei deklariert und müssen ohne Qualifizierung
angesprochen werden.
Wie der Import-Pfad interpretiert wird, hängt von der Go-Implementierung ab; er ist aber typischerweise ein Teil des vollständigen Dateinamens des kompilierten Pakets; er kann ein Pfad relativ zu einem Repositorium installierter Pakete sein.
Go-Implementierungseinschränkung: Ein Compiler kann Import-Pfade weiter
einschränken auf nicht-leere Zeichenketten, die nur aus Zeichen der allgemeinen
Unicode-Kategorien
L, M, N, P und S bestehen (grafische Zeichen ohne Leerzeichen), und darf auch
die Zeichen !"#$%&'()*,:;<=>?[\]^`{|}
sowie das
Unicode-Ersatzzeichen U+FFFD ausschließen.
Nehmen wir an, wir haben ein Paket mit der Paketklausel package math
kompiliert, die eine Funktion Sin
exportiert, und nehmen wir weiter an,
dass wir dieses Paket in eine Datei, die durch "lib/math
"
identifiziert wird, installiert haben. Dann zeigt die folgende Tabelle, wie man
Sin
nach den verschiedenen Arten der Importdeklaration anspricht:
Importdeklaration Lokaler Name von Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin
Eine Importdeklaration deklariert die Abhängigkeitsbeziehung zwischen dem importierenden und dem importierten Paket. Es ist nicht erlaubt, dass ein Paket sich selbst direkt oder indirekt importiert, und es ist nicht erlaubt, ein Paket zu importieren, ohne einen der exportierten Bezeichner anzusprechen. Um ein Paket nur wegen eines Nebeneffekts (Initialisierung) zu importieren, benutzen sie den Leeren Bezeichner als Paketnamen:
import _ "lib/math"
Ein Beispielpaket
Hier ist ein komplettes Go-Paket, das ein nebenläufiges Primzahlensieb implementiert:
package main import "fmt" // Sende die Sequenz 2, 3, 4, … in den Kanal 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Sende 'i' in den Kanal 'ch'. } } // Kopiere die Werte aus dem Kanal 'src' in den Kanal 'dst', // sortiere aber solche aus, die durch 'prime' teilbar sind. func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Für alle von 'src' empfangenen Werte. if i%prime != 0 { dst <- i // Sende 'i' in den Kanal 'dst'. } } } // Das Primzahlensieb: Verkette die Filter-Prozesse. func sieve() { ch := make(chan int) // Erzeuge einen neuen Kanal. go generate(ch) // Starte generate() als Unterprozess. for { prime := <-ch fmt.Print(prime, "\n") ch1 := make(chan int) go filter(ch, ch1, prime) ch = ch1 } } func main() { sieve() }
Initialisierung und Ausführung
Nullwerte
Immer wenn für eine Variable Speicherplatz zugeteilt wird,
sei es durch eine Deklaration, sei es durch einen Aufruf von new
,
oder wenn ein neuer Wert erzeugt wird, sei es mittels Verbundliteral, sei es durch
Aufruf von make
, und ohne dass für eine explizite Vorbelegung gesorgt
wird, dann erhält die Variable oder der Wert einen Standardwert.
Jeder Bestandteil einer solchen Variablen oder eines solchen Werts wird
auf den Nullwert seines jeweiligen Typs gesetzt: false
für
Boole'sche Werte, 0
für Zahlen,
""
für Strings und nil
für
Zeiger, Funktionen, Interfaces, Slices, Kanäle und Maps.
Diese Vorbelegung erfolgt rekursiv, so dass zum Beispiel jedes einzelne Element
eines Arrays aus Strukturen nullisiert wird, wenn kein Wert angegeben ist.
Die folgenden zwei einfachen Deklarationen sind gleichwertig:
var i int var i int = 0
Und nach:
type T struct { i int; f float64; next *T } t := new(T)
gilt:
t.i == 0 t.f == 0.0 t.next == nil
Dasselbe würde gelten nach:
var t T
Paketinitialisierung
Innerhalb eines Pakets geschieht das Initialisieren der Variablen auf Paketebene schrittweise, wobei jeder Schritt die Variable ausgewählt, die in der Deklarationsreihenfolge als erste auftritt und nicht von uninitialisierten Variablen abhängt.
Genauer gesagt gilt eine Variable auf Paketebene als zum Initialisieren bereit, wenn sie noch nicht initialisiert wurde, und entweder ohne Initialisierungsausdruck ist oder dieser nicht mehr von nicht-initialisierten Variablen abhängt. Schrittweise wird solange die jeweils nächste Variable in der Reihenfolge auf Paketebene initialisiert, die zum Initialisieren bereit ist, bis es keine solche mehr gibt.
Wenn dieser Prozess endet und es gibt noch immer nicht-initialisierte Variablen, so sind diese Variablen Teil einer oder mehrerer Initialisierungsschleifen, und damit ist das Programm ungültig.
Mehrere Variablen auf der linken Seite einer Variablendeklaration, die von einem einzigen Ausdruck (für mehrere Werte) auf der rechten Seite initialisiert werden, werden zusammen initialisiert: Wird also eine der Variablen auf der linken Seite initialisiert, so werden alle diese Variablen im selben Schritt initialisiert.
var x = a var a, b = f() // a und b werden zusammen initialisiert, bevor x initialisiert wird
Zum Zwecke der Paketinitialisierung werden Leere Variablen wie alle anderen Variablen in Deklarationen behandelt.
Die Deklarationsreihenfolge von Variablen aus verschiedenen Quelldateien wird durch die Reihenfolge festgelegt, in der diese Dateien dem Compiler präsentiert werden: Variablen, die in der ersten Datei deklariert sind, werden auch vor allen Variablen deklariert, die in der zweiten Datei deklariert sind, und so weiter.
Die Analyse der Abhängigkeiten baut nicht auf die tatsächlichen Werte der
Variablen auf, sondern auf lexikalischen Verweise auf sie, die transitiv
im Quelltext gesucht werden. Wenn zum Beispiel der Initialisierungsausdruck
einer Variablen x
auf eine Funktion verweist, deren
Funktionsrumpf wiederum auf eine Variable y
verweist,
dann hängt x
von y
ab. Im einzelnen:
- Eine Verweis auf eine Variable oder eine Funktion ist ein Bezeichner, der für diese Variable oder Funktion steht.
-
Eine Verweis auf eine Methode
m
ist ein Methodenwert oder ein Methodenausdruck der Formt.m
, wobei der (statische) Typ vont
kein Interface-Typ ist und die Methodem
zur Methodenmenge vont
gehört. Es ist unerheblich, ob der Funktionswertt.m
aufgerufen wird. -
Eine Variable, Funktion oder Methode
x
hängt von einer Variableny
ab, wenn ihr Initialisierungsausdruck bzw. der Rumpf (für Funktionen und Methoden) einen Verweis aufy
oder auf eine Funktion oder Methode, die vony
abhängt, enthält.
Für die folgenden Deklarationen zum Beispiel:
var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 nach dem Ende der Initialisierung ) func f() int { d++ return d }
ist die Initalisierungsreihenfolge:
d
, b
, c
, a
.
Beachten Sie, dass in Initialisierungsausdrücken die Reihenfolge
von Unterausdrücken unerheblich ist: In unserem Beispiel werden
a = c + b
und a = b + c
in derselben
Reihenfolge initialisiert.
Abhängigkeitsanalyse wird je Paket durchgeführt; ausschließlich Verweise auf Variablen, Funktionen und Methoden (ohne Interfaces) im aktuellen Paket werden berücksichtigt. Sollten andere, versteckte Abhängigkeiten zwischen Variablen existieren, so ist die Initialisierungsreihenfolge dieser Variablen nicht vorherbestimmt.
Zum Beispiel wird aufgrund folgender Deklarationen
var x = I(T{}).ab() // für x besteht eine versteckte Abhängigkeit von a und b, var _ = sideEffect() // ohne direkten Bezug auf x, a oder b var a = b var b = 42 type I interface { ab() []int } type T struct{} func (T) ab() []int { return []int{a, b} }
die Variable a
nach b
initialisiert,
jedoch ist nicht festgelegt, ob x
vor
b
, zwischen b
und a
,
oder nach a
also mit dem Aufruf von
sideEffect()
(vor oder nachdem x
initialisiert wurde) initialisiert wird.
Variablen können auch vorbelegt werden mithilfe von Funktionen namens init
ohne Argumente und Ergebnisparameter, die im Paketblock deklariert sind.
func init() { … }
In einem Paket dürfen mehrere solcher Funktionen definiert werden,
sogar in derselben Quelldatei.
Im Paketblock darf der Bezeichner init
nur benutzt werden,
um init
-Funktionen zu deklarieren, aber der Bezeichner selbst
ist nicht deklariert, also
kann auf init
-Funktionen nicht verwiesen werden.
Ein Paket ohne Importdeklaration wird initialisiert, indem allen Variablen
auf Paketebene Initialwerte zugewiesen werden und danach alle
init
-Funktionen in der Reihenfolge ihres Erscheinens im Quelltext
aufgerufen werden, möglicherweise abhängig davon, in welcher Reihenfolge
Quelltext aus mehreren Dateien dem Compiler präsentiert wird.
Enthält ein Paket Importdeklarationen, so werden die importierten Pakete
vor dem betrachteten Paket initialisiert. Wenn mehrere Pakete dasselbe
Paket importieren, dann wird das importierte Paket nur einmal initialisiert.
Durch seine Konstruktion garantiert der Importvorgang, dass keine
zyklischen Initialisierungsabhängigkeiten auftreten können.
Paketinitialisierung — also die Variableninitialisierungen und die
Aufrufe der init
-Funktionen — geschieht in einer einzigen
Goroutine sequentiell, für ein Paket nach dem anderen.
Eine init
-Funktion darf weitere Goroutinen starten, die nebenläufig
zum Initialisierungskode laufen können.
Wie auch immer, die Initialisierung reiht die init
-Funktionen
hintereinander: keine init
-Funktion wird aktiviert, bevor nicht die
vorhergehende geendet hat.
Um für das Initialisieren ein reproduzierbares Verhalten zu garantieren, wird für Umwandlungswerkzeuge empfohlen, dem Compiler die Dateien eines Pakets in lexikalischer Reihenfolge der Programmnamen zu präsentieren.
Programmausführung
Ein vollständiges Programm wird erzeugt, indem ein einziges, nicht importiertes
Paket, genannt main
-Paket, mit allen von ihm — auch transitiv
— importierten Paketen gebunden wird.
Das main
-Paket muss den Paketnamen main
tragen
und eine Funktion main
ohne Argumente und ohne Ergebnisparameter
deklarieren.
func main() { … }
Die Programmausführung beginnt mit dem Initialisieren des
main
-Pakets, gefolgt vom Aufruf der Funktion main
.
Wenn diese Funktion endet, endet auch das Programm; es wartet nicht auf das
Ende anderer Nicht-main
-Goroutinen.
Fehler
Der vordeklarierte Typ error
ist so definiert:
type error interface { Error() string }
Dies ist das übliche Interface, um Fehler darzustellen; der nil
-Wert
steht für "kein Fehler". Zum Beispiel kann eine Funktion zum Lesen aus einer Datei
so definiert sein:
func Read(f *File, b []byte) (n int, err error)
Laufzeitfehler
Fehler während der Programmausführung, wie zum Beispiel der Versuch, mit einem
Index über ein Array hinauszugreifen, veranlassen einen Laufzeitfehler,
äquivalent einem Aufruf der Standardfunktion
panic
mit einem Wert des von der
Go-Implementierung definierten Interface-Typs runtime.Error
.
Dieser Typ genügt dem vordeklarierten Interface-Typ
error
.
Die genauen Fehlerwerte, die für unterschiedliche Laufzeitfehler stehen, sind
nicht festgelegt.
package runtime type Error interface { error // und eventuell andere Methoden }
Systemüberlegungen
Das Paket "unsafe
"
Das Standardpaket unsafe
, welches dem Compiler bekannt ist
und das über den Importpfad
erreichbar ist, bietet Hilfsmittel für "Low-Level"-Programmierung,
wozu auch Operationen gehören, die das Typsystem verletzen.
Ein Paket, welches unsafe
benutzt, muss gründlichst
auf Typsicherheit geprüft werden, und ist möglicherweise nicht
portierbar. Unsafe
bietet die folgende Schnittstelle:
package unsafe type ArbitraryType int // Abkürzung für einen beliebigen Go-Typ; kein wirklicher Typ type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr type IntegerType int // Abkürzung für einen Ganzzahl-Typ; kein wirklicher Typ func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
Ein Zeiger
ist ein Zeigertyp, jedoch der
Zeigerwert
muss nicht aufgelöst
werden.
Jeder Zeiger oder Wert vom Ursprungstyp uintptr
kann zu einem Typ mit Ursprungstyp Zeiger
konvertiert werden, und umgekehrt.
Auswirkungen der Konversion zwischen Zeiger
und uintptr
sind implementierungsabhängig.
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) var p ptr = nil
Die Funktionen Alignof
und Sizeof
erwarten einen Ausdruck
x
beliebigen Typs, und geben entsprechend die Ausrichtung oder
die Größe einer hypothetischen Variablen v
zurück, als wäre
v
durch var v = x
deklariert worden.
Die Funktion Offsetof
erwartet einen (möglicherweise geklammerten)
Selektor s.f
, welcher für ein Feld
f
steht aus einer Struktur, die mit s
oder *s
bezeichnet wird, und gibt den Versatz relativ zur Strukturadresse in Byte zurück.
Ist f
ein eingebettetes Feld, so muss
es ohne Zeigerauflösung direkt über Felder der Struktur erreichbar sein.
Für eine Struktur s
mit einem Feld f
gilt:
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
Eine Computerarchitektur kann verlangen, dass Speicheradressen ausgerichtet
sein müssen, d.h. die Adresse einer Variablen muss das Vielfache eines Faktors
sein; das ist die Ausrichtung des Typs der Variablen.
Die Funktion Alignof
erwartet einen Ausdruck, der für eine Variable
beliebigen Typs steht, und gibt die Ausrichtung des Variablentyps in Byte zurück.
Für eine Variable x
gilt:
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
Aufrufe von Alignof
, Offsetof
und Sizeof
sind konstante Ausdrücke vom Typ uintptr
zum Umwandlungszeitpunkt.
Die Funktion Add
addiert len
zu
ptr
und gibt den aktualisierten Zeiger
unsafe.Pointer(uintptr(ptr) + uintptr(len))
zurück.
Das Argument len
muss ein Ganzzahltyp sein
oder eine typfreie Konstante.
Ein Konstantenargument len
muss durch einen
Wert vom Typ int
repräsentierbar sein;
ist es eine typfreie Konstante, so bekommt sie den Typ
int
. Es gelten die Regeln für den
zulässigen Einsatz von Pointer
.
Die Funktion Slice
gibt ein Slice zurück,
dessen zugrundeliegendes Array bei ptr
beginnt und dessen Länge und Kapazität len
beträgt.
Slice(ptr, len)
ist gleichbedeutend mit:
(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
nur in dem Spezialfall, dass ptr
nil
ist und len
Null,
gibt Slice
nil
zurück.
Das Argument len
muss ein Ganzzahltyp oder
eine typfreie Konstante sein.
Ein Konstantenargument len
muss nicht-negativ
und repräsentierbar
durch einen Wert vom Typ int
sein;
ist es eine typfreie Konstante, so bekommt sie den Typ
int
.
Wenn zur Laufzeit len
negativ ist, oder wenn
ptr
nil
ist und len
nicht Null, so tritt ein
Laufzeitfehler auf.
Größen- und Ausrichtungsgarantien
Für numerische Typen werden folgende Größen garantiert:
Typ Größe in Byte byte, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 complex128 16
Die folgenden Ausrichtungseigenschaften werden mindestens garantiert:
-
Für eine Variable
x
beliebigen Typs gilt:unsafe.Alignof(x)
ist mindestens 1. -
Für eine Variable
x
eines Strukturtyps gilt:unsafe.Alignof(x)
ist der größte der Werteunsafe.Alignof(x.f)
aller Felderf
vonx
, mindestens aber 1. -
Für eine Variable
x
vom Typ Array gilt:unsafe.Alignof(x)
ist gleich der Ausrichtung einer Variablen vom Typ des Map-Elementtyps.
Ein Struktur- oder Arraytyp hat die Größe null, wenn es keine Felder bzw. Elemente mit einer Größe größer null enthält. Zwei voneinander verschiedene Variablen der Größe null können dieselbe Speicheradresse haben.