Ein Ausdruck ist eine Folge von Operatoren und Operanden. Er kann einen Wert liefern und Seiteneffekte verursachen.
Im Allgemeinen ist die Reihenfolge, in der Operanden ausgewertet werden, undefiniert:
k = i + f(i++); // Wert von k undef. i = v[i++]; // Wert von i undef.
Die Bedeutung eines Ausdrucks ergibt sich aus den Regeln für
Op | Bedeutung | Typ | Assoz. | Präzedenz |
, | Komma-Operator | bin | links | 1 |
= | Wertzuweisung | bin | rechts | 2 |
+= | Wertzuweisung | bin | rechts | 2 |
-= | Wertzuweisung | bin | rechts | 2 |
*= | Wertzuweisung | bin | rechts | 2 |
/= | Wertzuweisung | bin | rechts | 2 |
|= | Wertzuweisung | bin | rechts | 2 |
^= | Wertzuweisung | bin | rechts | 2 |
&= | Wertzuweisung | bin | rechts | 2 |
%= | Wertzuweisung | bin | rechts | 2 |
<<= | Wertzuweisung | bin | rechts | 2 |
>>= | Wertzuweisung | bin | rechts | 2 |
? : | Bedingung | tern | rechts | 3 |
|| | Log. Oder | bin | links | 4 |
&& | Log. Und | bin | links | 5 |
| | Bitweises Oder | bin | links | 6 |
^ | Bitweises XOR | bin | links | 7 |
& | Bitweises Und | bin | links | 8 |
== | Gleichheit | bin | links | 9 |
!= | Ungleichheit | bin | links | 9 |
< | Kleiner | bin | links | 10 |
<= | Kleiner gleich | bin | links | 10 |
> | Größer | bin | links | 10 |
>= | Größer gleich | bin | links | 10 |
<< | Links-Shift | bin | links | 11 |
>> | Rechts-Shift | bin | links | 11 |
Op | Bedeutung | Typ | Assoz. | Präzedenz |
+ | Addition | bin | links | 12 |
- | Subtraktion | bin | links | 12 |
* | Multiplikation | bin | links | 13 |
/ | Division | bin | links | 13 |
% | Modulo | bin | links | 13 |
->* | Member-Zeiger | bin | links | 14 |
.* | Member-Zeiger | bin | links | 14 |
++ | Pre-Inkrement | un | rechts | 15 |
-- | Pre-Dekrement | un | rechts | 15 |
* | Dereferenzierung | un | rechts | 15 |
& | Adresse | un | rechts | 15 |
+ | Unäres Plus | un | rechts | 15 |
- | Unäres Minus | un | rechts | 15 |
! | Logisches Not | un | rechts | 15 |
~ | Bitweises Not | un | rechts | 15 |
++ | Post-Inkrement | un | rechts | 15 |
-- | Post-Dekrement | un | rechts | 15 |
(type) | Cast | bin | rechts | 15 |
new | Allokation | un/bin | rechts | 15 |
delete | Deallokation | un/bin | rechts | 15 |
[] | Indizierung | bin | links | 16 |
() | Funktionsaufruf | bin | links | 16 |
() | funktionaler Cast | bin | links | 16 |
. | Selektion | bin | links | 16 |
-> | Zeigerselektion | bin | links | 16 |
sizeof | Größe | un | rechts | 16 |
:: | Scope Resolution | un/bin | links | 17 |
Es gibt vier Arten von Gültigkeitsbereichen:
Der binäre :: Operator erlaubt Zugriff auf verdeckte Klassenelemente:
class X { public: static int xval; int getval(); }; X::xval = 0; int X::getval() {return xval;}
Der unäre :: Operator erlaubt Zugriff auf Namen im File-Gültigkeitsbereich, selbst wenn sie verdeckt sind:
int g = 0; int f(int g) { if (g) // lokales g return g; else return ::g; // globales g }
liefert die Größe in Bytes seines Operanden. Der Operand ist:
sizeof ist nicht anwendbar auf Funktionen, Bitfelder, void und Arrays ohne Dimensionsangabe.
Auf eine Referenz angewendet, liefert sizeof die Größe des Objekts, auf das die Referenz verweist:
class X { ... }; X obj; X& ref = obj; const int i = sizeof ref; // == sizeof(X)
Für Arrays liefert sizeof die Gesamtzahl der Bytes im Array:
int arr[100]; const int i = sizeof arr;
Beispiel:
int i=99; double d = 2.0; int main() { cout << "int: " << sizeof(int) << endl; cout << "i: " << sizeof i << endl; cout << "i++: " << sizeof i++ << endl; cout << "d=d+i: " << sizeof(d=d+i) << endl; cout << "wert(i): " << i << endl; cout << "wert(d): " << d << endl; return 0; }
liefert
int: 4 i: 4 i++: 4 d=d+i: 8 wert(i): 99 wert(d): 2
Der Operator new bewirkt
date *d; d = new date;
ruft den Default-Konstruktor von date auf.
date *d; d = new date(5,6,1993);
ruft den date(d,m,y)-Konstruktor auf.
Arrays werden wie folgt allokiert:
char *p = new char[n]; date *urlaub = new date[29];
Dabei wird der Default-Konstruktor für jedes Array-Element aufgerufen. Dynamisch allokierte Arrays können nicht explizit initialisert werden!
class elem { int i; public: elem(): i(0) { cout <<i<<' ';} elem(int v): i(v) {cout <<i<<' ';} ~elem() { cout <<'~'<<i<<' ';} }; elem e1; elem e2(1); elem e3[2]; elem e4[2] = {elem(2), elem(3)}; elem *p1 = new elem; elem *p2 = new elem(4); elem *p3 = new elem[2]; // Initialisierte Arrays gehn nicht!
erzeugt folgende Ausgabe:
0 1 0 0 2 3 0 4 0 0 ~3 ~2 ~0 ~0 ~1 ~0
Man sieht: Dynamisch allokierte Objekte werden nicht automatisch zerstört und deallokiert.
Die Initialisierung wird nur bei erfolgreicher Allokation (Rückgabewert von new ungleich 0) ausgeführt.
elem *p = new elem[n]; if (!p) { cerr << "New failed (elem[n]) \n"; exit 1; }
Beispiel:
#include <iostream.h> #include <stdlib.h> class vector { double *co; int dim; public: vector(int d); double& operator [] (int d); }; vector::vector(int d) : dim(d) { co = new double[dim]; if (!co) { cerr << "New failed\n"; exit (1); } } double& vector::operator[] (int d) { if (d >= 0 && d < dim) return co[d]; else { cerr << "index out of range\n"; exit(1); } }
Der Operator delete bewirkt
date *d = new date; ... delete d; // Zerstoeren und Freigeben
delete darf nur auf Zeiger angewandt werden, die durch new erzeugt wurden. Sonst ist das Verhalten undefiniert. delete auf den Nullpointer angewandt ist harmlos.
int i; int *p = &i; delete p; // FEHLER !!! p = new int[100]; p = p + 1; delete p; // FEHLER !!!
Arrays werden mit delete [] recycled. Diese Form führt dazu, daß der Destruktor für jedes Element des Arrays aufgerufen wird:
class elem { int i; public: elem() : i(0) {cout <<i<<' ';} ~elem() {cout <<'~'<<i<<' ';} }; int main() { elem *p3 = new elem[6]; delete [] p3; }
liefert:
0 0 0 0 0 0 ~0 ~0 ~0 ~0 ~0 ~0
Beispiel:
// Ein dynamisch wachsender Keller const int stksize = 5; const int stkgrow = 5; class stack { private: int *stkref; int *stkend; int *stkptr; public: stack(int size = stksize); ~stack() {delete [] stkref;} int push(int elem); int pop() { return *--stkptr;} int empty() const { return stkref == stkptr;} }; stack::stack(int size) { stkref = new int[size]; if (!stkref) { cerr << "No more Memory\n"; exit(2); } stkend = stkref+size; stkptr = stkref; }
int stack::push(int elem) { if (stkptr >= stkend) { int *snew = new int[stkend-stkref+stkgrow]; for (int *p = stkref; p < stkend; p++) snew[p-stkref] = *p; stkend = snew + (stkend-stkref)+stkgrow; stkptr = snew + (stkptr-stkref); delete [] stkref; stkref = snew; } return *stkptr++ = elem; }
.* und ->*: Zeiger auf Klassenelemente
Funktionen können über Zeiger indirekt aufgerufen werden:
void print(char *s) { cout << s << endl; } void (*fp)(char*) = &print; (*fp)("hallo");
Dies ist auch für Elementfunktionen möglich. Dazu muß der Klassennamen angegeben werden:
class X { .... public: void print (char*s); }; void (X::*fp)(char*) = &X::print;
Zum indirekten Aufruf brauchen wir ein Objekt der Klasse X:
X a; (a.*fp)("string1"); // Operator .* X *b; (b->*fp)("string2"); // Operator ->*
Zeiger auf public Datenelemente von Klassen sind auch möglich:
struct A { int a; int b; }; int A::*iptr = &A::a; A var; var.*iptr = 12; A *ptr; A::*iptr = &A::b; ptr->*iptr = 13;