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

BlitzBasic für Einsteiger – Grundrechenarten und eigene Funktionen

Rechner sind zum Rechnen da – das ist ihr Kerngeschäft und da bildet der Amiga keine Ausnahme. BlitzBasic/AmiBlitz3 bringt bereits eine große Anzahl an mathematischen Bibliotheksfunktionen für alle Anwendungsfälle mit, die sich bei Bedarf jederzeit um selbstgeschriebene, eigene Funktionen erweitern lassen – und darum geht es in diesem Teil unseres Tutorials.

Die Grundrechenarten

Um einfache Berechnungen anzustellen, bedarf es keiner umfangreichen Mathe-Bibliotheken und -funktionen. BlitzBasic beherrscht ohne weiteren Aufwand Addition (+), Subtraktion (-), Multiplikation (*) und Division (/). Um eine Berechnung durchzuführen genügt es, zwei Zahlen mit dem entsprechenden Operator zu verknüpfen und das Ergebnis einer Variablen zuzuweisen. Beispiele:

summe.w = 47 + 11     ; Addition
summe.w = 50 - 8      ; Subtraktion
produkt.w = 33 * 3    ; Multiplikation
quotient.q = 42 / 3   ; Division
quotient.q = 42 / 0   ; Division durch Null nicht erlaubt!

 

Priorität von Rechenoperationen

Tauchen in einem Term (einer Anweisung) unterschiedliche Rechenoperationen auf, so muss man die Priorität der einzelnen Operationen beachten. Die Hierarchie ist wie folgt aufgebaut:

  1. Potenzierung (hier nicht behandelt)
  2. Multiplikation und Division („Punktrechnung“)
  3. Addition und Subtraktion („Strichrechnung“)

Beachtet man diese Rangordnung nicht, so kann es zu unerwarteten Seiteneffekten aufgrund falscher Ergebnisse kommen. Beispiel:

meinErgebnis.w = 5 + 5 * 3

Erwartetes Ergebnis: 30
Tatsächliches Ergebnis: 20

Überrascht? Wir haben in diesem Beispiel die Faustregel „Punkt- vor Strichrechnung“ nicht beachtet und denken uns: 5 + 5 = 10, 10 * 3 = 30. Der Computer macht alles richtig und rechnet stattdessen: 5 * 3 = 15, 5 + 15 = 20.

Klammerung von Termen

Um die Priorität einer verketteten Berechnung zu ändern, setzt man runde Klammern entsprechend der gewünschten Priorisierung:

meinErgebnis.w = (5 + 5)* 3 

Erwartetes Ergebnis: 20 
Tatsächliches Ergebnis: 20

Jetzt werden tatsächlich 5 + 5 addiert, bevor die Multiplikation durchgeführt wird.

Die Wahl eines „passenden“ Datentyps

Wenn man das Ergebnis einer Berechnung einer Variablen zuweisen möchte, dann muss man sich bereits im Vorfeld Gedanken darüber machen, welche Art von Zahlen und Zahlengrößen dabei anfallen können. Die Zahl muss ja schließlich auch in die Variable hinein passen. Werden nur sehr kleine Zahlen als Ergebnis erwartet? Kann ich mich auf Ganzzahlen beschränken, oder sind Fließkommazahlen zu erwarten? Wie groß kann ein Ergebnis ausfallen, falls sich die Werte der Berechnung ändern?

Der Datentyp einer Variable bestimmt, ob eine abzulegende Zahl fehlerfrei dargestellt werden kann (siehe Anhang A) oder nicht. Ausschlaggebend ist dabei sein Wertebereich. Ist er zu klein bemessen, dann könnte die Variable überlaufen und falsche Resultate liefern. Bemisst man ihn zu groß, so verschwendet man Ressourcen. Weist man eine Fließkommazahl einem Ganzzahlen-Typen zu, dann gehen die Nachkommastellen verloren, was wiederum zu Fehlern im Programm führen kann. Gerade bei der Division zweier Zahlen muss z.B. regelmäßig mit einem Ergebnis in Form einer Fließkommazahl gerechnet werden. Du siehst, es erfordert Hirnschmalz. Der Leitsatz zur Wahl eines passenden Datentyps könnte etwa so lauten: „So viel wie nötig, aber so wenig wie möglich“.

Das Listing „calc1.ab3“ führt die Grundrechenarten vor und zeigt kleine Unterschiede zwischen Berechnungen mit Ganzzahlen und solchen mit Fließkommazahlen auf:

; -----------------------
; File:calc1.ab3
; Grundrechenarten
; Version: 1.0
; -----------------------
OPTIMIZE 1 ; Optimierung für Amiga mit Turbokarte/A1200
SYNTAX 1   ; strenge Syntaxprüfung

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

; Variablen deklarieren
DEFTYPE .l l_zahl1,l_zahl2, l_result   ; Datentyp: Long
DEFTYPE .f f_zahl1, f_zahl2, f_result  ; Datentyp: Float

; Variablen initialisieren
l_zahl1 = 3201
l_zahl2 = 500
f_zahl1 = 3201.5
f_zahl2 = 500.2

; Addition
l_result = l_zahl1 + l_zahl2
f_result = l_zahl1 + l_zahl2
NPrint "Addition (integer) Ergebnis          : " + Str$(l_result)
NPrint "Addition (float) Ergebnis            : " + Str$(f_result)
NPrint "Addition (float-Werte) Ergebnis      : " + Str$(f_zahl1 + f_zahl2)
NPrint ""

; Subtraktion
l_result = l_zahl1 - l_zahl2
f_result = l_zahl1 - l_zahl2
NPrint "Subtraktion (integer) Ergebnis       : " + Str$(l_result)
NPrint "Subtraktion (float) Ergebnis         : " + Str$(f_result)
NPrint "Subtraktion (float-Werte) Ergebnis   : " + Str$(f_zahl1 - f_zahl2)
NPrint ""

; Multiplikation
l_result = l_zahl1 * l_zahl2
f_result = l_zahl1 * l_zahl2
NPrint "Multiplikation (integer) Ergebnis    : " + Str$(l_result)
NPrint "Multiplikation (float) Ergebnis      : " + Str$(f_result)
NPrint "Multiplikation (float-Werte) Ergebnis: " + Str$(f_zahl1 * f_zahl2)
NPrint ""

; Multiplikation
l_result = l_zahl1 / l_zahl2
f_result = l_zahl1 / l_zahl2
NPrint "Division (integer) Ergebnis          : " + Str$(l_result)
NPrint "Division (float) Ergebnis            : " + Str$(f_result)
NPrint "Division (float-Werte) Ergebnis      : " + Str$(f_zahl1 / f_zahl2)
NPrint ""

End

Ausgabe:

Addition (integer) Ergebnis : 3701
Addition (float) Ergebnis : 3701
Addition (float-Werte) Ergebnis : 3701.699

Subtraktion (integer) Ergebnis : 2701
Subtraktion (float) Ergebnis : 2701
Subtraktion (float-Werte) Ergebnis : 2701.3

Multiplikation (integer) Ergebnis : 1600500
Multiplikation (float) Ergebnis : 1600500
Multiplikation (float-Werte) Ergebnis: 1601390

Division (integer) Ergebnis : 6
Division (float) Ergebnis : 6.401999
Division (float-Werte) Ergebnis : 6.400439

 

 

 


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

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]