Optimierte Operatoren

  • Kombinierte Zuweisungsoperatoren
  • Inkrement- und Dekrementoperatoren

Dieses Thema ist eigentlich grundlegend, aber ich habe es mir aufgespart, um die Geschichte mit den Ausdrücken nochmals zu wiederholen. Bisher kennen wir Zuweisungen in dieser Form:

int Variable = 1;
 
Variable = Variable + 1;

An dieser Zeile werden wir nun noch ein wenig optimieren.

Kombinierte Zuweisungsoperatoren

Fakt ist, dass der Computer einen Ausdruck wie

Ergebnis = Variable + 1;

überhaupt nicht verarbeiten kann. Er macht daraus mehrere Befehle in folgender Form:

Lade Wert in den Prozessor aus dem Speicher, da wo die Variable "Variable" steht
Addiere 1 auf Wert im Prozessor
Schreibe Wert in den Speicher, da wo die Variable "Ergebnis" steht

Diese drei Zeilen sind erforderlich, wenn man 'Ergebnis = Variable + 1' schreibt. Zuerst muss die Expression „Variable + 1“ berechnet werden und anschließend der berechnete Wert nach „Ergebnis“ geschrieben werden. Wenn eine Variable aber wie hier nur verändert werden soll, dann können viele Prozessoren dies in einem Schritt abfertigen:

Addiere 1 auf Wert, der an der Stelle "Variable" im Speicher steht

Und das kann man auch in C ausdrücken mit einer kombinierten Zuweisung:

Variable += 1;

Diese kombinierten Zuweisungen gibt es für viele mathematischen Operationen:

Operator Operation
+= Addieren und wieder zuweisen
-= Subtrahieren und wieder zuweisen
*= Multiplizieren und wieder zuweisen
/= Dividieren und wieder zuweisen
%= Modulo rechnen und wieder zuweisen

Das ganze gilt auch für binäre Operatoren (kommen noch), daher füge ich die Tabelle hier der Vollständigkeit halber ein.

Operator Operation
&= Verunden und wieder zuweisen
|= Verodern und wieder zuweisen
^= Exklusiv verodern und wieder zuweisen
<<= Linksschieben und wieder zuweisen
>>= Rechtsschieben und wieder zuweisen

Die kombinierten Zuweisungen sind grundsätzlich vorzuziehen, wenn eine Variable nur modifiziert werden soll. Auf der rechten Seite darf durchaus wieder eine Expression stehen, die rechte Seite wird dabei - wie bei der Zuweisung auch - zuerst ausgewertet.

int step = 5;
int factor = 2;
int value = 0;

value += factor * step;

Auch die zu verändernde Variable darf vorkommen, da sie erst verändert wird, nachdem die Expression ausgewertet wurde:

unsigned int value = 1;
 
while( value < 512 )
{
  printf( "Wert: %d\n", value );
 
  value += value;     // entspricht      value  = value + value;
                      // entspricht auch value  = 2 * value;
                      // entspricht auch value *= 2:
}

Ein Ausdruck mit += gibt natürlich auch wieder einen Wert zurück: Dieser entspricht dem Wert der modifizierten Variablen:

int exprValue;
int value = 2;

exprValue = value += 2;

printf("Wert des Ausdrucks: %s\n", exprValue );

Die Expression liefert den Wert 4.

Inkrement- und Dekrementoperatoren

Inkrementieren bedeutet erhöhen und dekrementieren bedeutet erniedrigen (im Sinne von herunterzählen). Computer arbeiten häufig einen großen Satz von Daten ab, die in Arrays gespeichert werden. Dabei wird der Index für jeden Datensatz um eins erhöht oder erniedrigt (wenn man die Daten rückwärts durchläuft). Auch hier haben viele Prozessoren besonders schnelle Implementierungen für Variable += 1.

Weiterhin ist das Durchlaufen von Datensätzen ein so üblicher Vorgang, dass man es mit einer eigenen Schreibweise viel deutlicher ins Auge sticht. In C verwendet man dafür folgende Schreibweise:

unsigned int myStrlen( char const * string )
{
  unsigned int length;
 
  while( string[ length ] )
    length++;
 
  return length;
}

Wem fällt's auf? Der Nachfolger von C heißt… C++. C und eins mehr ist der Nachfolger von C.

Kommen wir zu der Frage, was der ++-Operator zurückgibt.

unsigned int i = 5;
unsigned int j, k;
 
j = i++;
k = i--;

Welchen Wert hat j nun, also welchen Wert gibt i++ zurück?

j hat den Wert 5, es wird also der Wert zurückgegeben den i hat und danach wird um 1 erhöht. Dann ist die Zeile abgeschlossen und i hat den Wert 6. Bei k sieht es ähnlich aus: i hat den Wert 6, dieser wird zurückgegeben und anschließend wird k um 1 erniedrigt, so dass k == 6 ist und i == 5.

Wenn man jedoch das Ergebnis nach der Erhöhung oder Erniedrigung benötigt, so setzt man das '++' vor die Variable:

unsigned int i = 5;
unsigned int j,k;
 
j = ++i;
k = --i;

Hier ist j nun 6 und k entspricht 5. Klingt merkwürdig? Probiert es aus und gebt die Werte vor und zwischen den Zeilen mit Hilfe von printf() aus.

Wird der Operator vor den Operanden (hier i) gesetzt, so spricht man von einem Präfix-Operatoren. Steht der Operator hinter dem Operanden, so spricht man von einem Postfix-Operator. Leicht zu merken: Vorher schützen ⇒ Präservativ. Und was passiert, wenn man Weihnachtsgeschenke mit der Post bestellt? Sie kommen erst nach Weihnachten… Zusammengesetzt haben wir den Präfixinkrement-Operator (++i), Postfixinkrement-Operator (i++), den Präfixdekrement-Operator (--i) und den Postfixdekrement-Operator (i--). Informatiker verstehen das, alle anderen sagen einfach ++ oder -- Operator vor oder nach der Variable. ;-)

Mit diesem Wissen können wir die Operatoren nun auch in Expressions verwenden:

char buffer[ 15 ];
unsigned int i = 0;
 
buffer[ i++ ] = 'p';
buffer[ i++ ] = 'r';
buffer[ i++ ] = 'o';
buffer[ i++ ] = 'g';
buffer[ i++ ] = 'g';
buffer[ i++ ] = 'e';
buffer[ i++ ] = 'n';
buffer[ i++ ] = '.';
buffer[ i++ ] = 'o';
buffer[ i++ ] = 'r';
buffer[ i++ ] = 'g';

Auf eine Sache muss man bei diesen Operatoren aber achten: Ein Operator, der mit ++ oder -- verändert wird, darf in einer Expression nur einmal auftauchen. Folgendes

unsigned int i = 5, j;
 
j = i++ + i++;

Man könnte sich vorstellen, dass hier 5+6 herauskommen soll, also als Ergebnis 11.

Der C-Standard erlaubt aber diese Operatoren zusammen zu fassen, um die Operationen zu optimieren. Das mag im Alltag nicht sonderlich sinnvoll sein und sorgt vermutlich auch für mehr Fehler als für Nutzen, aber so ist der Standard und diesen fiesen Part muss man daher auch gut kennen. Schauen wir uns folgendes Programm an:

xin@trinity:~/proggen.org/tutorial$ cat inc.c 
#include <stdio.h>
 
int main( void )
{
  unsigned int i=5, j;
 
  j = i++ + i++;
 
  printf( "i: %d j: %d\n", i, j );
 
  return 0;
}

und das Programm in der Ausführung:

xin@trinity:~/proggen.org/tutorial$ gcc inc.c 
xin@trinity:~/proggen.org/tutorial$ ./a.out 
i: 7 j: 10

i ist wie gewünscht zweimal erhöht worden, aber das Ergebnis von j ist vermutlich überraschend. Wenn man sich vor Augen führt, dass man eine Variable, die man innerhalb einer Expression mit ++ oder -- verändert niemals mehrfach in der gleichen Expression verwendet, dann gibt es keine Probleme und die Vorteile bei der Lesbarkeit überwiegen.

Wenn man mit ++ oder -- eine Variable verändern möchte, ohne dass man am Rückgabewert des Ausdrucks interessiert ist, dann empfehle ich die Schreibweise mit dem vorangestellten Operator:

unsigned int myStrlen( char const * string )
{
  unsigned int length;
 
  while( string[ length ] )
    ++length;                // statt length++;
 
  return length;
}

Wenn Du später vielleicht C++ lernst, dann kannst Du diese Operatoren auch bei eigenen Datentypen beschreiben. Da Variable++ den alten Wert der Variablen zurück gibt, muss man zuerst den alten Wert zwischenspeichern und dann die entsprechenden Berechnungen durchführen, um anschließend den zwischengespeicherten Wert zurückgeben zu können. Das bedeutet mehr Aufwand als einfach nur zu Rechnen und den neu berechneten Wert zurückzugeben. Da eigene Datentypen beliebig aufwendig sein können, kann auch das Kopieren beliebig aufwendig sein. Im Idealfall unterlässt man das Kopieren also und benutzt einfach ++Variable.

Ziele dieser Lektion

Grundsätzlich dürften Dir nun alle wichtigen Sprachelemente der Sprache C bekannt sein. Es gibt noch einige Besonderheiten, die ich in die kommenden Lektionen einbauen werde, aber nachdem wir uns nun die Sprache soweit angeschaut haben, werden wir nun beginnen sie zu benutzen. Daher sollte Dir mit dieser Lektion nochmal bewusst werden, was ein Wert ist und was eine Variable und ein Ausdruck ist. Wir haben hier gesehen, dass man manche Ausdrücke kürzer oder auch optimierter schreiben kann.

In der nächsten Lektion über Castings werden wir uns ansehen, wie man Daten eine neue Rolle verschafft.