Zum Inhalt springen

C++-Programmierung: Operatoren

Aus Wikibooks

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.

typeid

[Bearbeiten]

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;