Zu viele Klammmern verringern aber auch oft die Übersichtlichkeit
(sonst hätte sich Lisp sicher besser durchesetzen können ;-)).
Deswegen sollten Klammern nicht zu üppig verwendet werden.
Dass die unären Operationen höhere Priorität haben als die binären, ist
intuitiv nachvollziehbar:
1 | x = -a + b; // gut
|
2 |
|
3 | x = (-a) + b; // unnötig kompliziert
|
4 |
|
5 | x = !a && b; // gut
|
6 |
|
7 | x = (!a) && b; // unnötig kompliziert
|
Man verdeutlich die höhere Priorität der unären Operatoren üblichweise
zusätzlich dadurch, dass diese ohne Leerzeichen vor den Operanden ge-
schrieben werden, während binäre Operatoren in zwei Leerzeichen einge-
schlossen werden (s. auch Anmerkung 1).
Ebenso ist es natürlich, dass Zuweisungsoperationen eine sehr niedrige
Priorität haben, da normalerweise erst gerechnet und erst dann das Er-
gebnis einer Variablen zugewiesen wird und nicht umgekehrt:
1 | x = a + b; // gut
|
2 |
|
3 | x = (a + b); // unnötig kompliziert
|
Niedrigere Priorität als die Zuweisungen hat nur noch der Kommaoperator,
der aber äußerst selten verwendet wird. Wer ihn überhaupt kennt, kennt
auch seine Priorität:
1 | for(i=0, p=1; i<8; i++, p*=2) // gut
|
2 | {...}
|
3 |
|
4 | for((i=0), (p=1); i<8; i++, (p*=2)) // unnötig kompliziert
|
5 | {...}
|
6 |
|
7 | for(((i=0), (p=1)); i<8; (i++, (p*=2))) // noch unnötig komplizierter
|
8 | {...}
|
Unklarheiten gibt es also höchstens zwischen den verbleibenden binären
Operationen. Diese lassen sich in C grob in drei Klassen unterteilen:
Rechenoperationen: Zahl op Zahl -> Zahl
Vergleichsoperationen: Zahl op Zahl -> Bool
Logikoperationen: Bool op Bool -> Bool
Man kann zwar dank der automatischen Typumwandlung in C auch mit boole-
schen Werten rechnen, boolesche Werte vergleichen oder die Ergebnisse
von Rechenoperationen logisch verknüpfen, aber das wird sehr selten
gemacht:
1 | if(a > b + c) {...} // gut
|
2 |
|
3 | if(a > (b + c)) {...} // unnötig kompliziert
|
4 |
|
5 | if(a > 0 && b > 0) {...} // gut
|
6 |
|
7 | if((a > 0) && (b > 0)) {...} // unnötig kompliziert
|
In den Beispielen wird das Weglassen der Klammern nicht zu Missverständ-
nissen führen, weil es dem gesunden Menschenverstand widerspräche, zum
Ergebnis eines Vergleichs noch einen Wert dazuzuaddieren oder das Ergeb-
nis einer logischen Und-Verknüpfung mit einem Zahlenwert zu vergleichen
(aber: s. Anmerkung 3).
Beschränkt man sich also auf die Verwendung "passender" Operandentypen
für die drei Operationsklassen, können die Operanden einer Vergleichs-
operation nur Zahlenkonstanten oder die Ergebnisse von Rechenoperationen
sein. Ebenso kommen als Operanden von Logikoperationen nur die Ergebnis-
se von Vergleichoperationen in Frage. Dadurch ist die Priorität dieser
drei Klassen untereinander auch ohne Nachschlagen im Handbuch eindeutig
festgelegt.
Meiner Meinung nach sollten Klammern deswegen nur innerhalb der drei
Klassen (insbesondere der Klasse der Rechenoperationen) gesetzt werden,
und dort auch nur dann, wenn entweder die Auswertereihenfolge geändert
werden soll (bspw. + vor *) oder wenn die Priorität ohne Nachschlagen
nicht sofort ersichtlich ist (bspw. + und <<).
1 | x = a + b * c; // gut
|
2 |
|
3 | x = a + (b * c); // unnötig kompliziert, da Punkt
|
4 | // vor Strich bereits in der Schule
|
5 | // gelehrt wird
|
6 |
|
7 | if(a > 0 || b > 0 && c > 0) {...} // gut
|
8 |
|
9 | if(a > 0 || (b > 0 && c > 0)) // unnötig kompliziert, da auch die
|
10 | {...} // Priorität logischer Operatoren
|
11 | // schon in der Schule gelehrt wird
|
12 |
|
13 | if((a > 0) || ((b > 0) && (c > 0))) // noch unnötig komplizierter
|
14 |
|
15 | x = 1 << a + 1; // akzeptabel
|
16 |
|
17 | x = 1 << (a + 1); // besser, da nicht jeder die Prio-
|
18 | // rität von + und auswendig kennt
|
Die aufgeführten Negativbeispiele sind noch relativ leicht zu lesen, da
die Ausdrücke wegen der kurzen Variablennamen mit einem Blick erfassbar
sind. Bei längeren Ausdrücken ist die Zuordnung der Klammen nicht mehr
so leicht:
1 | if((coord1.x > coord2.x) || ((coord1.y > coord2.y) && (coord1.z > coord2.z)))
|
2 | { ...}
|
Anmerkung 1:
Manchmal sieht es schöner aus, die Priorität "schwieriger" Operatoron
durch geschicktes Setzen bzw. Weglassen von Leerzeichen statt durch
Klammerung hervorzuheben. Beispiel aus der AVR-Welt:
1 | DDRA = 1 << PA0 | 1 << PA1 | 1 << PA2 | 1 << PA2; // akzeptabel
|
2 |
|
3 | DDRA = (1 << PA0) | (1 << PA1) | (1 << PA2) | (1 << PA2); // besser
|
4 |
|
5 | DDRA = 1<<PA0 | 1<<PA1 | 1<<PA2 | 1<<PA2; // noch besser
|
Anmerkung 2:
Auch durch geignete Zeilenumbrüche kann die Operatorpriorität
verdeutlicht werden: In dem Beispiel von Kar Heinz
1 | if( ( A+10 > c )
|
2 | || ( B+25 > c ) )
|
3 | d = 0;
|
sind die Teilausdrücke (also die beiden >-Vergleiche) schon so gut
voneinander abgesetzt, dass die zusätzlichen Klammern gar nicht mehr
nötig sind:
1 | if( A+10 > c
|
2 | || B+25 > c )
|
3 | d = 0;
|
oder je nach Geschmack auch
1 | if(A+10 > c ||
|
2 | B+25 > c)
|
3 | d = 0;
|
Anmerkung 3:
Dass Rechenoperationen eine höhere Priorität als Vergleichoperationen
haben, ist zwar logisch, in C aber bei den Operatoren &, | und ^ nicht
konsequent umgegesetzt. Deswegen müssen in einigen Fällen Klammern
gesetzt werden, obwohl es dem gesunden Menschenverstand widerspricht:
1 | #define DATA_MASK 0x07
|
2 |
|
3 | if((PINA & DATA_MASK) == 5) // richtig
|
4 | {...}
|
5 |
|
6 | if(PINA & DATA_MASK == 5) // logisch, liefert aber nicht das
|
7 | {...} // erwartete Ergebnis
|
Die C-Entwickler haben die Bitoperatoren wohl als Logikoperatoren ange-
sehen, obwohl sie in Wirklichkeit Rechenoperatoren (int op int -> int)
sind.
Damit ihr mich jetzt nicht haut: Das Vorgeschriebene ist nur meine
persönliche, subjektive Meinung ;-)