Index >>Überladen

Operator und Operand

In C und C++ wird die eigentliche Programmausführung mittels Operatoren beschrieben. Nebst mathematischen Verknüpfungen wie beispielsweise +, -, * und / sind selbst Funktionsaufrufe und Speicheralloziierungen als Operatoren zu verstehen. Die Strukturen, die durch Operatoren ausgelesen oder verändert werden, sind als Operanden zu bezeichnen. Jede Operation (zu verstehen als die Verknüpfung von Operanden mittels eines Operators) erzeugt einen Rückgabewert, welcher wiederum als Operand dienen kann. Um das Zusammenspiel von Operatoren und Operanden zu illustrieren wird hier folgendes kleines Beispiel aufgeführt:

c = a + b;

Hierbei sind zunächst die Variablen a und b als Operanden zu verstehen, welche durch den Additions-Operator + verknüpft werden. Das Resultat dieser Addition gilt wiederum als Operand, welcher mittels dem Zuweisungs-Operator = dem Operanden c zugewiesen wird.

Details

Das Verständnis von Operatoren und Operanden ist im Allgemeinen wenig problematisch, da es an die gebräuchliche Mathematik angelehnt ist, wo auch von Operatoren gesprochen wird und Resultate einer Operation als Operand für eine andere dienen können. In der Programmierung gibt es jedoch einige Besonderheiten, die beachtet werden müssen.

Operanden: lvalue und rvalue

Alle Operatoren lesen Werte und geben Werte zurück, doch nur einige Operatoren weisen Werte zu. Dabei unterscheidet man bei den verschiedenen Werten zwischen L-Werten und R-Werten, im Englischen bekannt als lvalue und rvalue. Die Bedeutung dieser beiden Buchstaben L und R ist niemals standartisiert worden, weswegen bis heute einige Unklarheit darüber herrscht. Die einfachste, jedoch nicht korrekte Erklärung dieser beiden Buchstaben L und R ist, dass L für Links steht und R für Rechts. Diese Erklärung leitet sich ab aus der Betrachtung des Zuweisungsoperators, welcher rechts stets einen R-Wert erwartet, den er einem L-Wert auf der linken Seite zuweist:

a = 5;

Tatsächlich jedoch ist es angebrachter, von Localizable (L) und Readable (R) zu sprechen: Adressierbar und Lesbar. Dies aus folgendem Grund: Um einen Wert zu lesen, muss er insbesondere eines sein: Lesbar. Um jedoch einen Wert zuweisen zu können, braucht man Platz, zu dem man ihn zuweisen kann. Ein Makro oder ein konstanter Wert beispielsweise braucht keinen Platz, er ist fest im Programm eingebunden. Eine Variable jedoch hat einen Platz reserviert, sei es im Speicher oder als Register des Prozessors, an eine solche Stelle kann der Compiler einen Wert schreiben, er kann zugewiesen werden, er hat eine Adresse, er ist lokalisierbar.

Für die Definition des Zuweisungsoperators bedeutet dies folgendes: Der Ausdruck auf der rechten Seite muss einen lesbaren Wert darstellen. Links des Operators jedoch muss ein Ausdruck stehen, dessen Auswertung einen adressierbaren Wert ergibt. Es gibt jedoch auch Operatoren, bei denen diese Position nicht stimmt. Beispielsweise gibt es die beiden Varianten des Inkrements: Post-Inkrement-Operator und Pre-Inkrement-Operator. Die Post-Variante steht nach dem Operanden, die Pre-Variante davor, jedoch erwarten beide Operatoren einen L-Wert. Dies deswegen, da die Operatoren diesen Wert selbst verändern, ihn somit also auch adressieren können müssen. Dies kann man leicht überprüfen, indem man versucht, folgende zwei fehlerhafte Zeilen zu kompilieren, beide Varianten erwarten einen L-Wert, obschon der Wert bei der zweiten Zeile rechts steht:

5++; ++5;

invalid lvalue in increment invalid lvalue in increment

Anders herum gibt es Operatoren, welche überhaupt keinen L-Wert erwarten, sondern zwei R-Werte. Beispielsweise der Additions-Operator:

5 + 5;

no error

Am besten sieht man die Bedeutung Localizable am Adress-Operator:

&5;

invalid lvalue in unary '&'

Man beachte, dass der Compiler den Fehler address of register variable ... requested meldet, wenn versucht wird, mittels des Adress-Operators die Adresse eines Prozessorregisters anzusprechen.

Operatoren: Unary, binary

Operatoren erwarten entweder einen oder zwei Operanden, was im Englischen mit unary und binary bezeichnet wird. Unäre Operatoren besitzen immer nur einen Operanden, der entweder links oder rechts von dem Operator steht. Ein Beispiel für einen unären Operator ist der Negativ-Operator, der auch oft mit unary minus bezeichnet wird. Dieser Operator hat einen Operanden, den er negiert.

-a

Binäre Operatoren hingegen haben zwei Operanden, wie beispielsweise der Multiplikations-Operator, der zwei Werte miteinander multipliziert.

a * b

Operatoren: Abarbeitungsrichtung

Zu jedem Operator ist zudem definiert, in welcher Reichenfolge die Operanden abgearbeitet werden müssen. Diese sogenannte Abarbeitungsrichtung definiert entweder von links nach rechts oder von rechts nach links, eine vollständige Auflistung der Abarbeitungsrichtung aller Operatoren findet man weiter unten. Durch die Festlegung der Abarbeitungsrichtung wird festgelegt, wie mehrere gleichartige Operatoren hintereinander abgearbeitet werden müssen. Beispielsweise wird die Addition von links nach rechts abgearbeitet, im folgenden Beispiel also zuerst a mit b addiert und danach das Resultat mit c. Die Zuweisung jedoch definiert die Abarbeitung von rechts nach links, womit zuerst der Wert c der Variablen b zugewiesen wird und dann der Wert, der nun in b gespeichert ist, in a geschrieben wird. Als Folge beinhalten alle drei Variablen denselben Inhalt wie c.

a + b + c; a = b = c;

Auch für unäre Operatoren ist eine Abarbeitungsrichtung definiert, was die Position des Operanden relativ zum Operator definiert. Der unäre Negativ-Operator - ist definiert als von rechts nach links, was bedeutet, dass der Operand rechts steht und der Operator links. Der unäre Post-Inkrement-Operator ++ hingegen definiert von links nach rechts, der Operand steht somit links und der Operator rechts.

-a a++

Operatoren: Rangordnung

Zusätzlich zur Abarbeitungsrichtung ist zu jedem Operator eine Priorität definiert. Diese Rangordnung stellt die Assoziativität verschiedenartiger Operatortypen aufgrund deren Priorität sicher. Beispielsweise werden mathematische Operatoren prioritärer behandelt als Zuweisungsoperatoren, man möchte ja zuerst eine Rechnung ausführen und erst dann das Resultat einer Variablen zuweisen. Die Rangordnung beinhaltet beispielsweise auch das bekannte Assoziativgesetzt (Punkt-vor-Strich-Regel) der Mathematik:

#include <stdio.h> int main(){ printf("%d\n", 4 + 3 * 2); printf("%d\n", (4 + 3) * 2); return 0; }

10 14

Sowohl der Additions-Operator +, als auch der Multiplikations-Operator * werden von links nach rechts abgearbeitet, jedoch hat der Multiplikations-Operator eine höhere Priorität wie der Additions-Operator. Somit wird ohne Klammerung die Multiplikation vor der Addition ausgeführt, genauso, wie es in der Mathematik üblich ist.

Die Prioritäten werden üblicherweise festgelegt, indem jedem Operator ein Rang zugeordnet wird. Operatoren mit tiefem Rang werden prioritärer behandelt wie solche mit einem höheren. Der prioritärste Rang ist 1. Leider sind die verschiedenen Quellen nicht immer ganz konsistent, manche definieren 15 Rangordnungen, andere deren 20, bei manchen sind bestimmte Operatoren nicht aufgeführt, bei anderen werden mehrere Operatoren zusammengefasst. Hier wurde versucht, allen Quellen möglichst gerecht zu werden und so wurden insgesamt 21 Ränge verteilt:

Rang 1, von links nach rechts abgearbeitet
::Bereichsoperator
::Global-Bereichsoperator
Rang 2, von links nach rechts abgearbeitet
()Operatorenklammerung
Rang 3, von links nach rechts abgearbeitet
()Funktionsaufruf-Operator
[]Array-Element-Zugriff-Operator
.Feld-Zugriff-Operator
->Pointer-Zugriff-Operator
const_cast<>const-Casting-Operator
reinterpret_cast<>reinterpret-Casting-Operator
static_cast<>static-Casting-Operator
dynamic_cast<>dynamic-Casting-Operator
typeid()Informationen über einen Typ
++Post-Inkrement-Operator
--Post-Dekrement-Operator
Rang 4, von rechts nach links abgearbeitet
newSpeicher-Reservation-Operator
deleteSpeicher-Freigabe-Operator
new []Array-Speicher-Reservations-Operator
delete []Array-Speicher-Freigabe-Operator
Rang 5, von rechts nach links abgearbeitet
++Pre-Inkrement-Operator
--Pre-Dekrement-Operator
&Adress-Operator
*Dereferenz-Operator
+Positiv-Operator
-Negativ-Operator
~Bitweise NOT
!Logisch NOT
sizeof()Typgrösse-Operator
Rang 6, von links nach rechts abgearbeitet
()expliziter Casting-Operator
Rang 7, von links nach rechts abgearbeitet
.*Feld-Dereferenz-Operator
->*Pointer-Dereferenz-Operator
Rang 8, von links nach rechts abgearbeitet
*Multiplikations-Operator
/Divisions-Operator
%Modulo-Operator
Rang 9, von links nach rechts abgearbeitet
+Additions-Operator
-Subtraktions-Operator
Rang 10, von links nach rechts abgearbeitet
<<Bitweise SHL
>>Bitweise SHR
Rang 11, von links nach rechts abgearbeitet
<Kleiner-Operator
<=Kleiner-Gleich-Operator
>Grösser-Operator
>=Grösser-Gleich-Operator
Rang 12, von links nach rechts abgearbeitet
==Gleich-Operator
!=Ungleich-Operator
Rang 13, von links nach rechts abgearbeitet
&Bitweise AND
Rang 14, von links nach rechts abgearbeitet
^Bitweise XOR
Rang 15, von links nach rechts abgearbeitet
|Bitweise OR
Rang 16, von links nach rechts abgearbeitet
&&Logisch AND
Rang 17, von links nach rechts abgearbeitet
||Logisch OR
Rang 18, von rechts nach links abgearbeitet
? :Bedingungs-Operator
Rang 19, von links nach rechts abgearbeitet
throwAusnahmebehandlungs-Operator
Rang 20, von rechts nach links abgearbeitet
=Zuweisungs-Operator
+=Zuweisung nach Addition-Operator
-=Zuweisung nach Subtraktion-Operator
*=Zuweisung nach Multiplikation-Operator
/=Zuweisung nach Division-Operator
%=Zuweisung nach Modulo-Operator
<<=Zuweisung nach SHL-Operator
>>=Zuweisung nach SHR-Operator
&=Zuweisung nach bitweisem AND
|=Zuweisung nach bitweisem OR
~=Zuweisung nach bitweisem NOT
^=Zuweisung nach bitweisem XOR
Rang 21, von links nach rechts abgearbeitet
,Aufzählungs-Operator

Man beachte, dass die Quellen sich teilweise auch widersprechen. Erst ab der Multiplikations-Gruppe mit den Operatoren *, / und % sind sich sämtliche Quellen einig. Bei den niederen Rängen ist beispielsweise der Unterschied der Rangordnung des Klammer-Operators aufzuführen, welcher bei manchen Quellen die absolute Priorität 1 erhält, bei anderen jedoch mit dem Type-Cast gleichgestellt wird. Allerdings wird man beim wirklichen Programmieren äusserst selten auf Probleme stossen, da viele Operatoren gar nicht erst gemeinsam auftreten und somit eine Rangfolge hinfällig ist.

 

Index >>Überladen