Funktionen

Zweck

Mit Funktionen ist es möglich bestimmte Programmteile auszulagern. Dadurch:

nach oben

Syntax

Die Funktion wird wie folgt definiert:

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

nach oben

Deklaration

Funktionen müssen vor der Stelle an der sie aufgerufen werden, also im Programmtext darüber, deklariert werden. Dies kann etweder geschehen, indem man die komplette Funktion darüber definiert, oder man deklariert sie zunächst nur oberhalb. Funktionen am Programmstart zunächst zu deklarieren erhöht die Übersichtlichkeit, da mann einen schnellen Überblick über die deklarierten Funktionen erhält. Dazu wird einfach nur der Funktionskopf mit den Typendefinitionen ohne den Anweisungsblock angegben:

	void hilfe(void);
	int groesser(int,int);

nach oben

Variablen-Gültigkeit

Für lokale Variablen gilt Folgendes: Bei gleichnamigen Variablen ist immer die lokalste Variable gültig, also die, die dem Anweisungsblock am nächsten steht.

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.

nach oben

statische Variablen

Wird innerhalb einer Funktion eine Variable definiert und initialisiert, so erhält sie bei jedem Aufruf der Funktion den Initialwert. Möchte man, das die Variable nur beim ersten Aufruf der Funktion definiert und initialisiert wird, so muss man die Variabel als statisch festlegen: Dabei gilt,Statische Variablen müssen schon bei ihrer Deklaration initialisiert werden!

	static int i = 1;

nach oben

Speicherklassen-Spezifizierer für Funktionen

extern ist das Standardverhalten und wird ggf. nur zum besseren Verständis bei der Deklaration gesetzt. Die Funktionsdefinition kann sich auch in einer anderen Quelldatei des Programms befinden, worauf man durch das Voranstellen dieses Schlüsselwortes expliziet hinweist.

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.

nach oben

Datentausch zwischen Funktionen

  • Call by Value
  • Call by Referenz
  • Optionale Parameter
  • Rückgabewert
  • Call by Value

    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);
    

    nach oben

    Call by Referenz

    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!

    nach oben

    Optionale Parameter

    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);
    

    nach oben

    Rückgabewert

    Die Funktion kann durch die return-Anweisung einen Wert zrückgeben (Funktion nicht von Typ void) und wird durch diese auch beendet:

    	return WERT;
    

    nach oben

    Überladen von Funktionen

    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);
    

    nach oben

    main-Funktion

    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.

    nach oben

    Beispiel
    Quellcode

    zur Erklärung

    #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);
    		}
    	}
    }
    
    

    nach oben

    Erklärung zum Code

    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.

    nach oben