Kohlrabi-Cremesuppe - Rezept www.mbergmann-sh.de

Kohlrabi-Cremesuppe

…mit Knoblauch-Kräuter-Croutons

Zutaten:

1 große Zwiebel
3 Zehen Knoblauch
600 g Kohlrabi
400 g Kartoffeln
1 EL Butter
800 ml Gemüsebrühe (am besten selbst gekocht, alternativ Bio-Instantbrühe)
1 Bd. frische Petersilie oder Schnittlauch
100 g Sahne (30 % Fett)
1 EL Zitronensaft
2 Prisen Muskatnuss /frisch gerieben)
1 TL provenzalische Kräuter
Pfeffer, Salz aus der Mühle

1 altbackenes Brötchen
2 EL Olivenöl

Zubereitung

  1. Zwiebel und Knoblauch abziehen und fein würfeln. Kohlrabi waschen, schälen und in ca. 2 cm große Stücke schneiden. Kartoffeln waschen, schälen und ebenfalls in 2 cm große Stücke schneiden.
  2. Butter in einem Topf zerlassen und Zwiebeln, Knoblauch, Kartoffeln und Kohlrabi darin rundherum bei kleiner Hitze anbraten. Mit der Gemüsebrühe ablöschen, einmal aufkochen lassen und bei mittlerer Hitze zugedeckt etwa 15-20 Minuten köcheln lassen. In der Zwischenzeit Kräuter abbrausen, trockenschütteln und fein hacken.
  3. Nach der Garzeit das Gemüse mit einem Pürierstab fein pürieren. Die Sahne unterrühren und die Suppe mit Zitronensaft, Muskatnuss, Kräutern, Pfeffer und etwas Meersalz abschmecken.
  4. Für die Croutons das Brötchen in Würfel schneiden, mit Kräutern bestreuen und 1 EL Öl darüber träufeln.
  5. In der gusseisernen Pfanne 1 EL Öl heiß werden lassen, dann die Brötchenwürfel beigeben und bei reduzierter Hitze von allen Seiten knusprig braun braten.
  6. Suppe in vorgewärmten Tellern anrichten, mit den Croutons und etwas gehackter Petersilie bestreuen, sofort servieren.

Guten Appetit!


Du findest meine Rezepte lecker und verwendest sie gern? Dann freue ich mich über einen kleinen Obolus für meine Gewürze-Kasse. Besten Dank!  🙂

[Zurück zur Übersicht]

BlitzBasic mit AmiBlitz3 - ein Tutorial für Amiga-Programmierer www.mbergmann-sh.de

BlitzBasic für Einsteiger – Variablen, Konstanten und Datentypen

Wer in den frühen Jahren der IT z.B. auf dem Commodore 64 in BASIC programmiert hat, der brauchte sich keine Gedanken um die Interna von Variablen und Konstanten zu machen. Es gab Variablen für Strings und Zahlen – und damit basta. Das hat sich mit modernen BASIC-Dialekten aus guten Gründen drastisch geändert.

Variablen belegen per se ein Stück zusammenhängenden Arbeitsspeicher (RAM) pro Stück. Aus heutiger Sicht ist es deshalb nicht effizient, für jede Variable eine gleich große Menge Arbeitsspeicher zu vergeuden. Manchmal möchte man nur mit sehr kleinen Zahlen arbeiten, ein andermal  mit Fließkommazahlen und wieder ein anderes Mal benötigt man Platz für sehr große Zeichenketten (Strings). Das ist der Punkt, an dem Datentypen ins Spiel kommen.

Wenn man für eine Variable immer nur so viel Speicher reserviert, wie tatsächlich gebraucht wird, dann verschwendet man weniger knappe Ressourcen (RAM) – aber es ergibt sich daraus noch ein weiterer Vorteil: Kleine Ganzzahlen werden vom Computer schneller verarbeitet, als z.B. große Fließkommazahlen.

Es ist auch unter BlitzBasic/AmiBlitz3 möglich, beim Schreiben von Programmen völlig darauf zu verzichten, sich mit Datentypen auseinanderzusetzen. Der Compiler geht dann intern einfach davon aus, dass er den Default-Datentyp verwenden soll. Unter AmiBlitz3 ist das QUICK (vergl. Anhang A – primitive Datentypen). Dieser Datentyp verbraucht entsprechend viel Ressourcen, da er ja groß genug für alle anfallenden Arten von Zahlen sein muss. Effizienter ist es da natürlich, sich die benötigte Größe der verwendeten Variablen vorher zu überlegen und sie mit einem passenden Datentyp zu deklarieren.

Variablen und ihr Datentyp

Eine Variable ist ein abstrakter Behälter für einen Wert, der bei der Ausführung eines Computerprogramms auftritt. Im Normalfall wird eine Variable im Quelltext durch einen Namen bezeichnet und hat eine Adresse im Speicher des Computers. Der durch eine Variable repräsentierte Wert (und gegebenenfalls auch die Größe) kann – im Unterschied zu einer Konstante – zur Laufzeit des Programms verändert werden. Variablen dienen also dazu, veränderbare Werte zu speichern.

Unter BlitzBasic/AmiBlitz3 definiert man eine Variable mittels des Schlüsselworts DEFTYPE nach dem folgenden Schema:

DEFTYPE .Datentyp Variablenname
; -- Beispiel, deklariert eine Variable vom Typ String: --
DEFTYPE .s altesKinderlied
altesKinderlied = "Alle meine Entchen"

Es ist auch mögliche, eine Variable direkt, ohne DEFTYPE zu deklarieren:

altesKinderlied.s = "Alle meine Entchen"

Der Variablenname ist im Rahmen syntaktischer Vorgaben frei wählbar. Er sollte aussagekräftig den Zweck der Variablen wiedergeben („sprechender“ Name). Erlaubt sind alphanumerische Zeichen und der Unterstrich mit den folgenden Ausnahmen:

  • der Variablenname darf nicht gleichlautend mit einem Schlüsselwort sein.
  • das erste Zeichen darf keine Ziffer sein.
  • Umlaute sind nicht erlaubt.
  • Sonderzeichen außer dem Unterstrich sind nicht erlaubt.
    Ausnahmen: Sonderzeichen, die zur Identifizierung eines Datentypen gehören, dürfen am Anfang oder Ende des Variablennamens verwendet werden.

Beispiele:

; Erlaubt:
meinString$, meinString.s
meine_variable1
aepfelZaehler

; Verboten:
mein$tring
1teVariable
äpfelZähler

Der Datentyp legt den Speicherverbrauch einer Variablen fest.

Primitive Datentypen

BlitzBasic/AmiBlitz3 verfügt über 7 Basis-Datentypen – die sogenannten primitiven Datentypen. Die Sprache verfügt auch über die Möglichkeit, aus diesen Typen erweiterte, die sogenannten zusammengesetzten Datentypen, zu erstellen – doch dazu später mehr.

Jedem primitiven Datentyp ist ein bestimmter Wertebereich zu eigen, in dessen Rahmen er Zahlen und Zeichen darstellen kann. Die Größe dieses Wertebereich bestimmt seinen Speicherverbrauch (vergl. Anhang A – primitive Datentypen).

Byte (.b)

Dieser Datentyp verarbeitet kleine Ganzzahlen (Integerwerte) im Wertebereich von -128 bis +127 (-128 … 0 … +128) und verbraucht 1 Byte (8 Bits) Speicher. Er eignet sich z.B. gut als Zählervariable in kurzen Zählschleifen oder zur numerischen Darstellung des ASCII-Zeichensatzes.
Beispiel: DEFTYPE .b kleinerZaehler =  0

Word (.w)

Dieser Datentyp verarbeitet mittelgroße Ganzzahlen im Wertebereich von -32768 bis +32767 (-32768 … 0 … +32767) und verbraucht 2 Bytes (16 Bits) Speicher.
Beispiel: DEFTYPE .w fatNumber =  22000

Long / Long Word (.l)

Dieser Datentyp verarbeitet sehr große Ganzzahlen im Wertebereich von -231  bis +231 und verbraucht 4 Bytes (32 Bits) Speicher.
Beispiel: DEFTYPE .l veryfatNumber =  4711081542

Quick (.q)

Dieser Datentyp verarbeitet kleine Fließkommazahlen im Wertebereich von -32768 bis +32767 unter Verwendung eines festen Dezimalpunkts und verbraucht 4 Bytes (32 Bits) Speicher, erlaubt bis zu 10 Nachkommastellen. Er ist schneller als die Emulation der Float-Typen in Software, aber langsamer als Integer und langsamer als Float-Typen auf einer Hardware-FPU.
Beispiel: DEFTYPE .q smallFloatNumber = 4711.0815

Float (.f)

Dieser Datentyp verarbeitet große, einfachpräzise Fließkommazahlen im Wertebereich von -9*1018 bis +9*1018-1 und verbraucht 4 Bytes (32 Bits) Speicher. Er eignet sich besonders zur Verwendung als einfachpräzise Fließkommazahl, wie sie von den Standard-Fließkommabibliotheken des Amiga unterstützt wird und arbeitet mit +/-23bits+/-7 bits (10 Nachkommastellen)Dieser Datentyp ist sehr langsam in Software zu emulieren, aber sehr schnell, wenn Hardware-FPU verwendet wird. Er arbeitet langsamer als Ganzzahlen.
Beispiel: DEFTYPE .f largeFloatNumber = 471143.0815

Double Float (.d)

Dieser Datentyp verarbeitet sehr große, doppeltpräzise Fließkommazahlen mit riesigem Wertebereich und verbraucht 8 Bytes (64 Bits) Speicher, 9 Nachkommastellen. Keine Software-Emulation möglich, daher muss eine Hardware-FPU vorhanden sein, um diesen Datentyp nutzen zu können! Nicht unterstützt in Blitz Basic 2.1 und früher. Langsamer als einfachpräzise Float-Typen. Typische Anwendungsfälle wären ein Programm zur Berechnung von Zinsen über lange Zeiträume oder eine ähnliche Banking-Software und alle Anwendungen, die wissenschaftliche Berechnungen mit Bedarf an hoher Rechengenauigkeit durchführen.
Beispiel: DEFTYPE .d hugeFloatNumber = 471143.0815

Hinweis: Der Inhalt einer Variablen des neuen Datentyps Double Float kann nicht korrekt mit Print und NPrint ausgegeben werden! Beide Befehle liefern nur den ganzzahligen Anteil des Wertes einer Variablen dieses Datentyps – die Stellen nach dem Dezimalpunkt werden von beiden Befehlen unterschlagen. Derzeit unterstützt noch keine einzige BlitzLib diesen Datentypen, sodass er hier nur der Vollständigkeit halber aufgeführt ist! Wenn es vermeidbar ist, dann verwende diesen Datentyp nicht.
Benutze stattdessen den Datentyp Float.

String (.s oder $)

Dieser Datentyp verarbeitet Zeichenketten aus 8-Bit-Zeichen im Speicher, die automatisch durch ein Nullzeichen (\0) abgeschlossen werden und verbraucht 4 Bytes (32 Bits).
Beispiel 1: DEFTYPE .s myStringVar_1
myStringVar_1 = „Ich bin eine Zeichenkette!“
Beispiel 2: myStringVar_2.s = „Ich auch! Ich auch!“
Beispiel 3: myStringVar_3$ = „Und ich erst!“

Datentypen deklarieren

Bei der Deklaration von Datentypen unterscheidet man zwischen der Inline-Deklaration, der expliziten Deklaration und der globalen Festlegung eines bestimmten Default-Datentyps:

  • Inline: ergebnis.q = 4711.42 (die Variable wird mit dem angegebenen Typen – hier: QUICK – versehen und gleichzeitig mit einem Wert initialisiert)
  • Explizit: DEFTYPE .q ergebnis (die Variable – und nur diese – ist vom angegeben Typ. Hier: QUICK)
  • Global: DEFTYPE .q (alle nicht inline oder explizit deklarierten Variablen sind vom Typ, der hier angegeben wurde – in diesem Fall QUICK)

Das Listing „typesize.ab3“ demonstriert die Deklaration und Initialisierung der primitiven Datentypen und gibt deren Speicherbedarf in Bytes und Bits aus. Beachte, dass dieses Listing aufgrund der Verwendung des Datentyps DOUBLE FLOAT nur auf Amigas mit FPU compiliert werden kann!

; ---------------------------------
; Listing: typesize.ab3
; Speicherverbrauch von Datentypen
; Version 1.0
;
; HARDWARE-FPU ERFORDERLICH!
; ---------------------------------
OPTIMIZE 3 ; FPU zuschalten
SYNTAX 1   ; strenger Syntax-Check

; -- Variablendeklaration mit DEFTYPE --
DEFTYPE .b byteVar
DEFTYPE .w wordVar
DEFTYPE .l longVar
DEFTYPE .q quickVar
DEFTYPE .f floatVar
DEFTYPE .d doubleVar
DEFTYPE .s stringVar, byteRes, bitRes

; Amiga Version string und das Compilerdatum
!version {"typesize 1.0 (\\__DATE_GER__)"}

; Variablen initialisieren
byteVar   = -128
wordVar   = 32767
longVar   = 4711081542
quickVar  = 4711.0815234567
floatVar  = 471143.0815421112
doubleVar = 47114384.420815471
stringVar = "BlitzBasic macht Freude."

; Werte ausgeben
byteRes = Str$(SizeOf .b)
bitRes  = Str$((SizeOf .b) * 8)
Print "Speicherverbrauch BYTE: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint byteVar
NPrint ""

byteRes = Str$(SizeOf .w)
bitRes  = Str$((SizeOf .w) * 8)
Print "Speicherverbrauch WORD: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint wordVar
NPrint ""

byteRes = Str$(SizeOf .l)
bitRes  = Str$((SizeOf .l) * 8)
Print "Speicherverbrauch LONG: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint longVar
NPrint ""

byteRes = Str$(SizeOf .q)
bitRes  = Str$((SizeOf .q) * 8)
Print "Speicherverbrauch QUICK: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint quickVar
NPrint ""

byteRes = Str$(SizeOf .f)
bitRes  = Str$((SizeOf .f) * 8)
Print "Speicherverbrauch FLOAT: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint floatVar
NPrint ""

byteRes = Str$(SizeOf .d)
bitRes  = Str$((SizeOf .d) * 8)
Print "Speicherverbrauch DOUBLE FLOAT: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint doubleVar  ; ACHTUNG: NPrint und Print drucken nur den ganzzahligen 
                  ; Anteil des Wertes!
NPrint "ACHTUNG: Ungelöstes Ausgabe-Problem!"
NPrint ""

byteRes = Str$(SizeOf .s)
bitRes  = Str$((SizeOf .s) * 8)
Print "Speicherverbrauch STRING: " + byteRes + " Byte ("
NPrint bitRes + " Bits)"
Print "Wert: "
NPrint stringVar
NPrint ""

End

Ausgabe:

Speicherverbrauch BYTE: 1 Byte (8 Bits)
Wert: -128

Speicherverbrauch WORD: 2 Byte (16 Bits)
Wert: 32767

Speicherverbrauch LONG: 4 Byte (32 Bits)
Wert: 416114246

Speicherverbrauch QUICK: 4 Byte (32 Bits)
Wert: 4711.5781

Speicherverbrauch FLOAT: 4 Byte (32 Bits)
Wert: 471143.5625

Speicherverbrauch DOUBLE FLOAT: 8 Byte (64 Bits)
Wert: 47114384
ACHTUNG: Ungelöstes Ausgabe-Problem!

Speicherverbrauch STRING: 4 Byte (32 Bits)
Wert: BlitzBasic macht Freude.

Programmanalyse:

Das Programm benutzt zwei neue Befehle: Str$() und SizeOf. Str$() wandelt einen numerischen Wert in einen String um. Sizeof liefert den Speicherverbrauch eines Datentyps in Bytes zurück.

Wir verwenden Str$, um den (numerischen) Rückgabewert von Sizeof einer String-Variable zuweisen zu können, die dann bei der Ausgabe durch Print und NPrint zusammen mit anderen Zeichenketten zu einer neuen Zeichenkette zusammengesetzt wird (String Concatenation).

Ablauf:

  • In Zeile 8 sorgen wir mit OPTIMIZE 3 dafür, dass neben der Optimierung für die MC68020 CPU auch die FPU verwendet wird (siehe Abschnitt „Optimierte Programme erzeugen“ im Artikel „Das erste Programm„). Das ist nötig, da wir u.a. den Datentyp DOUBLE FLOAT verwenden, der nur auf einer physisch vorhandenen FPU und erst ab AmiBlitz3 verwendet werden kann. BlitzBasic v2 und älter kennen diesen Datentypen nicht.
  • Zeile 9 schaltet die strenge Syntax-Prüfung ein, bei der alle Variablen vor ihrer ersten Verwendung per DEFTYPE deklariert werden müssen.
  • In den Zeilen 12 bis 18 deklarieren wir per expliziter Deklaration die im Programm verwendeten Variablen mit einem Datentyp.
  • Zeile 21 legt den Versions-String fest, der bei Abfrage mit dem DOS-Befehl version meinProgramm full in einer Shell Auskunft über die Versionsnummer und das Erstellungsdatum eines Programms gibt.
  • In den Zeilen 24 bis 30 weisen wir einigen der zuvor deklarierten Variablen Werte zu (Initialisierung).
  • Die Zeilen 33 bis 39 geben Speicherverbrauch und Inhalt der BYTE-Variablen byteVar aus:
  • In Zeile 33 wandeln wir die per SizeOf .b abgefragte Speichergröße (Anzahl Bytes) mittels Str$ in eine Zeichenkette um und weisen sie der String-Variablen byteRes zu.
  • In Zeile 34 verfahren wir analog, berechnen hier aber die Anzahl Bits durch Multiplikation mit dem Faktor 8 ( 1 Byte = 8 Bits). Das Ergebnis der Berechnung wird der String-Variablen bitRes zugewiesen.
  • In Zeile 35 geben wir per Print (ohne Zeilenvorschub) eine Teilmeldung aus. Sie setzt sich aus mehreren Teilstrings zusammen, die wir mit dem Verknüpfungsoperatur „+“ zu einem Gesamtstring für die Ausgabe zusammensetzen.
  • In Zeile 36  geben wir mit NPrint (mit Zeilenvorschub) einen weiteren zusammengesetzten String aus, der die Ausgabe der Speichergröße abschließt.
  • Zeile 37 druckt per Print eine weitere Meldung ohne Zeilenvorschub, an welche dann in Zeile 38 per NPrint der Wert (Inhalt) der Variablen byteVar angehängt und anschließend ein Zeilenvorschub ausgeführt wird.
  • Zeile 39 druckt mittels einem an NPrint übergebenen Leerstring einen weiteren Zeilenvorschub (ohne Text).
  • Analog zu den Zeilen 33 bis 39 werden in den Zeilen 41 bis 89 nacheinander die Speichergrößen und Inhalte für die übrigen Datentypen ausgegeben.
  • Das Programm endet in Zeile 91 mit der Anweisung End.

Speicherüberlauf 

Wir haben gelernt, dass jeder Datentyp einen bestimmten Wertebereich besitzt, der die Größe der darstellbaren Inhalte einer Variablen bestimmt. Was aber, wenn dieser Wertebereich überschritten wird? Nun, in diesem Fall kommt es zum Speicherüberlauf. Bei numerischen Variablen hat das zur Konsequenz, dass ihr Inhalt nicht mehr mit dem vermuteten Wert übereinstimmt, was wiederum zu unvorhergesehenem Programmverhalten führt.

Wenn ein Wertebereich überschritten wird, so wird (normalerweise) kein Fehler erzeugt. Stattdessen wird der Wert auf das andere Ende des Wertebereichs umgeschlagen. Dies kann dazu führen, dass einige sehr schwer zu findende Fehler in deinen Code eingeschleust werden!

Ein Beispiel: Der Datentyp BYTE kann Ganzzahlen im Bereich zwischen -128 und + 127 darstellen. Wenn eine BYTE-Variable den Wert +127 besitzt und man addiert nochmal 1 dazu, so ist der Inhalt nicht, wie man vermuten könnte, +128, sondern -128. Das entspricht der unteren Grenze des Wertebereichs. Addiert man nun eine weitere 1 hinzu, so ist der Wert -127.

Im negativen Wertebereich verhält sich das genauso: Wenn eine BYTE-Variable den Wert -128 besitzt und man subtrahiert davon 1 weg, so ist der Inhalt nicht, wie man vermuten könnte, –129, sondern +127. Das entspricht der oberen Grenze des Wertebereichs. Subtrahiert man nun eine weitere 1, so ist der Wert +126. Das Listing „overflow.ab3“ verdeutlicht das eben Gesagte:

; ---------------------------------
; Listing: overflow.ab3
; Ueberlauf von Datentypen
; Version 1.0
; ---------------------------------
OPTIMIZE 1 ; MC68020+ Optimierungen
SYNTAX 1   ; strenger Syntax-Check

; -- Variablendeklaration mit DEFTYPE --
DEFTYPE .b byteVar

; Amiga Version string und das Compilerdatum
!version {"overflow 1.0 (\\__DATE_GER__)"}

; Variable initialisieren
byteVar   = 127 ; positive Obergrenze

; Titel ausgeben
NPrint "=============="
NPrint "-- Overflow --"
NPrint "=============="
NPrint ""

; Wert ausgeben (positiver Bereich)
NPrint "positiver Wertebereich:"
Print "byteVar hat den Anfangswert: "
NPrint byteVar
NPrint "Addiere 1..."
byteVar = byteVar + 1
Print "byteVar hat nun den Wert "
NPrint byteVar
NPrint "Addiere weitere 1..."
byteVar = byteVar + 1
Print "byteVar hat nun den Wert "
NPrint byteVar
NPrint ""

; Wert zuruecksetzen auf Untergrenze
byteVar = -128

; Wert ausgeben (negativer Bereich)
NPrint "Negativer Wertebereich:"
Print "byteVar hat den Anfangswert "
NPrint byteVar
NPrint "Subtrahiere 1..."
byteVar = byteVar - 1
Print "byteVar hat nun den Wert "
NPrint byteVar
NPrint "Subtrahiere weitere 1..."
byteVar = byteVar - 1
Print "byteVar hat nun den Wert "
NPrint byteVar
NPrint ""

NPrint "Habe fertig."
End

Ausgabe:

==============
-- Overflow --
==============

positiver Wertebereich:
byteVar hat den Anfangswert: 127
Addiere 1...
byteVar hat nun den Wert -128
Addiere weitere 1...
byteVar hat nun den Wert -127

Negativer Wertebereich:
byteVar hat den Anfangswert -128
Subtrahiere 1...
byteVar hat nun den Wert 127
Subtrahiere weitere 1...
byteVar hat nun den Wert 126

Habe fertig.

Zum Programmablauf gibt es eigentlich nichts zu sagen – alle vorkommenden Anweisungen und Abläufe haben wir bereits besprochen.

Konstanten

Eine Konstante (von lateinisch constans ‚feststehend‘) in einem Computerprogramm ist ein Behälter für einen Wert, der nach der Zuweisung nicht verändert werden kann. Im Gegensatz zu Variablen ist der einmal festgelegte Wert einer Konstanten zur Laufzeit des Programms bindend.

Ein Rautezeichen (#) vor einem Variablennamen bedeutet, dass es sich um eine Konstante handelt (nicht mehr um eine Variable!) Der Wert einer Konstante ist immer eine Ganzzahl. Anders als in anderen Hochsprache, wie z.B. C/C++, kann man in BlitzBasic keine Konstanten mit anderen Datentypen definieren.

Konstanten haben die folgenden Eigenschaften:

  • Sie sind schneller als Variablen und benötigen keinen Speicherplatz.
  • machen Programme besser lesbar als Zahlen
  • Können in Assembler verwendet werden
  • Können mit bedingten Kompilierauswertungen verwendet werden
  • Können nur Integer-Werte enthalten
  • Erleichtert das Ändern einer konstanten Menge, die in einem Programm verwendet wird
  • Können nur über den Quellcode zur Kompilierzeit, aber NICHT zur Laufzeit geändert werden

Neben der Option, eigene Konstanten zu definieren, bringt BlitzBasic schon viele „eingebaute“ Konstante, wie bspw. die Kreiszahl Pi mit. Der wohl wichtigste Aspekt von Konstanten aus der Sicht eines BASIC-Programmierers ist aber wohl, dass alle „magischen Zahlen“, die im Code auftauchen, durch sinnvolle Worte wie #width ersetzt werden können („sprechende“ Namen!).

Das Listing „constants.ab3“ demonstriert die Verwendung von Konstanten.:

; -------------------------
; File: constants.ab3
; Zeigt die Verwendung von
; Konstanten
; Version: 1.0
; -------------------------
OPTIMIZE 1
SYNTAX 1

; Amiga Version String und das Compilerdatum
!version {"constants 1.0 (\\__DATE_GER__)"}

; Konstanten definieren:
#width  = 5
#height = 5

; Variablen deklarieren:
DEFTYPE .w area

; Flaeche berechnen:
area = Abs(#width * #height)

; Ergebnis ausgeben:
Print "Die Flaeche aus " + Str$(#width)
Print " m mal " + Str$(#height)
NPrint " m betraegt " + Str$(Abs(area)) + " qm"
; Interne Konstante Pi ausgeben:
NPrint "Der Wert der Kreiszahl PI ist " + Str$(Pi)
End

Ausgabe:

Die Flaeche aus 5 m mal 5 m betraegt 25 qm
Der Wert der Kreiszahl PI ist 3.141592

Programmanalyse:

Das Programm verwendet den neuen Befehl Abs(). Er dient zur Umwandlung vorzeichenbehafteter Zahlen in vorzeichenlose Zahlen. Wir verwenden ihn zur Umwandlung des in der Variablen area gespeicherten Wertes, der als Produkt der Multiplikation zweier Konstanten sonst u.U. als negative Zahl ausgegeben werden könnte.

  • in den Zeilen 14 und 15 definieren wir die Konstanten #width und #height. Sie werden später zur Berechnung einer Fläche herangezogen.
  • in Zeile 18 deklarieren wir die Variable area als Variable vom Typ WORD.
  • in Zeile 21 berechnen wir die Summe der Fläche und weisen das Ergebnis der Variablen area zu.
  • die Zeilen 24 bis 26 dienen der Ausgabe der berechneten Werte. Dabei benutzen wir in Zeile 26 den zuvor erklärten Befehl Abs() zur Umwandlung des in area gespeicherten Wertes – just to make sure…
  • in Zeile 28 geben wir den Wert der internen Konstante Pi aus.
  • Das Programm endet mit Zeile 29.

Zusammenfassung:

In diesem Teil des Tutorials haben wir gelernt

  • Was Datentypen und Variablen sind, welche Datentypen es gibt und wie man Variablen deklariert und initialisiert.
  • das der neue Datentyp DOUBLE FLOAT nur mit AmiBlitz3 verwendet werden kann, eine vorhanden FPU voraussetzt – und das man ihn besser nicht verwenden sollte, was sich aber in einer späteren Version von AmiBlitz3 noch ändern kann.
  • was Konstanten sind und wie man sie definiert und verwendet.
  • dass Konstanten nur Ganzzahlen aufnehmen können und keinen Speicherplatz belegen.

Ausblick

Im nächsten Teil des Tutorials werden wir uns eingehend mit den Grundrechenarten und eigenen Funktionen unter BlitzBasic beschäftigen.

 

[Zurück zur Übersicht] | [zurück] | [vowärts]

BlitzBasic mit AmiBlitz3 - ein Tutorial für Amiga-Programmierer www.mbergmann-sh.de

BlitzBasic für Einsteiger – das erste Programm

Erste Schritte mit BlitzBasic – Programmeingabe und Übersetzung

Jedes gute Tutorial für Programmierer beginnt mit dem wohl langweiligsten Programm auf diesem Planeten: Hello, World.

Bevor wir jedoch loslegen, gibt es noch etwas Organisatorisches zu erledigen: Wir brauchen ein Projektverzeichnis für die im Lauf dieses Tutorials anfallenden Dateien. Erstelle dazu zunächst ein Hauptverzeichnis irgendwo auf deiner Festplatte. Dort werden wir dann bei Bedarf weitere Unterordner für Projekte anlegen und unsere Listings dort passenden abspeichern.

Beispiel:

makedir Work:ab3-Tutorial
makedir Work:ab3-Tutorial/001_hello

 

Starte nun AmiBlitz3 und gib im Editor-Fenster das folgende Listing buchstabengetreu, aber ohne Zeilennummern ein:

; ---------------------------
; Listing: hello1.ab3
; Hallo, Welt mit BlitzBasic
; Version 1.0
; ---------------------------

Print "Hallo, Welt!"
End

Speichere dein Programm (<Amiga> + <S>) unter dem Namen „hello1.ab3“ in deinem Projektverzeichnis 001_hello für unser Tutorial. Der Suffix „.ab3“ ist der Standard, um ein Listing als zu AmiBlitz3 gehörende Quelldatei zu kennzeichnen.

Stelle sicher, das die Option zum Starten des Debugger deaktiviert ist. Verwende die Tastenkombination <Amiga> + <#>, um dein Programm zu compilieren und zu starten. Du erhältst die folgende Ausgabe:

AmiBlitz3 BlitzBasic Tutorial www.mbergmann-sh.de

Betrachten wir uns Programm und Ausgabe einmal genauer:

  • Die Zeilen 1 bis 5 enthalten einen erklärenden Kommentar zum Programm. Kommentare werden in BlitzBasic mit dem Semikolon eingeleitet und gelten jeweils für eine Zeile. Alles, was in einem Kommentar steht, wird vom Compiler ignoriert.
  • Zeile 7 gibt mit dem Schlüsselwort Print die in Anführungszeichen gesetzte Meldung aus. Es erfolgt kein Zeilenvorschub!
  • Das Programm endet in Zeile 8 mit dem Schlüsselwort End.

Wenn du bereits Erfahrungen mit anderen BASIC-Dialekten gemacht hast, dann wirst du dich vielleicht wundern, warum nach dem Print-Befehl keine neue Zeile erzeugt wird. Nun, in BlitzBasic gibt es dafür den Befehl NPrint, der explizit einen Zeilenvorschub nach der Ausgabe erzeugt. Der Syntax ist mit dem von Print nahezu identisch.

Ändere unser erstes Programm nun so ab, dass es dem folgenden Listing entspricht und speichere es anschließend unter „hello2.ab3“ im gleichen Verzeichnis wie eben:

; ---------------------------
; Listing: hello2.ab3
; Hallo, Welt mit BlitzBasic
; Version 2.0
; ---------------------------

NPrint "Hallo, Welt!"
NPrint "Ich kann auch mit Zeilenvorschub..."
End

Drücke wieder <Amiga> + <#>, um dein Programm zu compilieren und zu starten. Diesmal sieht die Ausgabe des Programms so aus:

Ein Programm mit Variablen

Variablen dienen dazu bestimmte Werte, wie etwa Strings (Zeichenketten) oder Zahlenwerte, für die weitere Verwendung im Programm zwischen zu speichern. Sie werden im Arbeitsspeicher (RAM) angelegt. Sie besitzen einen Datentyp, der darüber entscheidet, welche Art von Wert eine Variable aufnehmen kann.

Lege für unser nächstes Projekt den Ordner 002_myname an. Gib dann das folgende Listing ein und speichere es  als „myname1.ab3“ im eben erstellten Ordner.

; ---------------------------
; Listing: myname1.ab3
; Ein Programm mit Variablen
; Version 1.0
; ---------------------------

meinName$ = "Callimero"  ; eine String-Variable deklarieren

NPrint "Hallo, Welt!"
Print "Mein Name ist "
Print meinName$
NPrint "."
NPrint "Nun ist es heraus..."
End

Ausgabe:

Hallo, Welt!
Mein Name ist Callimero.
Nun ist es heraus...
Program terminated.
Press <ENTER> to return to to the IDE...

Programmanalyse:

Unser Programm verwendet eine Variable vom Datentyp String. Das erkennt man schnell am an den Variablennamen angehängten Dollarzeichen ($). Stringvariablen nehmen alle Arten von alphanumerischen Zeichen und Leerstellen auf, sodass man ganze Sätze in einer String-Variablen ablegen kann. Eine String-Variable deklariert man, indem man ihr einen Variablenname mit angehängtem $ gibt. Bei einer vollständigen Definition weist man dann dieser Variablen mittels des Zuweisungsoperators (=) einen Text zu. Dieser wird zwischen Anführungszeichen gesetzt (In unserem Programm haben wir das in Zeile 7 getan). Beispiel:

  meinString$ = "Alle meine Entchen"
  • Die Zeilen 1 bis 5 enthalten einen erläuternden Kommentar zum Programm.
  • in Zeile 7 deklarieren wir eine String-Variable (kenntlich am angehängten „$“ hinter dem Variablennamen) mit dem Inhalt „Calimero“. Hinter der Variablendeklaration steht ein erläuternder Kommentar.
  • Zeile 9 gibt die Meldung „Hallo, Welt!“, gefolgt von einem Zeilenvorschub, aus.
  • In Zeile 10 geben wir mit Print (ohne Zeilenvorschub) die Meldung „Mein Name ist “ aus. Wir machen das so, weil wir in der selben Zeile weitere Textausgaben anhängen möchten. Beachte das Leerzeichen am Ende des Strings – es sorgt dafür, dass die nächste Print-Ausgabe nicht direkt am letzten Wort der Meldung klebt.
  • Zeile 11 hängt den in der zuvor in der String-Variablen meinName$ gespeicherten String „Callimero“ an.
  • Zeile 12 schließt den zuvor begonnen Satz mit einem Punkt ab und gibt einen Zeilenvorschub aus.
  • Zeile 13 gibt den Satz „Nun ist es heraus…“ aus und führt einen weiteren Zeilenvorschub durch.
  • Das Programm endet mit dem Schlüsselwort End in Zeile 14.

Ein Programm mit Benutzereingabe

Bis jetzt haben wir dem Computer alles, was er „sagen“ soll, fest vorgegeben. Wie aber können wir ihm die Werte, mit denen er arbeiten soll, auch zur Laufzeit mitteilen? Nun, unter BlitzBasic gibt es dafür mehrere infrage kommende Möglichkeiten. Für die Abfrage in Shell-Programmen kommen hier die beiden Schlüsselwörter Edit$ (für Strings) und Edit (für Zahlenwerte) in Frage. Das Listing „myname2.ab3“ illustriert das. Gib es ein, speichere es und führe es aus.

; ---------------------------
; Listing: myname2.ab3
; Ein Programm mit Variablen
; Version 2.0
; ---------------------------

meinName$ = "Callimero"  ; eine String-Variable deklarieren

NPrint "Hallo, Welt!"
Print "Mein Name ist "
Print meinName$
NPrint "."
NPrint "Nun ist es heraus..."
NPrint ""                ; gibt eine Leerzeile aus

; -- Benutzereingabe mit Input --
Print "Wie lautet dein Name? "
deinName$ = Edit$(30)

; -- Ausgabe --
greetStr$ = "Tach auch, " + deinName$ + "!"
NPrint greetStr$

End

Ausgabe:

Hallo, Welt!
Mein Name ist Callimero.
Nun ist es heraus...

Wie lautet dein Name? Micha B. 
Tach auch, Micha B.!

Programmanalyse:

  • Zeile 17 – Ausgabe der Aufforderung, den Namen einzugeben. Wir verwenden Print, damit die Eingabemarke nach dem folgenden Edit$-Befehl nicht in einer neuen Zeile ausgegeben wird.
  • In Zeile 18 weisen wir die Benutzereingabe per Edit$ der Variablen deinName$ zu. Der Syntax von Edit$ lautet
    Edit$([Default Text,] Eingabelänge in Zeichen)
  • In Zeile 21 setzen wir mit dem Pluszeichen aus mehreren Strings einen neuen String zusammen (String Concatenation). Der zusammengesetzte String wir der Stringvariablen greetStr$ zugewiesen.
  • Zeile 22 gibt den Inhalt der Variable greetStr$ aus.

Überprüfen von Variablen durch den Compiler

AmiBlitz3 verfügt – im Gegensatz zum alten BlitzBasic v2.1 – über die Möglichkeit, dem Compiler mitzuteilen, dass er eine strikte Syntaxprüfung vornehmen und u.a. verwendete Variablen vor ihrer Benutzung auf korrekte Verwendung überprüfen soll. Eine strikte Syntaxprüfung hat große Vorteile bei der Programmentwicklung, denn es erspart es dir von vorn herein, durch falsch genutzte Variablen und Funktionen schwer aufzuspürende Bugs in deinem Programm einzubauen. Die strikte Syntaxprüfung schaltest du mit dem compilerinternen Befehl SYNTAX am Anfang deines Quelltexts ein. Der Befehl übernimmt einen numerischen Wert für die Intensität der Prüfung:

  • SYNTAX 0 – Prüfung abschalten, Variablen müssen nicht vor der ersten Verwendung deklariert werden (wie BlitzBasic2)
  • SYNTAX 1 – Variablen müssen zwingend mit DEFTYPE deklariert werden
  • SYNTAX 2 – Variablen müssen bei der ersten Verwendung mit DEFTYPE deklariert werden

Es bleibt dir überlassen, ob du in deinen eigenen Programmen dieses Feature nutzen möchtest, allerdings rate ich dir dazu, wenigstens SYNTAX 2 zu verwenden. Der gute Grund: Man verliert nicht so leicht den Überblick über seine Variablen und ihren Zweck, wenn man sich von Anfang an daran gewöhnt, diese bereits am Anfang eines Programms oder wenigstens vor der ersten Verwendung zu deklarieren, anstatt sie wild während des Programmierens zu erfinden. Ich persönlich komme aus der C/C++ Welt und bin ohnehin daran gewöhnt, meinem Compiler von Anfang an mitzuteilen, welche Variable für welchen Zweck zu verwenden ist. Darum bevorzuge ich SYNTAX 1.

Ergänze unser eben geschriebenes Programm um den Eintrag SYNTAX 1 direkt nach dem einleitenden Kommentar, speichere es unter „myname3.ab3“ und führe es aus. Wie nicht anders erwartet, wartet es mit einer Fehlermeldung auf:

Mit SYNTAX 1 haben wir festgelegt, dass Variablen zwingend mit DEFTYPE deklariert werden müssen. Also tun wir das auch:

; -- Variablendeklaration mit DEFTYPE --
DEFTYPE .s meinName$, deinName$, greetStr$

Die Compiler-Anweisung DEFTYPE (Default Type) bestimmt den Datentyp von Variablen. Der Parameter .s gibt im vorliegenden Fall an, dass es sich bei den anschließend aufgezählten Variablen um String-Variablen handelt (siehe Anhang A – Primitive Datentypen).

Der Amiga Version-String

Auf dem Amiga kann man die Versionsinformationen eines systemkonformen Programms mit dem Version-Befehl in einer Shell abfragen. Dazu muss natürlich auch ein entsprechender Version-String vorhanden sein. So definiert man ihn:

; Amiga Version String und das Compilerdatum
!version {"MyName 3.0 (\\__DATE_GER__)"}

Hier noch einmal das vollständige, geänderte Listing:

; ---------------------------
; Listing: myname3.ab3
; Ein Programm mit Variablen
; Version 3.0
; ---------------------------
SYNTAX 1

; -- Variablendeklaration mit DEFTYPE --
DEFTYPE .s meinName$, deinName$, greetStr$

; Amiga Version String und das Compilerdatum
!version {"MyName 3.0 (\\__DATE_GER__)"}

meinName$ = "Callimero"  ; eine String-Variable initialisieren

NPrint "Hallo, Welt!"
Print "Mein Name ist "
Print meinName$
NPrint "."
NPrint "Nun ist es heraus..."
NPrint ""                ; gibt eine Leerzeile aus

; -- Benutzereingabe mit Input --
Print "Wie lautet dein Name? "
deinName$ = Edit$(30)

; -- Ausgabe --
greetStr$ = "Tach auch, " + deinName$ + "!"
NPrint greetStr$

End

Compiliere das Programm diesmal über das Menü Compiler->Create Executable und gib als Ziel für das ausführbare Programm RAM: und als Dateinamen myname an. Öffne danach eine Shell und überprüfe die Versionsnummer:

RAM Disk:> version RAM:myname file full
MyName 3.0 (16.03.25)

Optimierte Programme erzeugen

Jeder möchte, dass sein Programm so schnell wie möglich arbeitet. AmiBlitz3 unterstützt die Optimierung von Programmen mit dem compilerinternen Schlüsselwort OPTIMIZE n. Es sollte ganz am Anfang des zu optimierenden Quellcodes stehen und nimmt einen Zahlenwert als Parameter n:

  • OPTIMIZE 1 – optimiert für die Motorola MC68020 CPU
  • OPTIMIZE 2 – verwende eine vorhanden FPU (Achtung: ein so optimiertes Programm wird auf Amigas ohne FPU abstürzen!)
  • OPTIMIZE 4 – schaltet den Modus für neuen Syntax (AmiBlitz3) zu

Kombinierte Optimierungsstufen lassen sich durch die Addition dieser Werte erzeugen. Beispiel:

; -- ALLE Stufen verwenden --
; --    1 + 2 + 4 = 7      --
OPTIMIZE 7

Hinweis: Im Debug-Modus ist die Optimierung für FPU abgeschaltet.

Die Optimierung der Größe des ausführbaren Programmes kann man über das Menü Compiler->Create minimized Executable bewirken.

Zusammenfassung

Wir haben in diesem Teil des Tutorials gelernt,

  • was Kommentare sind und wie man sie verwendet
  • wie und wann man die Befehle Print und NPrint verwendet
  • was eine String-Variable ist
  • wie man mit dem Befehl Edit$ eine Benutzereingabe in einen String einliest
  • wie man Teilstrings zu einem neuen String zusammensetzt
  • wie man den Compiler dazu veranlasst, strengere Syntaxprüfung und Code-Optimierungen vorzunehmen
  • was der Amiga Version String ist und wie man ihn definiert

Im weiteren Verlauf des Tutorials werden wir grundsätzlich immer Variablen vor Gebrauch deklarieren und unseren lauffähigen Programmen einen Version-String mitgeben.

Ausblick

Im nächsten Teil werden wir uns etwas genauer mit Variablen, Konstanten und Datentypen befassen.


[Zurück zur Übersicht] | [zurück] | [vowärts]

BlitzBasic mit AmiBlitz3 - ein Tutorial für Amiga-Programmierer www.mbergmann-sh.de

BlitzBasic für Einsteiger – die Entwicklungsumgebung

AmiBlitz3 – Entwickler IDE für BlitzBasic auf dem Amiga

Herzlich willkommen bei meinem Tutorial zur Anwendungsentwicklung mit BlitzBasic auf dem Amiga!

Bevor wir richtig einsteigen können, müssen natürlich zunächst erst die Voraussetzungen zum Arbeiten mit BlitzBasic geschaffen werden. Grundsätzlich kannst du dieses Tutorial auch mit der alten BlitzBasic-Version v2.1 bearbeiten – aber warum solltest du dich quälen wollen? AmiBlitz3 ist eine moderne, feature-reiche IDE, gegen die der Editor aus AmiBlitz 2 einfach alt aussieht. Mit AmiBlitz3 hast du eine wesentlich leistungsfähigere Entwicklungsumgebung mit einem modernen Editor, Debugging-Features, einem Sourcecode-Browser, einem Bibliotheken-Browser, integrierten Hilfe-Funktionen und vielem mehr für lau zur Verfügung. Der neu überarbeitete Compiler erzeugt optimierten, schnellen Code für alle auf dem Amiga gebräuchlichen Motorola MC680x0 CPU und kann obendrein auch FPU-optimierten Code erzeugen. Dabei können Programme für jede klassische AmigaOS-Version erstellt werden.

AmiBlitz3 herunterladen und installieren

AmiBlitz3 wird derzeit aktiv von Sven Dröge entwickelt und ist auf GitHub gehostet:

Download:

  1. Option: https://github.com/AmiBlitz/AmiBlitz3 (der aktuelle Source Code auf GitHub)
  2. Option: https://github.com/AmiBlitz/AmiBlitz3/releases/download/v3.10.0/Amiblitz3100.lha (Programmarchiv)

Am einfachsten besorgst du dir das Archiv per Option 2 und entpackst es:

lha -a x Amiblitz3100.lha RAM:

list ram:AmiBlitz3.10/
Directory "ram:AmiBlitz3.10" on Samstag 15-Mär-25 
Tools Dir ----rwed Heute 15:00:50
Tools.info 6376 ----rwed 06-Mär-24 15:53:08
System Dir ----rwed Heute 15:00:50
System.info 6484 ----rwed 06-Mär-24 15:53:08
Sourcecodes Dir ----rwed Heute 15:00:50
Sourcecodes.info 5947 ----rwed 06-Mär-24 15:53:08
README.md.info 5880 ----rwed 06-Mär-24 15:53:08
README.md 2133 ----rwed 23-Mär-24 12:50:06
Locale Dir ----rwed Heute 15:00:49
Libs Dir ----rwed Heute 15:00:49
Docs Dir ----rwed Heute 15:00:49
Docs.info 5904 ----rwed 06-Mär-24 15:53:08
Debug Dir ----rwed Heute 15:00:49
Contributions Dir ----rwed Heute 15:00:48
Contributions.info 6296 ----rwed 06-Mär-24 15:53:06
Catalogs Dir ----rwed Heute 15:00:48
BlitzLibs Dir ----rwed Heute 15:00:48
AmiBlitz3.info 4832 ----rw-d 26-Feb-25 13:51:50
Amiblitz3 374456 ----rwed Gestern 10:30:44
9 files - 408K bytes - 10 directories - 442 blocks used

Kopiere das Verzeichnis AmiBlitz3.10 an einen beliebigen Platz auf deiner Festplatte. AmiBlitz3 benötigt keine zusätzlichen Einträge in s:user-startup – alle Abhängigkeiten sind über die ToolTypes des Programms geregelt. Du kannst den Editor also sofort starten.

Beim ersten Start dauert es ein Weilchen, bis die internen Indices des Programms generiert worden sind. Anschließend öffnet sich die IDE.

Der erste Kontakt mit der Benutzeroberfläche

AmiBlitz3 IDEBlitzBasic für Einsteiger www.mbergmann-sh.de

AmiBlitz3 IDE mit geladenem Quelltext (1280×960 Pixel)

Je nach verwendeter Monitorauflösung kann das Layout vom hier gezeigten Layout abweichen – AmiBlitz3 sucht sich automatisch ein passendes Layout für seine Fenster. Richtig Spaß macht die IDE auf einem Amiga mit Beschleuniger- und RGB-Grafikkarte bei einer Auflösung ab 1280×960 Pixeln, aber auch in niedrigeren Auflösungen lässt es sich sehr komfortabel arbeiten.

  1. Das Editor-Fenster ist der Bereich, in dem du deine Quelltexte erfasst. Der Editor verfügt über Zeilennummerierung und Syntax Highlighting für Schlüsselworte und Sprachkonstrukte. Alle Zeichensätze (und Vieles mehr) sind über das Menü
      Project->IDE Settings
    einstellbar.
  2. Der Instruction-Browser ist ein mächtiges Tool, mit dem du nach Bibliotheksbefehlen und ihrem Syntax suchen kannst. Auf Wunsch wird ein gefundener Befehl auch gleich an der aktuellen Cursor-Position in den Text eingefügt. Ein Doppelklick auf einen im Instruction-Browser gefundenen Befehl öffnet, falls vorhanden, die Zugehörige Beschreibung in deinem Viewer für AmigaGuide-Dateien.
  3. Der aus zwei Tabs bestehende Source-Browser ermöglicht u.a. die Anzeige von im Programm verwendeten eigenen Funktionen und Bibliotheksfunktionen, die man per Doppelklick anspringen kann.
  4. Der Definition-Browser ermöglicht die Überwachung von Konstanten, Variablen und Strukturen und deren Inhalten.
  • Wenn du den Cursor im Editor-Fenster über einem Schlüsselwort positionierst und danach die <Help>-Taste drückst, so wird dir in der Fensterleiste eine Kurzhilfe angezeigt.
  • Bei gleichem Szenario bewirkt ein Druck auf die F-Taste <F1> das Öffnen einer genaueren Beschreibung im AmigaGuide-Format (falls verfügbar) – ansonsten öffnet sich die globale Hilfedatei zu AmiBlitz3.
  • Bereits vorhandene Quelltexte lädst du über das Menü
      Source File->Open
    oder die Tastenkombination <Amiga> + <O> in den Editor.
  • Ein neues, leeres Editorfenster erzeugst du mittels des Menüs
      Source File->New->(weitere Optionen).
    Hier findest du dann Assistenten für bestimmte Anwendungstypen.
  • Einen in den Editor geladenen Quelltext schnell übersetzen und ausführen lassen kannst Du über das Menü
      Compiler->Compile and Run
    oder mit der Tastenkombination <Amiga> + <#>.
  • Bei der Ausführung eines Programms öffnet sich eine Shell. Wird das Programm beendet, so musst du in dieser Shell <ENTER> drücken, um zum Editor zurück zu gelangen.
  • Die mitgelieferten Beispiele findest du im Pfad <Festplatte>:AmiBlitz3/Sourcecodes.
  • Ein Klick auf das Iconify-Gadget im Editorfenster „versteckt“ die komplette AmiBlitz3 IDE. Um sie wieder zu öffnen, musst du auf der Amiga Workbench im Menü Hilfsmittel auf den Eintrag  AmiBlitz3 klicken.
  • Programme lassen sich aus der AmiBlitz3 IDE heraus entweder mit oder ohne Debugging-Informationen compilieren. Die entsprechende Option findest du im Menü
      Compiler->Create Debug Code
    Ist dieser Menüeintrag markiert, dann wird beim Start deines Programms auch der Debugger gestartet.
  • Die F-Taste <F1> öffnet das Online-Handbuch zu AmiBlitz3.

Weitere Optionen der AmiBlitz3 IDE klären wir im Verlauf des Tutorials dort, wo sie benötigt werden. Es schadet nichts, wenn du dir vorab das Handbuch schonmal durchgelesen hast (Taste <F1>).
Auf Facebook gibt es übrigens eine nette, englischsprachige Gruppe, die dir bei Fragen rund um BlitzBasic und AmiBlitz3 auch gern weiterhilft. 🙂

So, das war’s dann mit dem Schnelleinstieg in die AmiBlitz3 IDE. Im nächsten Beitrag beschäftigen wir uns dann mit dem Schreiben unseres ersten Programms, sowie einigen theoretischen Sprachgrundlagen zu BlitzBasic.

 


[Zurück zur Übersicht] | [zurück] | [vowärts]

AmigaNews – AmiBlitz v3.10 erschienen

 

 

Sven Dröge hat’s mal wieder möglich gemacht: Hochqualitatives BlitzBasic auf dem Amiga mit AmiBlitz v3.10
Eigentlich gab’s schon lange keinen Grund mehr, das alte BlitzBasic 2.1 mit seinem greislichen Editor zu verwenden, wo doch AmiBlitz3 ein durchdachtes modernes System aus Editor und Tools zur Verfügung stellt. Nun gibt’s dafür gar keinen Grund mehr (wenn man nicht auf einem sehr spartanischen Amiga entwickelt…).

AmiBlitz v3.10 wurde in vielen Punkten nochmals deutlich verbessert. Vieles ist schneller geworden, der Editor läuft stabil und zuverlässig. Das Paket lässt für BlitzBasic-Programmierer kaum noch Wünsche offen.

Download:

https://github.com/AmiBlitz/AmiBlitz3 (Source Code auf GitHub)
https://github.com/AmiBlitz/AmiBlitz3/releases/download/v3.10.0/Amiblitz3100.lha (Programmarchiv)

Zimt-Vanille-Milchreis – das perfekte Grundrezept für Feinschmecker

Milchreis für Feinschmecker www.mbergmann-sh.de

Milchreis gab’s bei uns in Kindertagen öfter und ich liebe ihn noch heute. Er muss cremig sein, dabei aber noch eine körnige Konsistenz bewahrt haben. Und er muss nach Vanille und Zimt duften und ein leicht zitroniges Nebenaroma mitbringen. So bereite ich ihn zu:

Zutaten:

  • 1 Liter zimmerwarme Vollmilch
  • 260 g rundkörniger Milchreis
  • 6 EL Zucker
  • 1 Prise Salz
  • anderthalb Esslöffel gute Butter
  • 1 Vanilleschote (Zur Not geht auch 2 Pck. Vanillezucker oder 1 TL Vanilleextrakt)
  • 1/2 Stange Zimt
  • 1 TL abgeriebene Zitronenschale (Bio-Zitrone)
  • 1/2 TL frischer Zitronensaft

Bei der Zubereitung kommt es darauf an, den Milchreis nicht aus dem Auge zu lassen, ggf. die Herdtemperatur nachzuregeln und regelmäßig vorsichtig umzurühren. Nichts darf anbrennen oder anhängen! Auch die Mengenangaben müssen genau beachtet werden. Ein meditatives Küchenerlebnis. Ich mach’s so:

  1. Die Vanilleschote der Länge nach aufschlitzen und ausschaben. Das Mark und die Schote beiseite stellen.
  2. In einem großen Topf zunächst auf kleiner Flamme die Butter schmelzen und mit einer Prise Salz würzen.
  3. Jetzt den trockenen Milchreis für ca. 1 Minute in der Butter anschwitzen, dann
  4. die zimmerwarme Milch angießen, Zucker, Zimtstange, Vanillemark und die Vanilleschotenhälften beigeben.
  5. Alles unter vorsichtigem Rühren mit einem Holzlöffel einmal aufkochen lassen und die Temperatur der Herdplatte auf die kleinstmögliche Stufe reduzieren.
  6. Das Ganze köchelt jetzt bei geschlossenem Deckel für die nächsten 30 Minuten so vor sich dahin. Es ist ausschlaggebend, dass die Temperatur nicht zu heiß ist – die Milch sollte gerade noch so köcheln – nichts darf größere Blasen schlagen. Nimm den Topf zur Not einen Moment vom Feuer, falls es zu stark kocht.
  7. Sichtkontrolle alle 10 Minuten, nach den ersten 10 Minuten vorsichtig umrühren und falls es Not tut, auch die anderen Male!
  8. Nach 20 Minuten die Sichtkontrollen auf 5 Minuten verkürzen – jetzt hat der Reis bereits eine festere Konsistenz und droht schnell anzubrennen!
  9. Nach 25 Minuten gebe ich den Zitronenabrieb bei und rühre ihn vorsichtig unter den Reis.
  10. Nach 30 Minuten sollte der Reis weich und durchgequollen sein. Jetzt noch 5 Minuten ohne Hitze nachquellen lassen!
  11. Nun rühre ich vorsichtig den Zitronensaft ein und verteile den Milchreis auf vorgewärmten Tellern.
  12. Zu guter Letzt bestreue ich den Milchreis noch mit je einem EL Zimtzucker pro Teller und serviere ihn warm.

Dazu passen alle Arten von eingelegtem Obst, Apfelkompott oder rote Grütze. Ich persönlich favorisiere das Letztgenannte, eingelegte Pfirsiche oder Aprikosen, kalt über dem Reis verteilt. Reste schmecken auch anderntags noch kalt als kleine Zwischenmalzeit.

Guten Appetit!

[zurück zu den Rezepten]


Du magst meine Rezepte und verwendest sie gerne? Dann freue ich mich über einen kleinen Obolus für meine Zutaten-Kasse. Besten Dank!  🙂

Handkäs‘ mit Musik deluxe

Handkäs mit Musik deluxe www.mbergmann-sh.de

Entweder man liebt ihn – oder man hasst ihn. Laut Internet zählt er sogar zu den am meisten verabscheuten deutschen Gerichten: Handkäs‘ mit Musik.

Ich als Wahl-Heidelberger Schleswig-Holsteiner mit hessischen Wurzeln liebe ihn natürlich – aber er muss gut gemacht sein. Unerlässlich dazu sind guter Apfelsaft, Apfelessig, Apfelwein, ein ordentliches Kontingent an frischen Zwiebeln – und natürlich ein guter Handkäs‘. Gerüchteweise kann man ja auch Harzer Käse nehmen – aber das ist nicht das Selbe!

Als ich noch in einen Fahrradanhänger gepasst habe, hat mein Opa, ein erklärter Veganer mit einer Vorliebe für Apfelwein, Schlachtplatte und Leberwurst, mich oft in so ein Gefährt hinter seinem Moped gesetzt und Ausflüge zu diversen hessischen Gartenwirtschaften und Äbbelwoi-Kneipen unternommen, wo es guten Handkäs‘ und das beste Stöffche gab. Ich habe also beides quasi mit der Muttermilch aufgesogen. Mir macht keiner was vor. 🙂

Beim Handkäs‘ ist die Qualität entscheidend – er muss echt sein. Über den Reifegrad lässt sich streiten. Ich bevorzuge ihn gut durchgereift, höchstens mit einem minimalen Restkern. Hier ist mein Rezept:

An Zutaten brauchst du für ca. 2 Personen:

  • 4 frische, scharfe Zwiebeln (rot und weiß gemischt)
  • 3 Frühlingszwiebeln
  • 12 EL Apfelessig
  • 12 EL Apfelsaft
  • 8 EL Apfelwein
  • 4 EL weiches Wasser
  • 6 EL Rapsöl
  • Pfeffer und Salz aus der Mühle
  • 1 TL Kümmelsamen
  • 1/2 TL Fenchelsaat
  • 400 g Handkäs‘ (Original, 8 Stück)
  • 1/2 Bd. Petersilie (zum Bestreuen)

Die Zubereitung:

  1. Am Besten fängst du frühmorgens mit der Zubereitung an: Nimm den Handkäs‘ aus dem Kühlschrank! Bei Zimmertemperatur nimmt er die Aromen der „Musik“ am besten auf.
  2. Verrühre alle flüssige Zutaten in einer Schüssel und schmecke sie mit Salz und Pfeffer aus der Mühle ab. Die „Musik“ (die Marinade) muss „apfelig“, fruchtig, leicht süß-säuerlich schmecken. Bessere ggf. mit einem kleinen Schluck Apfelsaft nach.
  3. Ziehe die Zwiebeln ab und hacke sie in kleine Würfel. Schneide die Frühlingszwiebeln in feine Ringe und gib beides zur Marinade.
  4. Zermahle die Hälfte des Kümmels und den Fenchel grob im Mörser und rühre alles unter die Marinade. Kümmel und Fenchel helfen dabei, die Nebenwirkungen der „Musik“ nicht zu doll ausarten zu lassen.
  5. Gib einen Teil der Marinade in ein dicht schließendes Gefäß, der Boden soll dabei bedeckt sein. Wähle das Gefäß so, das acht Stück Handkäs‘ bequem nebeneinander passen, ohne sich zu berühren.
  6. Lege deine Handkäs‘ in das Gefäß, bedecke sie mit dem Rest der „Musik“, streue den Rest des Kümmels darüber und verschließe das Gefäß.
  7. In vielen Rezepten liest man immer wieder etwas von Ziehzeiten von zwei Stunden Dauer – das ist Blödfug. Ein guter Handkäs‘ soll 24 bis 36 Stunden (nicht länger, sonst werden die Zwiebeln bitter!) ziehen. Die ersten 8 Stunden darf das – auch im Sommer – gern bei Zimmertemperatur sein.
  8. Wende das Gefäß nach 8 Stunden, damit der Handkäs‘ gleichmäßig von allen Seiten Marinade abbekommt. Dreh es dann wieder um und lagere es bis zum Verzehr am nächsten Tag kühl, z.B. in der Speisekammer oder im Gemüsefach des Kühlschranks.

Servieren:

Nimm den Handkäs‘ eine Stunde vor dem Verzehr aus der Kühlung und nimm den Deckel vom Gefäß runter, damit sich die Aromen gut entfalten können!

Zum Handkäs‘ passt am besten ein Stück kräftiges, gern selbstgebackenes Misch- oder Roggenbrot (z.B. dieses hier), das dick gebuttert sein muss. Eine schöne, zusätzliche Geschmacksnote und das optische i-Tüpfelchen erhältst du durch Aufstreuen von gewaschener, frisch gehackter Petersilie.

Guten Appetit!

 

[zurück zu den Rezepten]


Du findest meine Rezepte gut und verwendest sie gerne? Dann freue ich mich über einen kleinen Obolus für meine Zutaten-Kasse. Besten Dank!  🙂

C++ für einsteiger - ein Basis-Tutorial www.mbergmann-sh.de

C++ Tutorial – Ausdrücke und Anweisungen

„Ein Programm ist eigentlich nichts weiter als eine Folge von Befehlen, die nacheinander ausgeführt werden. Mächtig wird ein Programm erst dadurch, dass es in Abhängigkeit von einer Bedingung entscheiden kann, ob der eine oder ein anderer Anweisungsblock ausgeführt werden soll.“

In diesem Kapitel meines C/C++ Tutorials lernst du,

  • was Anweisungen sind,
  •  was Blöcke sind,
  •  was Ausdrücke sind,
  •  wie man auf der Basis von Bedingungen den Code verzweigt,
  •  was Wahrheitswerte sind und wie man darauf reagiert.

Anweisungen

 In C++ steuern Anweisungen die Reihenfolge der Ausführung, werten Ausdrücke aus oder bewirken nichts (die Leeranweisung). Alle C++-Anweisungen enden mit einem Semikolon (;), auch die Leeranweisung, die nur aus einem Semikolon besteht.

 Eine häufig gebrauchte einfache Anweisung ist die Zuweisung:

x = a + b;

 Diese Anweisung bedeutet im Gegensatz zur Algebra nicht x gleich a + b, sondern ist wie folgt zu interpretieren: »Weise den Wert der Summe aus a und b an x zu.« Auch wenn diese Anweisung zwei Dinge bewirkt, handelt es sich um eine einzige Anweisung und hat daher nur ein Semikolon. Der Zuweisungsoperator nimmt die Zuweisung des auf der rechten Seite stehenden Ausdrucks an die linke Seite vor.

Whitespace

 Leerzeichen gehören zusammen mit Tabulatoren und den Zeilenvorschüben zu den sogenannten Whitespace-Zeichen. Sie werden im allgemeinen in den Anweisungen ignoriert. Die oben behandelte Zuweisung lässt sich auch wie folgt schreiben:

x=a+b;

 oder

x                        =a
+          b         ;

 Die zweite Variante ist zwar zulässig, aber kompletter Blödsinn. Durch Whitespace- Zeichen sollen Programme leichter zu lesen und zu warten sein. Man kann damit aber auch einen unleserlichen Code produzieren. C++ stellt die Möglichkeiten bereit, für den sinnvollen Einsatz ist der Programmierer verantwortlich.

 Whitespace-Zeichen (Leerzeichen, Tabulatoren und Zeilenvorschübe) sind nicht sichtbar. Werden diese Zeichen gedruckt, bleibt das Papier weiß (white).

Anweisungsblöcke und Verbundanweisungen

 An allen Stellen, wo eine einzelne Anweisung stehen kann, ist auch eine Verbundanweisung (auch Block genannt) zulässig. Ein Block beginnt mit einer öffnenden geschweiften Klammer ({) und endet mit einer schließenden geschweiften Klammer (}).

 In einem Block ist zwar jede Anweisung mit einem Semikolon abzuschließen, der Block selbst endet aber nicht mit einem Semikolon. Dazu ein Beispiel:

{
     temp = a;
     a = b;
     b = temp;
}

 Dieser Codeblock tauscht die Werte der Variablen in a und b aus.

Ausdrücke

 Alles, was zu einem Wert ausgewertet werden kann, nennt man in C++ einen Ausdruck . Von einem Ausdruck sagt man, dass er einen Wert zurückliefert. Demzufolge ist die Anweisung 3 + 2;, die den Wert 5 zurückliefert, ein Ausdruck. Alle Ausdrücke sind auch Anweisungen.

 Die Unzahl der Codeabschnitte, die sich als Ausdruck entpuppen, mag dich vielleicht überraschen. Hier drei Beispiele:

3.2                   // liefert den Wert 3.2
PI                    // float-Konstante, die den Wert 3.14 zurückgibt
SekundenProMinute     // int-Konstante, die 60 liefert

 Vorausgesetzt, daß PI eine Konstante mit dem Wert 3.14 und SekundenProMinute eine Konstante mit dem Wert 60 ist, stellen alle drei Anweisungen gültige Ausdrücke dar.

 Der Ausdruck

x = a + b;

 addiert nicht nur a und b und weist das Ergebnis an x zu, sondern liefert auch den Wert dieser Zuweisung (den Wert in x). Daher ist diese Anweisung ebenfalls ein Ausdruck und kann somit auch auf der rechten Seite eines Zuweisungsoperators stehen:

y = x = a + b;

Diese Zeile wird in der folgenden Reihenfolge ausgewertet:

 Addiere a zu b.

Weise das Ergebnis des Ausdrucks a + b an x zu.

Weise das Ergebnis des Zuweisungsausdrucks x = a + b an y zu.

 Wenn a, b, x und y ganze Zahlen sind, a den Wert 2 und b den Wert 5 hat, enthält sowohl x als auch y nach Ausführung dieser Anweisung den Wert 7.

Das Listing „expressions.cpp“ demonstriert die Auswertung komplexer Ausdrücke:

// Listing: expressions.cpp
// demonstriert die Auswertung
// komplexer Ausdrücke
#include <iostream>

using namespace std;

int main(void)
{
  int a=0, b=0, x=0, y=35;
  
  cout << "a: " << a << " b: " << b;
  cout << " x: " << x << " y: " << y << endl;
  
  a = 9;
  b = 7;
  y = x = a+b;
  cout << "a: " << a << " b: " << b;
  cout << " x: " << x << " y: " << y << endl;
  
  return 0;
}

Ausgabe:

a: 0 b: 0 x: 0 y: 35
a: 9 b: 7 x: 16 y: 16

Analyse:

  • Zeile 10 deklariert und initialisiert die vier Variablen.
  • Die Ausgabe ihrer Werte erfolgt in den Zeilen 12 und 13.
  • Zeile 15 weist den Wert 9 an die Variable a zu.
  • Zeile 16 weist den Wert 7 an die Variable b zu.
  • Zeile 17 summiert die Werte von a und b und weist das Ergebnis x zu. Dieser Ausdruck (x = a + b) ergibt einen Wert (die Summe aus a und b), der wiederum y zugewiesen wird.

Operatoren

 Ein Operator ist ein Symbol, das den Compiler zur Ausführung einer Aktion veranlasst. Operatoren verarbeiten Operanden, und in C++ sind alle Operanden Ausdrücke. In C++ gibt es mehrere Arten von Operatoren. Zwei Arten von Operatoren sind:

  •  Zuweisungsoperatoren
  •  mathematische Operatoren

Zuweisungsoperator

 Der Zuweisungsoperator (=) bewirkt, dass der Operand auf der linken Seite des Operators den Wert von der rechten Seite des Operators erhält. Der Ausdruck

x = a + b;

 weist dem Operanden x den Wert zu, der als Ergebnis der Addition von a und b entsteht.

 Einen Operanden, der auf der linken Seite eines Zuweisungsoperators zulässig ist, bezeichnet man als L-Wert (linker Wert). Ein entsprechender Operand auf der rechten Seite heißt R-Wert.

 Konstanten sind R-Werte und können nicht als L-Werte vorkommen. Demzufolge ist die Anweisung

x = 35;    // OK

 zulässig, während die Anweisung

35 = x;    // Fehler, kein L-Wert!

 nicht erlaubt ist.

 Ein L-Wert ist ein Operand, der auf der linken Seite eines Ausdrucks stehen kann. Als R-Wert bezeichnet man einen Operanden, der auf der rechten Seite eines Ausdrucks vorkommen kann. Während alle L-Werte auch als R-Werte zulässig sind, dürfen nicht alle R-Werte auch als L-Werte verwendet werden. Ein Literal ist zum Beispiel ein R- Wert, der nicht als L-Wert erlaubt ist. Demzufolge kann man x = 5; schreiben, 5 = x; jedoch nicht (x kann ein R- und eine L-Wert sein, 5 hingegen ist nur ein R-Wert).

Mathematische Operatoren

C/C++++ kennt die fünf mathematischen Operatoren Addition (+), Subtraktion (-), Multiplikation (*), Division (/) und Modulo-Division (%).

Die zwei Grundrechenarten Addition und Subtraktion arbeiten wie gewohnt, wenn es auch bei der Subtraktion mit vorzeichenlosen Ganzzahlen zu überraschenden Ergebnissen kommen kann, falls das Ergebnis eigentlich negativ ist. Einen Eindruck davon hast du ja schon  erhalten, als der Variablenüberlauf beschrieben wurde. Im Listing „suboverflow.cpp“ kannst du nachvollziehen was passiert, wenn du eine große Zahl eines unsigned
-Typs von einer kleinen Zahl eines unsigned-Typs subtrahierst:

// Listing: suboverflow.cpp
// Subtraktion und Integer-Überlauf
#include <iostream>

using namespace std;

int main(void)
{
  unsigned int difference;
  unsigned int bigNumber = 100;
  unsigned int smallNumber = 50;
  
  // kein Überlauf
  difference = bigNumber - smallNumber;
  cout << "Die Differenz betraegt: " << difference;
  
  // Überlauf
  difference = smallNumber - bigNumber;
  cout << "\nJetzt betraegt die Differenz: " << difference <<endl; 
  
  return 0;
}

Ausgabe:

Die Differenz betraegt: 50
Jetzt betraegt die Differenz: 4294967246

Analyse:

  • Zeile 14 ruft den Subtraktions-Operator auf, und das Ergebnis, das ganz unseren Erwartungen entspricht, wird in Zeile 15 ausgegeben.
  • Zeile 18 ruft den Subtraktions- Operator erneut auf. Diesmal wird jedoch eine große unsigned-Zahl von einer kleinen unsigned-Zahl subtrahiert. Das Ergebnis wäre eigentlich negativ, doch da es als unsigned-Zahl ausgewertet (und ausgegeben) wird, ist das Ergebnis ein Überlauf. Auf dieses Problem wird noch genauer eingegangen, wenn wir über Operator- Vorrang sprechen.

Integer-Division und Modulo

 Die Integer-Division unterscheidet sich etwas von der gewohnten Division. Um genau zu sein: Integer-Division entspricht dem, was du in der zweiten Klasse gelernt habst. Dividiert man 21 durch 4 und betrachtet das Ganze als Integer-Division (wie in der Grundschule mit sieben Jahren) ist das Ergebnis 5 Rest 1.

 Um den Rest zu erhalten, bedient man sich des Modulo-Operators (%) und berechnen 21 modulus 4 (21 % 4) mit dem Ergebnis 1. Der Modulo-Operator gibt den Rest einer Ganzzahldivision zurück.

 Die Berechnung des Modulus kann recht nützlich sein, wenn du zum Beispiel eine Anweisung bei jeder zehnten Aktion drucken willst. Jede Zahl, deren Modulus 0 ergibt, wenn du deren Modulus mit 10 berechnest, ist ein Mehrfaches von 10. So ist 1 %
10
gleich 1, 2 % 10 gleich 2 und so weiter, bis 10 % 10 den Wert 0 ergibt. 11 % 10 ist erneut 1 und dieses Muster setzt sich bis zum nächsten Mehrfachen von 10, das heißt 20, fort. Diese Technik kommt zur Anwendung, wenn wir später im Detail auf die Schleifen zu sprechen kommen.

Frage: Wenn ich 5 durch 3 teile, erhalte ich 1. Was mache ich falsch?

Antwort: Wenn du einen Integerwert durch einen Anderen teilst, dann erhältst du auch einen Integerwert als Ergebnis. Und im Falle von 5/3 ist dies eben 1.

Um als Rückgabewert eine Bruchzahl zu erhalten, musst du Fließkommazahlen vom Typ float oder double verwenden.

5,0/3,0 ergibt als Bruchzahl: 1,66667

Falls deine Methode Integer als Parameter übernimmt, musst du diese in Fließkommazahlen von Typ float oder double umwandeln.

Um den Typ der Variablen zu ändern, musst du eine Typumwandlung (cast) vornehmen. Damit teilst du dem Compiler im wesentlichen mit, dass du weißt was du tust –  und das solltest du auch, denn der Compiler wird sich deinem Willen nicht widersetzen.

In diesem speziellen Fall teilest du dem Compiler mit: »Ich weiß, du denkst, dies ist ein Integer, aber ich weiß, was ich tue, darum behandle diesen Wert als Fließkommazahl«.

Für die Typumwandlung kannst du noch die alte C-Methode oder den neueren vom ANSI-Standard empfohlenen Operator static_cast verwenden. Ein Beispiel dazu findst du im Listing „typecast.cpp“:

// Listing: typecast.cpp
// Typumwandlung von int nach float
#include <iostream>

using namespace std;

// Funktionsprototypen
void intDiv(int x, int y);
void floatDiv(int x, int y);

// Funktion: void intDiv(int x, int y)
void intDiv(int x, int y)
{
  int z = x / y;
  cout << "z: " << z << endl;
}

// Funktion: void floatDiv(int x, int y)
void floatDiv(int x, int y)
{
  float a = (float)x;                 // Typecast: alter Stil
  float b = static_cast<float>(y);    // vorzuziehender Stil
  float c = a / b;

  cout << "c: " << c << endl; 
}

int main(void)
{
  int x = 5, y = 3;
  
  intDiv(x,y);
  floatDiv(x,y);
  
  return 0;
}

Ausgabe:

z: 1
c: 1.66667

Analyse:

  • Die Zeilen 8 und 9 definieren die Funktionsprototypen der beiden selbst definierten Funktionen.
  • Die Zeilen 12 bis 16 definieren die Funktion (Methode) intDiv(). Diese druckt einen Integerwert aus.
  • Die Zeilen 19 bis 26 definieren die Funktion(Methode) floatDiv(). Diese druckt einen Gleitkommawert (float) aus.
  • In Zeile 21 wird der Variablen x per Typecast im alten Stil der Datentyp float zugeordnet und deren Wert der Gleitkommavariablen a übergeben.
  • In Zeile 22 wird der Variablen y per Typecast im neuen Stil der Datentyp float zugeordnet und deren Wert der Gleitkommavariablen b übergeben.
  • In Zeile 23 wird der Gleitkommavariablen c das Ergebnis der Berechnung aus a geteilt durch b übergeben.
  • Zeile 30 deklariert zwei lokale Integer-Variablen innerhalb von main().
  • Als Parameter werden in Zeile 32 intDiv und in Zeile 33 floatDiv übergeben.

Zusammengesetzte Operatoren

 Häufig muss man einen Wert zu einer Variablen addieren und dann das Ergebnis an dieselbe Variable zuweisen. Im folgenden Beispiel wird der Wert der Variablen meinAlterum 2 erhöht:

int meinAlter = 5;
int temp;
temp = meinAlter + 2;  // 5 und 2 addieren und in temp ablegen
meinAlter = temp;      // nach meinAlter zurückschreiben

Dieses Verfahren ist allerdings recht umständlich. In C++ kann man dieselbe Variable auf beiden Seiten des Zuweisungsoperators einsetzen und das obige Codefragment eleganter formulieren:

meinAlter = meinAlter +2;

 In der Algebra wäre dieser Ausdruck unzulässig, während ihn C++ als »addiere 2 zum Wert in meinAlter, und weise das Ergebnis an meinAlter zu« interpretiert.

 Das Ganze läßt sich noch einfacher schreiben, auch wenn es im ersten Moment etwas unverständlich aussieht:

meinAlter += 2;

 Der zusammengesetzte Additionsoperator (+=) addiert den R-Wert zum L-Wert und führt dann eine erneute Zuweisung des Ergebnisses an den L-Wert durch. Der Operator heißt »Plus-Gleich«. Die Anweisung ist dann als »meinAlter plus gleich 2« zu lesen. Wenn meinAlter zu Beginn den Wert 4 enthält, steht nach Ausführung dieser Anweisung der Wert 6 in meinAlter.

 Weitere zusammengesetzte Operatoren gibt es für Subtraktion (-=), Division (/=), Multiplikation (*=) und Modulo-Operation (%=).

Inkrementieren und Dekrementieren

 Am häufigsten hat man den Wert 1 zu einer Variablen zu addieren (bzw. zu subtrahieren). In C++ spricht man beim Erhöhen des Wertes um 1 von Inkrementieren und beim Verringern um 1 von Dekrementieren. Für diese Operationen stehen spezielle Operatoren bereit.

 Der Inkrement-Operator (++) erhöht den Wert der Variablen um 1, der Dekrement- Operator (--) verringert ihn um 1. Möchte man die Variable C inkrementieren, schreibt man die folgende Anweisung:

 

C++;      // Beginne mit C und inkrementiere den enthaltenen Wert.

 Diese Anweisung ist äquivalent zur ausführlicher geschriebenen Anweisung

C = C + 1;

 Das gleiche Ergebnis liefert die verkürzte Darstellung

C += 1;

 

Präfix und Postfix

 Sowohl der Inkrement-Operator (++) als auch der Dekrement-Operator (--) existieren in zwei Spielarten: Präfix und Postfix. Die Präfix-Version wird vor den Variablennamen geschrieben (++mein Alter), die Postfix-Version danach (mein Alter++).

 In einer einfachen Anweisung spielt es keine Rolle, welche Version man verwendet. In einer komplexen Anweisung ist es allerdings entscheidend, ob man eine Variable zuerst inkrementiert (oder dekrementiert) und dann das Ergebnis einer anderen Variablen zuweist. Der Präfix-Operator wird vor der Zuweisung ausgewertet, der Postfix- Operator nach der Zuweisung.

 Die Semantik von Präfix bedeutet: Inkrementiere den Wert und übertrage ihn dann. Die Bedeutung des Postfix-Operators lautet dagegen: Übertrage den Wert und inkrementiere das Original.

 Das folgende Beispiel verdeutlicht diese Vorgänge. Es sei angenommen, dass x eine ganze Zahl mit dem Wert 5 ist. Bei der Anweisung

int a = ++x;

 inkrementiert der Compiler den Wert in x (und macht ihn damit zu 6), holt dann diesen Wert und weist ihn an a zu. Daher ist jetzt sowohl a als auch x gleich 6.

 Schreibt man anschließend

int b = x++;

 weist man den Compiler an, den Wert in x (6) zu holen, ihn an b zuzuweisen und dann den Wert von x zu inkrementieren. Demzufolge ist nun b gleich 6, während x gleich 7 ist. Das Listing „increment.cpp“ zeigt Verwendung und Besonderheiten beider Typen:

 

// Listing: increment.cpp
// demonstriert die Verwendung der
// Inkrement- u. Dekrement-Operatoren
// in Prä- und Postfix-Notation
#include <iostream>

using namespace std;

int main(void)
{
  int meinAlter = 39;      // initialisiert zwei Integer-Variablen
  int deinAlter = 39;
  
  cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
  cout << "Du bist: " << deinAlter << " Jahre alt\n";
  
  meinAlter++;       // Postfix-Inkrement
  ++deinAlter;       // Präfix-Inkrement
  
  cout << "Ein Jahr ist vergangen...\n";  
  cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
  cout << "Du bist: " << deinAlter << " Jahre alt\n";
  cout << "Noch ein Jahr ist vergangen\n";
  cout << "Ich bin: " << meinAlter++ << " Jahre alt.\n";
  cout << "Du bist: " << ++deinAlter << " Jahre alt\n";
  cout << "Und noch einmal ausgeben.\n";
  cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
  cout << "Du bist: " << deinAlter << " Jahre alt\n";
  
  return 0;
}

Ausgabe:

Ich bin: 39 Jahre alt.
Du bist: 39 Jahre alt
Ein Jahr ist vergangen...
Ich bin: 40 Jahre alt.
Du bist: 40 Jahre alt
Noch ein Jahr ist vergangen
Ich bin: 40 Jahre alt.
Du bist: 41 Jahre alt
Und noch einmal ausgeben.
Ich bin: 41 Jahre alt.
Du bist: 41 Jahre alt

Analyse:

  • Die Zeilen 11 und 12 deklarieren zwei Integer-Variablen und initialisieren sie jeweils mit dem Wert 39. Die Ausgabe der Werte findet in den Zeilen 14 und 15 statt.
  • Zeile 17 inkrementiert mein Alter mit dem Postfix-Operator, und Zeile 18 inkrementiert dein Alter mit dem Präfix-Operator. Die Ergebnisse werden in den Zeilen 21 und 22 ausgegeben und sind beide identisch (beide 40).
  • In Zeile 24 wird mein Alter mit dem Postfix-Operator als Teil einer Ausgabeanweisung inkrementiert. Durch den Postfix-Operator erfolgt die Inkrementierung nach der Ausgabe, und es erscheint auch hier in der Ausgabe der Wert 40. Im Gegensatz dazu inkrementiert Zeile 25 die Variable dein Alter mit dem Präfix-Operator. Das Inkrementieren findet jetzt vor der Ausgabe statt, und es erscheint der Wert 41 in der Anzeige.
  • Schließlich werden die Werte in den Zeilen 27 und 28 erneut ausgegeben. Da die Inkrement-Anweisung vollständig abgearbeitet ist, lautet der Wert in mein Alter jetzt 41, genau wie der Wert in dein Alter.

Rangfolge der Operatoren

 Welche Operation wird in der komplexen Anweisung

x = 5 + 3 * 8;

 zuerst ausgeführt, die Addition oder die Multiplikation? Führt man die Addition zuerst aus, lautet das Ergebnis 8 * 8 oder 64. Bei vorrangiger Multiplikation heißt das Ergebnis 5 + 24 oder 29.

 Jeder Operator besitzt einen festgelegten Vorrang. Eine vollständige Liste der Operatorprioritäten findest du in der Tabelle „Priorität von Operatoren“. Die Multiplikation hat gegenüber der Addition höhere Priorität. Der Wert des Ausdrucks ist demnach 29.

 Wenn zwei mathematische Operatoren gleichrangig sind, werden sie in der Reihenfolge von links nach rechts ausgeführt. Demzufolge wird in

x = 5 + 3 + 8 * 9 + 6 * 4;

 die Multiplikation zuerst, von links nach rechts, ausgeführt. Es ergeben sich die beiden Terme 8*9 = 72 und 6*4 = 24. Damit wird der Ausdruck zu

x = 5 + 3 + 72 + 24;

 Nun kommt noch die Addition von links nach rechts 5 + 3 = 8, 8 + 72 = 80, 80 + 24 = 104.

 Die Rangfolge ist unbedingt zu beachten. Bestimmte Operatoren wie der Zuweisungsoperator werden von rechts nach links ausgeführt. Was passiert nun, wenn die Rangfolge nicht deinen Vorstellungen entspricht? Sieh dir dazu folgenden Ausdruck an:

SekundenGsamt = MinutenNachdenken + MinutenTippen * 60

 In diesem Ausdruck soll nicht MinutenTippen zuerst mit 60 multipliziert und dann zu MinutenNachdenken addiert werden. Beabsichtigt ist, zuerst die Addition der beiden Variablen durchzuführen, um die Summe der Minuten zu ermitteln und anschließend diese Zahl mit 60 zu multiplizieren, um die Anzahl der Sekunden zu berechnen.

 In diesem Fall setzt man Klammern, um die Rangfolge zu ändern. Elemente in Klammern werden mit einer höheren Priorität ausgeführt als irgendein mathematischer Operator. Das gewünschte Ergebnis erhält man also mit

SekundenGesamt = (MinutenNachdenken + MinutenTippen) * 60

Verschachtelte Klammern

 Bei komplexen Ausdrücken sind eventuell Klammern zu verschachteln. Beispielsweise ist die Anzahl der Sekunden zu berechnen und danach die Anzahl der Mitarbeiter, bevor die Multiplikation der Mitarbeiter mit den Sekunden erfolgt (um etwa die gesamte Arbeitszeit in Sekunden zu erhalten):

PersonenSekundenGesamt = ( ( (MinutenNachdenken + MinutenTippen) * 60)
* (Mitarbeiter + Beurlaubt) )

 Dieser zusammengesetzte Ausdruck ist von innen nach außen zu lesen. Zuerst erfolgt die Addition von MinutenNachdenken und MinutenTippen, da dieser Ausdruck in den innersten Klammern steht. Anschließend wird diese Summe mit 60 multipliziert. Es schließt sich die Addition von Mitarbeiter und Beurlaubt an. Schließlich wird die berechnete Mitarbeiterzahl mit der Gesamtzahl der Sekunden multipliziert.

 Dieses Beispiel weist auf einen wichtigen Punkt hin: Für einen Computer ist ein solcher Ausdruck leicht zu interpretieren, für einen Menschen ist er dagegen nur schwer zu lesen, zu verstehen oder zu modifizieren. Der gleiche Ausdruck in einer anderen Form mit Variablen zur Zwischenspeicherung sieht folgendermaßen aus:

MinutenGesamt = MinutenNachdenken + MinutenTippen;
SekundenGesamt = MinutenGesamt * 60;
MitarbeiterGesamt = Mitarbeiter + Beurlaubt;
PersonenSekundenGesamt = MitarbeiterGesamt * SekundenGesamt;

 Dieses Beispiel verwendet zwar temporäre Variablen und erfordert mehr Schreibarbeit als das vorherige Beispiel, ist aber leichter zu verstehen. Füge am Beginn einen Kommentar ein, um die Absichten hinter diesem Codeabschnitt zu erläutern, und ändere die 60 in eine symbolische Konstante. Damit erhältst du einen Code, der leicht zu verstehen und zu warten ist.

Tipps zu Operatoren und verschachtelten Klammern:
  • Denke daran, dass Ausdrücke einen Wert haben.
  • Verwende den Präfix-Operator (++Variable), um die Variable zu inkrementieren oder zu dekrementieren, bevor sie in einem Ausdruck verwendet wird.
  • Verwende den Postfix-Operator (Variable++), um die Variable zu inkrementieren oder zu dekrementieren, nachdem sie verwendet wurde.
  • Verwende Klammern, um die Rangfolge bei der Abarbeitung der Operatoren zu ändern.
  • Verschachtele deine Ausdrücke nicht zu sehr, da sie sonst schwer verständlich werden und die Wartung erschwert wird.

Wahrheitswerte

 In früheren C++-Versionen wurden Wahrheitswerte durch Integerzahlen dargestellt. Die neueren ANSI-Standards haben einen neuen Datentyp eingeführt: bool. Dieser Typ hat zwei mögliche Werte false (unwahr) oder true (wahr).

 Jeder Ausdruck kann auf seinen Wahrheitsgehalt ausgewertet werden. Ausdrücke, die mathematisch gesehen eine Null ergeben, liefern false zurück, alle anderen liefern true zurück.

Vergleichsoperatoren

 Mit den Vergleichsoperatoren (oder relationalen Operatoren) ermittelt man, ob zwei Zahlen gleich sind, oder ob eine größer oder kleiner als die andere ist. Jeder Vergleichsausdruck wird entweder zu true oder zu false ausgewertet. Zu den Vergleichsoperatoren gehören: Gleich (==), Kleiner als (<), Größer als (>), Kleiner oder Gleich (<=), Größer oder Gleich (>=) und Ungleich (!=).  In der Tabelle „Bool’sche Vergleichsoperatoren“ sind die Vergleichsoperatoren zusammengefasst.

Ein Beispiel: Hat die Integer-Variable meinAlter den Wert 39 und die Integer-Variable deinAlter den Wert 40, kann man mit dem relationalen Operator »Gleich« prüfen, ob beide gleich sind:

meinAlter == deinAlter;  // ist der Wert in meinAlter der gleiche wie 
                         // in deinAlter?

 Dieser Ausdruck ergibt 0 oder false, da die Variablen nicht gleich sind. Der Ausdruck

meinAlter > deinAlter;  // ist meinAlter größer als deinAlter?

 liefert 0 oder false.

Beachte: Viele Neueinsteiger in die C++-Programmierung verwechseln den Zuweisungsoperator (=) mit dem Gleichheitsoperator (==). Das kann zu bösartigen Fehlern im Programm führen.

Das Listing „bools.cpp“ zeigt die Verwendung des Gleichheitsoperators:

// Listing: bools.cpp
// Veranschaulichung von Wahrheitswerten
#include <iostream>

using namespace std;

int main(void)
{
  int meinHund = 1, deinHund = 2, keinHund = meinHund;
  
  cout << "TRUE  = " << true << endl;
  cout << "FALSE = " << false << endl;
  
  if (meinHund == keinHund)
    cout << "TRUE: Ich habe gar keinen Hund." << endl;
  else
    cout << "FALSE: Mein Hund heisst Larry." << endl;
  
  if (meinHund == deinHund)
    cout << "TRUE: Mein Hund heisst Larry." << endl;
  else
    cout << "FALSE: Dein Hund heisst Schnitzel." << endl;
  
  
  return 0;
}

Ausgabe:

TRUE = 1
FALSE = 0
TRUE: Ich habe gar keinen Hund.
FALSE: Dein Hund heisst Schnitzel.

Analyse:

  • Zeile 9 definiert drei Integer-Variablen, dabei wird dem Wert der ersten Variablen meinHund der Wert der dritten Variablen keinHund zugeordnet.
  • Die Zeilen 11 und 12 geben die Werte für true und false aus Sicht des Compilers aus.
  • Die Zeilen 14 bis 17 ermitteln per if – else Verzweigung unter Verwendung des Gleichheitsoperators, ob ich denn nun einen Hund habe oder nicht. Da die gespeicherten Werte von meinHund und keinHund identisch sind, evaluiert der Ausdruck
       if(meinHund == keinHund)
    zu true.
  • Die Zeilen 19 bis 21 ermitteln per if – else Verzweigung unter Verwendung des Gleichheitsoperators, ob mein Hund genauso heißt wie meiner. Da die gespeicherten Werte von meinHund und deinHund nicht identisch sind, evaluiert der Ausdruck
       if(meinHund == deinHund)
    zu false.

Die if-Anweisung

Wir haben die if-Anweisung im Verlauf des C++ Tutorials bereit mehrfach verwendet, ohne näher auf sie einzugehen. Das holen wir nun nach.

 Normalerweise verläuft der Programmfluss zeilenweise in der Reihenfolge, in der die Anweisungen in Ihrem Quellcode stehen. Mit der if-Anweisung kann man auf eine Bedingung testen (beispielsweise ob zwei Variablen gleich sind) und in Abhängigkeit vom Testergebnis zu unterschiedlichen Teilen des Codes verzweigen.

 Die einfachste Form der if-Anweisung sieht folgendermaßen aus:

if (Ausdruck)
    Anweisung;

 Der Ausdruck in den Klammern kann jeder beliebige Ausdruck sein, er enthält aber in der Regel einen der Vergleichsausdrücke. Wenn der Ausdruck den Wert false liefert, wird die Anweisung übersprungen. Ergibt sich der Wert true, wird die Anweisung ausgeführt. Sehen Sie sich dazu folgendes Beispiel an:

if (grosseZahl > kleineZahl)
    grosseZahl = kleineZahl;

 Dieser Code vergleicht grosseZahl mit kleineZahl. Wenn grosseZahl größer ist, setzt die zweite Zeile den Wert von grosseZahl auf den Wert von kleineZahl.

 Da ein von Klammern eingeschlossener Anweisungsblock einer einzigen Anweisung entspricht, kann die folgende Verzweigung ziemlich umfangreich und mächtig sein:

if (Ausdruck)
{ 
     Anweisung1;
     Anweisung2;
     Anweisung3;
}

 Zur Veranschaulichung ein einfaches Beispiel:

if (grosseZahl > kleineZahl)
{
    grosseZahl = kleineZahl;
    cout <<  "grosseZahl: " << grosseZahl << "\n ";
    cout <<  "kleineZahl: " << kleineZahl << "\n ";
}

 Diesmal wird für den Fall, dass grosseZahl größer ist als kleineZahl, nicht nur grosseZahl
auf den Wert von kleineZahl gesetzt, sondern es wird auch eine Nachricht ausgegeben. Im Listing „branches.cpp“ findest du ein ausführlicheres Beispiel zu der Verzweigung auf der Grundlage von Vergleichsoperatoren.

// Listing: branches.cpp
// zeigt if-Anweisungen in Verbindung
// mit Vergleichsoperatoren
#include <iostream>

using namespace std;

int main(void)
{  
  int hsvScore, bmScore;
  cout << "Gib den Punktestand des Hamburger SV ein: ";
  cin >> hsvScore;

  cout << "\nGib den Punktestand von Bayern Muenchen ein: ";
  cin >> bmScore;

  cout << "\n";

  if (hsvScore > bmScore)
    cout << "Vorwärts HSV!\n";

  if (hsvScore < bmScore)
  {
    cout << "Zieht den Bayern die Lederhosen aus!\n";
    cout << "Tolle Tage in der 2. Liga!\n";
  }

  if (hsvScore == bmScore)
  {
   cout << "Ein Gleichstand? Nee, das kann nicht sein.\n";
   cout << "Gib den richtigen Punktestand der Bayern ein: ";
   cin >> bmScore;

   if (hsvScore > bmScore)
         cout << "Ich wusste es! HSV bringt Fischbroetchen mit!";

   if (bmScore > hsvScore)
         cout << "Ich wusste es! Vorwärts mit Weisswurst!";

   if (bmScore == hsvScore)
         cout << "Wow, es war wirklich ein Gleichstand!";
  }

  cout << "\nDanke fuer die Nachricht.\n";
  
  return 0;  
}

Ausgabe:

bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./branches
Gib den Punktestand des Hamburger SV ein: 33

Gib den Punktestand von Bayern Muenchen ein: 33

Ein Gleichstand? Nee, das kann nicht sein.
Gib den richtigen Punktestand der Bayern ein: 8
Ich wusste es! HSV bringt Fischbroetchen mit!
Danke fuer die Nachricht.

Analyse

Dieses Programm fordert den Anwender auf, den Punktestand für zwei Fußballteams einzugeben. Die Punkte werden in Integer-Variablen abgelegt. Die Zeilen 19, 22 und 28 vergleichen dann diese Variablen mit Hilfe einer if-Anweisung.

 Ist eine Punktzahl höher als die andere, wird eine Nachricht ausgegeben. Liegt ein Punktegleichstand vor, wird der Codeblock von Zeile 29 bis Zeile 42 ausgeführt. Die zweite Punktezahl wird erneut abgefragt und die Punkte abermals verglichen.

 Ist die Punktzahl der Bayern von Anfang an höher als die des HSV, wird die if– Anweisung in Zeile 19 zu false ausgewertet und Zeile 20 nicht aufgerufen. Der Vergleich in Zeile 19 ist demnach true und die Anweisungen in den Zeilen 24 und 25 werden ausgeführt. Anschließend wird die if-Bedingung in Zeile 28 überprüft und das Ergebnis ist folgerichtig (wenn Zeile 19 true ergab) false. Damit wird der ganze Codeblock bis Zeile 43 übersprungen.

 In diesem Beispiel werden trotz einer if-Anweisung mit dem Ergebnis true auch die anderen if-Anweisungen geprüft.

Beachte: Viele Neueinsteiger in die C++-Programmierung machen den Fehler, ein Semikolon an die if-Anweisung anzuhängen:

if(EinWert < 10);
   EinWert = 10;

Die Absicht obigen Codes ist, herauszufinden, ob EinWert kleiner ist als 10 und wenn ja, die Variable auf 10 zu setzen, so dass 10 der Minimalwert für EinWert ist. Bei Ausführung dieses Codefragments wirst du feststellen, dass EinWert immer auf 10 gesetzt wird! Und warum? Die if-Anweisung endet mit dem Semikolon (dem Tu-Nichts-Operator).

Denke daran, dass die Einrückung für den Compiler ohne Belang ist. Dies Fragment hätte korrekterweise auch so geschrieben werden können:

if (EinWert < 10)   // pruefen
;  // tue nichts
EinWert = 10;       // zuweisen

Durch das Entfernen des Semikolons wird die letzte Zeile Teil der if-Anweisung und der Code wird genau das ausführen, was du beabsichtigt hattest.

Einrückungsarten

Im Listing „branches.cpp“ konntest du eine der Möglichkeiten sehen, if-Anweisungen einzurücken. Nichts jedoch ist besser geeignet, einen Glaubenskrieg heraufzubeschwören, als eine Gruppe von Programmierern zu fragen, wie man am besten Anweisungen in Klammern einrückt. Es gibt Dutzende von Möglichkeiten. Die am weitesten verbreiteten drei Möglichkeiten möchte ich dir hier vorstellen:

  •  Die öffnende Klammer steht direkt hinter der Bedingung und die schließende Klammer bündig mit dem if, um den Anweisungsblock zu schließen:
    // 1. Möglichkeit
    if (Ausdruck){
        Anweisungen
    }
  •  Beide Klammern werden bündig mit dem if ausgerichtet und die Anweisungen werden eingerückt:
    // 2. Möglichkeit
    if (Ausdruck)
    {
        Anweisungen
    }
  •  Sowohl die Klammern als auch die Anweisungen werden eingerückt:
    // 3. Möglichkeit
    if (Ausdruck)
        {
        Anweisungen
        }

 In diesem Tutorial habe ich die zweite Alternative gewählt, da ich leichter feststellen kann, wo Anweisungsblöcke anfangen und enden, wenn die Klammern miteinander und mit der Bedingung bündig sind. Aber auch hier gilt: Es ist nicht wichtig, für welche Art der Einrückung du dich entscheidest, solange du konsequent bei der einmal gewählten Variante bleibst.

Die else-Klausel

 Oftmals soll ein Programm bei erfüllter Bedingung (true) den einen Zweig durchlaufen und bei nicht erfüllter Bedingung (false) einen anderen. In Listing 4.5 wollten Sie eine Nachricht (Vorwärts Sox!) ausgeben, wenn die erste Bedingung (RedSoxScore > Yankees
) erfüllt oder true wird, und eine andere Nachricht (Vorwärts Yanks!), wenn der Test false ergibt.

Die bisher gezeigte Form, die zuerst eine Bedingung testet und dann die andere, funktioniert zwar, ist aber etwas umständlich. Das Schlüsselwort else trägt hier zur besseren Lesbarkeit des Codes bei:

if (Ausdruck)
    Anweisung;
else
    Anweisung;

Das Listing „ifelse.cpp“ demonstriert die Verwendung des Schlüsselwortes else:

// Listing: ifelse.cpp
// zeigt die if-Anweisung mit
// der else-Klausel
#include <iostream>

using namespace std;

int main(void)
{
  int firstNumber, secondNumber;
  
  cout << "Bitte eine grosse Zahl eingeben: ";
  cin >> firstNumber;
  cout << "Bitte eine kleinere Zahl eingeben: ";
  cin >> secondNumber;
  
  if (firstNumber > secondNumber)
    cout << "\nDanke!\n";
  else
    cout << "\nDie zweite Zahl ist groesser!\n";

  return 0;
}

Ausgabe:

bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./ifelse
Bitte eine grosse Zahl eingeben: 10
Bitte eine kleinere Zahl eingeben: 12

Die zweite Zahl ist groesser!

Analyse:

Liefert die Bedingung der if-Anweisung in Zeile 17 das Ergebnis true, wird die Anweisung in Zeile 18 ausgeführt. Ergibt sich der Wert false, führt das Programm die Anweisung in Zeile 20 aus. Wenn man die else-Klausel in Zeile 19 entfernt, wird die Anweisung in Zeile 20 unabhängig davon ausgeführt, ob die if-Anweisung true ist oder nicht. Denke daran, dass die if-Anweisung nach Zeile 18 endet. Fehlt die else-Klausel, wäre Zeile 20 einfach die nächste Zeile im Programm.

Statt der einzelnen Anweisungen könntest du auch in geschweifte Klammern eingeschlossen Codeblöcke aufsetzen:

// Listing: ifelseblocks.cpp
// zeigt die if-Anweisung mit
// der else-Klausel, Anweisungen in Codeblocks
#include <iostream>

using namespace std;

int main(void)
{
  int firstNumber, secondNumber;
  
  cout << "Bitte eine grosse Zahl eingeben: ";
  cin >> firstNumber;
  cout << "\nBitte eine kleinere Zahl eingeben: ";
  cin >> secondNumber;
  
  if (firstNumber > secondNumber)
  {
    cout << "\nDanke!\n";
  }
  else
  {
    cout << "\nDie zweite Zahl ist groesser!\n";
  }

  return 0;
}

Beachte die neu hinzugekommenen geschweiften öffnenden und schließenden Klammern in den Zeilen 18, 20, 22 und 24: Jeweils eins dieser Paare bildet einen Codeblock. Tatsächlich musst du sogar Codeblocks verwenden, falls auf eine if-Abfrage mehrere Anweisungen ausgeführt werden sollen.

Syntax: if-Anweisung

Die Syntax einer if-Anweisung sieht wie folgt aus:

Format 1:

if (Ausdruck)
    Anweisung;
nächste_Anweisung;

Liefert die Auswertung von Ausdruck das Ergebnis true, wird die Anweisung ausgeführt und das Programm setzt mit nächste_Anweisung fort. Ergibt der Ausdruck nicht true, wird die Anweisung ignoriert, und das Programm springt sofort zu nächste_Anweisung.

Anweisung steht hierbei für eine einzelne, durch Semikolon abgeschlossene Anweisung oder eine in geschweifte Klammern (Codeblock) eingeschlossene Verbundanweisung.

if (Ausdruck)
    Anweisung1;
else
    Anweisung2; 
nächste_Anweisung;

Ergibt der Ausdruck true, wird Anweisung1 ausgeführt, wenn nicht, kommt die Anweisung2 zur Ausführung. Anschließend fährt das Programm mit nächste_Anweisung fort.

 Beispiel 1

if (EinWert < 10)
   cout << "EinWert ist kleiner als 10");
else
   cout << "EinWert ist nicht kleiner als 10!");
cout << "Fertig." << endl;

Erweiterte if-Anweisungen

In einer if– oder else-Klausel kann jede beliebige Anweisung stehen, sogar eine andere if– oder else-Anweisung. Dadurch lassen sich komplexe if-Anweisungen der folgenden Form erstellen:

if (Ausdruck1)
{
    if (Ausdruck2)
        Anweisung1;
    else
    {
        if (Ausdruck3)
            Anweisung2;
        else
            Anweisung3;
    }
}
else
    Anweisung4;
  • Diese umständliche if-Anweisung sagt aus: »Wenn Ausdruck1 gleich true ist und Ausdruck2 gleich true ist, führe Anweisung1 aus.
  • Wenn Ausdruck1 gleich true, aber Ausdruck2 nicht true ist, dann führe Anweisung2 aus, wenn Ausdruck3 true ist.
  • Wenn Ausdruck1 gleich true, aber Ausdruck2 und Ausdruck3 gleich false sind, führe Anweisung3 aus.
  • Wenn schließlich Ausdruck1 nicht true ist, führe Anweisung4 aus.«

Wie man sieht, können komplexe if-Anweisungen einiges zur Verwirrung beitragen!

 

 


[Inhaltsverzeichnis] | [zurück] | [vorwärts]

C++ für einsteiger - ein Basis-Tutorial www.mbergmann-sh.de

C++ Tutorial – Variablen, Konstanten und Aufzählungstypen

Was ist eine Variable?

Programme müssen auf irgendeine Weise die verwendeten Daten speichern. Variablen und Konstanten bieten verschiedene Möglichkeiten, diese Daten darzustellen und zu manipulieren. In C++ dient eine Variable dazu, Informationen zu speichern. Eine Variable ist eine Stelle im Arbeitsspeicher (Hauptspeicher, RAM) des Computers, in der man einen Wert ablegen und später wieder abrufen kann.

Man kann sich den Arbeitsspeicher als eine Reihe von Fächern vorstellen, die in einer langen Reihe angeordnet sind. Jedes Fach – oder Speicherstelle – ist fortlaufend nummeriert. Diese Nummern bezeichnet man als Speicheradressen oder einfach als Adressen. Eine Variable reserviert ein oder mehrere Fächer, in denen dann ein Wert abgelegt werden kann.

Der Name deiner Variablen (zum Beispiel meineVariable) ist ein Bezeichner für eines dieser Fächer, damit man es leicht finden kann, ohne dessen Speicheradresse zu kennen. Wie die folgende Abbildung zeigt, beginnt unsere Variable meineVariable an der Speicheradresse 103. Je nach Größe (= dem Datentyp) von meineVariable
kann die Variable eine oder mehrere Speicheradressen belegen:

Variable im Hauptspeicher - C++-Kurs für einsteiger www.mbergmann-sh.de

Speicher reservieren

Wenn man in C++ eine Variable definiert, muss man dem Compiler nicht nur deren Namen, sondern auch den Datentyp der Variablen mitteilen – also, ob es sich zum Beispiel um eine Ganzzahl (Integer) oder ein Zeichen (Buchstaben, Ziffern etc.) handelt. Anhand dieser Information weiß der Compiler, um welche Art Variable es sich handelt und wieviel Platz im Speicher für die Aufnahme des Wertes der Variablen zu reservieren ist.

 Jedes »Fach« im Speicher ist ein Byte groß. Wenn die erzeugte Variable vier Bytes benötigt, muss man vier Bytes im Speicher – oder vier Fächer – reservieren. Der Variablentyp (zum Beispiel int für Integer) teilt dem Compiler mit, wie viele Speicherplätze (oder Fächer) für diese Variable benötigt werden.

 Da Computer Werte in Bits und Bytes darstellen und Speicher in Bytes gemessen wird, ist es wichtig, dass du diese Begriffe verstehst und verinnerlichst.

Größe von Integer-Werten

Jeder Variablentyp belegt im Speicher einen bestimmten Bereich, dessen Größe immer gleichbleibend ist, auf verschiedenen Computern aber unterschiedlich groß sein kann. Das heißt, ein Integer-Wert (Datentyp int) nimmt auf der einen Maschine zwei Bytes, auf einer anderen vielleicht vier ein – aber auf ein und demselben Computer ist dieser Platz immer gleich groß.

 Eine Variable vom Typ char (zur Aufnahme von Zeichen) ist gewöhnlich ein Byte lang. Eine Ganzzahl vom Typ short belegt auf den meisten Computern zwei Bytes, eine Ganzzahl vom Typ long ist normalerweise vier Bytes lang, und eine Ganzzahl (ohne das Schlüsselwort short oder long) kann zwei oder vier Bytes einnehmen. Die Größe einer Ganzzahl wird vom Computer (16Bit oder 32Bit oder 64Bit) oder vom Compiler bestimmt. Auf einem 64-Bit-PC mit aktuellem C++ Compiler belegen die Ganzzahlen vier Bytes. Dieses Tutorial geht davon aus, dass Ganzzahlen vier Bytes groß sind. Das muss bei dir jedoch nicht so sein. Mit dem Listing „sizes.cpp“ lässt sich die genaue Größe der Typen auf Ihrem Computer bestimmen.

// Listing: sizes.cpp
#include <iostream>

using namespace std;

int main(void)
{
    cout << "Groesse eines int:\t\t"       << sizeof(int)        << " Bytes.\n";
    cout << "Groesse eines short int:\t"   << sizeof(short int)  << " Bytes.\n";
    cout << "Groesse eines long int:\t"    << sizeof(long int)   << " Bytes.\n";
    cout << "Groesse eines char:\t\t"      << sizeof(char)       << " Bytes.\n";
    cout << "Groesse eines float:\t\t"     << sizeof(float)      << " Bytes.\n";
    cout << "Groesse eines double:\t"      << sizeof(double)     << " Bytes.\n";
    cout << "Groesse eines bool:\t\t"      << sizeof(bool)       << " Bytes.\n";
    
    return 0;
}

Hinweis: Der sizeof()-Operator gibt den Speicherbedarf einer Variable oder eines Datentyps zurück.

Ausgabe:

Groesse eines int:       4 Bytes.
Groesse eines short int: 2 Bytes.
Groesse eines long int:  8 Bytes.
Groesse eines char:      1 Bytes.
Groesse eines float:     4 Bytes.
Groesse eines double:    8 Bytes.
Groesse eines bool:      1 Bytes.

Vorzeichenbehaftete Variablen: signed und unsigned

Alle genannten Typen kommen außerdem in zwei Versionen vor: mit Vorzeichen (signed
) und ohne Vorzeichen (unsigned). Dem liegt der Gedanke zugrunde, dass man manchmal zwar negative Zahlen benötigt, manchmal aber nicht. Ganze Zahlen (short und long) ohne das Wort unsigned werden als signed (das heißt: vorzeichenbehaftet) angenommen. Vorzeichenbehaftete Ganzzahlen sind entweder negativ oder positiv, während ganze Zahlen ohne Vorzeichen (unsigned int) immer positiv sind.

 Da sowohl für vorzeichenbehaftete als auch vorzeichenlose Ganzzahlen dieselbe Anzahl von Bytes zur Verfügung steht, ist die größte Zahl, die man in einem unsigned
int
speichern kann, doppelt so groß wie die größte positive Zahl, die man in einem signed int unterbringt. Ein unsigned short int kann Zahlen von 0 bis 65535 speichern. Bei einem signed short int ist die Hälfte der Zahlen negativ. Daher kann ein signed short int Zahlen im Bereich von -32768 bis 32767 darstellen. Sollte dich dieses etwas verwirren, so findest du unter Tabelle: Datentypen unter C/C++ eine ausführliche Beschreibung.

Grundlegende Datentypen

In C/C++ gibt es weitere Variablentypen, die man zweckentsprechend in ganzzahlige Variablen (die bisher behandelten Typen), Fließkommavariablen und Zeichenvariablen einteilt.

Die Werte von Fließkommavariablen lassen sich als Bruchzahlen ausdrücken – das heißt, es handelt sich um reelle Zahlen.

Zeichenvariablen (char) nehmen ein einzelnes Byte auf und dienen der Speicherung der 256 möglichen Zeichen und Symbole der ASCII- und erweiterten ASCII-Zeichensätze.

 Der ASCIIZeichensatz ist ein Standard, der die im Computer verwendeten Zeichen definiert. ASCII steht als Akronym für American Standard Code for Information Interchange (amerikanischer Standard-Code für den Informationsaustausch). Nahezu jedes Computer-Betriebssystem unterstützt ASCII. Daneben sind meistens weitere internationale Zeichensätze möglich.

 Die in C++-Programmen verwendeten gängigsten Variablentypen sind in der Tabelle „Variablentypen“ aufgeführt. Diese Tabelle zeigt den Variablentyp, den belegten Platz im Speicher (Grundlage ist der Computer des Autors) und den möglichen Wertebereich, der sich aus der Größe des Variablentyps ergibt. Vergleichen Sie dazu die Ausgabe des Programms aus dem Listing „sizes.cpp“.

 Tabelle: Variablentypen

Typ Größe Wert
bool1 Bytetrue oder false
unsigned short int2 Byte0 bis 65,535
short int2 Byte-32,768 bis 32,767
unsigned long int4 Byte0 bis 4,294,967,295
long int4 Byte-2,147,483,648 bis

2,147,483,647

int (16 Bit)2 Byte-32,768 bis 32,767
int (32/64 Bit)4 Byte-2,147,483,648 bis

2,147,483,647

unsigned int (16 Bit)2 Byte0 bis 65,535
unsigned int (32/64 Bit)4 Byte0 bis 4,294,967,295
char1 Byte256 Zeichenwerte
float4 Byte1.2e-38 bis 3.4e38
double8 Byte2.2e-308 bis 1.8e308

 Variablen definieren

Wenn du eine Variable deklarierst, wird dafür Speicherplatz allokiert (bereitgestellt). Was auch immer zu diesem Zeitpunkt sich in dem Speicherplatz befindet, stellt den Wert dieser Variablen dar. Wie du dieser Speicherposition einen neuen Wert zuweisen kannst, wirst du gleich erfahren.

Eine Variable erzeugt oder definiert man, indem man den Typ, mindestens ein Leerzeichen, den Variablennamen und ein Semikolon eintippt. Als Variablenname eignet sich nahezu jede Buchstaben-/Ziffernkombination, die allerdings keine Leerzeichen enthalten darf. Gültige Variablennamen sind zum Beispiel x, J23qrsnf und meinAlter. Gute Variablennamen sagen bereits etwas über den Verwendungszweck der Variablen aus („sprechende“ Variablennamen) und erleichtern damit das Verständnis für den Programmablauf. Die folgende Anweisung definiert eine Integer-Variable namens meinAlter:

  int meinAlter;

Für die Programmierpraxis möchte ich dir nahelegen, wenig aussagekräftige Namen wie J23qrsnf zu vermeiden und kurze, aus einem Buchstaben bestehende Variablennamen (wie x oder i) auf Variablen zu beschränken, die nur kurz, für wenige Zeilen Code benötigt werden (z. B. als Zählvariable). Verwende ansonsten lieber „sprechende“ Namen wie meinAlter oder wie_viele_Katzen. Solche Namen sind leichter zu verstehen, wenn du dich drei Wochen später kopfkratzend nach dem Sinn und Zweck deines Codes fragst.

Groß-/Kleinschreibung, Umlaute und Sonderzeichen

C/C++ beachtet die Groß-/Kleinschreibung und behandelt demnach Großbuchstaben und Kleinbuchstaben als verschiedene Zeichen. Eine Variable namens alter unterscheidet sich von Alter und diese wiederum von ALTER.

Für die Schreibweise von Variablennamen gibt es mehrere Konventionen. Unabhängig davon, für welche du dich entscheidest ist es ratsam, innerhalb eines Programms bei der einmal gewählten Methode zu bleiben.

 Viele Programmierer bevorzugen für Variablennamen Kleinbuchstaben. Wenn der Name aus zwei Wörtern besteht (zum Beispiel mein Auto), gibt es zwei übliche Konventionen: mein_auto oder meinAuto. Letztere Form wird auch als Kamel-Notation („CamelCase“) bezeichnet, da die Großschreibung im Wort selbst an einen Kamelhöcker erinnert.

Umlaute und bestimmte Sonderzeichen (äÄ oO üÜ ß \ /) dürfen in Variablennamen nicht vorkommen.

Ungarische Notation

Viele fortgeschrittene Programmierer schreiben Ihren Code in der sogenannten Ungarischen Notation. Dieser Notation liegt der Gedanke zugrunde, dass jede Variable mit einem oder mehreren Buchstaben beginnt, die auf den Typ der Variablen verweisen. So wird ganzzahligen Variablen (Integer) ein kleines ivorangestellt oder Variablen vom Typ long ein kleines l. Andere Notationen verweisen auf Konstanten, globale Variablen, Zeiger und so weiter. Dies ist jedoch für die C-Programmierung von wesentlich größerer Bedeutung als für C++, da C++ die Erzeugung benutzerdefinierter Datentypen unterstützt (siehe »Klassen«), und von sich aus typenstrenger ist.

Schlüsselwörter

In C++ sind bestimmte Wörter reserviert, die man nicht als Variablennamen verwenden darf. Es handelt sich dabei um die Schlüsselwörter, mit denen der Compiler das Programm steuert. Zu den Schlüsselwörtern gehören zum Beispiel if, while, for und main. Im allgemeinen fallen aussagekräftige Name für Variablen nicht mit Schlüsselwörtern zusammen. Eine Liste der C++-Schlüsselwörter findest du im Anhang: Schlüsselwörter in C/C++.

Mehrere Variablen gleichzeitig erzeugen

In einer Anweisung lassen sich mehrere Variablen desselben Typs gleichzeitig erzeugen, indem man den Typ schreibt und dahinter die Variablennamen durch Kommata getrennt aufführt. Dazu ein Beispiel:

unsigned int meinAlter, meinGewicht;   //Zwei Variablen vom Typ unsigned int
long Flaeche, Breite, Laenge;          //Drei Variablen vom Typ long

 Wie man sieht, werden meinAlter und meinGewicht gemeinsam als Variablen vom Typ unsigned int deklariert. Die zweite Zeile deklariert drei eigenständige Variablen vom Typ long mit den Namen Flaeche, Breite und Laenge. Der Typ (long) wird allen Variablen zugewiesen, so dass man in ein- und derselben Definitionsanweisung keine unterschiedlichen Typen festlegen kann.

Werte an Variablen zuweisen

Einen Wert weist man einer Variablen mit Hilfe des Zuweisungsoperators (=) zu. Zum Beispiel formuliert man die Zuweisung des Wertes 5 an die Variable Breite wie folgt:

unsigned short Breite;
Breite = 5;

Diese Schritte kann man zusammenfassen und die Variable Breite bei ihrer Definition initialisieren:

unsigned short Breite = 5;

 Die Initialisierung sieht nahezu wie eine Zuweisung aus, und bei Integer-Variablen gibt es auch kaum einen Unterschied. Bei der späteren Behandlung von Konstanten werden Sie sehen, das man bestimmte Werte initialisieren muss, da Zuweisungen nicht möglich sind. Der wesentliche Unterschied besteht darin, dass die Initialisierung bei der Erzeugung der Variablen stattfindet.

Ebenso wie Sie mehrere Variable gleichzeitig definieren können, ist es auch möglich, mehr als eine Variable auf einmal zu erzeugen. Betrachten wir folgendes Beispiel:

//Erzeugung von zwei long-Variablen und ihre Initialisierung
long Breite = 5, Laenge = 7;

In diesem Beispiel wird die Variable Breite vom Typ long mit 5 und die Variable Laenge
vom Typ long mit dem Wert 7 initialisiert. Sie können aber auch Definitionen und Initialisierungen mischen:

int meinAlter = 39, ihrAlter, seinAlter = 40;

Das Listing „flaeche.cpp“ zeigt ein vollständiges Programm, das du sofort kompilieren kannst. Es berechnet die Fläche eines Rechtecks und schreibt das Ergebnis auf den Bildschirm:

// Listing: flaeche.cpp
// - demonstriert den Einsatz von Variablen
#include <iostream>

using namespace std;

int main(void)
{
    unsigned short int Width = 5, Length;
    Length = 10;
    
    // eine Variable vom Typ 'unsigned short int' erzeugen und
    // mit dem Ergebnis der Multiplikation von Width und Length
    // initialisieren:
    unsigned short int Area = (Width * Length);
    
    // Werte ausgeben:
    cout << "Breite:  " << Width << endl;
    cout << "Laenge:  " << Length << endl;
    cout << "Flaeche: " << Area << endl;
    
    return 0;
}

Ausgabe:

Breite: 5
Laenge: 10
Flaeche: 50

Programmanalyse:

  • Zeile 3 enthält die include-Anweisung für die iostream-Bibliothek, die wir benötigen, um cout verwenden zu können. In Zeile 4 beginnt das Programm.

  • In Zeile 9 wird die Variable Width als vorzeichenloser short int definiert und mit dem Wert 5 initialisiert. Eine weitere Variable vom gleich Typ, Length, wird ebenfalls hier definiert, aber nicht initialisiert. In Zeile 10 erfolgt die Zuweisung des Wertes 10 an die Variable Length.

  • Zeile 15 definiert die Variable Area vom Typ unsigned short int und initialisiert sie mit dem Wert, der sich aus der Multiplikation von Width und Length ergibt. In den Zeilen 18 bis 20 erfolgt die Ausgabe der Variablenwerte auf dem Bildschirm. Beachte, dass das spezielle Wort endl eine neue Zeile erzeugt.

Pseudo-Datentypen mit typedef

Es ist lästig, zeitraubend und vor allem fehleranfällig, wenn man häufig unsigned short
int
schreiben muss. In C++ kann man einen Alias für diese Wortfolge mit Hilfe des Schlüsselwortes typedef (für Typendefinition) erzeugen.

 Mit diesem Schlüsselwort erzeugt man lediglich ein Synonym und keinen neuen Typ (letzteres heben wir uns für später auf). Auf das Schlüsselwort typedef folgt ein vorhandener Typ und danach gibt man den neuen Namen an. Den Abschluss bildet ein Semikolon. Beispielsweise erzeugt

typedef unsigned short int USHORT;

den neuen Namen USHORT, den man an jeder Stelle verwenden kann, wo man sonst unsigned short int schreiben würde. Listing „typedef.cpp“ ist eine Neuauflage von Listing „flaeche.cpp“ und verwendet die Typendefinition USHORT anstelle von unsigned short int. Schauen wir uns das am praktischen Beispiel an:

// Listing: typedef.cpp
// Zeigt die Verwendung des Schlüsselworts typedef
#include <iostream>

using namespace std;

typedef unsigned short int USHORT;        // mit typedef definiert

int main()
{
  USHORT  Width = 5;
  USHORT Length;
  Length = 10;
    
  USHORT Area  = Width * Length;
    
  cout << "Breite:  " << Width << "\n";
  cout << "Laenge:  "  << Length << endl;
  cout << "Flaeche: " << Area <<endl;
  return 0;
}

Das Programm macht dasselbe wie sein Vorgänger flaeche.cpp – allerdings haben wir uns per typedef ein wenig Tipparbeit gespart.

Ausgabe:

Breite: 5
Laenge: 10
Flaeche: 50

Wann verwendet man short und wann long?

 Neueinsteiger in die C++-Programmierung wissen oft nicht, wann man eine Variable als long und wann als short deklarieren sollte. Die Regel ist einfach: Wenn der in der Variablen zu speichernde Wert zu groß für seinen Typ werden kann, nimmt man einen größeren Typ.

 Wie die Tabelle Datentypen unter C/C++ zeigt, können ganzzahlige Variablen vom Typ unsigned short (vorausgesetzt, dass sie aus 2 Bytes bestehen) nur Werte bis zu 65535 aufnehmen. Variablen vom Typ signed short verteilen ihren Wertebereich auf negative und positive Zahlen. Deshalb ist das Maximum eines solchen Typs nur halb so groß.

 Obwohl Integer-Zahlen vom Typ unsigned long sehr große Ganzzahlen aufnehmen können (bis 4.294.967.295), hat auch dieser Typ einen begrenzten Wertebereich. Benötigt man größere Zahlen, kann man auf float oder double ausweichen und einen gewissen Genauigkeitsverlust in Kauf nehmen. Variablen vom Typ float oder double können zwar extrem große Zahlen speichern, allerdings sind auf den meisten Computern nur die ersten 7 bzw. 19 Ziffern signifikant. Das bedeutet, dass die Zahl nach dieser Stellenzahl mit Verlust gerundet wird. Alternativ könnte man aber auch den Datentyp unsigned long long verwenden – dieser speichert Werte zwischen 0 und 18446744073709551615.

Schlechter Tipp für Fulderer: Kürzere Variablen belegen weniger Speicher. Heute jedoch ist Speicher billig und das Leben kurz. Deshalb lassen Sie sich nicht davon abhalten, int zu verwenden, auch wenn damit 4 Byte auf Ihrem PC belegt werden.

Ein guter Tipp hingegen ist es, vor der Vergabe eines Datentyps für eine Variable genau zu prüfen, wie groß eine zu speichernde Zahl tatsächlich werden könnte.

Bereichsüberschreitung bei Integer-Werten vom Typ unsigned

 Die Tatsache, dass Ganzzahlen vom Typ unsigned long nur einen begrenzten Wertebereich aufnehmen können, ist nur selten ein Problem. Aber was passiert, wenn der Platz im Verlauf des Programms zu klein wird?

Wenn eine Ganzzahl vom Typ unsigned ihren Maximalwert erreicht, schlägt der Zahlenwert um und beginnt von vorn. Vergleichbar ist das mit einem Kilometerzähler. Das Listing „ueberlauf.cpp“ demonstriert den Versuch, einen zu großen Wert in einer Variablen vom Typ short int abzulegen:

// Listing: ueberlauf.cpp
// demonstriert den Überlauf einer Variablen
#include <iostream>
 
using namespace std;

int main(void)
{
  unsigned short int smallNumber;

  smallNumber = 65535;
  cout << "Kleine Zahl: " << smallNumber << endl;
  smallNumber++;
  cout << "Kleine Zahl: " << smallNumber << endl;
  smallNumber++;
  cout << "Kleine Zahl: " << smallNumber << endl;
    
  return 0;
}

Ausgabe:

Kleine Zahl: 65535
Kleine Zahl: 0
Kleine Zahl: 1

Programmanalyse:

  • Zeile 9 deklariert smallNumber vom Typ unsigned short int (auf dem Computer des Autors 2 Bytes für einen Wertebereich zwischen 0 und 65535). Zeile 11 weist den Maximalwert smallNumber zu und gibt ihn in Zeile 12 aus.
  • Die Anweisung in Zeile 13 inkrementiert smallNumber, das heißt, addiert den Wert 1. Das Symbol für das Inkrementieren ist ++ (genau wie der Name C++ eine Inkrementierung von C symbolisieren soll). Der Wert in smallNumber sollte nun 65536 lauten. Da aber Ganzzahlen vom Typ unsigned short keine Zahlen größer als 65535 speichern können, schlägt der Wert zu 0 um. Die Ausgabe dieses Wertes findet in Zeile 14 statt.
  • Die Anweisung in Zeile 15 inkrementiert smallNumber erneut. Es erscheint nun der neue Wert 1.

Bereichsüberschreitung bei Integer-Werten vom Typ signed

Im Gegensatz zu unsigned Integer-Zahlen besteht bei einer Ganzzahl vom Typ signed die Hälfte des Wertebereichs aus negativen Werten. Den Vergleich mit einem Kilometerzähler stellen wir nun so an, dass er bei einem positiven Überlauf vorwärts und bei negativen Zahlen rückwärts läuft. Vom Zählerstand 0 ausgehend erscheint demnach die Entfernung ein Kilometer entweder als 1 oder -1. Wenn man den Bereich der positiven Zahlen verlässt, gelangt man zur größten negativen Zahl und zählt dann weiter herunter bis Null. das Listing „ueberlauf2.cpp“ zeigt die Ergebnisse, wenn man auf die maximale positive Zahl in einem signed short int eine 1 addiert:

 // Listing: ueberlauf2.cpp
 // demonstriert den Überlauf einer Variablen
 #include <iostream>
 
 using namespace std;
 
 int main()
{
  short int smallNumber;
  smallNumber = 32767;
    
  cout << "Kleine Zahl: " << smallNumber << endl;
  smallNumber++;
  cout << "Kleine Zahl: " << smallNumber << endl;
  smallNumber++;
  cout << "Kleine Zahl: " << smallNumber << endl; 
    
  return 0;
}

Ausgabe:

Kleine Zahl: 32767
Kleine Zahl: -32768
Kleine Zahl: -32767

Programmanalyse:

  • Zeile 9 deklariert smallNumber dieses Mal als signed short int (wenn man nicht explizit unsigned festlegt, gilt per Vorgabe signed). Das Programm läuft fast genau wie das vorherige, liefert aber eine andere Ausgabe. Um diese Ausgabe zu verstehen, muss man die Bit-Darstellung vorzeichenbehafteter (signed) Zahlen in einer Integer-Zahl von 2 Bytes Länge kennen.

     Analog zu vorzeichenlosen Ganzzahlen findet bei vorzeichenbehafteten Ganzzahlen ein Umschlagen vom größten positiven Wert in den höchsten negativen Wert statt.

Zeichen

Zeichen-Variablen (vom Typ char) sind in der Regel 1 Byte groß und können damit 256 Werte aufnehmen. Eine Variable vom Typ char kann als kleine Zahl (0-255) oder als Teil des ASCII-Zeichensatzes interpretiert werden. ASCII steht für American Standard Code for Information Interchange (amerikanischer Standard-Code für den Informationsaustausch). Mit dem ASCII-Zeichensatz und seinem ISO-Gegenstück (International Standards Organization) können alle Buchstaben, Zahlen und Satzzeichen codiert werden.

Computer haben keine Ahnung von Buchstaben, Satzzeichen oder Sätzen. Alles was sie verstehen, sind Zahlen. Im Grunde genommen kann ein Computer nur feststellen, ob genügend Strom an einem bestimmten Leitungspunkt vorhanden ist. Wenn ja, wird dies intern mit einer 1 dargestellt, wenn nicht mit einer 0. Durch die Kombination von Einsen und Nullen erzeugt der Computer Muster, die als Zahlen interpretiert werden können. Und diese Zahlen können wiederum Buchstaben und Satzzeichen zugewiesen werden

Im ASCII-Code wird dem kleinen »a« der Wert 97 zugewiesen. Allen Klein- und Großbuchstaben sowie den Zahlen und Satzzeichen werden Werte zwischen 1 und 128 zugewiesen. Weitere 128 Zeichen und Symbole sind für den Computer-Hersteller reserviert. In der Realität hat sich aber der erweiterte IBM-Zeichensatz und auf modernen Rechnern der UTF-8 Zeichensatz als Quasi-Standard durchgesetzt.

Zeichen und Zahlen

 Wenn du ein Zeichen, zum Beispiel »a«, in einer Variablen vom Typ char ablegst, steht dort eigentlich eine Zahl zwischen 0 und 255. Der Compiler kann Zeichen (dargestellt durch ein einfaches Anführungszeichen gefolgt von einem Buchstaben, einer Zahl oder einem Satzzeichen und einem abschließenden einfachen Anführungszeichen) problemlos in ihren zugeordneten ASCII-Wert und wieder zurück verwandeln.

 Die Wert/Buchstaben-Beziehung ist zufällig. Dass dem kleinen »a« der Wert 97 zugewiesen wurde, ist reine Willkür. So lange jedoch, wie jeder (Tastatur, Compiler und Bildschirm) sich daran hält, gibt es keine Probleme. Du solltest jedoch beachten, dass zwischen dem Wert 5 und dem Zeichen »5« ein großer Unterschied besteht. Letzteres hat einen Wert von 53, so wie das »a« einen Wert von 97 hat.

Schreiben wir ein Programm, welches Zeichen anhand ihres ASCII-Codes ausdruckt (Listing „asciicode.cpp“):

// Listing: asciicode.cpp
// druckt Zeichen anhand von Zahlen
#include <iostream>

using namespace std;

int main(void)
{
  for (int i = 32; i<128; i++)
    cout << (char) i;
    
  return 0;
}

Ausgabe:

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Dieses einfache Programm druckt die Zeichenwerte für die Integer 32 bis 127.

Besondere Zeichen

 Der C++-Compiler kennt einige spezielle Formatierungszeichen. Die Tabelle Escape-Codes listet die geläufigsten auf. In deinem Code gibst du diese Zeichen mit einem vorangestellten Backslash (auch Escape-Zeichen genannt) ein. Um zum Beispiel einen Tabulator in deinen Code mit aufzunehmen, würdest du ein einfaches Anführungszeichen, den Backslash, den Buchstaben t und ein abschließendes einfaches Anführungszeichen eingeben.

char tabZeichen = '\t';

 Dies Beispiel deklariert eine Variable vom Typ char und initialisiert sie mit dem Zeichenwert \t, der als Tabulator erkannt wird. Diese speziellen Druckzeichen werden benötigt, wenn die Ausgabe entweder auf dem Bildschirm, in eine Datei oder einem anderen Ausgabegerät erfolgen soll.

 Ein Escape-Zeichen ändert die Bedeutung des darauf folgenden Zeichens. So ist das Zeichen n zum Beispiel nur der Buchstabe n. Wird davor jedoch ein Escape-Zeichen gesetzt (\), steht das Ganze für eine neue Zeile.

Tabelle: Escape-Codes

Zeichen Bedeutung
 \n Neue Zeile
 \t Tabulator
\b Backspace
 \“ Anführungszeichen
 \‘ Einfaches Anführungszeichen
 \? Fragezeichen
 \\ Backslash

Was ist eine Konstante?

Konstanten sind ebenso wie Variablen benannte Speicherstellen. Während sich Variablen aber ändern können, behalten Konstanten – wie der Name bereits sagt – immer ihren Wert. Du musst eine Konstante bei der Erzeugung initialisieren und kannst ihr dann später keinen neuen Wert zuweisen.

Literale Konstanten

 C++ kennt zwei Arten von Konstanten: literale und symbolische.

 Eine literale Konstante ist ein Wert, den man direkt in das Programm an der Stelle des Vorkommens eintippt. In der Anweisung

int meinAlter = 39;

 ist meinAlter eine Variable vom Typ int, während die Zahl 39 eine literale Konstante bezeichnet. Man kann 39 keinen Wert zuweisen oder diesen Wert ändern.

Symbolische Konstanten

 Eine symbolische Konstante wird genau wie eine Variable durch einen Namen repräsentiert. Allerdings lässt sich im Gegensatz zu einer Variablen der Wert einer Konstanten nicht nach deren Initialisierung ändern.

 Wenn dein Programm eine Integer-Variable namens Studenten und eine weitere namens Klassen enthält, kann man die Anzahl der Studenten berechnen, wenn die Anzahl der Klassen bekannt ist und man weiß, dass 15 Studenten zu einer Klasse gehören:

Studenten = Klassen * 15;

Wenn man später die Anzahl der Stunden pro Klasse ändern möchte, braucht man das nur in der Definition der Konstanten StudentenProKlasse vorzunehmen, ohne dass man alle Stellen ändern muss, wo man diesen Wert verwendet hat.

 Es gibt zwei Möglichkeiten, eine symbolische Konstante in C++ zu deklarieren. Die herkömmliche und inzwischen veraltete Methode aus dem Sprachumfang von C erfolgt mit der Präprozessor-Direktiven #define.

Konstanten mit #define definieren

Um eine Konstante auf die herkömmliche Weise zu definieren, gibt man ein:

#define StudentenProKlasse 15

 Beachte, dass StudentenProKlasse keinen besonderen Typ (etwa int oder char) aufweist. #define nimmt eine einfache Textersetzung vor. Der Präprozessor schreibt an alle Stellen, wo StudentenProKlasse vorkommt, die Zeichenfolge 15 in den Quelltext.

 Da der Präprozessor vor dem Compiler ausgeführt wird, kommt Ihr Compiler niemals mit der symbolischen Konstanten in Berührung, sondern bekommt immer die Zahl 15 zugeordnet.

Konstanten mit const definieren

 Obwohl #define nach wie vor funktioniert, gibt es in C++ eine neue, bessere und elegantere Lösung zur Definition von Konstanten:

const unsigned short int StudentenProKlasse = 15;

Dieses Beispiel deklariert ebenfalls eine symbolische Konstante namens StudentenProKlasse
, dieses Mal ist aber StudentenProKlasse als Typ unsigned short int definiert. Diese Version bietet verschiedene Vorteile. Zum einen lässt sich der Code leichter warten und zum anderen werden unnötige Fehler vermieden. Der größte Unterschied ist der, dass diese Konstante einen Typ hat und der Compiler die zweckmäßige – sprich typgerechte – Verwendung der Konstanten frühzeitig prüfen kann. Es liegt auf der Hand, dass du dieser Lösung den Vorzug geben solltest.

Erinnerung: Konstanten können nicht während der Ausführung des Programms geändert werden. Wenn du gezwungen bist, die Konstante studentsPerClass zu ändern, dann musst du den Code ändern und neu kompilieren.

Aufzählungstypen (Enumeration)

 Mit Hilfe von Aufzählungskonstanten (enum) können Sie neue Typen erzeugen und dann Variablen dieser Typen definieren, deren Werte auf einen bestimmten Bereich beschränkt sind. Beispielsweise kann man FARBE als Aufzählung deklarieren und dafür fünf Werte definieren: ROT, BLAU, GRUEN, WEISS und SCHWARZ.

 Die Syntax für Aufzählungstypen besteht aus dem Schlüsselwort enum, gefolgt vom Typennamen, einer öffnenden geschweiften Klammer, einer durch Kommata getrennte Liste der möglichen Werte, einer schließenden geschweiften Klammern und einem Semikolon. Dazu ein Beispiel:

enum FARBE { ROT, BLAU, GRUEN, WEISS, SCHWARZ };

 Diese Anweisung realisiert zwei Aufgaben:

  1. FARBE ist der Name der Aufzählung, das heißt, ein neuer Typ.
  2. ROT wird zu einer symbolischen Konstanten mit dem Wert 0, BLAU zu einer symbolischen Konstanten mit dem Wert 1, GRUEN zu einer symbolischen Konstanten mit dem Wert 2 usw.

Jeder Aufzählungskonstanten ist ein Integer-Wert zugeordnet. Wenn man nichts anderes festlegt, weist der Compiler der ersten Konstanten den Wert 0 zu und nummeriert die restlichen Konstanten fortlaufend durch. Jede einzelne Konstante lässt sich aber auch mit einem bestimmten Wert initialisieren, wobei die Werte der nicht initialisierten Konstanten immer um 1 höher sind als die Werte ihres Vorgängers. Schreibt man daher

enum FARBE { ROT=100, BLAU, GRUEN=500, WEISS, SCHWARZ=700 };

erhält ROT den Wert 100, BLAU den Wert 101, GRUEN den Wert 500, WEISS den Wert 501 und SCHWARZ den Wert 700.

Damit kannst du Variablen vom Typ FARBE definieren, denen dann allerdings nur einer der Aufzählungswerte (in diesem Falle ROT, BLAU, GRUEN, WEISS oder SCHWARZ oder die Werte 100, 101, 500, 501 oder 700) zugewiesen werden kann. Du kannst Ihrer Variablen FARBE beliebige Farbwerte zuweisen, ja sogar beliebige Integer-Werte, auch wenn es keine gültige Farbe ist. Ein guter Compiler wird in einem solchen Fall jedoch eine Fehlermeldung ausgeben. Merke dir unbedingt, dass Aufzählungsvariablen vom Typ unsigned int sind und dass es sich bei Aufzählungskonstanten um Integer-Variablen handelt. Es ist jedoch von Vorteil, diesen Werten einen Namen zu geben, wenn Sie mit Farben, Wochentagen oder ähnlichen Wertesätzen arbeiten. Im Listing „enum.cpp“ findest du ein Programm, das eine Aufzählungskonstante verwendet:

// Listing: enum.cpp
// demonstriert die Verwendung von
// Aufzählungskonstanten
#include <iostream>

using namespace std;

int main(void)
{
    // Variable für die Auswahl eines Wochentags deklarieren:
    int auswahl;
    // Aufzählung definieren:
    enum Wochentage
    {
        Sonntag, Montag, Dienstag,
        Mittwoch, Donnerstag, Freitag,
        Samstag
    };
    
    // Programmtitel ausgeben:
    cout << "\nWochentage" << endl;
    cout << "----------" << endl;
    
    // Benutzereingabe holen:
    cout << "\nGib die Kennziffer eines Wochentags an (0 - 6): ";
    cin >> auswahl;
    
    // -- Benutzereingabe auswerten --
    switch(auswahl)
    {
        case Sonntag:
            cout << "\nDu hast den Sonntag im Sinn...";
            break;
        case Montag:
            cout << "\nDu hast den Montag im Sinn...";
            break;
        case Dienstag:
            cout << "\nDu hast den Dienstag im Sinn...";
            break;
        case Mittwoch:
            cout << "\nDu hast den Mittwoch im Sinn...";
            break;
        case Donnerstag:
            cout << "\nDu hast den Donnerstag im Sinn...";
            break;
        case Freitag:
            cout << "\nDu hast den Freitag im Sinn...";
            break;
        case Samstag:
            cout << "\nDu hast den Samstag im Sinn...";
            break;
        default:
            cout << "\nFehler: Nur Ziffern 0 bis 6 erlaubt!";
            break;
    }
    
    if ((auswahl == Sonntag) || (auswahl == Samstag))
        cout << "\nDu bist ja schon im Wochenende!\n";
    else
        cout << "\nOK - nimm dir mal 'nen Tag frei!\n";

  return 0;
}

Ausgabe:

bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./enum

Wochentage
----------

Gib die Kennziffer eines Wochentags an (0 - 6): 3

Du hast den Mittwoch im Sinn...
OK - nimm dir mal 'nen Tag frei!

Programmanalyse:

Der Anwender wird gebeten, einen Wert zwischen 0 und 6 einzugeben. Die Eingabe von z. B. »Sonntag« als Tag ist nicht möglich. Das Programm hat keine Ahnung, wie es die Buchstaben in Sonntag in einen der Aufzählungswerte übersetzen soll. Es kann jedoch die numerischen Werte, die der Anwender eingibt, mit einer oder mehreren Aufzählungskonstanten (wie in den Zeilen 15 bis 17 notiert) abgleichen. Die Verwendung von Aufzählungskonstanten verdeutlicht die Absicht des Vergleichs besser. Du hättest das Ganze natürlich auch mit Integer-Konstanten erreichen können – mehr dazu im nächsten Beispiel.

  • Zeile 11 deklariert eine Integer-Variable, in der die Benutzereingabe gespeichert werden soll
  • In den Zeilen 13 bis 18 definieren wir eine Aufzählung mit dem Namen Wochentage. Sie beinhaltet die Wochentage als symbolische Konstanten. Da keiner Konstanten explizit ein Wert zugewiesen wurde, beginnt die Aufzählung mit Sonntag = 0 und endet mit Samstag = 6.
  • Die Zeilen 25 und 26 dienen der Benutzereingabe: Zeile 25 fordert per cout zur Eingabe auf und Zeile 26 weist die Eingabe per cin der Variablen auswahl zu.
  • Ab Zeile 29 werten wir die Benutzereingabe aus und reagieren darauf. Dazu bedienen wir uns zunächst der Verzweigung mittels switch – case – break. Der switch-Anweisung folgt, in geschweifte Klammern gepackt, ein Anweisungsblock. Er besteht aus mehreren case-Anweisungen, die mittels einer break-Anweisung abgeschlossen werden. Zwischen case und break notiert man weitere auszuführende Anweisungen – in unserem Fall die Ausgabe des gewählten Wochentags.
  • In Zeile 52 wird eine default-Aktion für switch angegeben. Sie wird ausgeführt, wenn der Benutzer eine Zahl (oder gar ein Zeichen) eingibt, die nicht durch die Fallunterscheidungen der einzelnen case-Anweisungen abgedeckt wird. In unserem vorliegenden Fall gibt default eine Fehlermeldung aus. Die switch-Anweisung eignet sich besonders gut, um auf bestimmte, einzelne Werte zu reagieren.Die switch-Anweisung wertet die symbolischen Konstanten Sonntag (0) bis Samstag (6) aus, indem sie deren hinterlegte numerische Werte für Vergleiche heranzieht.
  • Ab Zeile 57 benutzen wir eine if – else Verzweigung, um dem Benutzer, je nach seiner getroffenen Auswahl, noch eine kleine Meldung zukommen zu lassen. Das Schema dieses Instruments zu Fallunterscheidung sieht wie folgt aus:
    if (1. Bedingung || 2. Bedingung)  // WENN Bedingung 1 ODER Bedingung 2 zutrifft, dann
      gib Meldung 1 aus
    else // ANDERNFALLS...
      gib Meldung 2 aus
  • Zeile 57 prüft die erste Bedingung: Hat die Variable auswahl den Wert 0 (Sonntag) ODER 6 (Samstag)? Falls Bedingung 1 zutrifft, dann wird in Zeile 58 die Meldung „Du bist ja schon im Wochenende!“ ausgegeben und die Verzweigung wird verlassen. Der else-Zweig wird dabei übersprungen, da die Bedingung ja bereits erfüllt ist. Andernfalls geht es weiter in Zeile 59.
  • Zeile 59: Der else-Zweig prüft, ob Bedingung 1 nicht erfüllt wurde (weder Sonntag, noch Samstag gewählt). Trifft das zu, so wird in Zeile 60 die Meldung „OK – nimm dir mal ’nen Tag frei!“ ausgegeben.Die if-Anweisung eignet sich gut, wenn mehrere Bedingungen miteinander kombiniert werden müssen. Die Bedingungen können dabei mittels bool’scher Operatoren miteinander verknüpft werden. Die Zeichenfolge „||“ steht in C/C++ für das logische ODER.
  • Das Programm prüft zuerst per switch-Anweisung, ob ein gültiger Wert eingegeben wurde und arbeitet danach die verknüpften Bedingungen der if-Anweisung ab. Beide Programmsegmente liefern auf jeden Fall eine Meldung.
  • Auch bei Eingabe einer nicht erlaubten Ziffer ändert sich daran nichts. Das Programm gibt dann zwar eine (gewollte) Fehlermeldung aus, bietet dir aber trotzdem einen Urlaubstag an. 😉

Ich hatte ja eingangs schon erwähnt, dass man das Programm, anstatt eine Aufzählung zu verwenden, auch mit einfachen Integer-Konstanten umsetzen könnte. Das Listing „intchoice.cpp“ zeigt dir in einer abgespeckten Version, wie das funktioniert:

// Listing: intchoice.cpp
// demonstriert die Verwendung von
// Integer-Konstanten
#include <iostream>

using namespace std;

int main(void)
{
  // Variable für die Auswahl eines Wochentags deklarieren:
  int auswahl;
  // Integer-Konstanten für Wochentage definieren:
  const int Sonntag    = 0;
  const int Montag     = 1;
  const int Dienstag   = 2;
  const int Mittwoch   = 3;
  const int Donnerstag = 4;
  const int Freitag    = 5;
  const int Samstag    = 6;
  
  // Benutzereingabe holen:
  cout << "\nGib die Kennziffer eines Wochentags an (0 - 6): ";
  cin >> auswahl;
  
  // äußere Schleife: Fehleingaben abfangen
  if ((auswahl > -1) && (auswahl < 7))	
    // innere Schleife: Eingabe auswerten
    if ((auswahl == Sonntag) || (auswahl == Samstag))
      cout << "\nDu bist ja schon im Wochenende!\n";
    else
      cout << "\nOK - nimm dir mal 'nen Tag frei!\n";
  else
    cout << "Fehler: nur 0 bis 6 erlaubt!" << endl;
  
  return 0;
}

Ausgabe:

# 1. Programmlauf: Ungültige Eingabe
bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./intchoice

Gib die Kennziffer eines Wochentags an (0 - 6): 8
Fehler: nur 0 bis 6 erlaubt!

# 2. Programmlauf: Tag 3 gewählt
bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./intchoice

Gib die Kennziffer eines Wochentags an (0 - 6): 3

OK - nimm dir mal 'nen Tag frei!

Programmanalyse:

In diesem Programm wurde  jede Konstante (Sonntag, Montag etc.) einzeln definiert, ein Aufzählungstyp Tage existiert hier nicht. Aufzählungskonstanten haben allerdings den Vorteil, dass sie selbsterklärend sind – die Absicht des Aufzählungstyps Tage ist jedem sofort klar.

  • Anstatt einen Aufzählungstyp zu verwenden, definieren wir hier in den Zeilen 13 bis 19 einzelne Integer-Konstanten und weisen ihnen einen entsprechenden Wert zu.
  • Die Zeilen 22 und 23 dienen der Benutzereingabe mittels cout und cin.
  • Zeile 23: Der Variablen auswahl wird per cin der eingelesene Wert übergeben.
  • In Zeile 25 beginnt die äußere if-else Schleife – sie dient zum Abfangen unzulässiger Zifferneingaben und endet in Zeile 33. Die Bedingung lauten dieses Mal:
    WENN auswahl GRÖSSER als -1 UND auswahl KLEINER als 7... (die Zeichenfolge „&&“ steht für das logischen UND in C/C++)
  • Falls die Bedingung nicht zutrifft (Eingabe einer nicht erlaubten Ziffer), dann wird das Programm beim else in Zeile 32 fortgesetzt und gibt in Zeile 33 eine Fehlermeldung aus. Die äußere if-Schleife ist damit abgeschlossen und das Programm endet in Zeile 35.
  • Trifft die Bedingung zu, so wird die innere if-Schleife ab Zeile 28 abgearbeitet. Sie prüft, ob einer der Werte für Sonntag oder Samstag vorliegt, verzweigt entsprechend (zu Zeile 29 oder Zeile 31) und endet dann. Somit kann dann auch die äußere if-Schleife geschlossen werden, da alle Bedingungen abgearbeitet sind. Auch in diesem Fall endet das Programm anschließend mit Zeile 35.

Das Beispiel demonstriert auch den Umstand, dass if-else Schleifen ineinander verschachtelt werden können. In diesem Fall wird zunächst die äußere Schleife verarbeitet/ausgewertet und erst danach die Innere.

Zusammenfassung

In diesem Kapitel hast du Variablen und Konstanten für numerische Werte und Zeichen kennengelernt, in denen du in C/C++ während der Ausführung deines Programms Daten speichern kannst. Numerische Variablen sind entweder Ganzzahlen (char, short und long int) oder Fließkommazahlen (float und double). Die Zahlen können darüber hinaus vorzeichenlos oder vorzeichenbehaftet (unsigned und signed) sein. Wenn auch alle Typen auf unterschiedlichen Computersystemen unterschiedlich groß sein können, so wird jedoch mit dem Typ für einen bestimmte Computertyp immer eine genaue Größe angegeben.

 Bevor man eine Variable verwenden kann, muss man sie deklarieren. Damit legt man gleichzeitig auch den Datentyp fest, der sich in der Variablen speichern lässt. Wenn man eine zu große Zahl in einer Integer-Variablen ablegt, erhält man ein falsches Ergebnis.

 Dieses Kapitel hat auch literale und symbolische Konstanten, sowie Aufzählungskonstanten behandelt und die beiden Möglichkeiten zur Deklaration symbolischer Konstanten aufgezeigt: Die Verwendung von #define und des Schlüsselwortes const.

C++ für einsteiger - ein Basis-Tutorial www.mbergmann-sh.de

C++ Tutorial – Funktionen

Schnelleinstieg: Funktionen in C/C++

C++ Funktionen sind kleine Unterprogramme, die Teilprobleme lösen. Funktionen sind ein wichtiges Werkzeug, um den Quelltext eines Programms zu ordnen und wesentliche Algorithmen und zusammenhängende Anweisungsblöcke der main()-Hauptfunktion in einer zusammenhängenden Form auszulagern. Sie werden außerhalb der main()-Hauptfunktion definiert und vereinfachen somit das Verständnis und die Lesbarkeit des Quelltextes.

Deklaration und Definition von Funktionen

Im Grunde bedeutet die Definition einer Funktion nichts Anderes, als eine Code-Block (Anweisungsblock) mit einem Funktionsnamen zu verbinden. Die Definition einer C++ Funktion besteht aus einer Deklaration und einem Anweisungsblock.

Für die Deklaration einer C++ Funktion sind gewisse Angaben erforderlich und gewisse zusätzliche Spezifikationen möglich (z.B. ‚inline‚ oder ‚virtual‚). Um eine Funktion schließlich zu definieren, muss man zusätzlich noch die zu erledigenden Anweisungen in einem Anweisungsblock zusammenfassen. Die allgemeine Definition einer C++ Funktion ist der formalen Definition einer mathematischen Funktion nicht unähnlich und man könnte sie auch wie folgt definieren: „Eine C++ Funktion ist eine Abbildung von dem Datenraum der Argumentenliste in den Datenraum des Rückgabetyps. Die dabei benutzte Abbildungsvorschrift findet sich in ihrem Anweisungsblock. Formal besitzt sie somit die folgende Struktur:“

'Rückgabe TypFunktionsname ('Argumentenliste') { 'Block von Anweisungen' }

  • Der Name der Funktion:
    Hier sollte der Programmierer einen Namen wählen, der die im Anweisungsblock definierten Anweisungen präzise und kurz in einem Wort zusammen fasst.
  • Rückgabe Typ:
    Welchen Datentyp gibt die Funktion an das Hauptprogramm zurück? Der „Rückgabe Typ“ steht vor dem Funktionsnamen.
  • Argumentenliste:
    Die „Argumentenliste“ steht direkt hinter dem Funktionsnamen, im Aufrufoperator „(…)“. Sie spezifiziert die Datentypen der Variablen, die die Funktion zur Berechnung ihrer Aufgabe benötigt.

Der ‚Rückgabe Typ‘ kann hierbei einer der C++ Datentypen (z.B. int oder double) oder ein Daten-Array  sein. Falls der ‚Rückgabe Typ‘ als void gekennzeichnet wird, gibt die Funktion keine Daten an das Hauptprogramm zurück, und führt nur den ‚Block von Anweisungen‘ aus. Die ‚Argumentenliste‘ setzt sich aus einer Liste von Datentypen der formalen Argumente (Parameter) der Funktion zusammen, die jeweils mit einem Komma voneinander getrennt sind.

Soviel zur Theorie – werden wir praktisch:

Funktionen erfüllen also den Zweck, wiederkehrende Aufgaben auszulagern, anstatt jedes Mal den gleichen Code nochmal einzutippen, wenn eine bestimmte Aufgabe ansteht.

Programme werden Zeile für Zeile ausgeführt, in der Reihenfolge, in der du den Quellcode aufgesetzt hast. Bei einem Funktionsaufruf verzweigt das Programm, um die Funktion auszuführen. Ist die Funktion beendet, springt die Programmausführung zurück zu der Zeile in der aufrufenden Funktion, die auf den Funktionsaufruf folgt. Funktionen dürfen andere Funktionen – auch sich selbst (Rekursion) – aufrufen.

Der Funktion main() kommt dabei eine Sonderstellung zu. Normalerweise muss eine Funktion, um etwas leisten zu können, im Verlauf Ihres Programms durch main() oder eine andere Funktion aufgerufen werden. main() ist die Hauptfunktion eines jeden Programms und wird beim Start des Programms vom Betriebssystem aufgerufen. Sie steuert den eigentlichen Ablauf des Programms. Die Hauptfunktion darf sich nicht selbst aufrufen.

Im realen Leben könnte das so aussehen: Stellen wir uns vor, dass du ein Bild von dir selbst zeichnest. Du zeichnest deinen Kopf, die Augen, die Nase – und plötzlich bricht dein Bleistift ab. Du »verzweigst« nun in die Funktion »Bleistift spitzen«. Das heißt, du hörst mit dem Zeichnen auf, stehst auf, gehst zur Spitzmaschine, spitzt den Stift, kehrst an deine Arbeit zurück und setzt sie dort fort, wo du aufgehört hast. Wenn ein Programm eine bestimmte Arbeit verrichtet haben möchte, kann es dafür eine Funktion aufrufen und nach Abarbeitung der Funktion genau an dem Punkt weitermachen, wo es aufgehört hat. Das Listing „demofunction.cpp“ verdeutlicht dieses Konzept:

// Listing: demofunction.cpp
#include <iostream>

// Namensraum für cout:
using namespace std;
// Funktion DemoFunction()
// - gibt eine Meldung aus und gibt danach
// - die Kontrolle zurück an den Aufrufer:   
void DemoFunction(void)  
{     
  cout << "In DemoFunction\n";
}

// Funktion main - gibt eine Meldung aus, ruft
// dann DemoFunction auf, gibt danach
// eine zweite Meldung aus:
int main()
{ 
  cout << "In main\n" ;
  DemoFunction();   // Verzweigung zur Funktion 'DemoFunction()'

  // Wieder in main() nach Abarbeitung der Funktion:    
  cout << "Zurueck in main\n";
  
  return 0;
}

Hinweis: Bei der Funktion DemoFunction() handelt es sich um eine Funktion ohne Rückgabewert (Datentyp für die Rückgabe: void).

Ausgabe:

./demofunction
In main
In DemoFunction
Zurueck in main

Programmanalyse:

  • Zeile 2 inkludiert IOSTREAM. Das ist eine Header-Datei, die Teil der Standardbibliothek von C++ ist. Diese Datei enthält Definitionen für die Ein- und Ausgabestreams, wie cin, cout, cerr und clog. Diese Streams sind Instanzen von Klassen wie istream und ostream, die wiederum von der Basisklasse ios abgeleitet sind.
  • Zeile 5 legt den Namensraum std fest und erspart dir damit Tipparbeit (vgl. std::cout).
  • Zeile 9 ist der Funktionskopf der Funktion DemoFunction().
  • Die Zeilen 10 und 12 beginnen und beenden den Anweisungsblock (Funktionsrumpf) der Funktion.
  • Zeile 11 sorgt für die Ausgabe der Meldung ‚In DemoFunktion‘, wenn die Funktion aufgerufen wird. Danach wird die Funktion ohne Rückgabewert beendet und die Programmkontrolle wieder an den Aufrufer (in diesem Fall: Die main()-Funktion) zurück gegeben.
  • Die Hauptfunktion main() beginnt in Zeile 17 (Funktionskopf). Ihr Anweisungsblock beginnt in Zeile 18 und endet in Zeile 26.
  • Zeile 19 gibt die Meldung ‚In main‘ und einen Zeilenvorschub aus.
  • Zeile 20 ruft die Funktion ‚DemoFunktion()‘ ohne Parameter auf und übergibt ihr die Programmkontrolle, bis die Funktion abgearbeitet ist.
  • Zeile 23 gibt die Meldung ‚Zurueck in main‘, gefolgt von einem Zeilenvorschub aus. Zu diesem Zeitpunkt liegt die Programmkontrolle bereits wieder bei main().
  • Zeile 25 gibt den Integer-Wert ‚0‘ (Null) an den Aufrufer (Das ist das Betriebssystem, bzw. die Konsole, in welcher das Programm gestartet wurde) zurück. Das Programm ist damit beendet.

Funktionen sind Arbeitstiere. Um ihren Aufgaben gerecht zu werden, können sie Daten (z. B. das Ergebnis einer Berechnung) über ihren Rückgabetyp an den Aufrufer zurückgeben. Um flexible Ergebnisse liefern zu können, verfügt eine Funktion über eine Parameterliste, mit der ihr Werte übergeben werden können. Soll eine Funktion zum Beispiel zwei Zahlen addieren, stellen die Zahlen die Parameter für die Funktion dar. Ein typischer Funktionskopf sieht folgendermaßen aus:

int summe (int a, int b)

Das Listing „addition.cpp“ zeigt eine Funktion, die zwei ganzzahlige Parameter übernimmt und einen ganzzahligen Wert zurückgibt. Kümmere dich momentan nicht um die Syntax oder die Einzelheiten, wie man mit Integer-Werten (beispielsweise int x) arbeitet. Wir kommen darauf zurück.

// Listing: addition.cpp
#include <iostream>

using namespace std;

// Funktion: int Add(int x, int y)
// - addiert zwei an die Funktion übergebene
// - Zahlen und gibt das Ergebnis an den
// - Aufrufer zurück:
int Add(int x, int y)
{
  cout << "\nFunktion 'Add()', erhalten: " << x << ", " << y << "\n";
  return(x + y);   // Addieren und zurückgeben
}

// --- Haupt-Funktion --
int main(void)
{
  int summand1, summand2, summe;

  cout << "Zahlen addieren:\n";
  cout << "================\n";
  cout << "Ich bin in 'main()'!\n";
  cout << "Gib die erste Zahl ein: ";
  cin >> summand1;
  cout << "Gib die zweite Zahl ein: ";
  cin >> summand2;

  // Berechnung:
  // Der Variablen 'summe' wird der Rückgabewert
  // der Funktion 'Add()' zugewiesen:
  summe = Add(summand1, summand2); // Funktionsaufruf und Wertzuweisung

  // Ausgabe des Ergebnisses:
  cout << "\nZurueck in 'main()'!\n";
  cout << summand1 << " + " << summand2 << " = " << summe << endl;
  cout << "\nProgramm beendet.\n";

  return 0;
}

Hinweis: Bei der Funktion ‚int Add(int x, int y)‘ handelt es sich um eine Funktion mit Rückgabewert und Parameterliste.

Compiliere das Programm (<F5> in Geany oder Konsole):

  g++ -Wall -s addition.cpp -o addition

Starte es anschließend und gib nach Aufforderung zwei Ganzzahlen ein (z. B. 4700 und 11).

Ausgabe:

./addition
Zahlen addieren:
================
Ich bin in 'main()'!
Gib die erste Zahl ein: 4700
Gib die zweite Zahl ein: 11

Funktion 'Add()', erhalten: 4700, 11

Zurueck in 'main()'!
4700 + 11 = 4711

Programm beendet.

Programmanalyse:

  • Die Zeilen 10 bis 14 definieren die Funktion ‚int Add(int x, int y)‘. Beachte: Dies geschieht vor der Definition von ‚main()‘!
  • Zeile 12 sorgt dafür, dass beim Aufruf von Add() eine Meldung ausgegeben wird, die auch die beiden übergebenen Parameter ausgibt.
  • Die Hauptfunktion main() erstreckt sich über die Zeilen 17 bis 40.
  • In Zeile 19 deklarieren wir drei Variablen vom Typ Ganzzahl (int). Sie dienen später der Aufnahme der beiden Summanden und der berechneten Summe.
  • Zeile 24 fordert zur Eingabe einer Zahl auf.
  • Zeile 25 liest diese Zahl mit dem Eingabestream cin ein. Dieses Schlüsselwort steht für „Console Input“ und wird vom Eingabeoperator „>>“ (dem Gegenstück zum Ausgabeoperator „<<„) gefolgt. Er verweist auf die Variable summand1, die die eingegebenen Daten (die erste Ganzzahl) aufnehmen soll.
  • In den Zeilen 26 und 27 wiederholt sich dieser Vorgang der Dateneingabe für die Variable summand2.
  • Zeile 32 – Hier wird die eigentliche Arbeit geleistet: Der Variablen summe wird das Ergebnis der Berechnung, also der Rückgabewert der Funktion Add() zugewiesen. Hierzu ruft die Zuweisung die Funktion Add() mit den Parametern (summand1, summand2) auf. Überraschung! Hatten wir nicht die Parameterlist von Add() als (int x, int y) definert? Doch, hatten wir – und das ist auch korrekt. Die Parameter x und y sind lokale, nur innerhalb der Funktion selbst sichtbare Variablen. Der Aufruf von außerhalb kann mit jeder passenden Variablen, gleich welchen Namens, erfolgen. Intern arbeitet die Funktion allerdings mit den Variablen x, y. Das kann man gut an den Zeilen 12 und 13 erkennen.
  • Zeile 36 gibt beide eingegebenen Zahlen und ihre Summe in der Form aus, wie du die Berechnung auch auf ein Blatt Papier schreiben würdest:
    4700 + 11 = 4711
  • Zeile 39 beendet das Hauptprogramm mit dem Rückgabewert Null.

Funktionsprototypen

Bisher haben wir eigene Funktionen immer vor der Hauptfunktion main() definiert. Das ist dem Umstand geschuldet, dass in C/C++ eine Variable oder Funktion immer vor ihrer ersten Verwendung deklariert sein muss. Es geht aber auch anders, wenn man dem Compiler eine Funktion durch ihren Funktionsprototypen rechtzeitig – also vor main() – bekannt macht. Dabei entspricht der Funktionsprototyp genau dem Funktionskopf, gefolgt von einem Semikolon:

// Listing: addition2.cpp
#include <iostream>

using namespace std;

// Funktionsprototyp:
int Add(int x, int y);

// -- Hauptprogramm --
int main(void)
{
  int summand1, summand2;

  cout << "Zahlen addieren:\n";
  cout << "================\n";
  cout << "Ich bin in 'main()'!\n";
  cout << "Gib die erste Zahl ein: ";
  cin >> summand1;
  cout << "Gib die zweite Zahl ein: ";
  cin >> summand2;

  // Berechnung und Ausgabe:
  cout << summand1 << " + " << summand2 << " = " << Add(summand1, summand2) << endl; 
  return 0;
}

// -- Funktion: int Add(int x, int y) --
int Add(int x, int y)
{
  return(x + y);   // Addieren und zurückgeben
}

Beachte: Im Listing „addition2.cpp“ erfolgt die Definition der Funktion „Add()“ erst nach der Hauptfunktion main(). Um die Funktion trotzdem bereitstellen zu können, wurde in Zeile 7 der Funktionsprototyp entsprechend dem Funktionskopf von Add() deklariert. Auch erfolgt die Berechnung nicht mehr mit Zwischenspeicherung des Ergebnisses in der Variablen summe – diese wurde eingespart. Stattdessen übergeben wir in Zeile 23 den Rückgabewert von Add() direkt an den Ausgabestream.

Die Verwendung von Prototypen hat übrigens den Vorteil, dass der Compiler die korrekte Verwendung einer Funktion bereits frühzeitig überprüfen und auf etwaige Regelverstöße genauer reagieren kann. Gute C/C++-Programme beinhalten deshalb einen Abschnitt, in dem alle Prototypen für Funktionen vor ihrer ersten Verwendung deklariert werden.

Zusammenfassung:

Funktionen geben entweder einen Wert oder void (das heißt: nichts) zurück. Eine Funktion zur Addition von zwei ganzen Zahlen liefert sicherlich die Summe zurück, und man definiert diesen Rückgabewert vom Typ Integer (int). Eine Funktion, die lediglich eine Meldung ausgibt, hat nichts zurückzugeben und wird daher als void (zu deutsch: leer) deklariert.

Funktionen gliedern sich in Kopf und Rumpf. Der Kopf besteht wiederum aus dem Rückgabetyp, dem Funktionsnamen und den Parametern. Mit Parametern lassen sich Werte an eine Funktion übergeben.

Funktionen müssen (wie Variablen) vor ihrer ersten Verwendung dem Compiler bekannt gemacht werden. Aus diesem Grund definiert man sie entweder vor main(), oder aber man verwendet stattdessen einen entsprechenden Funktionsprototypen an ihrer Stelle.


[Inhaltsverzeichnis] | [zurück] | [vorwärts]