C++-Programmierung: Operatoren

Vorzeichen

Bearbeiten

Gibt einem numerischen Wert ein negatives Vorzeichen, bzw. kehrt das Vorzeichen um.

int i = -5; // i erhält den Wert -5
int n = -i; // n erhält den Wert 5

Dient zur expliziten Angabe des Vorzeichens. Da Zahlen ohne explizites Vorzeichen immer positiv sind, kann dieser Operator weggelassen werden. Manchmal sinnvoll, um explizit auf das Vorzeichen hinzuweisen.

int i = 5; // i erhält den Wert 5
int n = +i; // n erhält den Wert 5

Arithmetische Operatoren

Bearbeiten

Auf Operanden, die einen arithmetischen Typ tragen, werden die usual arithmetic conversions angewendet, um die Typen einander anzugleichen und den Typ des Resultats zu bestimmen.

+ (Addition)

Bearbeiten

Addiert die Werte seiner Operanden und gibt das Ergebnis zurück.

int i = 3;
int n = i + 5;

- (Subtraktion)

Bearbeiten

Subtrahiert die Werte seiner Operanden und gibt das Ergebnis zurück.

int i = 5;
int n = i - 3;

* (Multiplikation)

Bearbeiten

Multipliziert die Werte seiner Operanden und gibt das Ergebnis zurück.

int i = 5;
int n = 2 * i;

/ (Division)

Bearbeiten

Dividiert die Werte seiner Operanden und gibt das Ergebnis zurück. Bei der Division von Ganzzahlen fällt ein eventueller Rest weg, es wird also nicht gerundet.

int i = 10 / 3; // i erhält den Wert 3

% (Modulo)

Bearbeiten

Dividiert die Werte seiner Operanden und gibt den Divisionsrest zurück. Kann nur auf ganzzahlige Operanden angewendet werden. Ist mindestens ein Operand negativ, so ist das Vorzeichen des Resultats implementationsabhängig.

int i = 10 % 3; // i erhält den Wert 1

Zuweisungen

Bearbeiten

++ (Inkrement)

Bearbeiten

Erhöht den Wert seines Operanden um 1.

i = 2;
i++; // i hat den Wert 3

Bezüglich der Priorität unterscheidet man zwischen Postfix- und Präfix-Notation. Die Postfix-Notation (i++) hat eine höhere Priorität als die Präfix-Notation (++i).

-- (Dekrement)

Bearbeiten

Vermindert den Wert seines Operanden um 1.

i = 2;
i--; // i hat den Wert 1

Bezüglich der Priorität unterscheidet man zwischen Postfix- und Präfix-Notation. Die Postfix-Notation (i--) hat eine höhere Priorität als die Präfix-Notation (--i).

= (Zuweisung)

Bearbeiten

Weist seinem linken Operanden den Wert des rechten Operanden zu.

i = 3; // i hat den Wert 3

Kombinierte Zuweisungsoperatoren

Bearbeiten

Die kombinierten Zuweisungsoperatoren kombinieren den Zuweisungsoperator (=) mit einem anderen Operator:

  • +=
  • -=
  • *=
  • /=
  • %=
  • &=
  • <<=
  • >>=
  • ^=
  • |=

Dabei wird der linke Operand sowohl als linker Operand für die Zuweisung als auch für den anderen Operator verwendet.

a += b;

bedeutet also

a = a + (b);

Die Klammer um b soll verdeutlichen, dass gesamte rechte Ausdruck zuerst berechnet wird.

Vergleiche

Bearbeiten

== (Gleichheit)

Bearbeiten

Ergibt den boolschen Wert true, wenn die beiden Operanden gleich sind, sonst false.

!= (Ungleichheit)

Bearbeiten

Ergibt den boolschen Wert true, wenn die beiden Operanden ungleich sind, sonst false.

<= (kleiner oder gleich)

Bearbeiten

Ergibt den boolschen Wert true, wenn der linke Operand kleiner oder gleich dem rechten ist, sonst false.

>= (größer oder gleich)

Bearbeiten

Ergibt den boolschen Wert true, wenn der linke Operand größer oder gleich dem rechten ist, sonst false.

< (kleiner)

Bearbeiten

Ergibt den boolschen Wert true, wenn der linke Operand kleiner als der rechte ist, sonst false.

> (größer)

Bearbeiten

Ergibt den boolschen Wert true, wenn der linke Operand größer als der rechte ist, sonst false.

<=> (Drei-Wege Vergleich)

Bearbeiten

Seit C++20: Ergibt ein Objekt, das

  • kleiner 0 ist, falls der linke Operand kleiner als der rechte ist
  • gleich 0 ist, falls der linke Operand gleich dem rechten ist
  • größer 0 ist, falls der linke Operand größer als der rechte ist

Aussagenlogik

Bearbeiten

&& (Und-Verknüpfung)

Bearbeiten

Verknüpft die beiden Operanden und gibt true zurück, wenn beide Operanden den Wert true haben, sonst false.

true
true
----
true

Kann das Ergebnis bereits vorhergesagt werden, nachdem der erste Operand ausgewertet wurde (d. h., wenn dieser false ist, ist das Ergebnis sicher false), wird der zweite Operand nicht mehr ausgewertet.

|| (Oder-Verknüpfung)

Bearbeiten

Verknüpft die beiden Operanden und gibt true zurück, wenn mindestens einer der beiden Operanden den Wert true hat, sonst false.

true
false
----     
true     

Kann das Ergebnis bereits vorhergesagt werden, nachdem der erste Operand ausgewertet wurde (d. h., wenn dieser true ist, ist das Ergebnis sicher true), wird der zweite Operand nicht mehr ausgewertet.

! (Negierung)

Bearbeiten

Invertiert den Wert seiner Operanden. Aus true wird false und umgekehrt.

true
----
false

Bitmanipulationen

Bearbeiten

& (bitweise Und-Verknüpfung)

Bearbeiten

Verknüpft jedes Bit beider Operanden.

1100
1010
----
1000

| (bitweise Oder-Verknüpfung)

Bearbeiten

Verknüpft jedes Bit beider Operanden.

1100
1010
----
1110

^ (exklusive Oder-Verknüpfung)

Bearbeiten

Verknüpft jedes Bit beider Operanden.

1100
1010
----
0110

~ (bitweise Negation)

Bearbeiten

Negiert den Wert jedes Bits.

10
--
01

<< (Linksverschiebung)

Bearbeiten

Verschiebt die Bits des linken Operanden um die durch den rechten Operanden angegebene Anzahl von Stellen nach links und füllt die Stellen rechts mit Nullen.

int i = 1; // dual: 00000001
int n = i << 1; //dual: 00000010 = 2

>> (Rechtsverschiebung)

Bearbeiten

Verschiebt die Bits des linken Operanden um die durch den rechten Operanden angegebene Anzahl von Stellen nach rechts. Die nach rechts verschobenen Ziffern fallen sozusagen heraus.

int i = 5; //dual: 00000101
int n = i >> 1; //dual: 00000010 = 2
int a = -2; //dual: 11111110
int b = a >> 1; //dual: 11111111 = -1

Datenzugriff

Bearbeiten

* (Zeigerdereferenzierung)

Bearbeiten

Dereferenziert einen Zeiger, damit nicht auf dessen wahren Inhalt (die Adresse) zugegriffen wird, sondern auf den Speicherbereich, auf den er verweist.

int variable = 5;
int* zeiger = &variable;
*zeiger = 3; //ändert den Wert von variable auf 3

. (Zugriff auf Member eines Objekts)

Bearbeiten

Greift auf einen Member eines Objekts zu.

obj.member = wert;
obj.funktion();

-> (Zugriff auf Member eines Objekts über einen Zeiger)

Bearbeiten

Dereferenziert einen Zeiger auf ein Objekt, der durch den linken Operanden angegeben wird, und greift auf den durch den rechten Operanden angegebenen Member zu.

obj_zeiger->member = wert;
obj_zeiger->funktion();

Das ist gleichwertig zu folgender Schreibweise:

(*obj_zeiger).member = wert;
(*obj_zeiger).funktion();

:: (Qualifizierung)

Bearbeiten

Qualifizierung eines Bezeichners (Variable, Funktion, Klasse) mit seinem übergeordneten Element (Namespace, Klasse).

std::cout << "Hallo!" << std::endl;

.* (Zugriff auf und gleichzeitige Dereferenzierung eines Members)

Bearbeiten

Zugriff auf, und gleichzeitige Dereferenzierung eines Zeiger-Members eines Objekts.

objekt.*zeiger_member = 5;

->* (Zugriff auf und gleichzeitige Dereferenzierung eines Members über einen Zeiger)

Bearbeiten

Zugriff auf, und gleichzeitige Dereferenzierung eines Zeiger-Member eines Objekt-Zeigers

obj_zeiger->*zeiger_member = wert;

Typumwandlung

Bearbeiten

() (explizites Casting)

Bearbeiten

Wandelt den Wert des Ausdrucks rechts der Klammer in den Typ innerhalb der Klammer.

float f = 3.3;
int n = (int)f;
  • Je nach Kontext benutzt der Compiler einen const_cast, static_cast oder reinterpret_cast ( siehe folgende) um die Anweisung umzusetzen.
  • Diese Art der Umwandlung stammt von C und sollte in C++ möglichst vermieden werden. Es können schwerwiegende Fehler entstehen, wenn man einen static_cast erwartet, der Compiler aus dem Kontext heraus aber einen reinterpret_cast nutzt.
  • Man kann nicht nur von/in eingebaute Datentypen casten.
  • Konstruktorschreibweise (nur bei nicht mehrteiligen eingebauten Datentypen):
n = int(f);
  • Zahlen wie 3.3 sind standardmäßig doubles. Möchte man aber ein float haben, genügt es, 3.3f zu schreiben.

static_cast

Bearbeiten

Pendant zum C-Casting.

float f = 3.3;
int n = static_cast<int>(f);

In den spitzen Klammern steht der Zieltyp. Wenn man Zeiger auf Objekte einer Hierarchie umwandeln möchte, sollte man eher dynamic_cast benutzen.

const_cast

Bearbeiten

Ermöglicht den Schreibzugriff auf eine konstant deklarierte Variable.

const int c = 42;
int* pc = const_cast<int*>(&c);

dynamic_cast

Bearbeiten

Korrekte Umwandlung eines Zeigers oder einer Referenz auf ein Objekt einer Basisklasse auf ein Objekt einer abgeleiteten Klasse. Dynamic_cast kann nur verwendet werden, wenn die Klasse mindestens eine virtuelle Methode besitzt. Üblicherweise wird der Destruktor virtuell gemacht.

class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Base *bptr1 = new Derived;
Base *bptr2 = new Base;
Derived *dptr1 = dynamic_cast<Derived*>(bptr1); // i. o.
Derived *dptr2 = dynamic_cast<Derived*>(bptr2); // Fehler: bptr2 zeigt auf eine Instanz von Base
class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived dobj;
Base bobj;
Base &bref1 = dobj;
Base &bref2 = bobj;
Derived &dref1 = dynamic_cast<Derived&>(bref1); // i. o.
Derived &dref2 = dynamic_cast<Derived&>(bref2); // Fehler: bref2 referenziert eine Instanz von Base
  • Diese Art der Umwandlung funktioniert nur, wenn das umzuwandelnde Objekt wirklich eines des Zieltyps ist.
  • Für die umgekehrte Richtung passiert die Umwandlung implizit, dynamic_cast wird nicht benötigt.
  • Achtung: Schlägt die Umwandlung eines Zeigers fehl, gibt dynamic_cast einen Nullzeiger (0) zurück!
  • Achtung: Schlägt die Umwandlung einer Referenz fehl, wirft dynamic_cast die Ausnahme std::bad_cast!

reinterpret_cast

Bearbeiten

Gefährlichster und mächtigster Cast, der selten wirklich benötigt wird.

Mit ihm können u.a. Zeiger in Datentypen (und umgekehrt) „uminterpretiert“ werden.

int i = 25;
float *fp = reinterpret_cast<float*>(i);

In diesem Beispiel zeigt fp auf eine höchstwahrscheinlich undefinierte Float-Variable an der Speicheradresse 25.

Mit diesem Operator können während der Laufzeit Informationen über eine Variable, eine Referenz, einen (dereferenzierten) Zeiger oder eine Klasse abgefragt werden. Der Operator liefert eine Referenz vom Typ type_info& zurück, der in der Header-Datei typeinfo definiert ist.

#include <typeinfo>
class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived dobj;
Base &bref = dobj;
if ( typeid(bref) == typeid(Derived) )
  std::cout << "bref referenziert Derived" << std::endl;

Achtung: Um diesen Operator verwenden zu können, muss bei den meisten Compilern RTTI explizit aktiviert werden (beim GNU-Compiler bspw. mit der Kommandozeilenoption -frtti), da die Introspektion sehr viel Aufwand vom Compiler erfordert.

Speicherbehandlung

Bearbeiten

& (Adressermittlung)

Bearbeiten

Ermittelt die Adresse des Operanden.

int i = 0;
int* zeiger = &i;

sizeof (Speicherbedarfsermittlung)

Bearbeiten

Ermittelt den Speicherbedarf eines Typs oder eines Ausdrucks.

int n = sizeof(int);
int m = sizeof n;

Für einen Typ als Argument müssen Klammern gesetzt werden, für einen Ausdruck nicht.

new (Objekterstellung)

Bearbeiten

Erstellt ein Objekt vom angegebenen Typ. Gibt einen Zeiger auf das neue Objekt zurück.

Object * p = new Object(parameter);

new[] (Anlegen eines Objekt-Arrays)

Bearbeiten

Erstellt ein Objekt-Array.

Object * object_array = new Object[12];

delete (Objektzerstörung)

Bearbeiten

Zerstört das angegebene Objekt.

Object * p = new Object;
// Tu irgendetwas mit dem Objekt.
delete p;

Verlangt einen Zeiger auf das Objekt als Argument. Das Objekt muss mit dem Operator new angelegt worden sein. Ausnahme: Der Zeiger darf auch NULL sein.

delete[] (Zerstörung eines Objekt-Arrays)

Bearbeiten

Zerstört die Objekte im angegebenen Array.

delete [] objekt_array;

Die Objekte müssen mit dem Operator new[] angelegt worden sein.

Sonstige

Bearbeiten

() (Funktionsaufruf)

Bearbeiten

Aufruf einer Funktion und eventuelle Angabe von Parametern.

funktion();
objekt.funktion(1, "asdf");

, (Aufzählung)

Bearbeiten

Das Komma hat in C++ eine doppelte Bedeutung.
Zum einen hat es die (rein syntaktische) Aufgabe eines Trennzeichens bei Funktionsaufrufen und Initialisierungen:

int x = funktion(1, 2, 3);
int y[] = { 4, 5, 6 };

Zum anderen bezeichnet es den Sequentialoperator. Die durch Komma getrennten Ausdrücke werden von links nach rechts bewertet. Im Beispiel

 for (int n=0, m=0; n < 5; n++, m=2*n) 
 {
 // ...
 }

wird bei der Reinitialisierung der Schleife zuerst n++ und dann m=2*n ausgeführt.

* Zeigerdeklaration

Bearbeiten

Erstellt einen Zeiger auf einen bestimmten Datentypen.

int* int_zeiger;
void* void_zeiger;

?: (Bedingung)

Bearbeiten

Der Bedingungsoperator (übrigens der einzige ternäre Operator, also ein Operator mit drei Operanden) ist eine Verkürzung für ein if-else-Konstrukt.

int max = (a>b) ? a : b;

Ergibt der erste Operand (in diesem Fall a>b) true, ergibt der gesamte Ausdruck den zweiten Operanden, sonst den dritten. Somit könnte die obige Zeile wie folgt geschrieben werden:

int max;
if (a>b)
{
  max = a;
}
else
{
  max = b;
}

Der Operand, der nicht das Ergebnis darstellt, wird nicht ausgewertet.

[ ] (Indizierung)

Bearbeiten

Zugriff auf ein bestimmtes Element eines Arrays.

array[index] = 5;

: (Vererbung)

Bearbeiten

Erben von Variablen und Funktionen einer Klasse

class Klasse : public Basisklasse, public AndereBasisklasse
{
  ...
};

: (Initialisierung)

Bearbeiten

Initialisieren von Oberklassen und Membervariablen innerhalb der Konstruktor-Definition

Klasse::Klasse(int a, int b) // Konstruktor mit zwei Parametern
 : Oberklasse(a), // Weiterleitung des Parameters 'a' an den Oberklassen-Konstruktor
   _b(b)          // Initialisierung der Membervariablen '_b' mit dem Wert von 'b'
{
  ...
}

Zeiger sollten nicht, dürfen aber in der Initialisierung mit new belegt werden. Das führt zu Speicherlecks.

Klasse::Klasse(int a, int b) // Konstruktor mit zwei Parametern
 : array1_(new int[a]), // Anlegen von Speicher für a Elemente vom Typ int
   array2_(new int[b])  // Anlegen von Speicher für b Elemente vom Typ int
      // bekommt array2_ keinen Speicher, wird array1_ nicht freigegeben
{
  ...
}

Besser ist es wie folgt:

Klasse::Klasse(int a, int b) // Konstruktor mit zwei Parametern
 : array1_(NULL),
   array2_(NULL)
      // bekommt array2_ keinen Speicher, wird array1_ nicht freigegeben
{
   try
   {
      array1_ = new int[a]; // Anlegen von Speicher für a Elemente vom Typ int
      array2_ = new int[b]; // Anlegen von Speicher für b Elemente vom Typ int
   }
   catch (std::bad_alloc)
   {
      delete[] array1_;
      delete[] array2_;
      throw;
      // ein Destruktoraufruf erfolgt nicht, da das Objekt nicht konstruiert wurde
   }
  ...
}

throw (Exception-Auslösung)

Bearbeiten

Wirft die als Operand angegebene Exception.

throw exception;