Go Home Page
Die Programmiersprache Go

The Go Blog — Constants — Deutsche Übersetzung

Das Original:
https://blog.golang.org/constants
August 25, 2014
Diese Übersetzung:
http://www.bitloeffel.de/DOC/golang/constants_de.html
Stand: 15.1.2016
© 2016 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Attribution 3.0"-Lizenz erlaubt.

Go Blog: Konstanten

25 August 2014

Einführung

Go ist eine Programmiersprache mit statischer Typprüfung. Sie erlaubt es nicht, in Operationen numerische Typen zu vermischen. Man kann kein float64 zu einem int addieren, noch nicht mal ein int32 zu einem int. Trotzdem ist es möglich, 1e6*time.Second zu schreiben oder math.Exp(1) oder sogar 1<<('\t'+2.0). Anders als Variablen verhalten sich die Konstanten in Go ziemlich genau wie ganz normale Zahlen. Dieser Aufsatz erklärt, warum das so ist, und was daraus folgt.

Im Hintergrund: C

Damals, als wir anfingen über Go nachzudenken, diskutierten wir eine Reihe von Problemen, die durch die Art und Weise verursacht werden, wie C und seine Abkömmlinge es erlauben, numerische Typen zu vermischen und zu vergleichen. Eine Menge mysteriöser Fehler, Abstürze sowie Portabilitätsprobleme werden durch Ausdrücke verursacht, die Ganzzahlen verschiedener Größe und Vorzeicheneigenschaft kombinieren. Auch wenn für einen eingefleischten C-Programmierer das Ergebnis einer Berechnung wie

unsigned int u = 1e9;
long signed int i = -1;
... i + u ...
		

vertraut sein mag, so ist es doch nicht a priori offensichtlich. Welche Größe hat das Ergebnis? Wie lautet sein Wert? Ist er vorzeichenbehaftet oder vorzeichenfrei?

Hier lauern ekelhafte Fehler.

C hat eine Reihe von Regeln, die als "die üblichen arithmetischen Umwandlungen" bekannt sind, und es ist ein Zeichen ihrer Raffinesse, dass sie sich über die Jahre geändert haben — wodurch nachträglich neue Bugs eingeführt wurden.

Beim Entwerfen von Go entschieden wir uns, dieses Minenfeld zu meiden: wir verfügten, das es kein Mischen von numerischen Typen geben würde. Wenn Sie i zu u addieren wollen, müssen Sie explizit sagen, welcher Art das Ergebnis sein soll. Wenn

var u uint
var i int
		

gegeben sind, dann können Sie entweder uint(i)+u oder i+int(u) schreiben, so dass sowohl Bedeutung als auch Art der Addition klar vorgegeben sind; anders als in C dürfen Sie nicht i+u schreiben. Sie dürfen noch nicht mal int und int32 kombinieren, selbst dann nicht, wenn int ein 32-Bit-Typ ist.

Diese Strenge beseitigt eine weit verbreitete Ursache von Fehlern und Störfällen. Sie ist eine wesentliche Eigenheit von Go. Doch sie bringt Kosten mit sich: hin und wieder wird der Programmierer gezwungen, seinen Kode mit plumpen numerischen Konvertierungen zu verzieren.

Und was ist mit den Konstanten? Mit den obigen Deklarationen: Was würde es erlauben, i = 0 oder u = 0 zu schreiben? Was ist der Typ von 0? Es wäre unsinnig, für eine Konstante Typkonversion zu verlangen, wenn es um einen so einfachen Ausdruck wie i = int(0) geht.

Wir erkannten bald, dass unsere numerischen Konstanten anders arbeiten mussten als die in den C-artigen Sprachen. Viel Nachdenken und Experimentieren war nötig, bis wir ein Konzept hatten, von dem wir glauben, dass es sich fast immer richtig anfühlt, dass es Programmierer vom Konvertieren von Konstanten befreit, und dass es trotzdem möglich ist, math.Sqrt(2) zu schreiben, ohne dass der Compiler meckert.

Kurz gesagt, Konstanten in Go tun, was sie sollen, einfach so, jedenfalls meistens. Schauen wir uns jetzt an, wie das funktioniert.

Terminologie

Als Erstes eine kurze Definition: In Go ist const ein Schlüsselwort, das einen Namen für einen skalaren Wert wie 2 oder 3.14159 oder "sagenhaft" einführt. Solche Werte, mit oder ohne Namen, nennt man in Go Konstanten. Sie können auch durch Ausdrücke wie 2+3 oder 2+3i oder math.Pi/2 oder ("go"+"pher") erzeugt werden.

Es gibt Sprachen ohne Konstanten. Andere Sprachen wiederum haben eine allgemeinere Definition von "Konstante" oder verwenden das Schlüsselwort const allgemeiner. Beispielsweise ist const in C und C++ ein Typkennzeichner, der komplizierte Eigenschaften noch komplizierterer Werte kodifizieren kann.

In Go aber ist eine Konstante nur ein einfacher, unveränderlicher Wert, und ab jetzt werden wir nur noch über Go reden.

String-Konstanten

Es gibt viele Arten von numerischen Konstanten: Ganzzahlen, Gleitkommazahlen, Runen, welche mit Vorzeichen, welche ohne, imaginäre und komplexe. Fangen wir deshalb mit etwas einfacherem an. String-Konstanten sind leicht zu verstehen und grenzen das Gebiet ein, das wir für das Thema "Konstanten in Go" erkunden müssen.

Eine String-Konstante schließt etwas Text in Anführungsstrichen "" ein. (Es gibt in Go auch rohe String-Literale, die durch Gravis-Zeichen `` eingeschlossen werden, doch im Rahmen unserer Diskussion haben die auch keine anderen Eigenschaften.) Hier nun eine String-Konstante:

"Hello, 世界"
		

(Mehr über Repräsentation und Interpretation von Strings finden Sie in diesem Blog-Beitrag.)

Welchen Typ hat nun diese String-Konstante? Die naheliegende Antwort lautet string, aber das ist falsch.

Sie ist eine typfreie String-Konstante, was heißen soll: sie ist ein konstanter textueller Wert, der noch keinen fixierten Typ hat. Ja natürlich, sie ist ein String, aber kein Go-Wert vom Typ string. Und sie bleibt eine typfreie String-Konstante, auch wenn wir ihr einen Namen geben:

const hello = "Hello, 世界"
		

Nach dieser Deklaration ist hello also eine typfreie String-Konstante. Eine typfreie Konstante ist nur ein Wert, und zwar ohne einen definierten Typ, der ihn zur Einhaltung der strengen Regeln zwingen würde, die das Kombinieren verschieden typisierter Werte verhindern.

Es ist dieses Konzept einer typfreien Konstanten, die uns Konstanten in Go unbeschwert benutzen lässt.

Und was ist nun eine typbehaftete String-Konstante? Das ist eine, der ein Typ zugewiesen wurde, wie hier:

const typedHello string = "Hello, 世界"
		

Beachten Sie den expliziten Typ string vor dem Gleichheitszeichen. Das bedeutet, dass typedHello vom Go-Typ string ist, und keiner Go-Variablen eines anderen Typs zugewiesen werden kann. Also funktioniert dieser Kode:

    var s string
    s = typedHello
    fmt.Println(s)
		

aber der hier nicht:

    type MyString string
    var m MyString
    m = typedHello // Typfehler
    fmt.Println(m)
		

Die Variable m ist vom Typ MyString; ihr kann also kein Wert eines anderen Typs zugewiesen werden. Man kann ihr nur Werte vom Typ MyString zuweisen, also etwa so:

    const myStringHello MyString = "Hello, 世界"
    m = myStringHello // OK
    fmt.Println(m)
		

oder indem man eine Typkonversion erzwingt, also so:

    m = MyString(typedHello)
    fmt.Println(m)
		

Kommen wir zurück zu unserer typfreien String-Konstanten. Sie hat die nützliche Eigenschaft, dass man sie einer typbehafteten Variablen zuweisen kann, ohne einen Fehler auszulösen. Also können wir schreiben:

    m = "Hello, 世界"
		

oder:

    m = hello
		

weil die typfreien Konstanten "Hello, 世界" und hello, anders als die typbehafteten Konstanten typedHello und myStringHello, keinen Typ haben. Die Zuweisung zu einer Variablen beliebigen Typs, solange der nur mit Strings kompatibel ist, funktioniert fehlerfrei.

Diese typfreien String-Konstanten sind natürlich Strings, können also nur benutzt werden, wo Strings erlaubt sind, aber sie haben nicht den Typ string.

Standardtypen

Als Go-Programmiererin sind Ihnen Deklarationen, wie die folgende, vertraut:

    str := "Hello, 世界"
		

und inzwischen werden Sie sich fragen, "Wenn die Konstante typfrei ist, wie kommt str dann hier zu ihrem Typ?" Die Antwort lautet, dass typfreie Konstanten jeweils einen Standardtyp haben, einen impliziten Typ, den sie auf den Wert transferieren, wenn kein anderer Typ bereit steht. Für typfreie String-Konstanten ist der Standardtyp offensichtlich string, so dass

    str := "Hello, 世界"
		

oder

    var str = "Hello, 世界"
		

dasselbe bedeuten wie

    var str string =  "Hello, 世界"
		

Man kann sich das so vorstellen: Typfreie Konstanten leben in einer Art idealem Werteraum, der weniger restriktiv ist als Go's komplettes Typsystem. Nur, um überhaupt etwas mit ihnen anfangen zu können, müssen wir sie Variablen zuweisen, und in diesem Augenblick braucht die Variable (nicht die Konstante) einen Typ, und die Konstante kann der Variablen mitteilen, welcher Typ das sein könnte. In unserem Beispiel wird str ein Wert vom Typ string, weil die typfreie Konstante der Deklaration seinen Standardtyp string mitgibt.

In so einer Deklaration wird eine Variable mit Typ und Initialwert deklariert. Aber manchmal, wenn wir eine Konstante benutzen, ist ihre Bestimmung nicht so klar. Nehmen wir zum Beispiel diese Anweisung:

    fmt.Printf("%s", "Hello, 世界")
		

Die Signatur von fmt.Printf ist

func Printf(format string, a ...interface{}) (n int, err error)
		

und das bedeutet, dass seine Argumente (also die nach dem Format-String) Interface-Werte sind. Was passiert also, wenn fmt.Printf mit einer typfreien Konstanten gerufen wird und dabei ein Interface-Wert als Argument erzeugt wird, wobei der konkrete Typ, weicher für das Argument gespeichert wird, der Standardtyp der typfreien String-Konstanten wird.

Das Ergebnis sehen Sie an diesem Beispiel, welches das Format %v benutzt, um den Wert zu drucken, und %T für den Typ des an fmt.Printf übergebenen Werts. [Das Ausführen des Beispielkodes funktioniert leider nur im Originaldokument. A.d.Ü.]

    fmt.Printf("%T: %v\n", "Hello, 世界", "Hello, 世界")
    fmt.Printf("%T: %v\n", hello, hello)
		

Und wenn die Konstante typbehaftet ist, dann wandert dieser Typ ins Interface, wie das folgende Beispiel zeigt:

    fmt.Printf("%T: %v\n", myStringHello, myStringHello)
		

(Mehr Informationen dazu, wie Interfaces funktionieren, gibt's im ersten Abschnitt dieses Blog-Beitrags)

Zusammengefasst: Eine typbehaftete Konstante gehorcht allen Regeln typbehafteter Werte in Go. Eine typfreie Konstante hingegen transportiert einen Go-Typ nicht in dieser Weise; sie kann freier vermischt und verglichen werden. Allerdings hat sie einen Standardtyp, der dann und nur dann sichtbar wird, wenn keine andere Typinformation zur Verfügung steht.

Durch Syntax festgelegter Standardtyp

Der Standardtyp einer typfreien Konstanten ist durch ihre Syntax festgelegt. Für String-Konstanten ist der einzig mögliche implizite Typ der string. Für numerische Konstanten [de] gibt es da eine größere Auswahl. Standardtyp für ganzzahlige Konstanten ist int, für Gleitkommakonstanten float64, für Runenkonstanten rune (ein Alias für int32) und für Imaginäkonstanten complex128. Im folgenden wird die schon bekannte Printf-Anweisung mehrfach benutzt, um die Standardtypen in Aktion zu zeigen:

    fmt.Printf("%T %v\n", 0, 0)
    fmt.Printf("%T %v\n", 0.0, 0.0)
    fmt.Printf("%T %v\n", 'x', 'x')
    fmt.Printf("%T %v\n", 0i, 0i)
		

(Übung: Erklären Sie das Ergebnis für 'x'.)

Bool'sches

Alles, was wir über typfreie String-Konstanten gesagt haben, kann auch über typfreie Bool'sche Konstanten gesagt werden. Die Werte true und false sind typfreie Bool'sche Konstanten, die jeder Bool'schen Variablen zugewiesen werden können; doch einmal mit Typ versehen, kann Bool'sches nicht mehr vermischt werden.

    type MyBool bool
    const True = true
    const TypedTrue bool = true
    var mb MyBool
    mb = true      // OK
    mb = True      // OK
    mb = TypedTrue // Schlecht
    fmt.Println(mb)
		

Starten Sie das Beispiel, um zu sehen, was passiert. Dann kommentieren Sie die "schlechte" Zeile aus und starten neu. Hier wie bei den String-Konstanten ergibt sich dasselbe Verhaltensmuster.

Gleitkommazahlen

Gleitkommakonstanten sind größtenteils wie die Bool'schen. Unser übliches Beispiel, übersetzt für Gleitkommazahlen, funktioniert wie erwartet:

    type MyFloat64 float64
    const Zero = 0.0
    const TypedZero float64 = 0.0
    var mf MyFloat64
    mf = 0.0       // OK
    mf = Zero      // OK
    mf = TypedZero // Schlecht
    fmt.Println(mf)
		

Dumm nur, dass es in Go zwei Gleitkommatypen gibt: float32 und float64. Der Standardtyp für Gleitkommakonstanten ist float64, ungeachtet dessen, dass eine typfreie Gleitkommakonstante ohne weiteres auch einem float32-Wert zugewiesen werden kann:

    var f32 float32
    f32 = 0.0
    f32 = Zero      // OK: Zero ist typfrei
    f32 = TypedZero // Schlecht: TypedZero ist float64 nicht float32
    fmt.Println(f32)
		

Die Gleitkommawerte eignen sich gut dazu, hier das Konzept des Überlaufs bzw. des Wertebereichs einzuführen.

Numerische Konstanten leben in einem numerischen Universum beliebiger Genauigkeit; sie sind ganz normale Zahlen. Nur wenn sie einer Variablen zugewiesen werden, müssen sie in ihren Zielort hineinpassen. Wir könne Konstanten mit sehr großen Werten deklarieren:

    const Huge = 1e1000
		

— das ist schließlich nur eine Zahl — aber wir können sie weder zuweisen noch drucken. Die folgende Anweisung lässt sich noch nicht mal kompilieren:

    fmt.Println(Huge)
		

Die Fehlermeldung lautet "constant 1.00000e+1000 overflows float64", was ja auch stimmt. Huge kann trotzdem von Nutzen sein: Wir können es in Ausdrücken mit anderen Konstanten benutzen und dann die Ergebnisse der Ausdrücke verwenden, solange sie im Wertebereich eines float64 liegen. Die Anweisung

    fmt.Println(Huge / 1e999)
		

druckt 10, was man auch erwarten würde.

Damit verbunden ist, dass Gleitkommakonstanten sehr genau sein können, so dass Berechnungen mit ihnen korrekter sind. Konstanten im Paket math haben sehr viel mehr Ziffern als ein float64 fassen könnte. Hier die Definition von math.Pi:

Pi = 3.14159265358979323846264338327950288419716939937510582097494459
		

Sobald dieser Wert einer Variablen zugewiesen wird, geht ein Teil der Genauigkeit verloren; die Zuweisung ergibt einen float64- oder float32-Wert möglichst nahe am Hochpräzisionswert. Dieses Programmschnipsel

    pi := math.Pi
    fmt.Println(pi)
		

druckt 3.141592653589793.

Dass so viele Ziffern zur Verfügung stehen, bedeutet, dass eine Berechnung wie Pi/2 oder kompliziertere Auswertungen mit größerer Präzision durchgeführt werden können, bevor das Ergebnis zugewiesen wird. Dadurch sind Berechnungen mit Konstanten leichter zuschreiben, ohne dass dabei Genauigkeit verloren ginge. Es bedeutet außerdem, dass Grenzfälle wie unendlich, arithmetischer Unterlauf oder NaN in konstanten Ausdrücken nicht auftreten können. (Division mit einer Konstanten Null ist ein Umwandlungsfehler; und wenn alles eine Zahl ist, dann gibt es einfach kein "not a number".)

Komplexe Zahlen

Komplexkonstanten verhalten sich ziemlich genau wie Gleitkommakonstanten. Hier eine Version der bekannten Litanei, übersetzt für Komplexzahlen:

    type MyComplex128 complex128
    const I = (0.0 + 1.0i)
    const TypedI complex128 = (0.0 + 1.0i)
    var mc MyComplex128
    mc = (0.0 + 1.0i) // OK
    mc = I            // OK
    mc = TypedI       // Schlecht
    fmt.Println(mc)
		

Der Standardtyp für Komplexzahlen ist complex128, also die genauere der beiden Version; sie ist aus zwei float64-Werten zusammengesetzt.

Nur wegen der Klarheit haben wir den vollständigen Ausdruck (0.0+1.0i) hingeschrieben; diesen Wert kann aber man kürzen zu 0.0+1.0i oder 1.0i oder sogar 1i.

Versuchen wir mal einen Trick. Wir wissen, dass in Go eine numerische Konstante einfach eine Zahl ist. Was nun, wenn diese Zahl eine Komplexzahl ohne imaginären Anteil ist, d.h. wenn sie eine Reale Zahl ist? Hier ist so eine:

    const Two = 2.0 + 0i
		

Das ist eine typfreie Komplexkonstante. Auch wenn sie keinen Imaginäranteil hat, definiert die Syntax des Ausdrucks, dass sie den Standardtyp complex128 hat. Deshalb wird, wenn wir sie zum Deklarieren einer Variablen hernehmen, diese vom Typ complex128 sein. Das Programmschnipsel

    s := Two
    fmt.Printf("%T: %v\n", s, s)
		

druckt complex128: (2+0i). Die Zahl kann jedoch auch als skalare Gleitkommazahl in einem float64- oder float32-Wert ohne Informationsverlust gespeichert werden. Also können wir Two problemlos einer float64-Variablen zuweisen, beim Initialisieren und woanders:

    var f float64
    var g float64 = Two
    f = Two
    fmt.Println(f, "und", g)
		

Die Ausgabe lautet 2 und 2. Obwohl Two eine Komplexkonstante ist, kann sie einer skalaren Gleitkommavariablen zugewiesen werden. Diese Fähigkeit von Konstanten, den Typ zu "wechseln", wird sich noch als nützlich erweisen.

Ganzzahlen

Zu guter Letzt kommen wir zu den Ganzzahlen. hier gibt es mehr bewegliche Teile, vielerlei Größen, mit Komma, ohne Komma, und so weiter [de], doch sie spielen alle nach denselben Regeln. Ein letztes Mal das bekannte Beispiel, nur dieses Mal mit int:

    type MyInt int
    const Three = 3
    const TypedThree int = 3
    var mi MyInt
    mi = 3          // OK
    mi = Three      // OK
    mi = TypedThree // Schlecht
    fmt.Println(mi)
		

Das gleiche Beispiel könnten wir für jeden der Ganzzahltyp bilden, welche da wären:

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
uintptr
		

(Dazu kommen noch die Aliase byte für uint8 und rune für int32.) Das ist eine ganze Menge. Das Muster, nach dem Konstanten arbeiten, sollte aber inzwischen so vertraut sein, dass Sie schon sehen können, worauf es hinausläuft.

Wie schon erwähnt kommen Ganzzahlen in einer Reihe von Formen vor, und jede Form hat ihren eigenen Standardtyp: int für einfache Konstanten wie 123 oder 0xFF oder -14, und rune für Zeichen in Anführungsstrichen wie 'a', '世' or '\r'.

Keine Konstantenform hat einen vorzeichenlosen Integer-Typ als Standardtyp. Doch so flexibel unsere typfreien Konstanten sind, können wir Ganzzahlvariablen ohne Vorzeichen mit einfachen Konstanten initialisieren, solange wir den Typ klarstellen. Das ist analog zum float64, das wir mit einer Komplexzahl mit Imaginäranteil Null initialisiert hatten. Es folgen verschiedene Möglichkeiten, ein uint zu initialisieren; sie sind alle gleichwertig, aber alle müssen den für das Ergebnis gewünschten Typ explizit erwähnen.

var u uint = 17
var u = uint(17)
u := uint(17)
		

Wie beim Problem des Wertebereichs bei Gleitkommas passen auch nicht alle Ganzzahlwerte in alle Ganzzahltypen. Zwei Probleme können auftreten: der Wert könnte zu groß sein, oder es könnte ein negativer Wert einem Ganzzahltyp ohne Vorzeichen zugewiesen werden. Beispielsweise hat int8 den Wertebereich -128 bis 127, also darf man eine Konstanten außerhalb dieses Bereichs einer Variablen vom Typ int8 nicht zuweisen:

    var i8 int8 = 128 // Fehler: zu groß
		

Ähnlich hat uint8, auch bekannt als byte, den Wertebereich 0 bis 255, so dass ein großer oder ein negativer Wert nicht zugewiesen werden darf:

    var u8 uint8 = -1 // Fehler: negativer Wert
		

Mit dieser Typprüfung erwischt man auch den folgenden Fehler:

    type Char byte
    var c Char = "世" // Fehler: "世" hat den Wert 0x4e16, zu groß
		

Wenn der Compiler über Ihre Art, Konstanten zu verwenden, meckert, dann handelt es sich wahrscheinlich um einen echten Programmierfehler.

Übung: Die größte Ganzzahl ohne Vorzeichen

Dies ist eine kleine aber lehrreiche Übung. Was schreiben wir, damit eine Konstante den größten Wert erhält, der in ein uint passt? Würden wir von uint32 anstatt von uint reden, dann könnten wir das so schreiben:

const MaxUint32 = 1<<32 - 1
		

aber wir wollen ja uint, nicht uint32. Die Typen int und uint haben dieselbe, aber unbestimmte, Anzahl von Bits, entweder 32 oder 64. Weil das von der Prozessorarchitektur abhängt können wir nicht einfach eine Zahl hinschreiben.

Ganzzahlen in Go benutzen die Zweierkomplement-Arithmetik, und Fans wissen, dass die Darstellung von -1 alle Bits auf 1 hat; also ist die interne Darstellung dieselbe, wie für die größte Ganzzahl ohne Vorzeichen. Wir könnten auf die Idee kommen

    const MaxUint uint = -1 // Fehler: negativer Wert
		

zu schreiben, aber das ist verboten, weil -1 nicht in eine Variable ohne Vorzeichen hineinpasst; -1 liegt nicht im Wertebereich von Werten ohne Vorzeichen. Konvertieren hilft genausowenig — aus demselben Grund:

    const MaxUint uint = uint(-1) // Fehler: negativer Wert
		

Auch wenn zur Laufzeit ein Wert von -1 in eine Ganzzahl ohne Vorzeichen konvertiert werden kann, so verbieten doch die Regeln [de] für Konstanten eine solche Zwangsmaßnahme. Was wir damit meinen, ist, das das hier funktioniert:

    var u uint
    var v = -1
    u = uint(v)
		

aber nur, weil v eine Variable ist; wenn wir aber aus v eine Konstante machen, sind wir zurück auf verbotenem Gelände, selbst wenn sie typfrei ist:

    var u uint
    const v = -1
    u = uint(v) // Fehler: negativer Wert
		

Probieren wir's nochmal mit dem vorhergehenden Ansatz. Nur, dass wir es diesmal anstatt mit -1 mit ^0 versuchen, also mit der bitweise Negation einer beliebigen Anzahl von Null-Bits. Aber das scheitert auch, aus einem ähnlichen Grund: Im Bereich numerischer Werte steht ^0 für eine unendliche Anzahl von Einsen; wir würden also Präzision verlieren, wiesen wir sie einer Ganzzahl mit fester Länge zu:

    const MaxUint uint = ^0 // Fehler: Überlauf
		

Wie schaffen wir es also, die größte Ganzzahl ohne Vorzeichen zu einer Konstanten zu machen?

Entscheidend ist, die Negation auf die Anzahl der Bits eines uint einzugrenzen, und nicht-darstellbare Werte, wie negative Zahlen zu vermeiden. Der einfachste uint-Wert ist die typbehaftete Konstante uint(0). Uints haben 32 oder 64 Bits, also hat uint(0) entsprechend ebenfalls 32 oder 64 Null-Bits. Wenn wir diese alle invertieren, erhalten wir die korrekte Anzahl an Einser-Bits, und das ist der größte uint-Wert.

Wir drehen also nicht die Bits der typfreien Konstanten 0 um, sondern die der typbehafteten Konstanten uint(0). Hier ist unsere Konstante:

    const MaxUint = ^uint(0)
    fmt.Printf("%x\n", MaxUint)
		

Für jede Anzahl von Bits, die ein uint in der jeweils aktuellen Laufumgebung hat (auf dem "Playground" sind es 32) steht diese Konstante korrekt für den größten Wert, die eine Variablen vom Typ uint enthalten kann.

Wenn Sie dieser Analyse bis zum Ergebnis folgen konnten, dann verstehen Sie alles, was über Konstanten in Go zu verstehen wichtig ist.

Zahlen

Das Konzept typfreier Konstanten in Go bedeutet, dass alle numerischen Konstanten, ob jetzt Ganzzahlen, Gleitkomma- oder komplexe Zahlen, oder sogar Zeichenwerte, in einer Art einheitlichem Raum leben. Erst, wenn wir sie in die Welt der Variablen, in die Welt der Zuweisungen und Berechnungen bringen, spielen Typen eine Rolle. Solange wir aber in der Welt der numerischen Konstanten bleiben, können wir Werte vermischen und vergleichen, g'rad wie es uns gefällt.. Die folgenden Konstanten haben alle den numerischen Wert 1:

1
1.000
1e3-99.0*10-9
'\x01'
'\u0001'
'b' - 'a'
1.0+3i-3.0i
		

Deshalb, und obwohl sie verschiedene implizite Standardtypen haben: als typfreie Konstanten können sie Variablen eines jeden Ganztahltyps zugewiesen werden:

    var f float32 = 1
    var i int = 1.000
    var u uint32 = 1e3 - 99.0*10.0 - 9
    var c float64 = '\x01'
    var p uintptr = '\u0001'
    var r complex64 = 'b' - 'a'
    var b byte = 1.0 + 3i - 3.0i
    fmt.Println(f, i, u, c, p, r, b)
		

Die Ausgabe davon ist: 1 1 1 1 1 (1+0i) 1.

Man kann auch verrückte Sachen machen:

    var f = 'a' * 1.5
    fmt.Println(f)
		

was dann 145.5 ergibt — ziemlich sinnlos, aber es geht!

Worum es hier im Kern geht, ist Flexibilität. Das bedeutet, dass es trotz dem Verbot, in einem Go-Ausdruck Gleitkomma- und Ganzzahlvariablen (oder nur int and int32) zu mischen, in Ordnung ist, zu schreiben

sqrt2 := math.Sqrt(2)
		

oder

const millisecond = time.Second/1e3
		

oder

bigBufferWithHeader := make([]byte, 512+1e6)
		

und man erhält das erwartete Ergebnis.

Weil numerische Konstanten in Go tun, was sie tun sollen. Sie sind wie Zahlen.

[Original] von Rob Pike