Funktionen (C++)

Eine Funktion ist ein Codeblock, der einige Vorgänge ausführt. Eine Funktion kann optional Eingabeparameter definieren, die Aufrufern ermöglichen, Argumente in die Funktion weiterzugeben. Eine Funktion kann einen Wert optional als Ausgabe zurückgeben. Funktionen sind für das Kapseln allgemeiner Vorgänge in einem einzelnen wiederverwendbaren Block nützlich, und zwar ideal unter Verwendung eines Namens, der deutlich das beschreibt, was die Funktion vornimmt. Die folgende Funktion akzeptiert zwei ganzzahlige Zahlen von einem Aufrufer und gibt ihre Summe zurück. a und b sind Parameter vom Typ int.

int sum(int a, int b)
{
    return a + b;
}

Die Funktion kann von einer beliebigen Anzahl von Stellen im Programm aufgerufen oder aufgerufen werden. Die Werte, die an die Funktion übergeben werden, sind die Argumente, deren Typen mit den Parametertypen in der Funktionsdefinition kompatibel sein müssen.

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

Es gibt keine praktische Grenze für die Funktionslänge, aber ein gutes Design zielt auf Funktionen ab, die eine einzige gut definierte Aufgabe ausführen. Komplexe Algorithmen sollten möglichst in leicht verständliche einfachere Funktionen aufgeschlüsselt werden.

Im Klassenbereich definierte Funktionen werden als Memberfunktionen bezeichnet. In C++ kann eine Funktion im Gegensatz zu anderen Sprachen auch im Namespacebereich (einschließlich des impliziten globalen Namespace) definiert werden. Solche Funktionen werden als freie Funktionen oder Nicht-Member-Funktionen bezeichnet; sie werden umfassend in der Standardbibliothek verwendet.

Funktionen können überladen sein, was bedeutet, dass unterschiedliche Versionen einer Funktion denselben Namen aufweisen können, wenn sie sich von der Anzahl und/oder dem Typ der formalen Parameter unterscheiden. Weitere Informationen finden Sie unter Funktionsüberladung.

Bestandteile einer Funktionsdeklaration

Eine minimale Funktionsdeklaration besteht aus der Rückgabetyp-, Funktionsnamen- und Parameterliste (die leer sein kann), zusammen mit optionalen Schlüsselwort (keyword)s, die weitere Anweisungen für den Compiler bereitstellen. Das folgende Beispiel ist eine Funktionsdeklaration:

int sum(int a, int b);

Eine Funktionsdefinition besteht aus einer Deklaration und dem Textkörper, der alle Code zwischen den geschweiften Klammern ist:

int sum(int a, int b)
{
    return a + b;
}

Eine Funktionsdeklaration, auf die ein Semikolon folgt, wird möglicherweise an mehreren Stellen in einem Programm angezeigt. Sie muss vor dem Aufrufen dieser Funktion in jeder Übersetzungseinheit angezeigt werden. Die Funktionsdefinition darf gemäß der Regel mit einer Definition (One Definition Rule, ODR) nur einmal im Programm angezeigt werden.

Die erforderlichen Bestandteile einer Funktionsdeklaration lauten:

  1. Der Rückgabetyp, der den Typ des von der Funktion zurückgegebenen Werts angibt oder void wenn kein Wert zurückgegeben wird. In C++11 ist ein gültiger Rückgabetyp, der den Compiler anweist, auto den Typ von der Return-Anweisung abzuleiten. In C++14 decltype(auto) ist ebenfalls zulässig. Weitere Informationen finden Sie unten unter „Typableitung in Rückgabetypen“.

  2. Der Funktionsname, der mit einem Buchstaben oder Unterstrich beginnen muss und keine Leerzeichen enthalten darf. Im Allgemeinen geben führende Unterstriche in den Namen der Standardbibliotheksfunktion private Memberfunktionen oder Nicht-Member-Funktionen an, die nicht für die Verwendung durch Ihren Code vorgesehen sind.

  3. Die Parameterliste, ein durch geschweifte Klammern getrennter, kommagetrennter Satz von mindestens null Parametern, die den Typ und optional einen lokalen Namen angeben, anhand dessen die Werte im Funktionsrumpf möglicherweise aufgerufen werden.

Optionale Bestandteile einer Funktionsdeklaration sind:

  1. constexpr. Gibt an, dass es sich beim Rückgabewert der Funktion um einen konstanten Wert handelt, der zur Kompilierungszeit berechnet werden kann.

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. Seine Verknüpfungsspezifikation extern oder static.

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    Weitere Informationen finden Sie unter Übersetzungseinheiten und Verknüpfungen.

  3. inline, der den Compiler anweist, jeden Aufruf der Funktion durch den Funktionscode selbst zu ersetzen. Durch den Inlinevorgang kann die Leistung in Szenarien verbessert werden, in denen eine Funktion schnell ausgeführt und wiederholt in einem leistungskritischen Codeabschnitt aufgerufen wird.

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    Weitere Informationen finden Sie unter Inlinefunktionen.

  4. Ein noexcept Ausdruck, der angibt, ob die Funktion eine Ausnahme auslösen kann. Im folgenden Beispiel löst die Funktion keine Ausnahme aus, wenn der is_pod Ausdruck ausgewertet wird true.

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    Weitere Informationen finden Sie unter noexcept.

  5. (Nur Memberfunktionen) Die CV-Qualifizierer, die angeben, ob die Funktion ist const oder volatile.

  6. (Nur Memberfunktionen) virtual, oder overridefinal. virtual Gibt an, dass eine Funktion in einer abgeleiteten Klasse überschrieben werden kann. override bedeutet, dass eine Funktion in einer abgeleiteten Klasse eine virtuelle Funktion überschreibt. final bedeutet, dass eine Funktion in keiner weiteren abgeleiteten Klasse überschrieben werden kann. Weitere Informationen finden Sie unter "Virtuelle Funktionen".

  7. (nur Memberfunktionen) static auf eine Memberfunktion angewendet bedeutet, dass die Funktion keiner Objektinstanz der Klasse zugeordnet ist.

  8. (Nur nicht statische Memberfunktionen) Der Verweisqualifizierer, der den Compiler angibt, welche Überladung einer Funktion ausgewählt werden soll, wenn der implizite Objektparameter (*this) ein Rvalue-Verweis im Vergleich zu einem lvalue-Verweis ist. Weitere Informationen finden Sie unter Funktionsüberladung.

Funktionsdefinitionen

Eine Funktionsdefinition besteht aus der Deklaration und dem Funktionstext, eingeschlossen in geschweifte Klammern, die Variablendeklarationen, Anweisungen und Ausdrücke enthalten. Das folgende Beispiel zeigt eine vollständige Funktionsdefinition:

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

Im Textteil deklarierte Variablen werden als lokale Variablen bezeichnet. Sie verlassen den Gültigkeitsbereich, wenn die Funktion beendet wird. Daher sollte eine Funktion niemals einen Verweis zu einer lokalen Variable zurückgeben!

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

const- und constexpr-Funktionen

Sie können eine Memberfunktion deklarieren, um const anzugeben, dass die Funktion die Werte aller Datenmember in der Klasse nicht ändern darf. Indem Sie eine Memberfunktion als constdeklarieren, helfen Sie dem Compiler, die Const-Korrektheit zu erzwingen. Wenn jemand versehentlich versucht, das Objekt mithilfe einer als deklarierten constFunktion zu ändern, wird ein Compilerfehler ausgelöst. Weitere Informationen finden Sie unter const.

Deklarieren Sie eine Funktion so, als constexpr ob der erzeugte Wert zur Kompilierungszeit bestimmt werden kann. Eine Constexpr-Funktion wird in der Regel schneller als eine normale Funktion ausgeführt. Weitere Informationen finden Sie unter constexpr.

Funktionsvorlagen

Eine Funktionsvorlage ähnelt einer Klassenvorlage. Sie generiert auf Grundlage der Vorlagenargumente konkrete Funktionen. In vielen Fällen kann die Vorlage die Typargumente ableiten. Daher ist es nicht erforderlich, sie explizit anzugeben.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

Weitere Informationen finden Sie unter Funktionsvorlagen

Funktionsparameter und Argumente

Eine Funktion weist eine durch Kommas getrennte Liste von mindestens null Typen auf. Jede davon verfügt über einen Namen, unter dem es im Funktionsrumpf aufgerufen werden kann. Eine Funktionsvorlage kann mehr Typ- oder Wertparameter angeben. Der Aufrufer gibt Argumente weiter, bei denen es sich um konkrete Werte handelt, deren Typen mit der Parameterliste kompatibel sind.

Argumente werden standardmäßig zur Funktion nach Wert weitergegeben. Die Funktion empfängt demnach eine Kopie des weiterzugebenden Objekts. Für große Objekte kann das Erstellen einer Kopie teuer sein und ist nicht immer erforderlich. Fügen Sie dem Parameter einen Verweis-Quantifizierer hinzu, um zu bewirken, dass Argumente per Verweis übergeben werden (insbesondere lvalue-Verweis):

void DoSomething(std::string& input){...}

Wenn eine Funktion ein Argument ändert, das nach Verweis weitergegeben wird, ändert sie das ursprüngliche Objekt und nicht eine lokale Kopie. Um zu verhindern, dass eine Funktion ein solches Argument ändert, qualifizieren Sie den Parameter als "const&":

void DoSomething(const std::string& input){...}

C++11: Verwenden Sie zum expliziten Behandeln von Argumenten, die von rvalue-reference oder lvalue-reference übergeben werden, ein Double-amper-Sand für den Parameter, um einen universellen Verweis anzugeben:

void DoSomething(const std::string&& input){...}

Eine Funktion, die mit der einzelnen Schlüsselwort (keyword) void in der Parameterdeklarationsliste deklariert wird, akzeptiert keine Argumente, solange die Schlüsselwort (keyword) void das erste und einzige Element der Argumentdeklarationsliste ist. Argumente vom Typ an void anderer Stelle in der Liste führen zu Fehlern. Beispiel:

// OK same as GetTickCount()
long GetTickCount( void );

Obwohl es unzulässig ist, ein void Argument anzugeben, außer wie hier beschrieben, können Typen, die vom Typ void (z. B. Zeiger auf void und Arrays von void) abgeleitet sind, an einer beliebigen Stelle der Argumentdeklarationsliste angezeigt werden.

Standardargumente

Der oder die letzten Parameter in einer Funktionssignatur werden möglicherweise einem Standardargument zugewiesen. Der Aufrufer kann demnach das Argument auslassen, wenn die Funktion aufgerufen wird, es sei denn, es soll ein anderer Wert angegeben werden.

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

Weitere Informationen finden Sie unter "Standardargumente".

Funktionsrückgabetypen

Eine Funktion gibt möglicherweise keine andere Funktion oder ein integriertes Array zurück; Es kann jedoch Zeiger auf diese Typen oder eine Lambda-Funktion zurückgeben, die ein Funktionsobjekt erzeugt. Mit Ausnahme dieser Fälle kann eine Funktion einen Wert eines beliebigen Typs zurückgeben, der sich im Bereich befindet, oder sie kann keinen Wert zurückgeben, in diesem Fall ist voidder Rückgabetyp .

Nachstehende Rückgabetypen

Ein „gewöhnlicher“ Rückgabetyp befindet sich auf der linken Seite der Funktionssignatur. Ein nachfolgender Rückgabetyp befindet sich auf der rechten Seite der Signatur und wird dem -> Operator vorangestellt. Nachstehende Rückgabetypen sind insbesondere in Funktionsvorlagen nützlich, wenn der Typ des Rückgabewerts auf Vorlagenparametern beruht.

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Wenn auto er in Verbindung mit einem nachfolgenden Rückgabetyp verwendet wird, dient er lediglich als Platzhalter für den erzeugten Decltype-Ausdruck und führt nicht selbst typabzug aus.

Lokale Funktionsvariablen

Eine Variable, die innerhalb eines Funktionstexts deklariert wird, wird als lokale Variable oder einfach als lokale Variable bezeichnet. Nicht statische Lokale sind nur innerhalb des Funktionstexts sichtbar, und wenn sie im Stapel deklariert werden, gehen sie außerhalb des Gültigkeitsbereichs, wenn die Funktion beendet wird. Wenn Sie eine lokale Variable erstellen und nach Wert zurückgeben, kann der Compiler in der Regel die benannte Rückgabewertoptimierung ausführen, um unnötige Kopiervorgänge zu vermeiden. Wenn Sie eine lokale Variable nach Verweis zurückgeben, stellt eine Compiler eine Warnung aus, da jeder Versuch durch den Aufrufer, diesen Verweis zu verwenden, erst nach der Zerstörung der lokalen Variablen auftritt.

In C++ kann eine lokale Variable als statisch deklariert werden. Die Variable ist nur innerhalb des Funktionsrumpfs sichtbar. Es ist jedoch eine einzelne Kopie der Variable für alle Instanzen der Funktion vorhanden. Lokale statische Objekte werden während der Beendigung zerstört, die von atexit angegeben wird. Wenn ein statisches Objekt nicht erstellt wurde, weil der Steuerungsfluss des Programms seine Deklaration umgangen hat, wird kein Versuch unternommen, dieses Objekt zu zerstören.

Typabzug in Rückgabetypen (C++14)

In C++14 können Sie den Compiler anweisen auto , den Rückgabetyp vom Funktionstext ableiten zu müssen, ohne einen nachfolgenden Rückgabetyp angeben zu müssen. Beachten Sie, dass auto immer ein Rückgabe-nach-Wert abgeleitet wird. Verwenden Sie auto&& um den Compiler anzuweisen, einen Verweis abzuleiten.

In diesem Beispiel auto wird eine Nichtkonstwertkopie der Summe von lhs und rhs abgeleitet.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

Beachten Sie, dass auto die Übereinstimmung des Typs, den er ablegt, nicht erhalten bleibt. Für Weiterleitungsfunktionen, deren Rückgabewert die Konstante oder Abweichung der Argumente beibehalten muss, können Sie die decltype(auto) Schlüsselwort (keyword) verwenden, die die decltype Typrückleitungsregeln verwendet und alle Typinformationen beibehalten. decltype(auto) kann als gewöhnlicher Rückgabewert auf der linken Seite oder als nachgestellter Rückgabewert verwendet werden.

Im folgenden Beispiel (basierend auf Code aus N3493) wird gezeigt decltype(auto) , wie die perfekte Weiterleitung von Funktionsargumenten in einem Rückgabetyp ermöglicht wird, der erst bekannt ist, wenn die Vorlage instanziiert wird.

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

Zurückgeben mehrerer Werte aus einer Funktion

Es gibt verschiedene Möglichkeiten, mehrere Werte aus einer Funktion zurückzugeben:

  1. Kapseln Sie die Werte in einer benannten Klasse oder struktur.Encapsulate the values in a named class or struct object. Erfordert, dass die Klassen- oder Strukturdefinition für den Aufrufer sichtbar ist:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. Zurückgeben eines std::tuple- oder std::p air-Objekts:

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Visual Studio 2017, Version 15.3 und höher (verfügbar im /std:c++17 Modus und höher): Verwenden Sie strukturierte Bindungen. Der Vorteil strukturierter Bindungen besteht darin, dass die Variablen, die die Rückgabewerte speichern, gleichzeitig initialisiert werden, was in einigen Fällen wesentlich effizienter sein kann. In der Anweisung auto[x, y, z] = f(); werden in den Klammern Namen eingeführt und initialisiert, die sich im Gültigkeitsbereich für den gesamten Funktionsblock befinden.

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. Zusätzlich zur Verwendung des Rückgabewerts selbst können Sie "Rückgabewerte" verwenden, indem Sie eine beliebige Anzahl von Parametern definieren, die pass-by-reference verwendet werden sollen, damit die Funktion die Werte von Objekten ändern oder initialisieren kann, die der Aufrufer bereitstellt. Weitere Informationen finden Sie unter "Reference-Type Function Arguments".

Funktionszeiger

C++ unterstützt Funktionszeiger auf die gleiche Weise wie die C-Sprache. Eine typsichere Alternative besteht jedoch darin, ein Funktionsobjekt zu verwenden.

Es wird empfohlen, typedef einen Alias für den Funktionszeigertyp zu deklarieren, wenn eine Funktion deklariert wird, die einen Funktionszeigertyp zurückgibt. Beispiel:

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

Wenn dies nicht geschehen ist, kann die richtige Syntax für die Funktionsdeklaration von der Deklaratorsyntax für den Funktionszeiger abgeleitet werden, indem sie den Bezeichner (fp im obigen Beispiel) durch den Namen und die Argumentliste der Funktionen wie folgt ersetzen:

int (*myFunction(char* s))(int);

Die vorangehende Deklaration entspricht der Deklaration, die zuvor verwendet typedef wird.

Siehe auch

Funktionsüberladung
Funktionen mit Variablenargumentlisten
Explizit vorgegebene und gelöschte Funktionen
Argumentbezogene Namenssuche (Koenig) in Funktionen
Standardargumente
Inlinefunktionen