Eine Referenz ist ein alternativer Name für ein Objekt:
int i = 42; int& r = i; // r und i stehen fuer Dasselbe. int x = r; // x = 42; r = r - 1; // i = 41;
Der Wert einer Referenz kann nicht verändert werden
eine Referenz muß initialisiert werden.
Der Initialisierer einer Referenz muß ein L-Wert (ein zuweisbares Objekt) sein:
double &r = 3.14; // FALSCH: kein L-Wert
Ausnahme: Referenzen auf Konstante:
const double &r = 3.14; // OK.
entspricht der Sequenz
double temp = 3.14; double &r = temp;
Referenzen als Parameter: Call-by-Reference
Funktionen, die ihre Parameter verändern:
void inc(int& i) { i++;} int main() { int x = 1; inc(x); // x = 2; }
Zeitersparnis bei der Parameterübergabe:
class bank { kunden viele[10000]; ... }; void pruefe(bank b);
Diese Parameterübergabe benutzt den Copy-Konstruktor.
Besser:
// ein Referenzargument, // das nicht veraendert wird: void pruefe(const bank &b);
Referenzen als Funktionsergebnis
Ebenfalls zur Vermeidung von Kopien:
bank& zahlezinsen() { ... }
Vorsicht: Solche Funktionen können links von Wertzuweisungen stehen:
int& index(int *ip, int i) { return ip[i]; } int a[] = {1,2,3}; int main() { index(a,1) = 17; // a = {1,17,3}; }
Wenn dies nicht beabsichtigt ist:
const int& index(int *ip, int i) { return ip[i]; }
Vorsicht: niemals Referenzen auf lokale Objekte zurückgeben:
string& anhaengen(string& s1, string& s2) { string tmp = s1; tmp += s2; return tmp; }
Solche Objekte sind nach Verlassen der Funktion nicht mehr da!
Eine Funktionsdeklaration enthält
int strlen(char *); void printdate(date); void stop(void);
Als Hilfe für den Leser darf die Funktionsdeklaration Parameternamen enthalten:
int inset(myset menge, int suchmich);
Jede verwendete Funktion muß irgendwo (genau einmal) definiert werden. Eine Funktions-Definition ist eine Funktions-Deklaration mit Rumpf:
void swap(int*, int*); ... void swap(int *p, int *q) { int tmp = *p; *p = *q; *q = tmp; }
Alle Parameter, die im Rumpf benutzt werden, müssen einen Namen haben.
Alle die nicht benutzt werden, brauchen keinen Namen.
Wie Elementfunktionen und Konstruktoren dürfen Funktionen auch inline definiert werden:
inline void printexpr(int val) { cout << "(" << val << ")";
Inline ist eleganter und sicherer als cpp-Makros:
#define mul(x,y) (x*y) ... cout << mul(6+1,4) // SURPRISE!
Parameterübergabe
Beim Aufruf werden die formalen Wert-Parameter mit den aktuellen
Parametern initialisiert ( Copy-Konstruktor).
Die Typen werden abgeglichen durch standardmäßige und
Benutzer-definierte Typumwandlungen.
Die formalen Referenz-Parameter werden zu Referenzen auf die aktuellen Parameter.
Call-by-Value
Call-by-Reference
void f(int i, int& k) { i++; // veraendert lokales i k++; // veraendert den akt. Parameter }
Call-by-Reference wegen der möglichen Seiteneffekte vermeiden. Wenn wegen Effizienz eingesetzt: verwende const-Argumente:
void f(const large& arg) { ... }
Referenzübergabe für
ist nur an formale Parameter der Art const T&
möglich:
float sqrt(const float&); // Wurzel int main() { float r; double d; r = sqrt(2.0f); // Ref. auf temp=2.0f r = sqrt(r); // Ref. auf r r = sqrt(d); // Ref. auf temp=(float)d
Funktionsergebnisse
Funktionen, die nicht als void deklariert sind, müssen ein Funktionsergebnis liefern.
int fac(int n) { if (n==0) return 1; else return n*fac(n-1); }
Die Semantik der Ergebnisrückgabe entspricht (wie die der Argumentübergabe) der der Initialisierung.
Der Typ des return-Ausdrucks und der Typ der Funktion werden abgeglichen durch standardmäßige und Benutzer-definierte Typumwandlungen.
Default-Argumente
Funktionen und Konstruktoren dürfen Default-Argumente haben, die beim Aufruf weggelassen werden können:
void printdate(date d, int kurz=1); { cout << d.day << "."; if (kurz) cout << d.month << "."; else cout << monthname(d.month) << "."; cout << d.year << "."; } ... printdate(date(17.5.1993),1); // kurz printdate(date(17.5.1993)); // dasselbe printdate(date(17.5.1993),0); // lang
Default-Argumente sind nur für die ``hinteren'' Funktionsparameter möglich.
int print(int basis = 10, int value); // FEHLER!
Vorsicht vor Syntaxfehlern:
void sonicht(char*=0); // Syntax-Fehler! // *= ist Zuweisungsoperator
Beliebig viele Argumente
Wenn Anzahl und Typen der Argumente nicht exakt angegeben werden können, werden Auslassungspunkte ... als letztes Element der Parameterliste benutzt:
int printf(const char*, ...);
Die Funktion muß Informationen über Anzahl und Art der Parameter erhalten. Im Falle von printf geschieht dies über den 1. Parameter (Format-String):
printf("Hello Number %d\n",7);
Der Compiler kann keine Typen prüfen und anpassen (char und short werden zu int, float wird zu double).
In <stdarg.h>
stehen Zugriffsmakros für nicht-spezifizierte
Parameter zur Verfügung.
Ein gut entworfenes Progamm sollte solche Funktionen jedoch möglichst nicht benutzen.
Es gibt zwei zulässige Operationen auf Funktionen:
&
Der aus der Adressbestimmung resultierende Zeiger kann zum Aufruf der Funktion benutzt werden:
void error(char *p); void (*fuptr)(char*) = &error; ... (*fuptr)("Du Pfeife"); // ruft error() auf fuptr("Selber"); // dto. mit impl. Konvertierung
Die Klammern sind nötig, da () höhere Priorität hat als *.
Die Deklaration des Zeigers enthält Argumentliste und Ergebnistyp. Diese müssen mit der Signatur der Funktion exakt übereinstimmen:
void (*fuptr)(char*); int f1(char*); void f2(int*); ... fuptr = &f1; // FEHLER: Ergebnistyp fuptr = &f2 // FEHLER: Parametertyp