Funktionen & References
Funktionen in C++: Parameter per Value, Reference oder Pointer, Default-Werte, Ueberladung und Lambdas.
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,Tfuer 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::functionfuer Funktions-Parameter, wenn Flexibilitaet vor Speed steht
Im naechsten Kapitel: Klassen und das wichtigste Konzept des modernen C++ - RAII.