Mit Funktionen ist es möglich bestimmte Programmteile auszulagern. Dadurch:
Rückgabetyp Funktionsname(Parameter) { /* Anweisungsblock mit Anweisungen */ return Rückgabewert }
Danach kann sie wie folgt aufgerufen werden:
Variable=Funktionsname(Parameter);
Eine Funktion kann als Rückgabetyp auch void besitzen. In dem Fall wird kein Wert von der Funktion zurückgegeben
void hilfe(void); int groesser(int,int);
globale Variablen werden im Programmkopf deklariert und sind dann in allen Funktionen gleichermaßen gültig. Wird allerdings innerhalb einer Funktion eine gleichnamige lokale Variable definiert, so gilt in dieser Funktion die lokale Variable.
Für das Anlegen von Variablen gilt, so lokal wie möglich und so global wie nötig.
static int i = 1;
Das Gegenteil erreicht man durch Voranstellen von static. Dies bewirkt, dass die Funktion nur innerhalb dieser Quelldatei gültig ist.
volatile verhindert eine Quellcodeoptimierung durch den Compiler und erzwingt somit, dass die Funktion immer wieder neu aus dem Hauptspeicher gelesen werden muss.
Neben der Möglichkeit mit globalen Variablen zu arbeiten, können der Funktion auch Werte per Parameter übergeben werden und Werte als Rückgabewert durch die Funktion zurückgegeben werden.
Bei der Call-by-value-Übergabe wird eine Kopie des Werts übergeben, mit dessen Argument die Funktion aufgerufen wurde.
halbieren(zahl);
Alternativ gibt es noch die call-by-reference-Übergabe. Dabei wird statt einem Wert eine Adresse kopiert. Der Aufruf der Funktion unterscheidet sich nicht, von der bei der Übergabe als Wert:
haelfte = halbieren(zahl);
Die Funktion wird aber wie folgt deklariert:
double halbieren(double& zuHalbieren);
Das hat vor allem Speichervorteile. So wird gerade bei der Übergabe größerer Objekte viel Speicherplatz gespart. Allerdings wirken sich alle Änderungen, die in der Funktion an dem übergebenen Objekt vorgenommen werden, direkt auf das Original aus. Deshalb sollte die Übergabe als Wert (Kopie) der der Referenz vorgezogen werden. Ist das Objekt allerdings sehr groß und soll ohnehin in der Funktion nicht verändert werden, kann eine nur lesbare Referenz an die Funktion übergeben werden, indem die Referenz als Konstante übergeben wird:
double halbieren(const double& zuHalbieren);
Wichtig ist es die Parameter in der richtigen Reihenfolge anzugeben. Daher ist es hilfreich bereits beim Prototyping die Variablennamen mit anzugeben. So wird leichter deutlich, an welcher Stelle, welcher Wert übergeben werden muss.
Verlangt die Deklaration der Funktion, die Übergabe einer Referenz, so ist es nicht mehr möglich der Funktion einen konstanten Wert zu übergeben. Beispiel:
haelfte = halbieren(12);
12 wird vom Kompiler direkt als Wert eingefügt und hat demzufolge keine
Adresse auf die referenziert werden könnte.
kurz: Konstante Literale können nicht per Referenz übergeben werden!
Indem man Paramter bereits bei der Deklaration mit einem (Vorgabe-)Wert
belegt, ist es möglich, dass dieser beim Aufrufen der Funktion nicht
angegeben wird und in diesem Fall eben den Vorgabewert annimmt.
Wird mit Prototypen gearbeitet wird der Vorgabewert nur im Prototyp definiert und
bei der Definition wird der Parameter wie ein nicht optionaler Parameter behandelt.
Solche optionalen Parameter müssen immer am Ende der Parameterliste sein,
da er ja bei der Übergabe auch weggelassen werden könnte, und mann sonst nicht wüsste,
das der übergebene Parameter nicht der optionale ist.
Beispiel:
int addiere(int Zahl,int Basis=10);
Die Funktion kann durch die return-Anweisung einen Wert zrückgeben (Funktion nicht von Typ void) und wird durch diese auch beendet:
return WERT;
In der Deklaration einer Funktion muss immer der Typ der Funktion und der Parameter definiert werden. Um jetzt für verschiedene Typen nicht unterschiedliche Funktionsnamen verwenden zu müssen, können die Funktionen überladen werden. Das bedeutet das der Compiler in der Lage ist den Unterschied zwischen den Funktionen aufgrund der unterschiedlichen Parameter erkennen kann:
int addiere(int SummandA, int SummandB); double addiere( double SummandA, double SummandB);
Eine besondere Rolle in einem C++-Programm hat die Fzunktion main(). Diese ist die Einstiegsfunktion mit der jedes Programm beim Programmstart beginnt und darf nur einmal vorhanden sein.
int main( int argc, char* argv[] ) { // Das eigentliche Programm // ... return 0; }
Die Parameter in runden Klammern können auch weg gelassen werden.
#include <iostream> #include <cctype> using namespace std; /* * Taschenrechner soll einen mathematischen Ausdruck verarbeiten, * bei dem zunächst die Klammern aufgelöst werden, * dann Punktrechnung vor Strichrechnung geht, * Der Ausdruck kann beliebig verschachtelt sein, * Darf aber nur ganze Zahlen enthalten, * Es ist auf die 4 Grundrechenarten beschränkt. */ long ausdruck(char& c); long summand(char& c); long faktor(char& c); long zahl(char& c); void clearSyntax(char& c, bool noOP=false, bool noKlammer=true); int main( int argc, char* argv[]) { char ch; do { cout << endl << ">>"; cin.get(ch); clearSyntax(ch); if (ch != 'e') { cout << ausdruck(ch); } } while(ch != 'e'); return 0; } long ausdruck(char& c) { long a; if (c == '-') { cin.get(c); // nächstes Zeichen einlesen a = -summand(c); // und an die Funktion summand übergeben // und das Ergebnis negiert a zuweisen clearSyntax(c); }else { if(c == '+') { cin.get(c); // nächstes Zeichen clearSyntax(c); } a = summand(c); // und an die Funktion summand übergeben // und das Ergebnis a zuweisen } while (c == '+' || c == '-') { if( c== '-') { cin.get(c); clearSyntax(c,true); a -= summand(c); }else { cin.get(c); clearSyntax(c); a += summand(c); } } return a; } long summand(char& c) { long s = faktor(c); while (c == '*' || c == '/') { if(c == '*') { cin.get(c); clearSyntax(c,true); s *= faktor(c); }else { cin.get(c); clearSyntax(c,true); s /= faktor(c); } } return s; } long faktor(char& c) { long f; if (c == '(') { cin.get(c); clearSyntax(c,true,false); f = ausdruck(c); if (c != ')') { cout << "Rechte Klammer fehlt!" << endl; }else { cin.get(c); clearSyntax(c); } }else { f = zahl(c); } return f; } long zahl(char& c) { long z=0; while (isdigit(c)) { z = 10*z + long(c-'0'); cin.get(c); clearSyntax(c); } return z; } void clearSyntax(char& c, bool noOP, bool noKlammer) { if (not isdigit(c)) { switch(c) { case '*': case '/': case '+': if(noOP) { cout << c << " darf nur einmal vorhanden sein und wird nicht berücksichtigt!" << endl; cin.get(c); clearSyntax(c,noOP); } break; case '-': if(noOP and noKlammer) { cout << "Klammern setzen, um ein Vorzeichen zu berücksichtigen!" << endl; cin.get(c); clearSyntax(c,noOP); } break; case '(': case ')': case char(10): case 'e': break; case ' ': cin.get(c); clearSyntax(c,noOP); break; default: cout << c << " ist nicht erlaubt und wird nicht berücksichtigt!" << endl; cin.get(c); clearSyntax(c,noOP); } } }
Hier wird die Verwendung von Funktionen gezeigt. Der Zweck des Programms ist ein Taschenrechner mit funktionierender Vorrang-Automatik. Das heist, es sind zuerst Klammern, dann Punktrechnung und dann Strichrechnung auszuwerten.
Dies wurde wie folgt realisiert:
Es werden Funktionen geschrieben, die sich gegenseitig vom allgemeinsten zum speziellsten in der Reihenfolge ausdruck-> summand-> faktor-> zahl aufrufen. In ausdruck werden die Summanden per Addition oder Subtraktion verrechnet, die in der Funktion summand aus der Multiplikation oder Division der Faktoren ermittelt werden. Die Faktoren werden in der Funktion faktor ermittelt und bestehen entweder aus einer Zahl, die in der Funktion zahl ermittelt wird oder der Auswertung eines geklammerten Ausdruckes, der durch den rekursiven Aufruf der Funktion ausdruck ermittelt wird.
zahl:
Eine Zahl kann immer direkt verarbeitet werden.
Somit wird in der Funktion Zahl die Zahl solange aus den Ziffern
zusammengesetzt, die in einer Schleife aus dem Tasteturpuffer gelesen werden,
wie es sich um Ziffern handelt. Dies wird mit der Funktion isdigit(c) geprüft,
die wir mit Hilfe von #include <cctype> am Anfang bereitgestellt haben.
Da wir im 10er-System rechnen, werden alle zuvor bearbeiteten Stellen
bei jedem Durchlauf mit jeweils 10 mutipliziert, so dass eine Ziffernfolge am
Ende immer in eine Zahl überführt wird und im zuvor per Referenz übergebenen
aktuell zu bearbeitenden Zeichen c ein nicht numerischer Wert steht.
Dieser beinhaltet gemäß Syntax immer die nächste Rechenoperation.
Dies wird durch die Funktion clearSyntax(c); sichergestellt, die alle
nicht syntaxgemäßen Zeichen überliest und eventuell Fehlermeldungen hierzu ausgibt.
Am Ende haben wir also die nächste Operation in c und geben den Zahlenwert an die
aufrufende Funktion faktor zurück.
faktor:
Einer eventuellen Punktrechnung kommt ggf. ein geklammerter Ausdruck zuvor.
Deshalb wird geprüft, ob es sich beim aktuellen
zu verarbeiteten Zeichen um eine öffnende Klammer handelt.
Ist dies der Fall wird das Zeichen zunächst wieder überlesen, um an die folgenden Zeichen heranzukommen,
wobei durch den Aufruf der Funktion clearSyntax(c); sichergestellt wird,
dass das folgende Zeichen ein gültiges Zeichen ist.
Alles was folgt ist zunächst wieder ein in sich eigenständiger
Ausdruck, der von der Funktion ausdruck von unterster Ebene,
aber eben von der Funktion Faktor rekursiv aufgerufen wird. Das bedeuted,
dass der Wert der sich aus der Verarbeitung
dieses Ausdrucks ergibt an die aufrufende Funktion Faktor in die Variable f zurückgegeben wird.
Da immer das nachfolgende Zeichen bereits in c gespeichert ist, muss dieses Zeichen gemäß Syntax
eine schließende Klammer sein. Falls also der Gesamtausdruck an dieser Stelle abgearbeitet ist
und hier keine schließende Klammer folgt, wird eine entsprechende Fehlermeldung ausgegeben.
Ansonnsten wird diese Klammer überlesen, so dass das nächste Operationszeichen ausgelesen wird.
Falls es sich nicht um einen geklammerten Ausdruck handelt, wird direkt die Funktion zahl
zur Ermittlung der Zahl aus der Ziffer und
dem Einlesen des folgenden Zeichens in die Variable c aufgerufen.
Somit ist am Ende immer das nächste Zeichen in der Variable C und
ein Zahlenwert aus entweder der Auswertung des geklammerten Ausdrucks
oder des Zahlenliterals wird an die aufrufende Funktion summand zurückgegeben.
summand:
Einer evetuellen Strichrechnung kommt ggf. eine Punktrechnung zuvor.
Am Anfang der Funktion wird immer der in c aus der Funktion ausdruck übergebene
Wert direkt an die Funktion faktor weitergeleitet.
Der Rückgabewert wird an die Variable s übergeben und enthält entsprechend immer
einen Zahlenwert, entweder, da der Wert ohnehin ein Zahlen-Literal war, oder da
über die Funktion faktor ein geklammerter Ausdruck ausgewertet und zurückggegeben wurde.
In c ist danach das Zeichen für die nachfolgende Rechenoperation.
Nun wird geprüft ob es sich dabei um Punktrechenoperatoren handelt.
Falls dies der Fall ist, wird entsprechend des Operators entweder der in der
Variable s gespeicherte bislang ermittelte Zahlenwert multipliziert oder dividiert mit
dem was die Funktion faktor aus dem nachfolgenden Zeichen,
das an der Stelle ermittelt und übergeben wird, für einen weiteren Wert ermittelt.
Das das nachfolgende Zeichen gültig ist, wird wieder durch
die Funktion clearSyntax(c); sichergestellt.
Aus faktor erhalten wir ja entweder wieder die Auswertung eines geklammerten Ausdrucks
oder eines Zahlenliterals, der in jedem Fall unserer Punktrechnung folgt
und somit direkt multipliziert, bzw, dividiert werden kann,
so dass am Ende in c wieder das nächste gültige zu verarbeitende Zeichen und
in s das entsprechnde Ergebnis aus der Punktrechnug ist, dass an die
aufrufende Funktion ausdruck zurückgegeben wird.
ausdruck:
Handelt es sich bei den in der Variable c gespeicheten Zeichen um einen
Strichrechenoperator, so wird zunächst das nächste Zeichen geholt.
Das dies gültig ist wird durch die Funktion clearSyntax(c); sichergestellt.
War das vorherige Zeichen ein Minuszeichen, dann wird die Rückgabe aus dem
Aufruf von summand negiert, sonst immer entsprechend positiv in der Variable
a gespeichert. Damit wird in a immer das positive oder negative Ergebnis aus
entweder einer nachfolgenden Punktrechnung oder einem Zahlenliteral gespeichert.
In c ist danach der nächste zu verarbeitende Rechnoperator.
Solange dies Strichrechenoperatoren sind werden die nachfolgenden Zeichen geholt,
deren Gültigkeit durch die Funktion clearSyntax(c); sichergestellt wird und
jeweils die Rückgaben aus dem erneuten Aufruf von summand
entweder wenn das Zeichen zuvor ein Minuszeichen war von a abgezogen
oder anderen falls addiert.
Am Ende wird das Ergebnis aus der Addition aller Summanden an die aufrufende
Funktion zurückgegeben. Die Aufrufende Funktion kann entweder die initiale Funktion,
in diesem Beispiel main() sein oder die Funktion faktor,
wenn es um die Auswertung eines geklammerten Ausdruckes geht.
main
Es wird zunächst das Promt ">>" ausgegeben und dann mit
cin.get(ch); Das erste Zeichen aus dem Tastaturpuffer ausgelesen.
Das es sich um ein für unsere Rechnung gültiges Zeichen handelt, wird
danach durch die Funktion clearSyntax sichergestellt.
Falls es sich nicht um das Zeichen für den Abbruch ('e') handelt,
wird die Funktion ausdruck mit dem Zeichen aufgerufen
und das zurückgegebene Ergebnis, dass das Ergebnis
der Berechnung des vollständigen Ausdruckes ist,
wird auf dem Bidschirm ausgegeben.
Der gesamte Vorgang wird solgange wiederholt, bis 'e' durch den Nutzer
eingegeben wird. Danach wird das Programm beendet.
clearSyntax
Ein Zahlenwert ist immer gültig. Falls es sich aber nicht um einen solchen handelt,
wird geprüft, ob es sich um ein gültiges Zeichen handelt.
Gültig sind die Rechenoperatoren, sofern sie keinem Rechenoperator direkt folgen,
die runden Klammern öffnend und schließend, die Ausdrucksendmarkierung char(10) und
die Eingabe für das programmende durch den Nutzer 'e'.
In allen ungültigen Fällen, wird das nächste Zeichen eingelesen und die Funktion ruft
sich zur Überprüfung dieses Zeichens selbst auf.
Am Ende befindet sich in der Variable C ein für die Rechnung gültiges Zeichen.