Funktionen

Funktionen werden hauptsächlich aus folgenden Gründen eingesetzt:

Syntax

Eine Funktion hat folgenden allgemeinen Aufbau:

RTYP FktName ([PARAMETER])
{
    ANWEISUNGEN
    [return RWERT;]
}

FktName ist der Name der Funktion und RTYP definiert den Datentyp des Wertes RWERT, den die Funktion zurückliefert. Nach dem Funktionsnamen stehen innerhalb einer Klammer die Daten PARAMETER, die an die Funktion übergeben werden.

Die Angaben in den Klammern [...] sind optional. Nach der geschweiften Klammer am Ende einer Funktion steht kein Semikolon.

Im Folgenden werden die Begriffe Parameter und Argument wie folgt verwendet:
Parameter: Datum, welches innerhalb der Klammer bei der Funktionsdefinition bzw. -deklaration steht.
Argument: Datum, welches beim Aufruf der Funktion an die Funktion übergeben wird.

void Function(int val); // Definition der Fkt.
{...}                   // val ist ein Parameter

Function(var1); // Aufruf der Fkt.
                // var1 ist das Argument

Funktionsdefinition

Der Name einer Funktion muss eindeutig sein, d.h., es darf keine weitere Funktion, Variable usw. mit dem gleichen Namen geben. Ausnahmen: Überladene Funktionen, Funktionen in einem anderen Namensraum oder inline-Funktionen, die alle später in gesonderten Kapiteln behandelt werden.

Die von einer Funktion auszuführenden Anweisungen werden in einen Block {...} eingeschlossen. Innerhalb einer Funktion können, bis auf eine Ausnahme, beliebige Anweisungen stehen. Lediglich innerhalb einer Funktion eine weitere Funktion zu definieren ist nicht erlaubt.

Der einfachste Fall einer Funktion ist eine Funktion, die keine Daten erhält und kein Ergebnis zurückliefert. Eine Funktion, die kein Ergebnis zurückliefert, besitzt den Returntyp void. Und da die Funktion keine Daten erhält, bleibt die Parameterklammer leer.

// Definition der Funktion
void PrintHeader()
{
   std::println("Seitenüberschrift");
}

Werden innerhalb einer Funktion Daten definiert, sind diese nur innerhalb der Funktion gültig.

void Func1()
{
   // Definition einer Variable und Konstante die nur
   // innerhalb dieser Funktion gültig sind
   int temp;
   constexpr auto PI = 3.1416;
   // weitere Anweisung
}

Funktionsaufruf

Fehlt noch die Anweisung um die Funktion aufzurufen. Der Aufruf der Funktion erfolgt durch Angabe des Funktionsnamens, gefolgt von einer Klammer () mit den optionalen Argumenten und einem abschließenden Semikolon.

#include <print>

// Definition der Funktion
void PrintHeader()
{
   std::println("Tabellenueberschrift");
}

int main()
{
   // Funktion aufrufen
   PrintHeader();
}

Tabellenueberschrift

Funktionsdeklaration (Signatur)

Bevor eine Funktion aufgerufen werden kann, muss sie entweder vorher definiert oder deklariert sein. Der Unterschied zwischen einer Funktionsdefinition und einer Funktionsdeklaration ist folgender:

#include <print>

// Funktionsdeklaration
void PrintFooter();

// Funktionsdefinition
void PrintHeader()
{
   std::println("Kopfzeile");
}

int main()
{
   // PrintHeader benoetigt keine Fkt-Deklaration
   // da sie vor dem Aufruf definiert ist
   PrintHeader();
   // PrintFooter erfordert aber eine Fkt-Deklaration
   // da die Fkt-Definition nach dem Fkt-Aufruf steht
   PrintFooter();
}

// Funktionsdefinition
void PrintFooter()
{
    std::println("Fusszeile");
}

Kopfzeile
Fusszeile

Im Zusammenhang mit Funktionen ist der Begriff Funktionssignatur zu erwähnen; er spielt später beim Überladen von Funktionen eine wichtige Rolle. Die Signatur einer Funktion besteht aus dem Funktionsnamen und den Datentypen der Parameter, aber ohne den Returntyp der Funktion.

Funktionsparameter

Parameter im Allgemeinen

Mithilfe von Parametern können Daten an eine Funktion übergeben werden.

Werden Daten an eine Funktion übergeben, sind bei der Deklaration der Funktion zumindest die Datentypen der Parameter anzugeben. Zusätzlich können (und sollten auch!) die Parameter nach dem Datentyp mit einem sinnvollen Namen versehen werden. Benötigt eine Funktion mehrere Parameter, sind diese durch Komma voneinander zu trennen.

Im nachfolgenden Beispiel erhält die Funktion PrintIt() im ersten Parameter die X-Position, im zweiten Parameter die Y-Position und im dritten Parameter den Text für die Ausgabe.

void CalcSqrt (double); // gleichbedeutend mit
                        // void CalcSqrt (double val);
void PrintIt (short xPos, short yPos,
              const char *pText);

Bei der Definition der Funktion sind, genauso wie bei der Deklaration, die Datentypen der Parameter anzugeben, nun jedoch zwingend gefolgt von Parameternamen. Innerhalb der Funktion wird über die Parameternamen auf die an die Funktion übergebenen Daten zugegriffen. Die Parameternamen bei der Deklaration und der Definition der Funktion müssen nicht zwingend übereinstimmen, der besseren Lesbarkeit wegen sollten sie es aber.

Im Beispiel ist eine Funktion für die Ausgabe eines Seitenkopfes aufgeführt.

#include <print>

// Funktionsdeklaration
void PrintHeader(int pageNum);

int main ()
{
   // hier folgt gleich der Fkt-Aufruf
}

// Definition der PrintHeader-Funktion
// erhält die Seitennummer als Parameter
void PrintHeader(int pageNum)
{
   std::println("Seite {}", pageNum);
}

Die Reihenfolge und Anzahl der Datentypen der Parameter bei der Funktionsdeklaration und -definition müssen übereinstimmen!

Prinzipiell gibt es zwei Arten von Parameter: call-by-value Parameter und Referenzparameter.

call-by-value Parameter

Bei einem Parameter vom Typ call-by-value erhält die aufgerufene Funktion eine Kopie des beim Funktionsaufruf angegebenen Datums. Daraus folgt, dass Änderungen des Parameterwerts innerhalb der Funktion sich nur auf die Kopie auswirken. Die Definition eines call-by-value Parameters erfolgt in der Art, dass innerhalb der Parameterklammer der Funktion lediglich der Datentyp und Name des Parameters angegeben wird.

Beim Aufruf der Funktion kann für einen call-by-value Parameter entweder eine Variable oder eine Konstante an die Funktion übergeben werden

#include <print>

// Funktionsdeklaration
void PrintHeader(int pageNum);

int main ()
{
   int page = 1;    // Seitennummer def/init

   // Gebe alle Seiten aus solange
   // Seitenummer kleiner 4
   do
   {
       PrintHeader(page);   // Funktion aufrufen
       page++;              // Seitenummer erhoehen
   } while (page<4);

   PrintHeader(10);         // Funktionsaufruf mit Konstante
}

// Definition der PrintHeader-Funktion
void PrintHeader(int pageNum)
{
   std::println("Seite {}", pageNum);
   pageNum = 99;    // Parameter verändern
}

Seite 1
Seite 2
Seite 3
Seite 10

Da die Funktion Kopien der übergebenen Daten erhält, ist es bei dieser Parameterart unter Umständen notwendig viele Daten zu duplizieren. Dies gilt insbesondere dann, wenn eine Funktion später Objekte als Parameter erhält. Diese Art der Parameterübergabe sollte nach Möglichkeit auf die Grunddatentypen beschränkt werden.

Referenzparameter

Ein Referenzparameter ermöglicht Funktionen, ein übergebenes Datum dauerhaft zu verändern, sodass innerhalb der Funktion durchgeführte Änderungen an einem Datum auch nach dem Verlassen der Funktion gültig sind.

Um einen Parameter als Referenzparameter zu kennzeichnen, wird bei der Deklaration und der Definition der Funktion nach dem Datentyp des Parameters der Operator & angefügt. Beim Aufruf der Funktion wird ein Referenzparameter wie ein Parameter vom Typ call-by-value übergeben.

Da die per Referenz übergebenen Daten innerhalb der Funktion verändert werden können, können erst einmal keine Konstanten per Referenz übergeben werden.

Das vorherige Beispiel nun mit einem Referenzparameter anstelle eines call-by-value.

#include <print>

// Funktionsdeklaration
void PrintHeader(int& pageNum);

int main ()
{
   int page = 1;    // Seitennummer def/init

   // Gebe alle Seiten aus solange
   // Seitenummer kleiner 4
   do
   {
       PrintHeader(page);   // Funktion aufrufen
       page++;              // Seitenummer erhoehen
   } while (page<4);
   // Fkt-Aufruf wuerde einen Fehler erzeugen
   // PrintHeader(10);         // Funktionsaufruf mit Konstante
}

// Definition der PrintHeader-Funktion
void PrintHeader(int& pageNum)
{
   std::println("Seite {}", pageNum);
   pageNum = 99;    // Parameter verändern
}

Seite 1

Konstante Referenzparameter

In vielen Fällen ist es durchaus sinnvoll, Daten per Referenz zu übergeben (damit kein Kopiervorgang der Daten notwendig wird), aber innerhalb der Funktion keine Änderung dieser Daten zuzulassen.

Um einen Referenzparameter als nicht veränderbar innerhalb einer Funktion zu kennzeichnen, wird dem Datentyp des Referenzparameters das Schlüsselwort const vorangestellt. Jeder Versuch, einen als const definierten Referenzparameter innerhalb der Funktion zu verändern, führt zu einem Übersetzungsfehler. An Funktionen mit konstanten Referenzparameter können dann auch Konstanten übergeben werden.

#include <print>

// Funktionsdeklaration
void PrintHeader(const int& pageNum);

int main ()
{
   int page = 1;    // Seitennummer def/init

   // Gebe alle Seiten aus solange
   // Seitenummer kleiner 4
   do
   {
       PrintHeader(page);   // Funktion aufrufen
       page++;              // Seitenummer erhoehen
   } while (page<4);

   PrintHeader(10);         // Funktionsaufruf mit Konstante
}

// Definition der PrintHeader-Funktion
void PrintHeader(const int& pageNum)
{
   std::println("Seite {}", pageNum);
   // Die wuerde nun zu einem Fehler fuehren
   // pageNum = 99;    // Parameter verändern
}

Seite 1
Seite 2
Seite 3
Seite 10

Kennzeichnen Sie grundsätzlich alle Parameter, die innerhalb einer Funktion nicht verändert werden und die nicht per call-by-value übergeben werden, als const. In der englisch-sprachigen Literatur wird dies als const-correctness bezeichnet.

Der Datentyp bei Parametern kann auch durch auto ersetzt werden, also z.B. auto Add(auto v1, auto v2) um zwei Zahlen zu addieren. Eine solchermaßen definierte Funktion ist ein Funktionstemplate und wird im Kapitel Funktions- und Variablentemplates beschrieben.

Übergabe von eindimensionalen Feldern

Um ein eindimensionales Feld an eine Funktion zu übergeben, wird i.d.R. dessen Anfangsadresse übergeben. Wie im Kapitel über Felder erwähnt, ist der Name eines eindimensionalen Feldes gleichbedeutend mit einem Zeiger auf den Beginn des Feldes. Und somit erhält die Funktion durch die Angabe des Feldnamens als Argument beim Aufruf einen Zeiger auf den Beginn des Feldes.

Der Zugriff auf die Feldelemente innerhalb der Funktion kann entweder durch dereferenzieren des Parameters (Zeiger!) oder per Indizierung erfolgen.

#include <print>

// Funktionsdeklaration
void DoSomething(const short* const ptr);

int main ()
{
   // Felddefinition und Initialisierung
   short data[] {10,20,30,40};
   // Übergabe des Feldes an die Funktion
   DoSomething(data);
}

// Funktionsdefinition
void DoSomething (const short* const ptr)
{
   // Zugriff auf 1. Element durch dereferenzieren
   std::println("1. Element: {}",*ptr);
   // Zugriff auf 3. Element durch dereferenzieren
   std::println("3. Element: {}",*(ptr+2));
   // Zugriff auf 2. Element über Index
   std::println("2. Element: {}",ptr[1]);
}

1. Element: 10
3. Element: 30
2. Element: 20

Und noch ein gut gemeinter Rat: Wird innerhalb der Funktion der Inhalt des Feldes nicht verändert, sollte das Feld als const übergeben werden. Im Beispiel oben erhält die Funktion DoSomething() einen konstanten Zeiger auf ein konstantes Feld. Die Funktion kann somit weder den Zeiger noch den Inhalt des durch den Zeiger adressierten Feldes verändern.

Alternativ kann bei der Deklaration und Definition der Funktion für den Parameter anstelle eines Zeigers auch Folgendes angeben:

void DoSomething (short arr[4]);    // arr ist Zeiger
void DoSomething (short arr[]);     // arr ist Zeiger
void DoSomething (short (&arr)[]);  // arr ist eine Referenz!!

Bei einer Übergabe eines Feldes per Referenz ist der Operator & und der Parametername in Klammer einzuschließen. Ohne die Klammern wäre arr ein Feld von Referenzen.

Übergabe von mehrdimensionalen Feldern

Bei der Deklaration und Definition der Funktion sind nun zwingend die Felddimensionen mit anzugeben. Lediglich die Angabe der ersten Dimension ist optional (siehe nachfolgendes Beispiel). Die Übergabe des Feldes beim Aufruf der Funktion bleibt gleich wie bei eindimensionalen Feldern, d.h. in der Parameterklammer der Funktion steht lediglich der Name des zu übergebenden Feldes.

#include <print>

// Feld definieren/initialisieren
const int ROWS=2;
const int COLUMNS=3;
short data[ROWS][COLUMNS] =
    { { 1, 2, 3},
      {10,20,30}
    };

// Funktionsdeklaration
void PrintVal(short arr[][COLUMNS]);

int main()
{
   // Funktion aufrufen
   PrintVal(data);
}
// Funktionsdefinition
void PrintVal(short arr[][COLUMNS])
{
   // Zugriff auf Feldelement
   std::println("Element[0][2]: {}", arr[0][2]);
}

Element[0][2]: 3

deleted function

Soll beim Aufruf einer Funktion eine implizite Typkonvertierung von Parametern verhindert werden, kann eine sogenannte deleted function deklariert werden. Sehen wir uns dies anhand eines Beispiels an:

#include <print>

// Funktionsdefinition
void DoAnything (short val)
{
    std::println("Wert: {}",val);
}
int main()
{
   short sVal = 10;
   long lVal = 1234567L;
   DoAnything(sVal); // Aufruf mit short-Argument
   DoAnything(lVal); // Aufruf mit long-Argument
}

Wert: 10
Wert: -10617

Die Funktion DoAnything() erwartet als Parameter einen short-Wert. Wird die Funktion z.B. mit einem mit einem long-Wert aufgerufen, erfolgt eine automatische Typkonvertierung des long-Werts in einen short-Wert, was (wie im Beispiel) zu einem fehlerhaften Verhalten des Programms führen kann. Um eine solche automatische Typkonvertierung beim Aufruf einer Funktion zu verhindern, wird eine Funktion als deleted function deklariert wie nachfolgend dargestellt.

#include <print>

// Funktionsdefinition
void DoAnything (short val)
{
    std::println("Wert: {}",val);
}
// Deklaration einer deleted function
void DoAnything (long) = delete;

int main()
{
   short sVal = 10;
   long lVal = 1234567L;
   DoAnything(sVal); // Aufruf mit short-Argument
   // Beiden folgenden Aufrufe fuehren nun zu einem
   // Fehler beim Uebersetzen
   DoAnything(lVal); // Aufruf mit long-Argument
   DoAnything(10);   // Aufruf mit int-Konstante
}

Und auch der 3. Aufruf der Funktion führt zu einem Übersetzungsfehler, da der Funktionsaufruf nicht eindeutig ist. Ein int-Wert kann ja implizit sowohl in einen short- wie auch in einen long-Wert konvertiert werden und der Aufruf mit einem long-Parameter wurde explizit unterbunden.

Funktionsrückgabewert

Außer dass Funktionen Daten per Parameter erhalten können, können Funktionen ein Datum zurückliefern, den sogenannten Returnwert. Die Rückgabe des Wertes erfolgt mit einer return-Anweisung, wobei nach dem Schlüsselwort return das zurückzugebende Datum folgt. Innerhalb einer Funktion können, nicht schön aber erlaubt, mehrere return-Anweisungen stehen. 

Später werden Sie ein Verfahren kennenlernen, welches es erlaubt, mehrere Werte in einem Datum zurückzugeben. Dazu werden die Werte in einem sogenannten compound Datentyp abgelegt.

Unabhängiger Datentyp des Rückgabewertes

Beim unabhängigen Datentyp des Rückgabewertes ist dessen Datentyp nicht vom Datentyp eines Parameters abhängig.

Der Datentyp des Returnwerts wird i.d.R. vor dem Funktionsnamen angegeben, wobei der Datentyp des zurückzugebenden Wertes mit dem Returndatentyp der Funktion übereinstimmen sollte. Stimmen die Datentypen nicht überein, versucht der Compiler den Returnwert in den Returntyp der Funktion zu konvertiert (siehe auch Typkonvertierungen (Promotions)).

#include <print>

// Funktionsdeklaration
float Deg2Rad (float deg);

int main ()
{
   std::println("90 Grad = {} RAD", Deg2Rad(90.0f));
}
// Funktionsdefinition
// Umrechnung von Grad in Bogenmass
// Liefert umgerechneten Wert
float Deg2Rad (float deg)
{
   // Umrechnungsformel Grad->Bogenmass
   constexpr decltype(deg) CONV = 3.1416*2.0 / 360.0;
   decltype(deg) result = deg * CONV;
   // Umgerechneten Wert zurueckgeben
   return result;
}

90 Grad = 1.5708 RAD

Besitzen alle Returnwerte denselben Datentyp, kann der Compiler den Returntyp der Funktion bestimmen. Dazu wird als Returntyp auto angegeben. Der Returntyp entspricht dann dem Datentyp des Wertes in der return-Anweisung.

auto FktName ([PARAMETER])
{
   ... // Anweisungen
   return RETURNWERT;
}

Bei diesem Funktionstyp muss die Funktion vor ihrem Aufruf definiert sein, da eine Funktionsdeklaration ist nicht möglich.

Vorsicht ist geboten, wenn der Returntyp der Funktion mittels decltype(auto) bestimmt wird.

// Liefert Wert aus theData zurueck
auto ValFunc(int index)
{
   static int theData[] {1,2,3};
   return theData[index];
}
// Liefert Referenz auf das Element
// theData[index] zurueck
decltype(auto) RefFunc(int index)
{
   static int theData[] {10,20,30};
   return theData[index];
}

Die erste Funktion ValFunc() liefert einen Wert aus dem Feld theData zurück, während die zweite Funktion RefFunc() eine Referenz auf ein Feldelement zurückliefert. Dieses unterschiedliche Verhalten der beiden Funktionen liegt darin begründet, dass auto u.a. die Referenz entfernt.

Abhängiger Datentyp des Rückgabewertes

Hängt der Datentyp des Rückgabewertes vom Datentyp eines Parameters ab, kann eine Funktion wie folgt definiert werden:

auto FktName ([PARAMETER]) ->RTYP
{
   ... // Anweisungen
   return RETURNWERT;
}

Beispiel:

#include <print>

// Funktion zur Umrechnung Grad->RAD
auto Deg2Rad (float deg) ->decltype(deg)
{
    constexpr decltype(deg) CONV = 3.1416*2.0 / 360.0;
    decltype(deg) result = deg * CONV;
    return result;
}

int main()
{
    float fVal = 45.f;
    std::println("{} Grad in RAD: {}",fVal, Deg2Rad(fVal));
}

45 Grad in RAD: 0.7854

Ändert sich der Datentyp des Parameters deg, ändert sich automatisch auch der Datentyp des Returnwertes entsprechend.

Diese Art der Funktionsdefinition kann ebenfalls bei einem fixen Returntyp verwendet werden.

auto AnyFunc(short para1) -> float;

Predicates

Funktionen, die ein boolsches Ergebnis zurückliefern, werden als Predicate bezeichnet und spielen später bei der Behandlung der Container und Algorithmen der C++-Standardbibliothek eine wichtige Rolle.

Unäres Predicate: eine Funktion mit einem Parameter und einem bool-Wert als Rückgabewert.

bool IsZero(int val)
{
    return (val == 0); 4:
}

Binäres Predicate: eine Funktion mit zwei Parameter und einen bool-Wert als Rückgabewert.

bool IsLesser(int val, int limit)
{
    return (val<limit);
}

Rekursive Funktionen

Da innerhalb einer Funktion (bis auf die eine erwähnte Ausnahme) alle Anweisungen erlaubt sind, können Funktionen wiederum Funktionen aufrufen. Ein Sonderfall stellt hierbei eine Funktion dar, die sich selbst aufruft. Solch eine Funktion wird als rekursive Funktion bezeichnet. Rekursive Funktionen benötigen immer ein Abbruchkriterium, um eine Endlos-Schleife zu vermeiden.

Nachfolgend ein Beispiel für eine solche rekursive Funktion.

#include <iostream>

// Funktionsdeklaration
void PrintLine(const short count);

int main ()
{
   // Funktionsaufruf
   PrintLine(4);
}

// Funktion PrintLine(), ruft sich selbst auf!
void PrintLine(const short count)
{
   // Sternchen und Zeilenvorschub ausgeben
   for (auto index=0; index < count; index++)
      std::cout << " *";
   std::cout << '\n';
   // Falls mehr als 1 Sternchen ausgegeben wurde
   if (count != 1)
      // Funktion erneut aufrufen, jetzt
      // jedoch mit einem Sternchen weniger
      PrintLine(count-1);
   // Nach dem letzten Sternchen fertig!
   return;
}

* * * *
* * *
* *
*

Die Funktion PrintLine() erhält von main() die Anzahl der in einer Reihe auszugebenden Sternchen. Nachdem die Sternchen in der for-Schleife ausgegeben sind, wird in der darauffolgenden if-Abfrage abgeprüft, ob mehr als 1 Sternchen ausgegeben wurde. Ist dies der Fall, ruft die Funktion sich erneut auf, jetzt mit einem Sternchen weniger. Dieses Spiel wiederholt sich so lange, bis nur ein Sternchen ausgegeben wird. In diesem Fall liefert die Bedingung der if-Abfrage false und die Funktion wird über die return-Anweisung beendet. Die return-Anweisung könnte hier entfallen, da sie die letzte Anweisung in der Funktion ist. Somit ergibt sich folgende Aufrufsequenz der Funktion:

PrintLine(4);
PrintLine(3);
PrintLine(2);
PrintLine(1);

constexpr- und consteval-Funktionen

Bei einer constexpr-Funktionen versucht der Compiler den Returnwert der Funktion zu berechnen und diesen anstelle des Funktionsaufrufs einzusetzen. Kann der Returnwert nicht durch den Compiler berechnet werden, wird die Funktion wie gewohnt instanziiert und zur Laufzeit aufgerufen. Um eine Funktion als constexpr-Funktion zu definieren, wird vor dem Returntyp das Schlüsselwort constexpr angegeben.

Doch was kann eine constexpr-Funktion leisten, wenn deren Ergebnis zur Compilezeit berechnet werden soll? Sehen Sie sich das folgende Programm an, bei dem Compiler für beliebige int-Daten die Fakultät berechnet.

#include <print>

// Fakultät berechnen
constexpr int fac(int val)
{
   return (val <= 1) ? 1: val*fac(val-1);
}

int main()
{
   // Fakultaeten ausgeben berechnen
   // (Compilezeit-Ausdruck!)
   std::println("Fakultaet von 5: {}",fac(5));
   std::println("Fakultaet von 3: {}",fac(3));
}

Fakultaet von 5: 120
Fakultaet von 3:

Die Funktion für die Fakultät ist eine rekursive Funktion, d.h., der Aufruf von fac(5) erzeugt folgende Aufrufe:

fac(5) = 5*fac(4)
fac(4) = 4*fac(3)
fac(3) = 3*fac(2)
fac(2) = 2*fac(1)
fac(1) = 1

Einen weiteren Einsatz einer constexpr-Funktion finden Sie später bei der Behandlung von überladenen Operatoren.

Die consteval-Funktion gleich prinzipiell der constexpr-Funktion, nur mit der Einschränkung, dass das Ergebnis der Funktion nun zur Compilezeit berechenbar sein muss. Damit gelten für consteval-Funktionen folgende Einschränkungen:

constexpr int exprFunc(int val)
{
   return val * val;
}
consteval int evalFunc(int val)
{
   return val * val;
}
int main()
{
   auto var = 10;
   auto exprVar = exprFunc(var);  // ok
   auto evalVar1 = evalFunc(var); // Fehler!
   auto evalVar2 = evalFunc(10);  // ok
}

Sonstiges zu Funktionen

Zum Schluss einige Hinweise auf Funktionseigenschaften, auf die später noch eingegangen wird:

Und zu guter Letzt gibt es Zeiger auf Funktionen. Was es damit auf sich hat, das erfahren Sie im Anhang H: Funktionszeiger.

Übungen

funk_01:

Schreiben Sie eine Funktion die für ein zu übergebendes unsigned char-Feld eine Prüfsumme (ebenfalls als unsigned char) berechnet und diese zurückliefert. Die Prüfsumme ist durch Exklusiv-Veroderung aller Feldelemente zu berechnen.

Schreiben Sie eine zweite Funktion, die als Parameter ein Feld sowie die dazugehörige Prüfsumme erhält und prüft, ob alle Daten im Feld gültig sind. Wenn die Daten gültig sind, soll die Funktion true zurückliefern und ansonsten false.

Definieren Sie in main() ein beliebig großes unsigned char-Feld und belegen es mit Zufallszahlen.

Berechnen Sie die Prüfsumme für dieses Feld. Anschließend überprüfen Sie die Daten auf ihre Gültigkeit.

Ändern Sie ein beliebiges Element des Datenfeldes und prüfen dann die Daten nochmals auf ihre Gültigkeit.

Daten sind in Ordnung.
Daten sind fehlerhaft!

funk_02:

Es ist eine Funktion zur Berechnung des Sinus zu erstellen. Der Sinus ist per Reihenentwicklung nach folgender Formel zu berechnen:

sin(x) = x - (x^3)/3! + (x^5)/5! - (x^7)/7!

Für die Berechnung der Potenz und der Fakultät werden sinnvollerweise ebenfalls Funktionen verwendet.

Berechnen Sie dann den Sinus einmal mit der erstellten Funktion und einmal mit der std::sin() Funktion in der Standardbibliothek. Die Sinusfunktion in der Standardbibliothek hat folgende Syntax:

DTYP erg = sin(DTYP val);

wobei DTYP ein beliebiger Gleitkomma-Datentyp ist. Binden Sie für den Sinus die Header-Datei cmath ein.

Geben Sie die berechneten Wert für den Sinus von 1.4 sowie die Differenz zwischen den Werten aus.

MySin: 0.9854, std:sin(): 0.9854
Differenz: -5.593e-05