Forum: Compiler & IDEs Verständnisfrage Klammersetzung


von jens D. (Gast)


Lesenswert?

Hallo Forum,

bin gerade am C lernen.

Was mir nicht klar ist:

Ist

if(a==b+5)

das Gleiche wie

if(a==(b+5))

Ich habe gesehen, dass es bei den Operatoren Prioritäten gibt, werde 
aber nicht recht schlau daraus.

Danke für Eure Antworten

Jens

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

jens D. schrieb:
> Ich habe gesehen, dass es bei den Operatoren Prioritäten gibt, werde
> aber nicht recht schlau daraus.

Ja, gibt es. Auswendig lernen oder bei Bedarf nachschlagen:

http://en.cppreference.com/w/c/language/operator_precedence

+ bindet stärker als ==

von Bildformator (Gast)


Lesenswert?

>if(a==b+5)
>das Gleiche wie
>if(a==(b+5))

Nein. Ist nicht das selbe. Der Unterschied ist:
bei 1) muss man nachschlagen oder im Forum fragen.
bei 2) isses klar.

Ich will damit sagen, dass die der Code nicht nur für den Compiler 
lesbar sein soll, sondern auch für Biomasse.
Wenn es also nicht klar sein sollte: Klammern setzen.

von Peter II (Gast)


Lesenswert?

Bildformator schrieb:
> Ich will damit sagen, dass die der Code nicht nur für den Compiler
> lesbar sein soll, sondern auch für Biomasse.
> Wenn es also nicht klar sein sollte: Klammern setzen.

naja, der der den code geschrieben hat wird wohl wissen wie es 
funktioniert, sonst hätte er ihn nicht so geschrieben. Oder willst du 
das jeder der code so schreibt das ihn jeder Anfänger auch versteht?

von Karl H. (kbuchegg)


Lesenswert?

Bildformator schrieb:

> Ich will damit sagen, dass die der Code nicht nur für den Compiler
> lesbar sein soll, sondern auch für Biomasse.
> Wenn es also nicht klar sein sollte: Klammern setzen.

Das man nicht alle Precedence Stufen im Kopf auswendig parat hat, 
gestehe ich dir gerne zu. Aber zumindest die wichtigsten sollte man 
schon kennen. Zumal die so angeordnet sind, dass sich die übliche und 
intuitiv richtige Reihung ergibt.

Wenn eine Programmiersprache so definiert wäre, dass ein (sinngemässses)
1
   if( a+b == c+d )
implizit als
1
   if( a + (b==c) + d )
geparst werden würde, dann würden sich viele Programmierer wohl mächtig 
wundern. In allen Programmiersprachen, die ich kenne, haben 
Vergleichsoperatoren eine geringere Priorität als arithmetische 
Operatoren. Und das ist auch gut so.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Karl Heinz (kbuchegg) (Moderator)

>Das man nicht alle Precedence Stufen im Kopf auswendig parat hat,
>gestehe ich dir gerne zu. Aber zumindest die wichtigsten sollte man
>schon kennen.

So ziemlich jedes der C-Standardwerke hat die auf den letzten Seiten 
drin, ein Griff ins Regal, alles im Blick.

von Yalu X. (yalu) (Moderator)


Lesenswert?

jens D. schrieb:
> Ich habe gesehen, dass es bei den Operatoren Prioritäten gibt, werde
> aber nicht recht schlau daraus.

Was genau ist dir nicht klar?

Die Prioritätstabelle (bspw. die oben von Hannes verlinkte) ist
folgendermaßen anzuwenden:

Angenommen, du hast den Ausdruck

  A op1 B op2 C

A, B, C sind dabei die Operanden (z.b. Zahlen oder Variablen), op1 und
op2 sind Operatoren (z.B. +, *, == oder =).

Um herauszufinden, wie der Ausdruck ausgewertet wird, suchst du die
beiden Operatoren in der Tabelle. Jetzt gibt es drei Möglichkeiten:

1. op1 steht in der Tabelle höher als op2 (in der Spalte "Precedence"
   steht für op1 eine kleinere Zahl):
   Dann hat op1 höhere Priorität und der Ausdruck wird ausgewertet wie

     (A op1 B) op2 C

2. op1 steht in der Tabelle tiefer als op2 (in der Spalte "Precedence"
   steht für op1 eine größere Zahl):
   Dann hat op1 niedrigere Priorität und der Ausdruck wird ausgewertet
   wie

     A op1 (B op2 C)

3. op1 und op2 stehen stehen im gleichen Feld in der Tabelle:
   Dann haben beide Operatoren die gleiche Priorität. Wie der Ausdruck
   ausgewertet wird, entscheidet sich anhand der Spalte "Associativity":

   a) Ist die Associativity "Left-to-right" (linksassoziativ), wird der
      Ausdruck ausgewertet wie

        (A op1 B) op2 C

   b) Ist die Associativity "Right-to-left" (rechtsassoziativ), wird der 
Ausdruck
      ausgewertet wie

        A op1 (B op2 C)

Bei den unären Operatoren verhält es sich ähnlich, nur dass dort
entweder der erste oder der zweite Operand fehlt.


Beispiele:

1. a==b+5

   + hat höhere Priorität als ==, also ist der Ausdruck gleich

   a==(b+5)

2. *p++

   ++ hat eine höhere Priorität als *, also ist der Ausdruck gleich

   *(p++)

3. a-b+c

   - und + haben die gleiche Priorität. Da sie linksassoziativ sind, ist
   der Ausdruck gleich

   (a-b)+c

4. a=b+=5

   = und += haben die gleiche Priorität. Da sie rechtsassoziativ sind,
   ist der Ausdruck gleich

   a=(b+=5)

   d.h. erst wird zur Variablen b der Wert 5 addiert und der neue Wert
   von b auch a zugewiesen.


Hier sind noch ein paar Tipps dafür, wo man sinnvollerweise Klammern
setzt (auch wenn sie nicht unbedingt benötigt werden), und wo besser
nicht. Natürlich gehen hier die Meinungen teilweise stark auseinander.
Anfänger neigen eher dazu, mehr Klammern als nötig zu setzen, während
Hardcore-C-Programmieren gerne auf jede überflüssige Klammer verzichten.

Man kann die C-Operatoren grob in folgende Gruppen unterteilen:

1. Unäre Suffix-Operatoren (Precedence 1 in der Tabelle)

2. Unäre Präfix-Operatoren (Precedence 2 in der Tabelle)

3. Binäre Rechenoperatoren (Precedence 3, 4 und 5 in der Tabelle)

4. Vergleichsoperatoren (Precedence 6 und 7 in der Tabelle)

5. Binäre Logikoperatoren (Precedence 11 und 12 in der Tabelle)

6. Zuweisungsoperatoren (Precedence 14 in der Tabelle)

Die Rangfolge der ersten beiden Gruppen (unäre Operatoren) sollte man
sich einfach merken, um nicht zu viele Klammern setzen zu müssen. In

  präfixop A suffixop

liegt die implizite Klammerung immer auf der rechten Seite, also

  präfixop (A suffixop)

Die Rangfolge der Gruppen 2 bis 5 entspricht der Intuition bzw. der
Formelnotation aus der Schulmathematik.

Da Zuweisungen i.Allg. nur in der obersten Ebene eines Ausdrucks (und
nicht in Teilausdrücken) auftauchen, entspricht auch hier die
prioritätsmäßige Einordnung der Intuition.

Man kann also in einem Ausdruck, dessen Operatoren aus unterschiedlichen
Gruppen stammen, optionale Klammern getrost weglassen.

Innerhalb der Gruppe 3 (Rechenoperatoren) gilt die ebenfalls aus der
Schulmathematik bekannte Punkt-vor-Strich-Regel.

Auch innerhalb Gruppe 5 (binäre Logikoperatoren) wird wird von den
meisten Leuten die Konjunktion (&&) als stärker bindend als die
Disjunktion (||) angesehen, da die Konjunktion eine gewisse
Verwandtschaft zur Multiplikation und die Disjunktion zur Addition hat.

In den vorgenannten Fällen sollte man meiner Meinung nach nur dann
Klammern zu setzen, wenn die Auswertung von der vorgegebenen Rangfolge
abweiche soll. Ausdrücke wie

  a = b + 5 * c
  a >= 0 && a <= 9
  -array[5]
  *b++

brauchen somit keine Klammerung. Bei

  b == 0 || a >= 0 && a <= 9

lasse ich normalerweise die Klammern weg, wenn der gesamte Ausdruck
nicht zu lang ist. Die Klammerung des rechten Teilausdrucks ist aber
ebenfalls in Ordnung, weil der Vorrang von && gegenüber || nicht jedem
geläufig ist:

  b == 0 || (a >= 0 && a <= 9)

Werden Shift-Operationen (<< und >>) mit anderen Rechenoperationen
gemischt, dürfen gerne Klammern gesetzt werden, da die Shift-Operationen
nicht Bestandteil der Schulmathematik sind und es somit keine allgemein
anerkannte Rangfolge gibt. Je nachdem, was genau berechnet werden soll,
wird man also entweder

  a << (i + 1)

oder

  (a << i) + 1

schreiben, auch wenn im ersten Fall die Klammern nicht unbedingt
erforderlich sind.


Ein paar der Operatoren in der Tabelle tauchen in der obigen
Gruppenbildung nicht aufund werden hier gesondert behandelt:

Da sind als erstes die bitweisen Operatoren (&, ^ und |). Sie sind
streng genommen Rechenoperatoren, da sie beliebige Integer-Zahlen als
Ergebnis liefern können. Folglich müssten sie prioritätsmäßig bei den
Operatoren aus Gruppe 3 (*, + usw.) liegen. Aus historischen Gründen
liegen sie aber bei den Logikoperatoren (&& und ||) und damit unterhalb
von den Vergleichsoperatoren, was ziemlich ungeschickt ist. Oft will man
nämlich das Ergebnis einer Bitoperation (Maskierung) mit einem Wert
vergleichen und würde dazu intuitiv schreiben:

  byte & 0x0f < 3

Das liest der Compiler aber als

  byte & (0x0f < 3)

was ein ziemlich unsinniger Ausdruck ist, den man in dieser Form wohl
extrem selten bis überhaupt nie brauchen wird. Stattdessen muss man hier
dummerweise explizite Klammern setzen, um das gewünschte Ergebnis zu
erhalten:

  (byte & 0x0f) < 3

Ein weiterer der ungruppierten Kandidaten ist der Operator für bedingte
Auswertung (?:, auch ternären Operator genannt, weil er als einziger der
C-Operatoren drei Operanden hat). Da er nur selten, und so gut wie nie
verschachtelt zum Einsatz kommt, ist es in Ordnung, seine drei Operanden
jeweils zu klammern:

  (a > 0) ? (b + 5) : (c - 5)

Dann gibt es noch den Komma-Operator, der ebenfalls nur selten gebraucht
wqird. Da er von allen Operatoren die niedrigste Priorität hat und er so
gut wie nie in Teilausdrücken auftaucht, muss man bei seiner Verwendung
normalerweise keine zusätzlichen Klammern setzen.

Bei Klammern, die nur der besseren Lesbarkeit dienen, kann man
überlegen, ob nicht ein geschicktes Einfügen oder Weglassen von
Leerzeichen den gleichen Zweck erfüllt. In der Mikrocontrollerei hat man
oft den Fall, dass man mehrere Bits (z.B. Bit 2, 4 und 5) eines
Hardwareregisters setzen und den Rest löschen möchte möchte. Man kann
dies mit

  HWREG = 0x34;

tun, allerdings sieht der Leser dabei nicht ohne Weiteres die Nummern
der gesetzten Bits. Oft besser ist deswegen

  HWREG = (1 << 2) | (1 << 4) | (1 << 5);

Die Klammern sind hier optional, aber wegen der in der Schulmathematik
nicht geläufigen Operatoren empfehlenswert. Man kann aber die Klammern
auch weglassen und dabei die Lesbarkeit durch Zusammenrücken der drei
Teilausdrücke wiederherstellen:

  HWREG = 1<<2 | 1<<4 | 1<<5;

Die 1<<n-Ausdrücke sind in der hardwarenahen Programmierung ein so
gängiges Muster, dass sie ein etwas geübterer Programmierer nicht mehr
als Ausdrücke, sondern als Literale mit der Bedeutung "Bit n" sieht.
Deswegen halte ich es hier für angebracht, die Leerzeichen vor und nach
dem << wegzulassen. Gleichzeitig werden dadurch die Prioritäten der
beteiligten Operatoren klar herausgestellt, weswegen man auf die
Klammern verzichten kann. Schließlich wird dadurch der Ausdruck kürzer,
so dass es bei einer größeren Zahl gesetzter Bits und bei symbolischen
Kostanten anstelle der Bitnummern nicht so schnell zu überlangen Zeilen
kommt.


Soviel zur einer in meinen Augen sinnvollen Klammersetzung bzw.
-nichtsetzung. Das Ganze soll primär als Anregung dienen, letztendlich
wird jeder einen seinem persönlichen Geschmack angepassten Stil suchen
und finden.

von jens D. (Gast)


Lesenswert?

Vielen Dank an alle,

vor allem an Yalu. Sehr aufschlussreich Deine Antwort, jetzt ist mir die 
Sache klar. Sehr einleuchtend.

Jens

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.