C++ Tutorial

 Funktionen

Aufbau einer Funktion

Eine Funktion fasst mehrere Anweisungen unter einem eindeutigen Namen zusammen. Diese Anweisungen können dann durch Angabe des Funktionsnamens von (fast) jeder Stelle im Programm beliebig oft ausgeführt werden.

Eine Funktion besteht aus vier Teilen: dem Returntyp RTYP, dem Funktionsname FNAME, den Parametern PARAMETER und den Anweisungen der Funktion.

RTYP FNAME ([PARAMETER])
{

   Anweisungen der Funktion
   [return [RETURNWERT];]
}

Die Angaben in eckigen Klammer sind optional.

Bevor eine Funktion aufgerufen werden kann, muss sie vorher definiert (sprich: der Funktionscode muss bekannt sein) oder deklariert sein. Bei einer Funktionsdeklaration wird lediglich der Funktionskopf, bestehend aus dem Returntyp, dem Funktionsnamen und den optionalen Parametern sowie einem abschließenden Semikolon angegeben.

Um eine Funktion aufzurufen, wird der Funktionsname gefolgt von einer Klammer ( ) mit den optionalen Argumenten und einem abschließenden Semikolon angegeben.

Funktion ohne Rückgabewert und ohne Parameter

Bei einer Funktion die keinen Wert zurückliefern und keine Daten erhält, ist als Returntyp void einzusetzen und die Parameterklammer bleibt leer.

#include <iostream>
#include <format>

// Funktionsdeklaration, nur notwendig wenn der
// Funktionsaufruf vor der Funktionsdefinition steht
void Fakultaet();

int main()
{
   // Funktionsaufruf
   Fakultaet();
}

// Funktionsdefinition
// Berechnet die Fakulaet von 5 und gibt diese aus
void Fakultaet()
{
   unsigned long fac = 1;
   for (unsigned long index=2; index<6; index++)
      fac *= index;
   std::cout << std::format("Fakultaet von 5 = {}\n",fac);
}

Funktion mit Rückgabewert

Eine Funktion kann ein Datum zurückliefern. Die Rückgabe des Datums erfolgt mit der return-Anweisung, wobei nach dem Schlüsselwort return das zurückzugebende Datum angegeben wird. Der Datentyp des zurückgegebenen Datums sollte dabei mit dem Returntyp der Funktion übereinstimmen. Stimmen die Datentypen nicht überein, versucht der Compiler das zurückzugebende Datum in den Returntyp der Funktion zu konvertieren.

#include <iostream>
#include <format>

// Funktionsdefinition
// Berechnet die Fakulaet von 5 und gibt diese zurueck
// Jetzt ist keine Funktionsdeklaration mehr notwendig,
// da die Funktion vor deren Aufruf definiert ist.
unsigned long Fakultaet()
{
   unsigned long fac = 1;
   for (unsigned long index=2; index<6; index++)
      fac *= index;
   return fac;
}

int main()
{
   // Funktionsaufruf, Ergebnis in res ablegen
   auto res = Fakultaet();
   // Ergebnis ausgeben
   std::cout << std::format("Fakultaet von 5 = {}\n",res);
}

Funktion mit Parameter

An eine Funktion können beliebig viele Daten übergeben werden. Dazu werden bei der Definition der Funktion die Datentypen der Parameter und die Parameternamen in einer komma-separierten Liste aufgelistet.

Wird die Funktion nur deklariert, müssen nur die Datentypen der Parameter angegebenen werden. Die Angabe von Parameternamen ist hier optional, sollten jedoch der besseren Lesbarkeit wegen immer vorhanden sein.

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 Parameterwertes zwar innerhalb der Funktion erlaubt sind, diese sich aber nur auf die übergebene Kopie auswirken.

#include <iostream>
#include <format>

// Funktionsdefinition
// Berechnet die Fakulaet und gibt diese zurueck
unsigned long Fakultaet(unsigned long value)
{
   unsigned long fac = 1;
   for (unsigned long index=2; index<=value; index++)
      fac *= index;
   // Nur zur Demonstration!
   // Aenderung hat keine Auswirkung auf das
   // Datum in main()
   value = 0;
   return fac;
}

int main()
{
   // Daten definieren/initialiseren
   unsigned long var1 = 5;
   // Funktionsaufruf, Ergebnis in res ablegen
   auto res = Fakultaet(var1);
   // Ergebnis ausgeben
   std::cout << std::format("Fakultaet von {} = {}\n",var1, res);
   // Funktion erneut aufrufen und Ergebnis ausgeben
   res = Fakultaet(++var1);
   std::cout << std::format("Fakultaet von {} = {}\n",var1, res);
}

Referenzparameter

Ein Referenzparameter ermöglicht einer Funktion Änderungen an einem übergebenen Datum dauerhaft vorzunehmen. Die Funktion erhält hierbei keine Kopie, sondern eine Referenz (Verweis) auf das übergebene Datum.

Ein Referenzparameter wird dadurch gekennzeichnet, dass nach dem Datentyp des Parameters der Referenzoperator & folgt.

#include <iostream>
#include <format>

// Funktion zum Tauschen des Inhalts
// zweier uebergebenen int-Variablen
void Swap(int& var1, int& var2)
{
   int temp = var2;
   var2 = var1;
   var1 = temp;
}

int main()
{
   // 2 int-Werte einlesen
   int var1, var2;
   std::cout << "Bitte zwei int-Werte eingeben: ";
   std::cin >> var1 >> var2;
   std::cout << std::format("{},{} vertauscht: ",var1,var2);
   // int-Werte vertauschen und ausgeben
   Swap(var1,var2);
   std::cout << std::format("{},{}\n",var1,var2);
}

Konstante Referenzparameter

In vielen Fällen ist es durchaus sinnvoll, ein Datum per Referenz zu übergeben (damit kein Kopiervorgang der Daten notwendig wird), aber innerhalb der Funktion keine Änderung des Datums zuzulassen. Um einen Referenzparameter als nicht veränderbar zu kennzeichnen, wird dem Datentyp des Referenzparameters das Schlüsselwort const vorangestellt. Damit ist es dann auch möglich, Referenzen auf Literale an eine Funktion zu übergeben.

// Programm zaehlt wie oft der Buchstabe 'e'
// in einem eingegebenen Text vorkommt

#include <iostream>
#include <format>
#include <string>

// Funktionsdefinition
// Erhaelt als Parameter den eingegebenen Text
int CountE(const std::string& text)
{
   int counter = 0; // Buchstabenzaehler
   // Den gesamten Text durchlaufen
   for (std::size_t index=0; index<text.size(); index++)
   {
      // Wenn ein 'e' gefunden, Zaehler erhoehen
      if ((text[index]=='e') || (text[index]=='E'))
         counter++;
   }
   // Zaehler zurueckgeben
   return counter;
}

int main()
{
   // Text einlesen
   std::string input;
   std::cout << "Bitte einen Text eingeben: ";
   std::getline(std::cin,input);
   // Anzahl der 'e' berechnen und ausgeben
   auto res = CountE(input);
   std::cout << std::format("{} e in der Eingabe.\n",res);
}

Felder als Parameter

Um ein eindimensionales Feld an eine Funktion zu übergeben, wird 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.

Der Zugriff auf die Feldelemente innerhalb der Funktion kann entweder durch Dereferenzierung des übergebenen Zeigers oder indiziert erfolgen.

#include <iostream>
#include <format>

// Ausgabe eines Feldes
// pArray: Zeiger auf int-Feld
// size: Anzahl Feldelemente
void PrintArray(int *pArray, unsigned int size)
{
   std::cout << "Feldinhalt:\n";
   // Gesamtes Feld indiziert ausgeben
   // Besser waere hierfuer ein range-for Schleife
   for (auto index = 0U; index < size; index++)
      std::cout << std::format("{},",pArray[index]);
   // 1. und letztes Element ausgeben per Derefenzierung
   std::cout << std::format("\n1. Element: {}\n",*pArray);
   std::cout << std::format("Letztes Element: {}\n",*(pArray + size - 1));
}

int main()
{
   // Auszugebendes Feld und Feldgroesse berechnen
   int intArray[]{ 1,2,3,4 };
   constexpr unsigned int SIZE = sizeof(intArray) / sizeof(intArray[0]);
   // Feld ausgeben
   PrintArray(intArray, SIZE);
   // Alternativ: PrintArray(&intArray[0], SIZE);
}

constexpr und consteval Funktion

Die Besonderheit einer constexpr Funktion ist, dass der Compiler beim Aufruf der Funktion versucht das Ergebnis der Funktion zu berechnen und dieses anstelle des Funktionsaufrufs einzusetzen. Damit der Compiler dies kann, muss jeder Parameter ein zur Compilezeit berechenbarer Literaltyp sein und der Returnwert muss ebenfalls ein Literaltyp sein.

Um eine Funktion als constexpr Funktion zu deklarieren bzw. zu definieren, wird vor dem Returntyp das Schlüsselwort constexpr gestellt.

Die consteval Funktion gleich prinzipiell der constexpr Funktion, nur mit der Einschränkung, dass das Ergebnis der Funktion nun zur Compilezeit berechenbar sein muss.

#include <iostream>
#include <format>
#include <numbers>

// Berechnung der Fakultaet
// Dies ist eine rekursive Funktion, dh. sie ruft sich
// solange selbst wieder auf, bis der Parameter val
// den Wert 1 annimmt
constexpr unsigned long Fakul(unsigned long val)
{
   if (val <= 1)
      return 1;
   else
      return val * Fakul(val - 1);
}
// Umrechung von Grad in Bogenmass
// 360 Grad = 2 PI
consteval double Deg2Rad(double deg)
{
   return 2. * std::numbers::pi / 360. * deg;
}

int main()
{
   // Fakultaeten berechnen
   unsigned long var = 3UL;
   // Das kann der Compiler im voraus berechnen
   std::cout << std::format("F(2): {}\n", Fakul(2UL));
   // Und das wird zur Programmlaufzeit berechnet
   std::cout << std::format("F(5): {}\n", Fakul(var));
   // Umrechnung Grad in Bogenmass
   std::cout << std::format(" 90 Grad = {:.5f}\n", Deg2Rad(90));
   std::cout << std::format("360 Grad = {:.5f}\n", Deg2Rad(360));
   // Das geht aber nicht!
   //    double val = 180.;
   //    std::cout << std::format("360 Grad = {:.5f}\n", Deg2Rad(val));
}