2. Variablen, Typen und Umfang
Lernen Sie lokale Variablen, den Ganzzahlbereich, bool und Bereiche kennen.
Wave Foundation
Werte namentlich behandeln
Wenn Sie Ihre Preise direkt an mehreren Stellen angeben, müssen Sie beim Ändern des Preises alle finden. Eine Variable ist ein Speicherplatz, der Werten Namen gibt und es Ihnen ermöglicht, sie mithilfe dieser Namen zu lesen und zu ändern. In diesem Kapitel erfahren Sie mehr über den Umfang von Deklarationen, Zuweisungen, Typen und den Umfang sichtbarer Namen innerhalb von Blöcken.
Die folgenden Programme sind jeweils Teil eines separaten main.wave. Speichern Sie ein Beispiel, führen Sie es als wavec run main.wave aus und ersetzen Sie es durch das nächste Beispiel.
Deklaration und Initialisierung
fun main() {
var price: i32 = 1200;
var quantity: i32 = 3;
println("price={}", price);
println("quantity={}", quantity);
println("total={}", price * quantity);
}
Ausführungsergebnis:
price=1200
quantity=3
total=3600
Lesen Sie die Erklärung in vier Teilen.
| Teil | dieses Beispiel | Rolle |
|---|---|---|
| Deklarationsschlüsselwort | var | Lokale Variable erstellen |
| Namen | price | Bezeichner, der später verwendet werden soll |
| Typ | i32 | Typ und Bereich der zu speichernden Werte |
| Anfangswert | 1200 | Erster zu speichernder Wert |
Der Doppelpunkt vor dem Typ und das Gleichheitszeichen vor dem Anfangswert haben unterschiedliche Rollen. Geben Sie Ihrem Namen eine Bedeutung. In diesem Beispiel ist price der Stückpreis und quantity die Menge. Selbst wenn dasselbe i32 synonym verwendet wird, können falsche Berechnungen ohne grammatikalische Fehler durchgeführt werden.
Zuweisung ist keine Formel, die Beziehungen aufrechterhält.
Wenn Sie das Berechnungsergebnis in einer Variablen speichern, wird der Wert an dieser Stelle eingegeben. Berechnungen werden nicht gespeichert und später automatisch neu ausgewertet.
fun main() {
var price: i32 = 1200;
var quantity: i32 = 3;
var total: i32 = price * quantity;
quantity = 5;
println("before recalculation={}", total);
total = price * quantity;
println("after recalculation={}", total);
}
Ausführungsergebnis:
before recalculation=3600
after recalculation=6000
quantity = 5 schreibt einen neuen Wert in eine bereits vorhandene Variable. Es muss von einer Neudeklaration wie var quantity unterschieden werden. total ist ebenfalls 3600, bevor es erneut ersetzt wird. Wenn ein Programm Beziehungen zwischen mehreren Variablen aufrechterhalten muss, sollte es so geschrieben sein, dass es Berechnungen durchführt, wenn sich die Beziehungen ändern.
Berechnen Sie den nächsten Wert aus dem vorherigen Wert
Berechnen Sie zuerst die rechte Seite der Zuweisungsanweisung und schreiben Sie das Ergebnis in den Speicherplatz auf der linken Seite. Im Gegensatz zu Gleichungen in der Mathematik ist count = count + 1 ein gültiges Update.
fun main() {
var count: i32 = 0;
count = count + 1;
count += 2;
count *= 3;
println("{}", count);
}
Ausführungsergebnis:
9
Der Werteverlauf ist 0 → 1 → 3 → 9. +=, *= drücken Berechnung und Speicherung gemeinsam aus. Wenn Sie die Berechnungsreihenfolge aufschreiben, können Sie herausfinden, zu welchem Zeitpunkt Ihre Gedanken anders waren, als die Ergebnisse anders ausfielen als erwartet.
Breite und Vorzeichen von Ganzzahltypen
Wenn es mit i beginnt, wird es zu signed, und wenn es mit u beginnt, wird es zu unsigned. Die letzte Zahl ist die Anzahl der Bits. Mit zunehmender Bitzahl erhöht sich auch der darstellbare Bereich und damit auch der Speicherplatz.
| Typ | Mindestwert | Maximalwert | Beispielanwendung |
|---|---|---|---|
| i8 | -128 | 127 | kleiner vorzeichenbehafteter Wert |
| u8 | 0 | 255 | ein Byte |
| i16 | -32768 | 32767 | kleine ganzzahlige Daten |
| u16 | 0 | 65535 | Port/16-Bit-Feld |
| i32 | -2147483648 | 2147483647 | Gängige kleine Ganzzahlberechnungen |
| u32 | 0 | 4294967295 | 32-Bit-Bitfeld |
Wave stellt auch vorzeichenbehaftete und vorzeichenlose Ganzzahlen mit 64, 128, 256, 512 und 1024 Bit bereit. Ein breiterer Typ macht nicht jede Berechnung sicher: Das Ergebnis kann immer noch den ausgewählten Bereich überschreiten. Wählen Sie zunächst den Bereich aus, den Sie benötigen. isz und usz folgen der Zieladressbreite.
Es muss zwischen dem Speichern eines großen Literals in einem kleinen Typ und dem absichtlichen Verwerfen von Bits durch Ausführen von cast unterschieden werden. Die Konvertierung in einen kleineren Typ, nur um Fehler zu beseitigen, kann den Wert selbst ändern. Transformationen werden im nächsten Kapitel behandelt.
Gleitkommatypen und Bool
f32·f64 sind Gleitkommazahlen. Im Gegensatz zu ganzen Zahlen können sie Dezimalteile darstellen, aber nicht alle Dezimalzahlen exakt speichern. Dies ist ein Grund, warum Beträge in kleinen ganzzahligen Einheiten verwaltet werden.
bool steht für wahr und falsch. Sie können Ihrer Bedingung einen Namen geben, indem Sie die Vergleichsergebnisse wie folgt speichern:
fun main() {
var balance: i32 = 5000;
var cost: i32 = 3600;
var can_buy: bool = balance >= cost;
if (can_buy) {
println("purchase allowed");
} else {
println("not enough money");
}
}
Ausführungsergebnis:
purchase allowed
can_buy folgt nicht automatisch den Änderungen in balance und cost. Nachdem die beiden Werte ausgetauscht wurden, wird der Vergleich erneut durchgeführt, wenn der aktuelle Status benötigt wird.
nicht initialisierter Speicherplatz
var value: i32; ist ein Formular, das nur den Speicherplatz deklariert. Vor dem Lesen muss ein gültiger Wert geschrieben werden. Gehen Sie nicht davon aus, dass 0 automatisch eingefügt wird, nur weil Sie es deklarieren. In einem Einführungskurs ist es einfacher zu verstehen, ob der Wert sofort bekannt ist und gleichzeitig mit der Deklaration initialisiert wird.
Bei Bibliotheksaufrufen, die Werte als Ausgabeargumente erhalten, gibt es Fälle, in denen zuerst ein Leerzeichen deklariert und dann bei Erfolg gelesen wird. Zu diesem Zeitpunkt sollten Sie das Erfolgsergebnis der Funktion überprüfen. Vermeiden Sie den Fehler, nach einem fehlgeschlagenen Aufruf nicht initialisierte Ausgaben zu lesen.
Gültiger Bereich von Blöcken und Namen
Ein Block ist ein in geschweifte Klammern eingeschlossener Codebereich. Wenn Sie eine neue Variable mit demselben Namen darin deklarieren, wird die neue Variable in diesem Block verwendet. Dies wird als shadowing bezeichnet.
fun main() {
var value: i32 = 10;
if (true) {
var value: i32 = value + 5;
println("inner={}", value);
value += 1;
println("inner changed={}", value);
}
println("outer={}", value);
}
Ausführungsergebnis:
inner=15
inner changed=16
outer=10
Der anfängliche Ausdruck value + 5 in der inneren Deklaration liest den äußeren value. Nachdem die Initialisierung der neuen Variablen abgeschlossen ist, ist der innere value 15. Auch wenn Sie den inneren Wert auf 16 ändern, ändert sich der äußere Speicherplatz nicht. Nach der Sperrung sehen Sie draußen wieder value.
Wenn Sie umgekehrt nur value += 1 ohne var im inneren Block ausführen, werden die vorhandenen sichtbaren Variablen geändert. Sehen Sie sich das Schlüsselwort an, um festzustellen, ob es sich um eine neue Deklaration oder eine Änderung eines vorhandenen Werts handelt.
Lokale Variablen und Speicher auf oberster Ebene
Außerhalb der Funktion können Sie const und static verwenden. const stellt einen konstanten Wert dar und static ist der während der Ausführung beibehaltene Speicherplatz. Sie können nicht überall wie lokale Variablen deklariert werden.
const LIMIT: i32 = 3;
static visits: i32 = 0;
fun visit() {
visits += 1;
println("visit={}", visits);
}
fun main() {
visit();
visit();
println("limit={}", LIMIT);
}
Ausführungsergebnis:
visit=1
visit=2
limit=3
Selbst wenn visit zweimal aufgerufen wird, wird static nicht bei jedem Aufruf auf 0 zurückgesetzt. Wenn Sie es hingegen innerhalb einer Funktion als var visits: i32 = 0; deklarieren, wird der lokale Speicher bei jedem Aufruf initialisiert. Ein gemeinsam genutzter veränderlicher Zustand kann die Verfolgung des Verhaltens erschweren. Überlegen Sie daher zunächst, ob es mit den Ein- und Ausgängen der Funktion aufgelöst werden kann.
häufige Fehler
- Wenn Deklaration und Zuweisung verwechselt werden und derselbe Name unnötigerweise erneut deklariert wird.
- Wenn Sie glauben, dass die Variable, die das Berechnungsergebnis speichert, automatisch Änderungen in der Eingabevariablen folgt.
- Wenn Sie denken, dass aufgrund des gleichen Typs auch die Einheiten wie Anzahl und Anzahl der Bytes gleich sind.
- Beim Lesen ohne Initialisierung oder beim Lesen des Ausgabearguments, wenn die Funktion fehlschlägt.
- Wenn Sie denken, dass der Name einer lokalen Variablen außerhalb des Blocks sichtbar ist.
Wenn Sie fehlerhaft nach einem Namen suchen, überprüfen Sie die Deklarationsposition und den Klammerbereich des Namens.
Übung: Bestandsveränderungen berechnen
Der Anfangsbestand beträgt 20 Einheiten und es werden zweimal je 3 Einheiten verkauft. Drucken Sie den Restbestand und die Gesamtzahl der verkauften Einheiten aus. Bei jeder Bestandsänderung werden dieselben Variablen aktualisiert und auch die Verkaufsmengen werden separat kumuliert.
Vollständige Lösung
fun main() {
var stock: i32 = 20;
var sold: i32 = 0;
var order: i32 = 3;
stock -= order;
sold += order;
stock -= order;
sold += order;
println("stock={} sold={}", stock, sold);
}
Ausführungsergebnis:
stock=14 sold=6
Lagerbestand und Verkaufsvolumen müssen sich gemeinsam verändern. Durch die Aktualisierung eines dieser Werte wird die Beziehung zwischen den Werten unterbrochen. Wir werden die wiederholte Auftragsverarbeitung durch Lernfunktionen und Schleifenanweisungen gruppieren.
Ganzzahl- und Gleitkommatypen
Ganzzahltypen sind wie folgt:
- Unterzeichnet:
i8,i16,i32,i64,i128,i256,i512,i1024 - Ohne Vorzeichen:
u8,u16,u32,u64,u128,u256,u512,u1024 - Ganzzahlige Adressgröße:
isz,usz - Gleitkomma:
f32,f64
isz ist ein vorzeichenbehafteter Ganzzahltyp, der der Adressgröße entspricht, und usz ist ein vorzeichenloser Ganzzahltyp, der der Adressgröße entspricht.
Andere eingebaute Typen
| Typ | Benutzen |
|---|---|
bool |
true oder false |
char |
Ein vorzeichenloser 8-Bit-Zeichenwert. Nicht willkürlicher Codepunkttyp Unicode |
byte |
8-Bit-Byte-Wert |
str |
String-Bytes, die mit NUL enden |
ptr<T> |
Pointer-Targeting T |
array<T, N> |
Array fester Länge mit Elementtyp T und Länge N |
An Typspeicherorten können auch benutzerdefinierte Strukturen, Aufzählungen und Typaliase verwendet werden.
var ist die Syntax zum Deklarieren lokaler Variablen. Ein Typalias ist eine Grammatik, die denselben Typ mit einem Namen ausdrückt, der zum Kontext des Codes passt.