Zum Inhalt springen
C++ Anfรคnger 30 min

Funktionen & References

Funktionen in C++: Parameter per Value, Reference oder Pointer, Default-Werte, Ueberladung und Lambdas.

Aktualisiert:
Inhaltsverzeichnis

Funktionen & References

Funktionen sind einfach - aber wie Parameter uebergeben werden, ist in C++ entscheidend. Das Verstaendnis von Pass-by-Value vs. Pass-by-Reference ist fundamental.

Grundform

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

int main() {
    int summe = addiere(3, 4);
}
  • Rueckgabetyp vor dem Namen
  • Parameter mit Typ und Name

void - keine Rueckgabe

void gruessen(const std::string& name) {
    std::cout << "Hallo, " << name << "!\n";
}

Rueckgabetyp mit auto (C++14)

Der Compiler leitet den Rueckgabetyp aus dem return-Statement ab:

auto addiere(int a, int b) {
    return a + b;   // Rueckgabetyp: int
}

Bei komplexen Typen hilfreich, aber fuer APIs ist ein expliziter Typ oft besser fuer die Lesbarkeit.

Trailing Return Type

Fuer Metaprogrammierung oder wenn der Typ vom Parameter abhaengt:

auto addiere(auto a, auto b) -> decltype(a + b) {
    return a + b;
}

Pass-by-Value vs. Reference

Pass-by-Value

void verdoppeln(int x) {
    x *= 2;     // aendert nur die lokale Kopie
}

int y = 5;
verdoppeln(y);
std::cout << y;   // immer noch 5

Wichtig: Fuer grosse Objekte ist Pass-by-Value teuer - eine komplette Kopie wird erstellt.

Pass-by-Reference

void verdoppeln(int& x) {
    x *= 2;     // aendert das Original
}

int y = 5;
verdoppeln(y);
std::cout << y;   // 10

Mit & bekommt die Funktion einen Alias fuer die Variable - keine Kopie, kann aendern.

Pass-by-Const-Reference (der Standard)

void drucke(const std::string& name) {
    std::cout << name << "\n";
}

Die haeufigste Form fuer Parameter:

  • Keine Kopie (schnell)
  • Funktion kann den Wert nicht veraendern
  • Ideal fuer grosse Objekte wie std::string, std::vector

Als Faustregel:

  • Primitive Typen (int, double, bool): Pass-by-Value ist fein
  • Grosse Objekte: Pass-by-const-Reference
  • Wenn Funktion den Wert aendern soll: Pass-by-Reference

Pointer-Parameter

void maybeSet(int* p) {
    if (p != nullptr) {
        *p = 42;
    }
}

int x;
maybeSet(&x);   // Adresse uebergeben

Pointer erlauben nullptr - Referenzen nicht. Nutze Pointer, wenn der Parameter optional fehlen kann.

Noch besser: std::optional

Fuer optionale Rueckgabe ist das meist besser:

#include <optional>

std::optional<int> parseInt(const std::string& s) {
    try {
        return std::stoi(s);
    } catch (...) {
        return std::nullopt;
    }
}

Default-Werte

void gruessen(const std::string& name = "Welt",
              const std::string& gruss = "Hallo") {
    std::cout << gruss << ", " << name << "!\n";
}

gruessen();                     // "Hallo, Welt!"
gruessen("Anna");                // "Hallo, Anna!"
gruessen("Leo", "Hi");           // "Hi, Leo!"

Nur rechts der Parameter-Liste koennen Defaults stehen.

Overloading

Mehrere Funktionen mit gleichem Namen, aber unterschiedlichen Parametern:

int addiere(int a, int b) { return a + b; }
double addiere(double a, double b) { return a + b; }
std::string addiere(const std::string& a, const std::string& b) { return a + b; }

addiere(3, 4);         // int-Version
addiere(3.5, 4.5);     // double-Version
addiere("Hi ", "Du");  // string-Version

Der Compiler waehlt die passende Version anhand der Argument-Typen.

Templates - generische Funktionen

template <typename T>
T addiere(T a, T b) {
    return a + b;
}

addiere(3, 4);       // int
addiere(3.5, 4.5);   // double
addiere(std::string{"a"}, std::string{"b"});

Templates sind eines der maechtigsten C++-Features - der Compiler generiert fuer jeden verwendeten Typ eine eigene Version.

Lambdas

Anonyme Funktionen:

auto verdoppeln = [](int n) { return n * 2; };
std::cout << verdoppeln(5);  // 10

Mit explizitem Rueckgabetyp

auto teile = [](double a, double b) -> double {
    if (b == 0) return 0;
    return a / b;
};

Capture - Variablen einfangen

Lambdas sehen ausser den Parametern nichts aus dem umgebenden Scope. Mit [] fangst du sie ein:

int faktor = 3;

// [] - nichts einfangen
auto f1 = [](int n) { return n * 2; };

// [=] - alles per Value
auto f2 = [=](int n) { return n * faktor; };

// [&] - alles per Reference
auto f3 = [&](int n) { return n * faktor; };

// Selektiv
auto f4 = [faktor](int n) { return n * faktor; };

// Als Referenz
int sum = 0;
auto addiere = [&sum](int n) { sum += n; };

Lambdas mit Algorithmen

Der Hauptnutzen - Kurz-Funktionen fuer <algorithm>:

#include <vector>
#include <algorithm>
#include <iostream>

std::vector<int> zahlen = {3, 1, 4, 1, 5, 9, 2, 6};

// Sortieren mit Lambda
std::sort(zahlen.begin(), zahlen.end(),
          [](int a, int b) { return a > b; });    // absteigend

// Zaehle Zahlen > 3
auto anzahl = std::count_if(zahlen.begin(), zahlen.end(),
                             [](int n) { return n > 3; });

// Transform
std::vector<int> verdoppelt;
std::transform(zahlen.begin(), zahlen.end(),
               std::back_inserter(verdoppelt),
               [](int n) { return n * 2; });

Funktionen als Parameter

std::function (generisch, flexibel)

#include <functional>

void anwenden(const std::function<int(int)>& f, int wert) {
    std::cout << f(wert) << "\n";
}

anwenden([](int n) { return n * 2; }, 5);   // 10

Templates (schneller, Compile-Zeit)

template <typename F>
void anwenden(F f, int wert) {
    std::cout << f(wert) << "\n";
}

Templates sind schneller, std::function ist flexibler. Wenn Performance zaehlt, Template. Sonst std::function.

Praktisches Beispiel

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>

struct Produkt {
    std::string name;
    double preis;
};

double gesamtwert(const std::vector<Produkt>& produkte) {
    return std::accumulate(produkte.begin(), produkte.end(), 0.0,
                           [](double summe, const Produkt& p) {
                               return summe + p.preis;
                           });
}

std::vector<Produkt> guenstigerAls(
    const std::vector<Produkt>& produkte, double grenze)
{
    std::vector<Produkt> ergebnis;
    std::copy_if(produkte.begin(), produkte.end(),
                 std::back_inserter(ergebnis),
                 [grenze](const Produkt& p) { return p.preis < grenze; });
    return ergebnis;
}

int main() {
    std::vector<Produkt> sortiment = {
        {"Buch", 19.99},
        {"Kaffee", 3.50},
        {"Tasse", 8.00},
    };

    std::cout << "Gesamt: " << gesamtwert(sortiment) << "\n";

    auto guenstig = guenstigerAls(sortiment, 10.0);
    for (const auto& p : guenstig) {
        std::cout << p.name << ": " << p.preis << "\n";
    }
}

Zusammenfassung

  • Parameter: const T& fuer grosse Objekte, T fuer kleine Typen
  • T& wenn die Funktion den Wert aendern soll
  • Default-Werte nur rechts, Overloading nach Signatur
  • Lambdas [capture](params) { body } - fundamental in modernem C++
  • Templates fuer generische Funktionen
  • std::function fuer Funktions-Parameter, wenn Flexibilitaet vor Speed steht

Im naechsten Kapitel: Klassen und das wichtigste Konzept des modernen C++ - RAII.

Zurรผck zum C++ Kurs