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