4. Datentypen in C
4.1. Ausdruck
Ein Ausdruck besteht aus einem oder mehreren Operanden, die miteinander durch Operatoren
verknüpft sind. Der Ausdruck hat einen Wert als Ergebnis, der an die Stelle des
Ausdrucks tritt. Der einfachste Ausdruck besteht aus einer einzigen Konstanten oder
Variablen. Die Operatoren müssen zu den Datentypen der Operanden passen.
Beispiele:
25
1 + 2
"Text"
Der Ausdruck a = 1 + 2 ist ein zusammengesetzter Ausdruck. Die
Operanden 1 und 2 sind Zahlenwerte (Literale), die durch den +-Operator
verknüpft werden. Der resultierende Wert 3 tritt nun an die Stelle des Ausdrucks
1 + 2 und wird der Variablen a zugewiesen. Das
Ergebnis des gesamten Ausdrucks ist der Wert von a, also 3.
Daraus folgt, dass in einem Ausdruck mehrere Zuweisungen an Variablen erfolgen
können, z.B. a = b = c = 1 + 2.
Der Wert 3 wird zuerst der Variablen c zugewiesen. Das Ergebnis der
Zuweisung ist der neue Wert von c, also 3. Dieser Wert wird nun der
Variablen b zugewiesen. Das Ergebnis dieser Zuweisung wird
schließlich der Variablen a zugewiesen. Das Ergebnis dieser
Zuweisung wird nicht weiter verarbeitet, da links vom Ausdruck a nichts
mehr steht.
4.2. Ganze Zahlen
Es gibt mehrere verschiedene Repräsentationen von ganzen Zahlen, die sich durch die
Anzahl der verwendeten Bits unterscheiden. Diese heißen short,
int, long und long long.
Letzterer Datentyp wurde erst mit C99 eingeführt. Hierbei gilt: Anzahl Bits von
short <= Anzahl Bits von int <= Anzahl Bits
von long <= Anzahl Bits von long long.
Standard-C schreibt nur die Mindestanzahl der verwendeten Bits vor. Typische Werte für
ein System mit einem 32-Bit-Prozessor sind in der folgenden Tabelle dargestellt. Die
tatsächlichen Werte für Ihr System finden Sie in der Headerdatei
limits.h.
Datentyp |
Mindestanzahl Bits |
Typische Anzahl von Bits |
short |
16 Bits |
16 Bits |
int |
16 Bits |
32 Bits |
long |
32 Bits |
32 Bits |
long long |
64 Bits |
64 Bits |
In der folgenden Tabelle werden die alternativen Schreibweisen für die verschiedenen
Datentypen der ganzen Zahlen aufgeführt:
Datentyp |
alternative Schreibweisen |
||
short |
short int |
signed short |
signed short int |
int |
signed int |
signed |
|
long |
long int |
signed long |
signed long int |
long long |
long long int |
signed long long |
signed long long int |
Das Schlüsselwort signed wurde erst mit C89 eingeführt und
sollte aus Kompatibilitätsgründen mit älteren Compilern weggelassen
werden.
Entsprechend der Anzahl der verwendeten Bits lassen sich unterschiedliche Zahlenbereiche
darstellen:
16 Bits |
-215 |
... |
+215 - 1 |
entspricht |
-32.768 |
... |
+32.767 |
32 Bits |
-231 |
... |
+231 - 1 |
entspricht |
-2.147.483.648 |
... |
+2.147.483.647 |
64 Bits |
-263 |
... |
+263 - 1 |
entspricht |
-9.223.372.036.854.775.808 |
... |
+9.223.372.036.854.775.807 |
Wie oben bereits erwähnt, sind die tatsächlichen Zahlenbereiche für jedes System in der Headerdatei
limits.h als Konstante hinterlegt. Das folgende Beispielprogramm zeigt, wie Sie sich diese
Konstanten anzeigen lassen können.
kap04_01.c
01 #include <stdio.h>
02 #include <limits.h>
03
04 int main()
05 {
06 printf("CHAR_BIT = %i\n" , CHAR_BIT ); // Anzahl Bits fuer ein Byte
07 printf("SCHAR_MIN = %i\n" , SCHAR_MIN ); // Min-Wert (signed char)
08 printf("SCHAR_MAX = %i\n" , SCHAR_MAX ); // Max-Wert (signed char)
09 printf("UCHAR_MAX = %i\n" , UCHAR_MAX ); // Max-Wert (unsigned char)
10 printf("CHAR_MIN = %i\n" , CHAR_MIN ); // Min-Wert (char)
11 printf("CHAR_MAX = %i\n" , CHAR_MAX ); // Max-Wert (char)
12 printf("MB_LEN_MAX = %i\n" , MB_LEN_MAX); // max. Anzahl Bytes
13 // fuer ein Multibytezeichen
14 printf("SHRT_MIN = %i\n" , SHRT_MIN ); // Min-Wert (short)
15 printf("SHRT_MAX = %i\n" , SHRT_MAX ); // Max-Wert (short)
16 printf("USHRT_MAX = %i\n" , USHRT_MAX ); // Max-Wert (unsigned short)
17 printf("INT_MIN = %i\n" , INT_MIN ); // Min-Wert (int)
18 printf("INT_MAX = %i\n" , INT_MAX ); // Max-Wert (int)
19 printf("UINT_MAX = %u\n" , UINT_MAX ); // Max-Wert (unsigned int)
20 printf("LONG_MIN = %li\n" , LONG_MIN ); // Min-Wert (long)
21 printf("LONG_MAX = %li\n" , LONG_MAX ); // Max-Wert (long)
22 printf("ULONG_MAX = %lu\n" , ULONG_MAX ); // Max-Wert (unsigned long)
23 printf("LLONG_MIN = %lli\n", LLONG_MIN ); // Min-Wert (long long)
24 printf("LLONG_MAX = %lli\n", LLONG_MAX ); // Max-Wert (long long)
25 printf("ULLONG_MAX = %llu\n", ULLONG_MAX); // Max-Wert (unsigned long long)
26
27 return 0;
28 }
Die Zahlen werden im Rechner als Binärzahlen dargestellt. Dabei werden positive Zahlen
mit führender 0 und negative Zahlen mit führender 1 dargestellt. Um eine positive
Zahl zu negieren, werden alle Bits der Zahl invertiert und anschließend eine 1
addiert. Dieses Verfahren wird Zweier-Komplement-Darstellung (engl:
two-complement notation) genannt. Um beispielsweise die Zahl 2 (8Bit binär:
000000102) zu negieren, werden erst alle Bits invertiert (8Bit binär:
111111012) und anschließend eine 1 addiert. Das Ergebnis lautet dann
-2 (8Bit binär: 111111102).
Zwei Zahlen ändern sich nicht, wenn sie negiert werden: 0 und die kleinste negative
Zahl. Wenn bei der 0 (8Bit binär: 000000002) alle Bits negiert werden
(8Bit binär: 111111112) und anschließend eine 1 addiert wird,
ergibt dies 1000000002. Da es nun aber 9 Bits sind, wird für die Darstellung in
8 Bit das linke Bit "abgeschnitten" und übrig bleibt die 000000002. Wird
die kleinste negative Zahl (8Bit binär: 100000002 = -128 dezimal)
negiert, ergibt sich nach der Negation aller Bits als Zwischenergebnis eine
011111112 und nach der Addition einer 1 die Ausgangszahl
100000002.
Es gibt noch zwei weitere Verfahren, um Zahlen zu negieren, die beide vom Standard-C
akzeptiert werden: Die Einer-Komplement-Darstellung (engl. ones-complement
notation) und die Vorzeichen-Wert-Darstellung (engl. sign magnitude
notation).
In der Einer-Komplement-Darstellung werden - wie bei der Zweier-Komplement-Darstellung -
beim Negieren alle Bits negiert, aber es wird keine 1 mehr addiert. Dadurch reduziert sich
der Zahlenbereich um eins: Mit beispielsweise 16 Bits können dann nur noch die Zahlen
von -32767 bis +32767 dargestellt werden, d.h. die kleinste negative Zahl -32768
entfällt. Dafür gibt es zwei verschiedene Zahlen, die beide den Wert 0 haben:
+0 (binär: 00000000 000000002) und -0
(binär: 11111111 111111112).
In der Vorzeichen-Wert-Darstellung wird beim Negieren nur das Vorzeichen-Bit negiert; die
restlichen Bits bleiben unverändert. Genau wie bei der Einer-Komplement-Darstellung
ist der Zahlenbereich um eins reduziert, da auch hier die kleinste negative Zahl -32768
wegfällt. Für die Zahl 0 gibt es auch hier zwei verschiedene
Darstellungsmöglichkeiten: +0 (binär: 00000000 000000002) und
-0 (binär: 10000000 000000002).
Im weiteren Verlauf werden auf diese beiden alternativen Darstellungsmöglichkeiten
für negative Zahlen nicht weiter eingegangen und statt dessen von der üblichen
Zweier-Komplement-Darstellung ausgegangen.
Durch das Schlüsselwort unsigned wird gekennzeichnet, dass es
sich um rein positive Zahlen handelt. Das Bit, das sonst für das Vorzeichen
benötigt wird, wird hierbei nun für einen größeren Zahlenbereich
verwendet. Die zulässigen Bereiche für ganze positive Zahlen sehen dann wie
folgt aus:
16 Bits |
0 |
... |
+216 - 1 |
entspricht |
0 |
... |
+65.535 |
32 Bits |
0 |
... |
+232 - 1 |
entspricht |
0 |
... |
+4.294.967.295 |
64 Bits |
0 |
... |
+264 - 1 |
entspricht |
0 |
... |
+18.446.744.073.709.551.615 |
Beim Rechnen mit ganzen Zahlen ist die begrenzte Genauigkeit zu beachten, denn die
oben angegebenen Zahlenbereiche sind ja nur eine Untermenge der ganzen Zahlen. Daraus
folgt, dass das Ergebnis einer Folge von arithmetischen Operationen nur dann korrekt
ist, wenn kein Zwischenergebnis den durch den Datentyp vorgegebenen maximalen Zahlenbereich
überschreitet.
kap04_02.c
01 #include <stdio.h>
02
03 int main()
04 {
05 short a = 50; /* a,b,c sind short-Zahlen, */
06 short b = 1000; /* also 16 Bit-Zahlen */
07 short c; /* => -32.768 ... 32.767 */
08
09 c = a * b;
10 printf("%i * %i =", a, b);
11 printf("%i\n", c); /* Ergebnis: -15.536 statt 50.000 ! */
12
13 return 0;
14 }
Grundsätzlich immer den größten Datentyp zu verwenden (nach dem Motto:
Sicher ist sicher!), ist nicht sinnvoll, weil Variablen dann mehr Speicherplatz und auch
mehr Rechenzeit benötigen.
Ganze Zahlen können in drei Zahlensystemen verwendet werden:
Oktalzahlen: Wenn eine Zahl mit einer 0 beginnt, wird sie als Oktalzahl interpretiert,
z.B. 0377 = 3778 = 3 * 82 + 7 * 81 + 7 * 80 = 25510 (dezimal).Hexadezimalzahlen: Wenn eine Zahl mit "0x" oder "0X" beginnt, wird sie als Hexadezimalzahl interpretiert,
z.B. 0XAFFE = 45054 dezimal.Dezimalzahlen
4.3. Operatoren für ganze Zahlen
Auf Daten eines bestimmten Typs kann man nur bestimmte Operationen durchführen. Eine
Zeichenkette kann beispielsweise nicht mit einer anderen multipliziert werden. Also:
Ein Operand und der zugehörige Operator gehören zusammen!
Im folgenden werden alle Operatoren für ganze Zahlen aufgelistet:
Arithmetische Operatoren:
Operator |
Beispiel |
Bedeutung |
+ |
+i |
unäres Plus (kann weggelassen werden) |
- |
-i |
unäres Minus |
++ |
++i |
vorherige Inkrementierung (Erhöhung um 1) |
|
i++ |
nachfolgende Inkrementierung |
-- |
--i |
vorherige Dekrementierung (Erniedrigung um 1) |
|
i-- |
nachfolgende Dekrementierung |
+ |
i + 2 |
binäres Plus (Addition) |
- |
i - 5 |
binäres Minus (Subtraktion) |
* |
5 * i |
Multiplikation |
/ |
i / 6 |
Division |
% |
i % 4 |
Modulo (Divisionsrest) |
= |
i = 3 + j |
Zuweisung |
Arithmetische Kurzform-Operatoren:
Operator |
Beispiel |
Bedeutung |
+= |
i += 3 |
i = i + 3 |
-= |
i -= 3 |
i = i - 3 |
*= |
i *= 3 |
i = i * 3 |
/= |
i /= 3 |
i = i / 3 |
%= |
i %= 3 |
i = i % 3 |
Relationale Operatoren:
Operator |
Beispiel |
Bedeutung |
< |
i < 3 |
kleiner als |
> |
i > 3 |
größer als |
<= |
i <= 3 |
kleiner als oder gleich |
>= |
i >= 3 |
größer als oder gleich |
== |
i == 3 |
gleich |
!= |
i != 3 |
ungleich |
4.4. Bitoperatoren
Weil ganze Zahlen auch als Bitvektoren aufgefasst werden können, sind
zusätzlich Bitoperationen möglich. Dabei werden die Zahlen in
Binärdarstellung betrachtet.
Beispiele:
int a = 5, b;
b = a << 2;
Dies bewirkt eine Bitverschiebung um 2 Stellen nach links, wobei von rechts Nullen nachgezogen
werden (beim Verschieben nach rechts werden von links Nullen nachgezogen; bei vorzeichenbehafteten
Zahlen wird - je nach Compiler - entweder eine Null oder das Vorzeichen nachgezogen). Dies
entspricht der Multiplikation mit 22, also mit 4.
0000 0000 0000 0101 binäre Darstellung der Zahl 5
0000 0000 0001 0100 alle Bits um 2 Stellen nach links verschoben
Die Variable a hat nun den Wert 20 (0000 0000 0001 01002).
a = a & b;
Diese Anweisung bewirkt eine bitweise UND-Verknüpfung, d.h. das Ergebnis-Bit ist 1,
wenn die Bits der beiden Operanden auch gleich 1 sind, ansonsten 0. In der binären
Darstellung sind das wie folgt aus:
0000 0000 0001 0100 binäre Darstellung der Zahl 20
0000 0000 0000 0101 binäre Darstellung der Zahl 5
0000 0000 0000 0100 bitweises UND; Ergebnis: 4
In den folgenden zwei Tabellen werden alle Bit-Operatoren aufgelistet.
Bitoperatoren:
Operator |
Beispiel |
Bedeutung |
<< |
i << 2 |
Bits nach links schieben (Multiplikation mit 2er-Potenzen) |
>> |
i >> 1 |
Bits nach rechts schieben (Division durch 2er-Potenzen) |
& |
i & 7 |
bitweises UND |
^ |
i ^ 7 |
bitweises Exklusiv-ODER (XOR) |
| |
i | 7 |
bitweises ODER |
~ |
~i |
bitweises Negieren |
Bit-Kurzform-Operatoren:
Operator |
Beispiel |
Bedeutung |
<<= |
i <<= 2 |
i = i << 2 |
>>= |
i >>= 1 |
i = i >> 1 |
&= |
i &= 3 |
i = i & 3 |
^= |
i ^= 3 |
i = i ^ 3 |
|= |
i |= 3 |
i = i | 3 |
4.5. Reelle Zahlen
Reelle Zahlen, auch Fließkomma- oder Gleitkommazahlen genannt, bauen sich
folgendermaßen auf:
- Vorzeichen (optional)
- Vorkommastellen
- Dezimalpunkt (KEIN Komma!)
- Nachkommastellen
- e oder E und Ganzzahl-Exponent (optional)
- Suffix f,F oder l,L (optional)
(f,F für
float; l, L für
long double; Zahlen ohne Suffix sind vom Typ double)
Einige Beispiele für reelle Zahlen sind:
-236.265e6f
3.4E3
3.1415
1e-08
9.2L
Mit dem Exponenten sind Zehnerpotenzen gemeint, d.h. 3.4E3 ist also identisch mit
3.4 * 103 oder 3400.
Reelle Zahlen werden durch drei Datentypen dargestellt. Sie sind ähnlich wie bei den
ganzen Zahlen durch Zahlenbereiche eingeschränkt, bedingt durch die Anzahl der
verwendeten Bits pro Zahl. Die angegebenen Bits sowie die Genauigkeiten sind beispielhaft,
da sie von System zu System variieren.
Typ | Bits | Zahlenbereich | Stellen Genauigkeit |
float | 32 | +/-1.17549 * 10-38 ... +/-3.40282 * 1038 | 7 |
double | 64 | +/-2.22507 * 10-308 ... +/-1.79769 * 10308 | 15 |
long double | 80 | +/-3.3621 * 10-4932 ... +/-1.18973 * 104932 | 19 |
Genauso wie bei den ganzen Zahlen gibt es auch für die Fließkommazahlen eine Headerdatei, in der die
Zahlenbereiche für das aktuelle System als Konstante hinterlegt sind. Diese Headerdatei heißt
float.h. Im folgenden Beispielprogramm wird gezeigt, wie Sie sich diese Konstanten
anzeigen lassen können.
kap04_03.c
01 #include <stdio.h>
01 #include <float.h>
03
04 int main()
05 {
06 // FLT: float; DBL: double; LDBL: long double
07 printf("FLT_RADIX = %i\n" , FLT_RADIX ); // Basis Exponentendarst.
08 printf("FLT_MANT_DIG = %i\n" , FLT_MANT_DIG ); // Anz. Stellen Mantisse
09 printf("DBL_MANT_DIG = %i\n" , DBL_MANT_DIG );
10 printf("LDBL_MANT_DIG = %i\n" , LDBL_MANT_DIG );
11 printf("FLT_DIG = %i\n" , FLT_DIG ); // Genauigkeit Dez.ziffern
12 printf("DBL_DIG = %i\n" , DBL_DIG );
13 printf("LDBL_DIG = %i\n" , LDBL_DIG );
14 printf("FLT_MIN_EXP = %i\n" , FLT_MIN_EXP ); // min. neg. FLT_RADIX-Exp.
15 printf("DBL_MIN_EXP = %i\n" , DBL_MIN_EXP );
16 printf("LDBL_MIN_EXP = %i\n" , LDBL_MIN_EXP );
17 printf("FLT_MIN_10_EXP = %i\n" , FLT_MIN_10_EXP ); // min. neg. 10er-Exponent
18 printf("DBL_MIN_10_EXP = %i\n" , DBL_MIN_10_EXP );
19 printf("LDBL_MIN_10_EXP = %i\n" , LDBL_MIN_10_EXP);
20 printf("FLT_MAX_EXP = %i\n" , FLT_MAX_EXP ); // max. FLT_RADIX-Exponent
21 printf("DBL_MAX_EXP = %i\n" , DBL_MAX_EXP );
22 printf("LDBL_MAX_EXP = %i\n" , LDBL_MAX_EXP );
23 printf("FLT_MAX_10_EXP = %i\n" , FLT_MAX_10_EXP ); // max. 10er-Exponent
24 printf("DBL_MAX_10_EXP = %i\n" , DBL_MAX_10_EXP );
25 printf("LDBL_MAX_10_EXP = %i\n" , LDBL_MAX_10_EXP);
26 printf("FLT_MAX = %g\n" , FLT_MAX ); // max. Fließkommawert
27 printf("DBL_MAX = %g\n" , DBL_MAX );
28 printf("LDBL_MAX = %Lg\n", LDBL_MAX );
29 printf("FLT_EPSILON = %g\n" , FLT_EPSILON ); // kleinster Wert, fuer den
30 printf("DBL_EPSILON = %g\n" , DBL_EPSILON ); // 1.0 + x ungleich 1.0
31 printf("LDBL_EPSILON = %Lg\n", LDBL_EPSILON ); // gilt
32 printf("FLT_MIN = %g\n" , FLT_MIN ); // min. Fließkommawert
33 printf("DBL_MIN = %g\n" , DBL_MIN );
34 printf("LDBL_MIN = %Lg\n", LDBL_MIN );
35
36 return 0;
37 }
Eine beliebige Genauigkeit ist allerdings nicht für alle Zahlen möglich! Für
die Darstellung der reellen Zahlen mit 32 Bits beispielsweise existieren nur 232
= 4.294.967.296 verschiedene Möglichkeiten, eine Zahl zu bilden. Ein reelles
Zahlenkontinuum ist das nun nicht gerade und alle Illusionen von der computertypischen
Genauigkeit und Korrektheit sind über den Haufen geschmissen! Mögliche Folgen der
nicht exakten Darstellung können sein:
Werden zwei fast gleich große Werte subtrahiert, heben sich die signifikanten Ziffern auf und das Ergebnis ist ungenau (numerische Auslöschung).
Die Division durch betragsmäßig zu kleine Werte hat einen Überlauf (Overflow) zum Ergebnis, d.h. das Ergebnis liegt außerhalb des Zahlenbereiches des Datentyps. Ähnlich ist es bei der Unterschreitung (Underflow). Diese tritt auf, wenn das Ergebnis zu klein ist, als das es mit dem gegebenen Datentyp dargestellt werden kann. Das Ergebnis wird dann auf 0 gesetzt.
Die Reihenfolge einer Berechnung kann entscheidend sein! Wenn beispielsweise die drei Variablen a, b und c addiert werden sollen, ist es mathematisch gesehen kein Unterschied, ob zuerst a und b addiert und zu diesem Ergebnis c addiert wird oder ob zuerst b und c addiert und dann a dazuaddiert wird. Anders dagegen beim Computer und der Programmiersprache C/C++. Andere Programmiersprachen haben die gleichen oder ähnliche Probleme. Daher muss zu kritischen Berechnungen auch immer eine Genauigkeitsbetrachtung gemacht werden!
Beispiel für Probleme mit der Rechengenauigkeit:
kap04_04.c
01 #include <stdio.h>
02
03 int main()
04 {
05 float a = 1.234567E-9f, b = 1.000000f, c = -b;
06 float s1, s2;
07
08 s1 = a + b;
09 s1 += c;
10 s2 = b + c;
11 s2 += a;
12 printf("%e\n", s1); /* Erg.: 0.000000e+00 */
13 printf("%e\n", s2); /* Erg.: 1.234567e-09 */
14
15 return 0;
16 }
Der interne Aufbau einer Fließkommazahl ist durch den IEEE Standard for Binary
Floating-Point Arithmetic (ISO/IEEE Standard 754-1985) oder kurz IEEE Standard 754
festgelegt. Dieser wird im folgenden anhand des Datentyps float
vorgestellt:
Bei einer 32Bit-float-Zahl ist das ganz linke Bit für das
Vorzeichen S, die nächsten 8 Bits von links (Bit 2 bis 9) bilden den
vorzeichenlosen Exponenten E und die restlichen 23 Bits die Mantisse M
(nicht vergleichbar mit einer Mantisse im mathematischen Sinne). Die Zahl f wird
dann wie folgt berechnet:
, wobei Mi das i-te Bit der Mantisse von links ist.
Beispiel:
float-Zahl binär dargestellt: 00111101 11001100 11001100 110011012
Dann ist S gleich 0, E gleich 011110112 (gleich 123 dezimal) und M gleich
100110011001100110011012. Diese Werte werden in die Formel eingesetzt:
Bei sieben Stellen Genauigkeit (1 Stelle vor und 6 Stellen nach dem Komma) ergibt diese
Zahl also 0,1.
Es sind nun noch einige Sonderfälle zu betrachten:
Exponent E gleich 255: |
||
|
Mantisse M ungleich 0: |
f = NaN (Not a Number) |
|
Mantisse M gleich 0: |
f = +/- Infinity (Unendlich; entsprechend des Vorzeichens S) |
Exponent E gleich 0: |
||
|
Mantisse M ungleich 0: |
Zahl ist nicht normalisiert und lässt sich wie folgt berechnen: |
|
Mantisse M gleich 0: |
f = +/- 0 (entsprechend des Vorzeichens S) |
4.6. Operatoren für reelle Zahlen
Die Operatoren für reelle Zahlen sind die folgenden:
Arithmetische Operatoren:
Operator |
Beispiel |
Bedeutung |
+ |
+f |
unäres Plus (kann weggelassen werden) |
- |
-f |
unäres Minus |
+ |
f + 2 |
binäres Plus (Addition) |
- |
f - 5 |
binäres Minus (Subtraktion) |
* |
5 * f |
Multiplikation |
/ |
f / 6 |
Division |
= |
f = 3 + g |
Zuweisung |
Arithmetische Kurzform-Operatoren:
Operator |
Beispiel |
Bedeutung |
+= |
f += 3 |
f = f + 3 |
-= |
f -= 3 |
f = f - 3 |
*= |
f *= 3 |
f = f * 3 |
/= |
f /= 3 |
f = f / 3 |
Relationale Operatoren:
Operator |
Beispiel |
Bedeutung |
< |
f < 3 |
kleiner als |
> |
f > 3 |
größer als |
<= |
f <= 3 |
kleiner als oder gleich |
>= |
f >= 3 |
größer als oder gleich |
== |
f == 3 |
gleich |
!= |
f != 3 |
ungleich |
4.7. Regeln zum Bilden von Ausdrücken
Es gelten im allgemeinen die Regeln der Algebra beim Berechnen eines Ausdrucks, z.B.
Klammerregeln und Punkt- vor Strichrechnung. In der folgenden Tabelle werden die
Prioritäten der einzelnen Operatoren aufgelistet. Dabei ist die Priorität 1 die
höchste und 16 die niedrigste Priorität.
Priorität | Operator | Reihenfolge |
1 |
[] |
links nach rechts |
2 |
++ -- (prefix) |
rechts nach links |
3 |
(type name) (Typkonvertierung) |
rechts nach links |
4 |
* / % |
links nach rechts |
5 |
+ - |
links nach rechts |
6 |
<< >> |
links nach rechts |
7 |
< > <= >= |
links nach rechts |
8 |
== != |
links nach rechts |
9 |
& (bitweises UND) |
links nach rechts |
10 |
^ (bitweises Exklusiv-ODER) |
links nach rechts |
11 |
| (bitweises ODER) |
links nach rechts |
12 |
&& (logisches UND) |
links nach rechts |
13 |
|| (logisches ODER) |
links nach rechts |
14 |
?: |
rechts nach links |
15 |
alle Zuweisungen wie |
rechts nach links |
16 |
, |
links nach rechts |
Auf gleicher Prioritätsstufe wird ein Ausdruck von links nach rechts abgearbeitet.
Dies wird auch linksassoziativ genannt. Ausnahme: Die unären und
Zuweisungsoperatoren werden von rechts nach links abgearbeitet (rechtsassoziativ).
Generell werden aber zuerst immer die Klammern ausgewertet.
Beispiele:
a = b + c + d; ist gleich mit
a = ((b + c) + d);
a = b = c = d; ist gleich mit
a = (b = (c = d));
Die Auswertungsreihenfolge der Teilausdrücke einer Priorität untereinander ist
jedoch nicht festgelegt und kann von jedem Compiler anders gesetzt werden. Daher sollte es
vermieden werden, in einem Ausdruck einen Wert gleichzeitig zu verändern und zu
benutzen, wie es die folgenden Beispiele zeigen:
Beispiele:
int Teil = 0;
Summe = (Teil = 3) + (++Teil);
Das Ergebnis von Summe kann sowohl den Wert 4 als auch den Wert 7
annehmen, je nachdem welche Klammer zuerst ausgewertet wird.
int i = 2;
i = 3 * i++;
Erste Möglichkeit: Es wird 3 * i berechnet und das Ergebnis 6 wird
der Variablen i zugewiesen. Anschließend wird
i um 1 erhöht. Das Endergebnis ist dann 7.
Zweite Möglichkeit: Es wird 3 * i berechnet. Nun wird
i um 1 (von 2 auf 3) erhöht. Anschließend wird aber
i das Ergebnis der Berechnung 3 * i zugewiesen.
Das Endergebnis ist in diesem Fall 6.
4.8. Zeichen
Zeichen sind Buchstaben wie a, B,
C, d, Ziffernzeichen wie 4,
5, 6 und Sonderzeichen wie ;,.!
sowie andere Zeichen. Für sie gibt es den Datentyp char. Der
Datentyp Zeichen beinhaltet immer nur ein Zeichen, das durch eine 1-Byte-Zahl (ganze Zahl)
intern gespeichert wird. Daraus folgt, dass es 256 verschiedene Zeichen geben kann.
Davon sind die ersten 128 international festgelegt, während die anderen 128 regional
unterschiedlich sind (diese werden für nationale Sonderzeichen genutzt). Der
Zusammenhang zwischen den Zeichen und den intern gespeicherten Zahlen ist in der
sogenannten
ASCII-Tabelle
festgelegt (siehe Kapitel 14 im Skript "Grundlagen der Informatik").
Konstante Zeichen werden in Hochkommata eingeschlossen, also beispielsweise
'y', '9', '?'.
Hinweis: Speziell bei den Ziffernzeichen muss zwischen dem
Zeichen (z.B. '1') und der Ziffer (z.B. 1)
unterschieden werden!
Es wird generell zwischen den Datentypen signed char (interner
Zahlenbereich: -128 ... +127) und unsigned char (interner
Zahlenbereich: 0 ... 255) unterschieden. Meist wird aber nur char
verwendet, wobei damit bei den meisten Compilern der Datentyp
signed char gemeint ist.
Da Zeichen intern als ganze Zahlen gespeichert werden, können alle Operatoren der
ganzen Zahlen auch auf Zeichen angewendet werden, wobei nicht alle Operationen auch Sinn
machen (beispielsweise die Addition zweier Zeichen). Daher noch einmal eine Tabelle mit den
Operatoren, die für Zeichen sinnvoll sind (a ist eine Variable vom Typ
char).
Operator |
Beispiel |
Bedeutung |
= |
a = 'X' |
Zuweisung |
< |
a < 'z' |
kleiner als |
> |
a > 'c' |
größer als |
<= |
a <= 'M' |
kleiner als oder gleich |
>= |
a >= 'k' |
größer als oder gleich |
== |
a == 'J' |
gleich |
!= |
a != 'n' |
ungleich |
Es gibt besondere Zeichenkonstanten, die nicht direkt gedruckt bzw. auf dem Bildschirm
angezeigt werden können. Um diese darzustellen, werden sie als zwei Zeichen
geschrieben, benötigen aber wie alle Zeichen nur ein Byte. Das erste der zwei Zeichen
ist ein Backslash ('\'). Diese Zeichen werden auch
Escape-Sequenzen genannt, weil der Backslash als Escape-Zeichen
verwendet wird, um der normalen Interpretation als ein einzelnes Zeichen zu entkommen (to
escape). In der nächsten Tabelle werden einige dieser Escape-Sequenzen aufgelistet.
Zeichen |
Bedeutung |
\a |
Signalton |
\f |
Seitenvorschub |
\n |
neue Zeile |
\r |
Zeilen- oder Wagenrücklauf |
\t |
Tabulator |
\\ |
Backslash \ |
\' |
Hochkomma ' |
\" |
Anführungszeichen " |
\0 |
Nullbyte (z.B. für String-Ende) |
\? |
Fragezeichen ? (C99) |
4.9. Multibyte und Wide Characters
Ein Wide Character wird durch den Datentyp wchar_t dargestellt und ist
auf den meisten Systemen als int definiert (in der Headerdatei
stddef.h). Ein Array von Wide Characters ist ein Wide String.
4.10. Logischer Datentyp
Vor dem C99-Standard gab es keinen direkten logischen Datentypen. Statt dessen werden die
logischen Werte durch ganze Zahlen dargestellt. Dabei wird eine ganze Zahl ungleich Null
als logischen Wert wahr (im englischen: true) und eine ganze Zahl gleich Null als
falsch (im englischen: false) interpretiert. Ergebnisse von logischen Operationen sind
gleich 1 für wahr und gleich 0 für falsch.
Beispiel:
int i = 17, j;
j = !i; /* ergibt 0 (falsch), da i ungleich 0 (wahr) ist */
i = !j; /* ergibt 1 (wahr), da j gleich 0 (falsch) ist */
Erst mit C99 wurde ein logischer Datentyp eingeführt: _Bool. Aber
auch dieser Datentyp ist eine ganze Zahl, der allerdings nur die Zahlen 0 und 1 (für
falsch und wahr) speichern kann.
Zusätzlich wurde mit dem C99-Standard die Headerdatei stdbool.h
eingeführt. In ihr werden das Makro bool als Datentyp sowie die
Konstanten false und true (0 und 1) definiert.
Dies sind Definitionen und keine Schlüsselwörter (im Gegensatz zum Datentyp
_Bool)! Dies ist gerade für ältere C-Programme wichtig, da
in diesen meistens der Datentyp bool mit den Konstanten
false und true vom Programmierer selber definiert
wurde. In diesen Fällen sollte diese Headerdatei nicht eingefügt werden.
Da logische Variablen intern als ganze Zahlen gespeichert werden, können alle
Operatoren der ganzen Zahlen hier auch angewendet werden, wobei nicht alle Operationen auch
Sinn machen (beispielsweise die Addition wahr und wahr). Daher noch einmal eine Tabelle mit
den Operatoren, die für logische Variablen sinnvoll sind.
Operator |
Beispiel |
Bedeutung |
! |
!i |
logische Negation |
&& |
i && j |
logisches UND |
|| |
i || j |
logisches ODER |
= |
h = i && j |
Zuweisung |
Werden mehrere Bedingungen mit einem logischen Und oder einem logischen Oder verknüpft, ist zu beachten,
dass die Compiler im allgemeinen versuchen, die Ermittlung des Ergebnisses der verknüpften Bedingungen zu
optimieren. Das bedeutet, dass bei einem logischen Und die zweite Bedingung nur dann ausgewertet wird, wenn die
erste Bedingung wahr ist. Denn ist die erste Bedingung falsch, ist beim logischen Und auch das Ergebnis der
Verknüpfung falsch, ganz gleich wie die zweite Bedingung aussieht. Und bei einem logischen Oder wird die
zweite Bedingung nur dann ausgewertet, wenn die erste Bedingung falsch ist. Denn ist die erste Bedingung wahr,
ist beim logischen Oder auch das Ergebnis der Verknüpfung unabhängig von der zweiten Bedingung wahr.
Dabei können unangenehme Seiteneffekte entstehen!
Im folgenden Beispiel wird diese Optimierung ausgenutzt:
kap04_05.c
01 #include <stdio.h>
02
03 int main()
04 {
05 int a = 0, b = 1;
06 char Zeichen = 'A';
07
08 (a && (Zeichen = 'B'));
09 printf("Zeichen = %c\n", Zeichen);
10
11 (b || (Zeichen = 'C'));
12 printf("Zeichen = %c\n", Zeichen);
13
14 return 0;
15 }
Durch die Optimierung werden die beiden Zuweisungen der Zeichen 'B' und
'C' nicht ausgeführt und es wird
Zeichen = A
Zeichen = A
ausgegeben. Diese Optimierung ist aber nicht vorgeschrieben. Und ohne Optimierung würden die beiden
Zuweisungen ausgeführt werden und das Ergebnis sieht anders aus. Von daher sollte das Programm wie
folgt umgeschrieben werden, um in jedem Fall das obige Ergebnis zu erhalten.
kap04_06.c
01 #include <stdio.h>
02
03 int main()
04 {
05 int a = 0, b = 1;
06 char Zeichen = 'A';
07
08 if (a)
09 Zeichen = 'B';
10 printf("Zeichen = %c\n", Zeichen);
11
12 if (!b)
13 Zeichen = 'C';
14 printf("Zeichen = %c\n", Zeichen);
15
16 return 0;
17 }
4.11. Konvertierung zwischen den Datentypen (Typumwandlung)
Da die Zeichen und der logische Datentyp intern als ganze Zahlen gespeichert werden, liegt
es nahe, zwischen den Datentypen konvertieren zu wollen. Dabei muss aber
berücksichtigt werden, dass sich nicht jeder Datentyp zu 100% konvertieren
lässt; beispielsweise wenn eine reelle Zahl in eine ganze Zahl konvertiert wird,
gehen alle Nachkommastellen verloren. Durch die Konvertierung wird die Typkontrolle des
Compilers umgangen und kann daher sehr schnell zu Fehlern führen.
Zum Konvertieren gibt es mehrere Möglichkeiten. Hier wird nur die erste Variante
vorgestellt (die anderen werden im C++-Teil beschrieben). Dazu wird zuerst der Datentyp, in
den konvertiert werden soll, und dahinter der Wert bzw. die Variable geschrieben. Dabei
wird der Datentyp in Klammern gesetzt. Diese Konvertierung wird auch
explizite Konvertierung bzw. explizite Typumwandlung genannt.
Beispiel:
char c = 'A';
int i;
i = (int) c;
Der ASCII-Wert des Buchstaben A (65) wird als ganze Zahl der Variablen i zugeordnet. Auch umgekehrt
- bei der Konvertierung von ganzen Zahlen nach Zeichen - wird die
ASCII-Tabelle verwendet.
Beispiel:
char c;
int i = 97;
c = (char) i; /* ergibt ein 'a' */
Um von Ziffernzeichen ('0', '1', ...) nach Ziffern zu konvertieren, wird das Ziffernzeichen in eine ganze Zahl konvertiert
und dann der ASCII-Wert des Ziffernzeichens '0' abgezogen.
Beispiel:
int i;
i = (int) '5' - (int) '0';
Bei diesem Beispiel ist der Wert der Variablen i gleich 5 (ASCII-Wert von '5' ist 53 und der ASCII-Wert von '0' ist 48).
Auch für die Umwandlung von Klein- in Großbuchstaben oder umgekehrt können die ASCII-Werte der Zeichen
verwendet werden. Um von Klein- nach Großbuchstaben zu kommen, muss das Zeichen in eine Zahl umgewandelt werden,
von dieser Zahl der Wert 32 abgezogen (von Groß- nach Kleinbuchstaben: dazuaddiert) und anschließend wieder in
ein Zeichen umgewandelt werden.
Beispiel:
char c = 'A';
c = (char) ((int) c + 32); /* Umwandlung in Kleinbuchstaben */
/* oder kurz: */
c = c + 32;
/* oder noch kürzer: */
c += 32;
Die kurzen Fassungen sind implizite Konvertierungen bzw. implizite Typumwandlungen.
Dabei wird der Datentyp, in den konvertiert werden soll, weggelassen. Dies kann nur zwischen Zeichen
und ganzen Zahlen sowie zwischen logischen Datentypen und ganzen Zahlen angewendet werden. Aber auch
zwischen den ganzen Zahlen (z.B. short nach long oder
long nach int) sowie zwischen den reellen Zahlen
(z.B. double nach float) können implizite
Konvertierungen verwendet werden (unter Berücksichtigung der Zahlenbereiche und mit
evtl. Genauigkeitsverlust).
Hinweis: Bei der Konvertierung einer ganzen Zahl größer als 255 in ein
Zeichen werden die überzähligen Bits nicht berücksichtigt.
Voriges Kapitel: 3. Variablen
Nächstes Kapitel: 5. Einfache Ein- und Ausgabe in C