Operatorenklammerung ()

In Programmen treten häufig mehrere Operatoren hintereinander auf. Die Reihenfolge, in welcher diese Operatoren abgearbeitet werden, ist eindeutig festgelegt anhand von Abarbeitungsrichtung und Rangordnung. In einigen Fällen jedoch ist eine andere Reihenfolge erwünscht. Die Klammerung von Operatoren dient dazu, diese Reihenfolge explizit festzulegen. Operatoren werden mit runden Klammern () geklammert.

Details

Die Operatorenklammerung erwartet einen Operanden als rvalue oder lvalue innerhalb der Umklammerung und besitzt keine Abarbeitungsrichtung. Der Rückgabewert entspricht dem umklammerten Operanden und kann somit sowohl ein rvalue, als auch ein lvalue sein.

Die Operatorenklammerung ist im eigentlichen Sinne kein Operator, da sie nur einen Hinweis für den Compiler gibt und somit keinen Code produziert. Dies ist auch der Grund, wieso dieser Operator keine Abarbeitungsrichtung besitzt: Es gibt nichts abzuarbeiten.

Die Klammerung muss wohlgeformt sein, sprich, jede schliessende runde Klammer ) muss irgendwann vorher durch eine eindeutige öffnende runde Klammer ( eingeleitet worden sein.

Die Operatorenklammerung wird entsprechend der Operatoren-Rangordnung stets prioritär behandelt. Im folgenden sind einige Fälle aufgelistet, wo Operatorenklammerung üblich ist.

Klammerung im Zweifelsfalle

Wenn unklar ist, was für eine Abarbeitungsrichtung oder Rangordnung Operatoren untereinander haben, wenn unerklärliche Phänomene in einer Codezeile auftreten, wenn sichergestellt werden soll, dass die Mathematik stimmt, wenn unklar ist, wo welcher Typ herrscht, wenn unsicher ist, ob ein Codefragment später mal erweitert werden könnte, ...

Es empfielt es sich STETS, Klammern zu setzen. Klammern schaden NIE!

Klammerung von arithmetischen Berechnungen

Die Klammerung von mathematischen Ausdrücken ist notwendig und hilfreich, da oft Operatoren hintereinander auftreten, die gleiche Abarbeitungsrichtung sowie gleiche Rangordnung besitzen.

Der Effekt von Klammerung kann mittels des Subtraktrions-Operator demonstriert werden:




0
10
6
4
#include <stdio.h>

int main(){
  printf("%d\n", 10 -   5 -   3 - 2     );
  printf("%d\n", 10 - ( 5 -   3 - 2   ) );
  printf("%d\n", 10 - ( 5 - ( 3 - 2 ) ) );
  printf("%d\n", 10 -   5 - ( 3 - 2 )   );
}

Grundsätzlich folgen die Abarbeitungsrichtung und Rangordnung im allgemeinem einem intuitiven Verständnis der jeweiligen Operatoren. Die üblichen mathematischen Operatoren folgen beispielsweise den gängigen Regeln der Mathematik, so beispielsweise dem Assoziativgesetzt (Punkt-vor-Strich-Regel):

10
14
printf("%d\n",  4 + 3  * 2);
printf("%d\n", (4 + 3) * 2);

Es darf jedoch nicht vergessen gehen, dass der Compiler grundsätzlich nichts von Mathematik versteht und somit gängige Vereinfachungen nicht automatisch korrekt umsetzt. In der Mathematik ist es beispielsweise üblich, dass der Ausdruck (Zwei Pi) als eine Einheit wahrgenommen wird. In C und C++ ist dies nicht der Fall. Im folgenden Beispiel wird die Umwandlung von Radiant in Grad berechnet. Zwar wurde in der ersten Zeile zur optischen Unterstützung der Umrechnungsfaktor umklammert, doch erst in der zweiten Zeile ist durch die zusätzliche Klammerung die Berechnung korrekt.

1776.525791
180.000000
printf("%f\n", 3.14159 * (360. /  2. * 3.14159 ));
printf("%f\n", 3.14159 * (360. / (2. * 3.14159)));

Klammerung zur optischen Unterstützung

Oftmals ist die Abfolge der Operatorenauswertung klar, dennoch werden Klammern gesetzt, um rein optisch dem Programmier einen besseren Überblick zu geben.

void cross_product(T* d, const T* a, const T* b){
  d[0] = (a[1] * b[2]) - (a[2] * b[1]);
  d[1] = (a[2] * b[0]) - (a[0] * b[2]);
  d[2] = (a[0] * b[1]) - (a[1] * b[0]);
}

Gerade bei Indizes-Berechnungen erleichtern Klammern die Lesbarkeit und Fehlersuche.





Arraysize: 8
Maxindex:  7

(1,2) = 5
(0,3) = 6
int i;
int numy = 4;
int numx = 2;
int a[numy * numx];
printf("Arraysize: %d\n", (numy * numx));
printf("Maxindex:  %d\n", (numy * numx) - 1);
for(i=0; i < (numy * numx); i++){a[i] = i;}
printf("(1,2) = %d\n", a[(2 * numx) + 1]);
printf("(0,3) = %d\n", a[(3 * numx) + 0]);

Klammerung bei Arrays, Pointern und Dereferenzen

Wenn Pointer oder Arrays mit einem Cast in einen bestimmten Typ umgewandelt werden, so ist nicht immer klar, welcher Teil des Ausdruckes nun tatsächlich gecastet wird. Im folgenden Beispiel wird versucht, ein zweidimensionales Array als ein eindimensionales Array anzusprechen:


Number: -1073743992
Number: 2
int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("Number: %i\n",  (int*)a [1]);
printf("Number: %i\n", ((int*)a)[1]);

In der ersten Zeile wird fälschlicherweise angenommen, dass der Cast auf das zweidimensionale Array a angewendet wird. Tatsächlich jedoch wird in dieser Zeile zuerst das Element mit Index 1 des zweidimensionalen Arrays angesprochen, was einen Pointer auf einen int zurückgibt. Daraufhin wird dieser (unnützerweise) gecastet und das Resultat (der Pointer) wird als Dezimalzahl ausgegeben. In der zweiten Zeile wurde durch die Operatorenklammerung sichergestellt, dass der Cast für das zweidimensionale Array a bestimmt ist, worauf danach mit dem Element-Operator das korrekte Element ausgelesen wird.

Im folgenden Beispiel wird ein zweidimensionales Array mittels dem Dereferenz-Operator angesprochen. Je nach Klammerung kann dies zu unterschiedlichen (und möglicherweise unerwünschten) Ergebnissen führen:


Number: 2
Number: 4
int numbers[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("Number: %d\n", (*numbers)[1]);
printf("Number: %d\n",  *numbers [1]);

Während bei der ersten Zeile zuerst das erste Dreier-Tupel {1,2,3} dereferenziert wird und danach das Element 2 mit Index [1] ausgewählt wird, wird bei der zweiten Zeile zuerst das Dreier-Tupel {4,5,6} mit Index [1] ausgewählt und dann das erste Element 4 dereferenziert.

Klammerung bei arithmetischer Umwandlung

Durch die in C und C++ eingebaute automatische arithmetische Umwandlung ist es möglich, dass Operatoren in einer suboptimalen Reihenfolge abgearbeitet werden. Im folgenden Beispiel wird eine Berechnung ausgeführt, dessen Ergebnis und alle Zwischenergebnisse zwar mittels einer float-Zahl ohne Probleme ausgedrückt werden könnten, die Berechnung selbst jedoch implizite Umwandlungen durchführt, wodurch Genauigkeit verlorengeht. Erst durch die Verwendung der Klammern kann das genaue Resultat ermittelt werden, da hierbei die beiden int-Werte nicht umgewandelt werden.

0.123001
0.123000
printf("%f\n",  0.123f +  100 - 100 );
printf("%f\n",  0.123f + (100 - 100));

Solche und ähnliche Effekte sind insbesondere bei extremen Werten vorzufinden, wo durch eine automatische arithmetische Umwandlung Genauigkeit verloren geht. Hier wird nicht weiter auf diese Besonderheiten eingegangen, es wird jedoch empfohlen, die automatische arithmetische Umwandlung nicht einfach sorglos zu verwenden und im Zweifelsfalle Klammern oder gar Casts zu setzen.

Bei der Verwendung von Casts müssen manchmal zusätzliche Klammern verwendet werden. Im folgenden Beispiel kann nur durch Klammerung der gesamten Berechnung das korrekte Resultat als Dezimalzahl ausgegeben werden.

1.5 + 1.5 = 3.000000
1.5 + 1.5 = 3
printf("1.5 + 1.5 = %f\n",       1.5 + 1.5 );
printf("1.5 + 1.5 = %d\n", (int)(1.5 + 1.5));

Klammerung des Bedingungsoperators

Der Bedingungsoperator ist eine beliebte Abkürzung, um umständliche if-Strukturen zu vermeiden. Dieser Operator wird jedoch aufgrund seiner Rangordnung weniger prioritär abgehandelt, wie die meisten anderen Operatoren. Folgendes Beispiel verdeutlicht die Notwendigkeit, den Bedingungsoperator korrekt zu klammern:




0
50
51
int i = 1 +     1 < 2   ? 50 : 0  ;
int j = 1 +   ( 1 < 2 ) ? 50 : 0  ;
int k = 1 + ( ( 1 < 2 ) ? 50 : 0 );
printf("%d\n", i);
printf("%d\n", j);
printf("%d\n", k);

Bei der Zuweisung zur Variablen i wird der Additions-Operator prioritär zum Kleiner-Operator behandelt, womit 1 + 1 zusammengezählt wird. Da sodann der Kleiner-Operator prioritär zum Bedingungsoperator behandelt wird, wird 2 < 2 zu false ausgewertet, was als erstes Argument für den Bedingungsoperator dient, welcher somit 0 zurückgibt.

Bei der Zuweisung zur Variablen j wird zwar der Kleiner-Operator aufgrund der Klammerung prioritär zum Additions-Operator behandelt, allerdings wird auch der Additions-Operator prioritär zum Bedingungsoperator behandelt, womit 1 + true zu 2 ausgewertet wird, was als erstea Argument für den Bedingungsoperator dient, welcher somit 50 zurückgibt.

Erst bei der Zuweisung zur Variablen k wird geprüft, ob 1 kleiner ist als 2, worauf 50 zu 1 hinzuaddiert wird.

Es sei angemerkt, dass diese letzte Klammerung bei einfachen Zuweisungen ohne weitere Operatoren nicht nötig ist, da Zuweisungen noch weniger prioritär behandelt werden wie der Bedingungsoperator.



50
50
int m =   1 ? 50 : 0  ;
int n = ( 1 ? 50 : 0 );
printf("%d\n", m);
printf("%d\n", n);

Weitere Klammerungen

Die runden Klammern werden auch für die Klammerung in Makros verwendet. Einige wichtige Fälle können bei der #define-Direktive nachgelesen werden.

Es gäbe noch viele spezielle Situationen, in denen Klammern nützlich oder gar nötig sind. Hier wurden nur einige wichtige Eigenheiten der Sprachen C und C++ angesprochen mit Verweis auf eine mögliche Fehlerquelle. Nach wie vor gilt der bereits oben genannte Grundsatz: Im Zweifelsfalle: Klammern setzen!