Programmieren in C

| ### Programmieren in C von Prof. Jürgen Plate

# 4 Datentypen unter C (Teil I)

## 4.1 Zahlen, Zahlensysteme

### Grundlagen der Zahlensysteme

Begriffe:

- Ziffer: Element einer Menge von Symbolen zur Darstellung von Zahlen
- Zahl: Anordnung von Ziffern, die in einem vereinbarten Zahlensystem einen bestimmten Wert darstellen.

Kennzeichen eines Zahlensystems sind:

- Zeichenvorrat, der die Menge der zulässigen Ziffern darstellt (z. B. Dezimalsystem: 0,1,2,3,4,5,6,7,8,9)
- Bildungsgesetz, das die Anordnung für die Ziffern und die Ermittlung eines Zahlenwertes festlegt.
  Der allgemeine Umgang mit dem Dezimalsystem und dessen Bildungsgesetz läßt uns vergessen, daß verschiedenen Bildungsgesetze existieren können, z. B.:
  - Ziffernwertsystem, bei denen der Zahlenwert alleine durch die Ziffer bestimmt wird (z. B. römische Zahlen)
  - Stellenwertsystem, bei dem der Zahlenwert, den die Ziffer repräsentiert, durch die Position der Ziffer innerhalb der Zahl (= Stelle) bestimmt wird.

### Stellenwertsysteme

Für den Wert einer Zahl Z = an an-1 ... a0 a-1 ... a-m in einem Stellenwertsystem zur Basis B gilt:

wobei für die Ziffern a gilt: `0 <= a < B`. Als Basis B bezeichnet man die kleinste, nicht mehr durch eine Ziffer darstellbare Zahl.

Am geläufigsten ist uns das Dezimalsystem mit der Basis 10 (die Festlegung ist rein willkürlich und auf die Zahl der Finger beider menschlicher Hände zurückzuführen).

Die verkürzte Schreibweise durch Aneinanderreihung von Ziffern ist eine abkürzende Schreibweise der Summenformel. Damit kann jede positive und negative reelle Zahl dargestellt werden, indem jede Stelle der Ziffernfolge mit einer Zehnerpotenz gewichtet wird.

#### Ganze Zahlen:

z. B.: 1972 = 1 * 103 + 9 * 102 + 7 * 101 + 2 * 100

allgemein:

an an-1 ... a1 a0 = an * 10n + an-1 * 10n-1 + ... + a1 * 101 + a0 * 100

#### Echter Dezimalbruch:

z. B.: 0,328 = 0 + 3 * 10-1 + 2 * 10-2 + 8 * 10-3

allgemein: 0, a-1 a-2 ... a-m = a-1 * 10-1 + a-2 * 10-1 + ... + a-m * 10-m

Dezimalzahl:

Radixschreibweise: Z = an ... a0 a-1 ... a-m
Potenzschreibweise: Z = an * 10n + ... + a1 * 101 + a0 * 100 + ... + a-m * 10-m

Potenz-Summen-Schreibweise

#### Dualzahlen

Prinzipiell kann jede ganze Zahl >1 Basis B eines Stellenwertsystems sein. Für das Dualsystem ist B = 2 und a aus der Menge { 0, 1 }, z. B. Z = 1010.1 = 10.5 dez. Dieses Zahlensystem ist speziell für die Digital- und Computertechnik von Bedeutung, da nur zwei Zustände eine physikalischen größe benötigt werden. Nachteil ist die unübersichtliche, monotone Ziffernfolge bei der Darstellung langer Dualzahlen. Daher werden beim Umgang mit EDV-Anlagen zwei andere Zahlensysteme verwendet:

#### Oktalzahlen

Zusammenfassung von 3 Dualstellen zu einer Oktalstelle (bessere Lesbarkeit, kürzer zu schreiben, leicht umzurechnen).
Oktalsystem: Basis B = 8, a aus der Menge {0,1,2,3, 4,5,6,7}
z. B.: 101001 dual = 51 oktal.

Bei der Eingabe von Oktalzahlen müssen diese - zur Unterscheidung von Dezimalzahlen - gekennzeichnet werden. Dies geschieht durch Hinzustellen der Basis, z. B.: 125178. Häufig (besonders bei Programmiersprachen) auch durch Voranstellen von @ oder Anfügen von O,Q,C z.B.: @154 154O 154Q 154C und bei C durch eine führende Null; es gilt also 37 = 37 dezimal, 037 = 37 oktal = 31 dezimal.

#### Sedezimalzahlen (Hexadezimalzahlen)

Zusammenfassung von 4 Dualstellen ergibt eine Sedezimalstelle.
Sedezimalsystem: B=16, a aus der Menge {0,1..,8,9,A,B,C,D,E,F}
z. B.: 101001 dual = 29 sedezimal.

Übliche Darstellung der eigentlich binären Information in einem Rechner (Kurzschreibweise binärer Info). Kennzeichnung bei der Programmierung durch Voranstellen von $, Anfügen von H z.B.: $FFC2 FFC2H und bei C durch Voranstellen von "0x" bzw. "0X", z. B. 0xFFC2.

### Umrechnung zwischen Stellenwertsystemen

#### Umwandlung Dezimalsystem → anderes Stellenwertsystem

Bei der Umwandlung ganzer Zahlen gibt es nur positive Potenzen der Basis B. Bei fortgesetzter Division durch die Basis B' des gesuchten Zahlensystems fallen die gesuchten Koeffizienten als Reste der ganzzahligen Division an. Die Division wird fortgesetzt, bis das Divisionsergebnis 0 geworden ist. Die Divisionsreste sind die Ziffern des Zielsystems in aufsteigender Reihenfolge (1. Rest = niederwertigste Ziffer, letzter Rest = höchstwertige Ziffer).

Dezimal-Dual-Wandlung ganzer Zahlen (Basis B = 2)

1. Dualzahl auf 0 setzen Wir führen einen Wert I ein, der die gerade bearbeitete Stelle der erzeugten Dualzahl enthält. I wird auf 1 gesetzt.
2. I-te Stelle der Dualzahl := Dezimalzahl mod 2. (Der Operator "mod" bezeichnet den Rest der Division)
3. Dividiere die Dezimalzahl durch 2. Dieser Wert ergibt die neue Dezimalzahl für die Berechnung der nächsten Stelle.
4. Erhöhe den Wert von I um 1. Wenn die Dezimalzahl > 0 ist, fahre fort bei Schritt 2.

Die Lösung als Programm hatten wir bereits in Abschnitt 3.4 zu sehen bekommen.

#### Dezimal-Dual-Wandlung echtgebrochener Zahlen (Basis B = 2)

Bei der Umwandlung von echten Brüchen wird nach dem gleichen Schema verfahren, nur wird hier fortgesetzt mit der Basis des Zielsystems multipliziert. Die ganzzahligen Anteile der einzelnen Multiplikationsschritte ergeben die Koeffizienten des Zielsystems. Die Rechnung ist zuende, ween der gebrochene Anteil des Multiplikationsergebnisses 0 wird. Die ganzzahligen Anteile der Multiplikationen werden dann in absteigender Reihenfolge aufgeschrieben (1. Anteil = höchste Stelle). Vorsicht: Ein endlicher Bruch in einem Stellenwertsystem ist nicht immer auch ein endlicher Bruch in einem anderen.

1. Dualzahl auf 0 setzen Wir führen einen Wert I ein, der die gerade bearbeitete Stelle der erzeugten Dualzahl enthält. I wird auf 1 gesetzt.
2. Die Dezimalzahl wird mit der Basis 2 multipliziert. Die I-te Stelle der Dualzahl ergibt sich aus dem ganzzahligen Anteil der Dezimalzahl (Vorkommastelle).
3. Nimm den gebrochenen Anteil der Dezimalzahl (Nachkommastellen) für die weitere Berechnungen.
4. Erhöhe den Wert von I um 1. Wenn die Dezimalzahl > 0 ist, fahre fort bei Schritt 2.

#### Umwandlung anderes Stellenwertsystem → Dezimalsystem

Der offensichtliche Weg ergibt sich aus der Definition einer Zahl im Stellenwertsystem. Die Umwandlung erfolgt durch Auswertung der Summe, wobei die Koeffizienten und die Potenzen der Basis B im Dezimalsystem dargestellt werden.

Beispiel: Umwandlung aus dem Dualsystem

Z = 101,11 dual = ? dez.

Z = 1 * 22 + 0 * 21 + 1 * 20 + 1 * 2-1 + 1 * 212
= 4 + 0 + 1 + 1/2 + 1/4
= 5,75

Eine andere Möglichkeit ist die Anwendung des Hornerschemas. Beginnend bei der höchstwertigen Ziffer wird bei ganzen Zahlen mit der Basis des Ausgangssystems (dargestellt im Zielsystem) multipliziert und zur nachfolgenden Stelle addiert. Dies wird bis zur niederwertigsten Ziffer fortgesetzt. Das letzte Ergebnis ist die Zahl im Zielsystem.

#### Dual-Dezimal-Umwandlung, ganzzahlig

1. Setze die Dezimalzahl auf 0. Beginne bei der höchstwertigen Stelle n der Dualzahl. Wir führen einen Wert I ein, der die gerade bearbeitete Stelle der erzeugten Dualzahl enthält. I wird auf n gesetzt.
2. Multipliziere die Dezimalzahl mit 2 und addiere die I-te Stelle der Dualzahl zur Dezimalzahl.
3. Wiederhole Schritt 2 solange, bis alle Stellen der Dualzahl verarbeitet sind.

Beispiel: 11011101 dual = ? dez.

         1   1   0   1   1   1   0   1
     2 * 1 + 1                           = 3
     2 * 3     + 0                       = 6
     2 * 6         + 1                   = 13
     2 * 13            + 1               = 27
     2 * 27                + 1           = 55
     2 * 55                    + 0       = 110
     2 * 110                       + 1   = 221

Beispiel: 7C2H = ? dez.

          7    C   2
     16 * 7 + 12        = 124
     16 * 124    + 2    = 1986

#### Dual-Dezimal-Umwandlung, echter Bruch

Bei echten Brüchen wird fortgesetzt durch die Basis des Ausgangssystems (dargestellt im Zielsystem) dividiert. Die Ziffern werden von der niederwertigsten Stelle aus abgearbeitet (rechts nach links) → Abbau zum Komma hin.

1. Setze die Dezimalzahl auf 0. Beginne bei der niederwertigen Stelle n der Dualzahl. Wir führen einen Wert I ein, der die gerade bearbeitete Stelle der erzeugten Dualzahl enthält. I wird auf n gesetzt.
2. Addiere die I-te Stelle der Dualzahl zur Dezimalzahl und dividiere das Ergebnis durch 2.
3. Wiederhole Schritt 2 solange, bis alle Stellen der Dualzahl verarbeitet sind.

Beispiel: 0,1011 dual = ? dez.

    0, 1   0   1   1
                   1    / 2 = 0,5
     (         1 + 0,5) / 2 = 0,75
     (     0 +    0,75) / 2 = 0,375
     ( 1 +       0,375) / 2 = 0,6875

### Darstellung negativer Zahlen

Ein sehr einfacher Ansatz wäre es, ein Bit als Vorzeichen zu definieren und den Betrag des Zahlenwerts in den restlichen Bits des Wortes darzu stellen: Betrags-Vorzeichen-Form.

In der Datenverarbeitung führt man die Subtraktion auf die Addition einer negativen Zahl zurück. Die Darstellung negativer Zahlen erfolgt durch die sogenannte Komplementdarstellung.

Rückführung der Subtraktion auf die Addition:

                  _              _
      a - b = a + b              b ist das Komplement von b

Es stellt sich nun sofort die Frage, wie man das Komplement einer Zahl erhält. Dazu wird die Gleichung erweitert:

a - b = a + (K - b) - K)

K ist die "Komplementärzahl". Dazu ein Beispiel:

      835               835
     -267       →    +733   (K - b) = 1000 - 267, K = 1000
    ------           -------
      568              1568
                      -1000   (K subtrahieren)
                     -------
                        568

Die Subtraktion von K zum Schluß kann man recht einfach durch Weglassen der vordersten Stelle beim Ergebnis realisieren.

K ist im Prinzip beliebig wählbar, jedoch würde für K = 1000 eine Zahl in Komplementdarstellung nicht von einer natürlichen Zahl unterscheidbar. Wir treffen daher zwei Vereinbarungen:

- Für alle Rechnungen wird eine maximale Stellenzahl n festgelegt.
- Für K wird (im Dezimalsystem) 10n+1 gewählt. Dann kann man nämlich eine Vorzeichenstelle verwenden. Wir vereinbaren:
  - Vorzeichenstelle = 0: positive Zahl
  - Vorzeichenstelle = 9: negative Zahl
  Die Vorzeichenstelle dient zwar als Indikator für das Vorzeichen, ist jedoch Bestandteil der Zahl!

Beispiele (Komplement mit VZ-Stelle):

      835               0835
     -267      →     +9733
    ------           --------
      568              10568
                       ||_____Vorzeichenstelle = 0 → Erg. positiv
                       |______Weglassen des Überlaufs

      267      →      0267
     -835              +9165
    ------            -------
     -568               9432
                        |_____Vorzeichenstelle = 9 → Erg. negativ

        Re-Komplement: -0568

     -535               9465
     -267      →     +9733
    ------            -------
     -802              19198
                       ||_____Vorzeichenstelle = 9 → Erg. negativ
                       |______Weglassen des Überlaufs

        Re-Komplement: -0802

Nun ist also auch ein negatives Ergebnis erkennbar und der Betrag (bzw. das Endergebnis) kann durch nochmaliges Komplementieren ermittelt werden. Interessant wird diese Methode jedoch erst im Dualsystem.

Zunächst eine allgemeine Definition. Es sind zwei Arten von Komplementen gebräuchlich. X sei eine n-stellige positive Zahl im Zahlensystem zur Basis B, dann ist:

                                             _    n+1
         B-Komplement (Zweierkomplement):    X = B    - X
         ("echtes Komplement")
                                             _     n+1
         (B-1)-Komplement (Einerkomplement): X = (B    - 1) - X
         ("unechtes Komplement, Stellenkomplement")

Auch hier gilt: Werden alle n Stellen für den Zahlenwert benutzt, dann ist nicht unterscheidbar, ob eine pos. Zahl oder das Komple- ment dargestellt wird → (n+1)-te Stelle hat VZ-Funktion. Die Bildung des Einerkomplements geschieht durch Invertieren jeder einzelnen Stellen (sehr einfach). Es gibt jedoch zwei "Nul- len", für n=4: 0000 = +0, 1111 = -0. Vor allem bei EDV-Systemen wird deshalb das Zweierkomplement verwendet. Die Bildung kann auf zwei Wegen erfolgen:

- Einerkomplement bilden und dann 1 addieren:

                0110 1100          X
                1001 0011          1-Kompl.
             +          1          addieren
                1001 0100          2-Kompl.

- Direkt: Übernahme aller Stellen von rechts bis zur ersten 1 einschließlich, alle weiteren Stellen invertieren:

                0110 1100          X
                1001 0100          übernehmen/invertieren
                1001 0100          2-Kompl.

- Wertebereich bei n Stellen: -2n-1 bis +2n-1 - 1

  Beispiel: 4 Stellen

           0111 +7
           0110 +6
           0101 +5
           0100 +4
           0011 +3
           0010 +2
           0001 +1
           0000  0
           1111 -1
           1110 -2
           1101 -3
           1100 -4
           1011 -5
           1010 -6
           1001 -7
           1000 -8

Die Gesamtheit der so dargestellten Zahlen nennt man konegative Zahlen (gesprochen "ko-negativ", kommt von "Komplement-negativ"). Der Vorteil der Komplementdarstellung von negativen Zahlen liegt in der Ausführbarkeit der Subtraktion als Addition.

Addiert man die beiden vierstelligen Dualzahlen 0101 und 0100, ergibt sich 1001, also -7. Das ist sicher falsch. Grund dafür ist die Überschreitung des zulässigen Zahlenbereichs. Welche Ergebnisse der Addition/Subtraktion nicht im darstellbaren Zahlenbereichzeigt folgende Tabelle:

Vorzeichen Operanden | Vorzeichen Ergebnis | Bereichs- Überschreitung
beide positiv | positiv | nein
negativ | ja
positiv und negativ | positiv | nein
negativ | nein
beide negativ | positiv | ja
negativ | nein

Merksatz: Wenn beide Operanden das gleiche Vorzeichen haben und das Ergebnis ein davon abweichendes Vorzeichen, dann gab es eine Bereichsüberschreitung (Overflow).

Die bisher gesammelten Erkenntnisse führen dann zur Rechenvorschrift für die Subtraktion durch Komplementaddition (B-Kompl.):

1. Stellenzahl von X und Y angleichen
2. B-Komplement Xk des Subtrahenden X bilden
3. Y und Xk addieren
4. Eventuellen Übertrag streichen
5. Ist VZ-Stelle von Y = VZ-Stelle von X und unterscheidet sie sich von der VZ-Stelle des Ergebnisses, dann ist ein Zahlenbereichsüberlauf aufgetreten → Fehlermeldung.
6. Ist die VZ-Stelle des Ergebnisses = 0, dann ist das Ergebnis positiv, sonst ist das Ergebnis negativ (in B-Komplement-Darstellung). Will man in diesem Fall den Betrag des Ergebnisses feststellen, muß man das Ergebnis komplementieren.

### Festpunkt- und Gleitpunktdarstellung

#### Festpunktzahlen

Die bisher betrachtete Schreibweise gebrochener Zahlen wird Festpunktdarstellung (Festkommadarstellung) genannt. Bei einer vorgegebenen Gesamtstellenzahl steht der Radixpunkt immer an der gleichen Stelle. Wie weit die erste von Null verschiedenen Ziffer vom Punkt entfernt steht, hängt von der Größe der Zahl ab, z. B.

Darstellung mit Vorzeichen, 8 Stellen vor dem Komma und 3 Stellen dahinter (dezimal).

Die Zahl wird mit führenden Nullen dargestellt und gegebenenfalls gerundet. Im obigen Beispiel reicht der darstellbare Bereich von -99999999,999 bis +99999999,999. Der Nachteil liegt im begrenzten Zahlenbereich → bei sehr kleinen Zahlen gehen durch die Rundung Stellen verloren (0,0009 würde z. B. zu 0), sehr große Zahlen sind nicht mehr darstellbar.

#### Gleitpunktzahlen

Gebrochene Zahlen (reelle Zahlen) werden i. a. in Gleitpunktdarstellung (Gleikomma-Darstellung) bearbeitet. → Gleitpunktzahlen, floating point numbers). Wegen der endlichen Stellenzahl können reelle Zahlen nur unvollkommen dargestellt werden, sie repräsentieren nur einzelne Punkte auf der reellen Zahlengerade → Rundungsfehler.

Bei der Gleitpunkt-Darstellung wird die Zahl so gespeichert, daß der Radixpunkt immer zur ersten von Null verschiedenen Ziffer gleitet. Dies erreicht man durch Abspalten einer entsprechenden Potenz der Basis:

Z = M * BE

M = 0.xxxxxxx.... 1/B <= M < 1

Da die Basis bekannt ist, kann die Zahl durch die Mantisse M und den Exponenten E dargestellt werden. → halblogarithmische Darstellung → normalisierte Darstellung. Die Anpassung der Gleitpunkt-Zahl an die angegebene Darstellungsform wird als "Normalisieren" bezeichnet. Beispiel:

Z = 42.5456 → 0.425456 * 102 → M = 425456, E = 2

Exponent: (bzw. Charakteristik) Größenordnung der Zahl
Mantisse: Angabe der gültigen Zifferndarstellungen

Die Art der Darstellung von Mantisse und Exponent ist recht unterschiedlich. Für Mantisse und Exponent wird jeweils eine feste Stellenzahl vorgegeben, in der auch das Vorzeichen von Mantisse und Exponent untergebracht werden muß. Am üblichsten ist Betrags-Vorzeichen-Darstellung der Mantisse und Exponentendarstellung mit verschobenem Wertebereich (Charakteristik) so, daß der Wert der Charakteristik immer positiv ist. Sie stellt also eine einfache Verschiebung des Wertebereichs dar und hat keinen Einfluß auf die Berechnung. Beispiel:

Wertebereich Exponent: | -128 ... 127
Charakteristik (C = E + V): | 0 ... 255 (V = 128)

Umwandlung einer Dezimalzahl in eine Gleitpunkt-Dualzahl:

1. Methode:
   - Getrennte Umwandlung des ganzahligen und gebrochenen Anteils
   - Verschieben des Radixpunktes bis zur ersten von Null verschiedenen Ziffer (Normalisierung).
   Beispiel (8 Stellen Mantisse, 4 Stellen Exponent, V = 8):
   27.75 dez. = 11011.11 dual
   → M=0.1101111, E=0101
   → M=0.1101111, C=1101

2. Methode:
   - Abspalten der nächstgrößeren Zweierpotenz (d.h. dividieren durch 2, bis das Ergebnis kleiner 1 ist)
   - Getrennte Umwandlung von Mantisse (echter Bruch) und Exponent (ganzzahlig).
   Beispiel (8 Stellen Mantisse, 4 Stellen Exponent, V = 8):
   27.75 dez. → M = 0.8671875, E = 5
   → M = 0.1101111, E = 0101
   → M = 0.1101111, C = 1101

Allgemein gilt:

- Die Gleitkommadarstellung ist immer auf eine bestimmte Zahl von Stellen genau (die Anzahl der Mantissenstellen) → bestimmte relative Genauigkeit, z.B. 32-Bit-Mantisse (einschl. VZ) → 31 Dualst. → 9 Dezimalstellen. Reelle Zahlen werden also immer auf die nächstgelegene GK-Zahl gerundet.
- Die GK-Darstellung hat den Vorteil, daß man mit bestimmter Stellenzahl einen viel größeren Zahlenbereich darstellen kann, als mit Fixkommazahlen.
- Manchmal wird auch eine Darstellung verwendet, bei welcher der Radixpunkt rechts der ersten von Null verschiedenen Ziffer steht (z.B. 1.100101). Im Dualsystem ist diese Ziffer immer 1 und kann deshalb bei der Speicherung in EDV-Systemen weggelassen werden (impliziter ganzer Teil der Mantisse).

#### Gleitpunkt-Arithmetik

Auch hier gilt wieder, daß die Rechenverfahren verwendet werden, die wir in der Schule gelernt haben - eben nur angewendet auf Dualzahlen.

- Addition und Subtraktion:
  1. Angleichen der Exponenten der beiden Operanden (Verschieben der Mantisse des Operanden mit kleineren Exponenten)
  2. Addition/Subtraktion der Mantissen
  3. Normalisieren des Ergebnisses
- Multiplikation und Division:
  1. Multiplikation/Division der Mantissen
  2. Addition/Subtraktion der Exponenten
  3. Normalisieren des Ergebnisses
- Assoziativität und Distributivität gilt nicht mehr:
  Durch die begrenzte Stellenzahl entstehen u. U. Rundungsfehler beim Angleichen der Operanden (Rundungsfehler). Das Assoziativgesetz und das Distributivgesetz gelten nicht mehr.

      a + (b + c) != (a + b) + c
      a * (b * c) =/ (a * b) * c
      (a + b) * c =/ (a * c) + (b * c)

  Einfaches Beispiel im Dualsystem: 10,11 + 11101,0
  `0 | 0 0 0 1 | 1 0 1 1 0 0`
  `0 | 1 1 0 1 | 1 1 1 0 1 0`
  1. Schritt (angleichen):
  `0 | 1 1 0 1 | 0 0 0 1 0 1 (1)`
  `0 | 1 1 0 1 | 1 1 1 0 1 0`

  Schon beim Angleichen geht die letzte Stelle des ersten Summanden verloren.

### Informationsdarstellung in Rechenanlagen

#### Integer-Zahlen

Integerzahlen (ganze Zahlen) werden in der Regel als konegative Dualzahlen dargestellt. Je nach Datenverarbeitungssystem (DVS) beträgt die Wortbreite 8, 16, 32, ... Bit.

#### Real-Zahlen

Real-Zahlen können, bedingt durch die Beschränkung auf endliche Stellenzahl, nur unvollkommen dargestellt werden. Die Darstellung erfolgt in Form von Gleitpunktzahlen. Die Folge sind Rundungsfehler bei der Arithmetik. Daraus ist ein eigener Zweig der Mathematik, die numerische Mathematik, entstanden, der sich mit der Entwicklung von Algorithmen befaßt, die trotz der Rundungsfehler optimale Ergebnisse liefern.

#### Zeichen

Jedes Zeichen wird durch ein Codewort eines festgelegten Codes verschlüsselt. Die Wahl des Codes ist beliebig. Bei DVS meist ASCII (American Standard Code for Information Interchange), z. T. erweitert auf den vollen Wertebereich eines Byte (z. B. IBM-PC). Andere verwendete Zeichencodes:

- EBCDIC (Extended Binary Code for Digital InterChange)
- CDC-Display-Code (6-Bit): CDC-Cyber, keine Kleinbuchstaben
- 12-Bit Lochkarten-Code: Zur Darstellung von Informationen auf Lochkarten

Zeichenketten(Strings) werden durch aufeinanderfolgende Zeichen dargestellt. Je nach Wortlänge des DV-Systems in gepackter oder ungepackter Darstellung (z. B. 8 Zeichen in einem 64-Bit-Speicherwort). Es gibt zwei Formen der Speicherung in aufsteigender Reihenfolge im Speicher des DV-Systems:

- Reservierung des Speicherplatzes für einen String der maximal vorgegebenen Länge (= Anzahl der Codeworte). Die Anzahl der gültigen Zeichen wird im ersten Codewort gespeichert.
- Bei einem String der Länge n Reservierung von n+1 Codeworten. Das letzte Codewort enthält ein speziellen Abschlusszeichen (z.B. Nullwort)

#### ASCII Tabelle (sedezimal)

    | 00 nul| 01 soh| 02 stx| 03 etx| 04 eot| 05 enq| 06 ack| 07 bel|
    | 08 bs | 09 ht | 0a nl | 0b vt | 0c np | 0d cr | 0e so | 0f si |
    | 10 dle| 11 dc1| 12 dc2| 13 dc3| 14 dc4| 15 nak| 16 syn| 17 etb|
    | 18 can| 19 em | 1a sub| 1b esc| 1c fs | 1d gs | 1e rs | 1f us |
    | 20 sp | 21  ! | 22  " | 23  # | 24  $ | 25  % | 26  & | 27  ' |
    | 28  ( | 29  ) | 2a  * | 2b  + | 2c  , | 2d  - | 2e  . | 2f  / |
    | 30  0 | 31  1 | 32  2 | 33  3 | 34  4 | 35  5 | 36  6 | 37  7 |
    | 38  8 | 39  9 | 3a  : | 3b  ; | 3c  < | 3d  = | 3e  > | 3f  ? |
    | 40  @ | 41  A | 42  B | 43  C | 44  D | 45  E | 46  F | 47  G |
    | 48  H | 49  I | 4a  J | 4b  K | 4c  L | 4d  M | 4e  N | 4f  O |
    | 50  P | 51  Q | 52  R | 53  S | 54  T | 55  U | 56  V | 57  W |
    | 58  X | 59  Y | 5a  Z | 5b  [ | 5c  \ | 5d  ] | 5e  ^ | 5f  _ |
    | 60  ` | 61  a | 62  b | 63  c | 64  d | 65  e | 66  f | 67  g |
    | 68  h | 69  i | 6a  j | 6b  k | 6c  l | 6d  m | 6e  n | 6f  o |
    | 70  p | 71  q | 72  r | 73  s | 74  t | 75  u | 76  v | 77  w |
    | 78  x | 79  y | 7a  z | 7b  { | 7c  | | 7d  } | 7e  ~ | 7f del|

#### Latin1-Tabelle

128 | 144 | 160 | 176 ° | 192 À | 208 Ð | 224 à | 240 ð
129 | 145 ‘ | 161 ¡ | 177 ± | 193 Á | 209 Ñ | 225 á | 241 ñ
130 ‚ | 146 ’ | 162 ¢ | 178 ² | 194 Â | 210 Ò | 226 â | 242 ò
131 ƒ | 147 “ | 163 £ | 179 ³ | 195 Ã | 211 Ó | 227 ã | 243 ó
132 „ | 148 ” | 164 ¤ | 180 ´ | 196 Ä | 212 Ô | 228 ä | 244 ô
133 … | 149 • | 165 ¥ | 181 µ | 197 Å | 213 Õ | 229 å | 245 õ
134 † | 150 – | 166 ¦ | 182 ¶ | 198 Æ | 214 Ö | 230 æ | 246 ö
135 ‡ | 151 — | 167 § | 183 · | 199 Ç | 215 × | 231 ç | 247 ÷
136 ˆ | 152 ˜ | 168 ¨ | 184 ¸ | 200 È | 216 Ø | 232 è | 248 ø
137 ‰ | 153 ™ | 169 © | 185 ¹ | 201 É | 217 Ù | 233 é | 249 ù
138 Š | 154 š | 170 ª | 186 º | 202 Ê | 218 Ú | 234 ê | 250 ú
139 ‹ | 155 › | 171 « | 187 » | 203 Ë | 219 Û | 235 ë | 251 û
140 Œ | 156 œ | 172 ¬ | 188 ¼ | 204 Ì | 220 Ü | 236 ì | 252 ü
141 | 157 | 173 | 189 ½ | 205 Í | 221 Ý | 237 í | 253 ý
142 | 158 | 174 ® | 190 ¾ | 206 Î | 222 Þ | 238 î | 254 þ
143 | 159 Ÿ | 175 ¯ | 191 ¿ | 207 Ï | 223 ß | 239 ï | 255 ÿ

#### Logische Werte

Die Zuordnung zwischen internen Binärwerten kann im Prinzip beliebig vorgenommen werden (über Software). Häufig gilt: "FALSE" = "0", "TRUE" = "1".

- ungepackte Darstellung: Ein logischer Wert wird in einem Byte oder auch einem Maschinenwort gespeichert (große Platzverschwendung). Die Zuordnung ist oft: "WAHR" ungleich 0, "FALSCH" = 0.
- gepackte Darstellung: Jedes einzelne Bit des Maschinenworts repräsentiert einen logischen Wert, also 8 Werte/Byte.

## 4.2 Elementare Typen, Qualifizierer, Speicherklassen

### Elementare Datentypen

`char` | Character, ASCII-Zeichen; Konstanten werden in single quotes eingeschlossen, z. B. `'c'`; formal: Integer mit kleinem Wertebereich; (= 1 Byte)
`int` | Integer; ganze Zahl, typischerweise von der Länge eines Maschinenwortes; (2 Byte)
`float` | Fließkommazahl (einfach genau); (4 Byte)
`double` | Fließkommazahl (doppelt genau); (8 Byte)
`void` | leer (vor allem bei Zeigern und Funktionen wichtig);

### Speicherklassen

`auto` | "normale" lokale Variable (Standard-(default-)Speicherklasse, zufällig initialisiert) alle Variablen, denen nicht explizit eine Speicherklasse zugewiesen wird und die nicht außerhalb von Funktionen vereinbart werden, fallen in die Speicherklasse auto man kann einer Variablen explizit die Speicherklasse auto zuordnen, indem man vor die Typangabe bei der Variablenvereinbarung das Schlüsselwort auto setzt automatische Variable werden bei jedem Funktionsaufruf neu erzeugt und beim Verlassen der Funktion wieder zerstört daher ist ihr Geltungsbereich auf die lokale Funktion, in der sie vereinbart wurden, beschränkt dies gilt auch für Blöcke (Variablen, die innerhalb von Blöcken vereinbart werden und die in die Speicherklasse auto fallen, sind nur lokal in diesem Block bekannt) auto Variablen können beliebig (nicht nur mit Konstanten) initialisiert werden nicht initialisierte auto Variablen haben einen undefinierten Wert (es existiert kein Weglasswert!) auto Vektoren können nicht initialisiert werden Beispiele:
    int y = 1;     /* global */
    int main(void)
      {
      int x = 5; /* lokal */

      printf("x=%d\n",x);    /* Ausgabe: 5 */
        {
        int x=10;
        printf("x=%d\n",x);  /* Ausgabe: 10 */
        }
      printf("x=%d\n",x);    /* Ausgabe: 5 */
      }
Das folgende Programm erzeugt einen Compilerfehler: "x nicht definiert". Da x nicht global ist, kann `print()` nicht darauf zugreifen.
    void print(void);

    int main(void)
      {
      int x=5;
      print();
      }

    void print(void)
      {
      printf("x=%d\n",x);
      }
`register` | der "Wunsch", `char`- oder `int`-Variable im Maschinenregister zu speichern (zufällig initialisiert) in dieser Speicherklasse können sich einfache Variable befinden sie entspricht in ihren sonstigen Eigenschaften der Speicherklasse auto der Compiler versucht register Variablen so zu verwenden, daß sie in einem wirklichen Hardwareregister der CPU gehalten werden ist dies nicht möglich, so wird register ignoriert und die Variable wie auto behandelt
- `extern` | lokale Deklaration von globalen Variablen (initialisiert mit 0) alle Objekte, die außerhalb von Funktionen vereinbart werden, sind in der Speicherklasse extern die Funktionsnamen selbst sind ebenfalls in der Speicherklasse extern externe Objekte sind ab ihrer Vereinbarung bis zum Ende des Quellencodes und sogar in anderen Quellencodedateien bekannt bei der Vereinbarung eines Objektes als extern kann es sich um eine Definition oder um eine Deklaration handeln die folgenden Vereinbarungen (immer außerhalb von Funktionen) unterscheiden sich wesentlich:
      int sp = 0;                /* Definition */
      double vector[N];
      extern int sp;             /* Deklaration */
      extern double vector[];
bei einer Definition wird eine Variable erzeugt nur hier ist eine Initialisierung möglich fehlt eine Initialisierung, so wird der Weglasswert 0 eingesetzt bei einer Deklaration werden nur die Variableneigenschaften festgelegt, die Variable selbst muß an anderer Stelle im Quellencode definiert sein Deklarationen dürfen für eine bestimmte Variable mehrmals im (gesamten) Quellencode vorkommen, eine Definition aber nur einmal Beispiel: Text einlesen und die Häufigkeit der Zeichen zählen
    #include <stdio.h>
    #define MAXCHARS 255

    int count[MAXCHARS];         /* global */

    void print_alle (void);

    void main(void)
      {
      extern int count[];        /* Deklaration von count */
      int c;
      while((c=getchar()) != EOF)
        count[(char) c]++ ;
      print_alle();
      }

    void print_alle(void)        /* kann z. B. in einer anderen Datei stehen */
      {
      extern int count[];        /* Deklaration von count */
      int i;

      for(i = 0; i < MAXCHARS; i++)
        printf("%d: %d\n",i,count[i]);
      }
- `static` | "statische" Variable, Inhalt bleibt erhalten (initialisiert mit 0) static Objekte können sowohl intern als auch extern sein static Objekte innerhalb von Funktionen sind nur lokal bekannt, behalten im Gegensatz zu auto Objekten aber ihre Werte zwischen den Funktionsaufrufen bei bzgl. der Initialisierung gilt dasselbe wie für externe Objekte, static Vektoren sind daher initialisierbar Zeichenketten innerhalb von Funktionen sind immer in der Speicherklasse static (z.B. printf() Parameterstring) static Objekte außerhalb von Funktionen sind externe Objekte, deren Namen aber nur in dieser Quellencodedatei bekannt ist Beispiele zur Initialisierung eines Vektors:
      static int ndigit[10] = { 0,1,2,3,4,5,6,7,8,9 };
      char string[] = "Dies ist ein String";
Beispiel: Zwischenspeicherung von Werten in einer Funktion
    #include <stdio.h>

    char *monatsname(int);  /* Gibt Zeiger auf ein Feld zurueck */

    int main(void)
      {
      int i;
      for(i = 1; i <= 12; i++)
        printf("%s\n",monatsname(i));
      }

    char *monatsname(int n)
      {
      static char *name[] = {"falscher Monat",
                             "Januar",
                             "Februar",
                             "Maerz",
                             "April",
                             "Mai",
                             "Juni",
                             "Juli",
                             "August",
                             "September",
                             "Oktober",
                             "November",
                             "Dezember"
                             };
        if ((n < 1) || (n > 12))
          return(name[0];
        else
          return(name[n]);
        }

### Qualifizierer

`signed` | vorzeichenbehaftet (default für integer, bei `char` implemantationsabhängig; nur für ganzzahlige Datentypen)
`unsigned` | nicht vorzeichenbehaftet (nur für ganzzahlige Datentypen)
`short int` | `int` mit evtl. kleinerem Wertebereich (2 Byte)
`long int` | `int` mit evtl. größerem Wertebereich (4 Byte)
`long double` | `double` mit evtl. größerem Wertebereich
`const` | Variable kann nur gelesen werden (wird u.U. compilerabhängig ignoriert, allerdings liefert ein Verstoß eine Compiler-Warnungen)
`volatile` | Wert wird möglicherweise durch parallel laufende Prozesse verändert. Q. verhindert "Wegoptimieren" (kann ebenfalls compilerabhängig ignoriert werden)

Außer eventuell bei `char` sind diese Größen nicht festgelegt, sondern abhängig vom jeweiligen Compiler. Definitiv festgelegt sind die angegebenen Mindestgrößen und die folgenden Relationen:
`short` <= `int` <= `long`;
`float` <= `double` <= `long double`.
Die tatsächliche Größe kann man den Header-files `<limits.h>` und `<float.h>` entnehmen.

## 4.3 Konstanten

1. Ganzzahlig
   - dezimal: `-1, 0, 2001,` ...
   - oktal: (beginnt mit `0`) `020, 01, 0777,` ...
   - hexadezimal: (beginnt mit ``0x`') `0x5F, 0xF7A,` ...
   Diese Konstanten gelten als `signed int`; sollen sie dagegen vom Typ `unsigned`, `long` oder `unsigned long` sein, müssen sie durch Anhängen von `u` oder `U`, bzw. `l` oder `L` als solche spezifiziert werden (z. B.:`54000UL`).

2. Gleitpunkt:
   - mit Dezimalpunkt oder Exponent: `3.141592 2.9979e8, 6e-31,` ...
     (`e` oder `E`)
   - Solche Konstanten sind automatisch `double`, wenn nicht `F` oder `f` (für `float`) bzw. `L` oder `l` (für `long double`) folgt.

3. Zeichenkonstanten:
   - zwischen `' '` eingeschlossen: `'a'`, `'B'`, `'\n'`, `'\0'`, ...
   - Zeichenkonstante können auch durch ihren Code angegeben werden:
     `\nnn` dezimal, z. B. `\053` für '+' oder
     `\0xnnn` sedezimal, z. B. `\0x40` für '@'.

4. Konstante Zeichenketten (sog. Strings):
   - zwischen `" "` eingeschlossen:
     `"ich bin ein String\n"`,
     `"Dieser Text piept!\a"`, ...
   - Zeichenkettenkonstante werden automatisch mit einem Nullbyte (`\0`) abgeschlossen.
     `"A"` belegt also zwei Speicherplätze, für 'a' und für '\0'.

Neben der Verwendung von Konstanten in beliebigen Anweisungen (z. B. `if (a > 1.0)` oder `Nachname := "unbekannt";`) besteht in C die Möglichkeit, Bezeichner für Konstante zu vergeben.
Bei der Programmiersprache C findet man zwei Varianten der Konstantenvereinbarungen:

    const Pi = 3.1415
    #define Pi 3.1415

Die erste Form entspricht dem aktuellen ANSI-Standard und ist zu bevorzugen. Die zweite Variante ist älter und definiert eigentlich nur ein Makro.

Wichtig:Bei der Verwendung von Gleitpunktkonstanten muß der Dezimalpunkt angegeben werden. 2000 ist eine Integer-Konstante, `2000.` oder `2000.0` sind Gleitpunkt-Konstante (float oder double).

## 4.4 Aufzählungstypen mit enum

Aufzählungstypen machen gegenüber Integer-Werten viele Programme lesbarer und durchschaubarer. Insbesondere, wenn bestimmte Ablaufzustände bei Steuerungen oder innerhalb des Programms bezeichnet werden, kann dies hilfreich beim Schreiben korrekter Programme sein. Letzlich sind Variable vom Typ `enum` bestimmte mit Namen versehene Integer-Konstanten; diese können bei der Typendefinition explizit angegeben werden. Beispiel:

    /* Aufzaehlung */
    enum lichtzeichenanlage {rot, gelb, gruen};

    enum wochentag {MO, DI, MI, DO, FR, SA, SO} termin;
    enum wochentag  ruhetag;
    enum lichtzeichenanlage ampel;

    termin = DI;
    ruhetag = FR;
    ampel = rot;

Die Integer-Zuweisung erfolgt durch Weiterzählen vom letzten zugewiesenen Wert (default: 0) `MO` = 0, `DI` = 1, ...
oder man gibt einen Anfangswert vor, zum Beispiel:
`enum monat {JAN=1, FEB, MAERZ,` ...
dann ist `JAN` = 1, `FEB` = 2, `MAERZ` = 3, ...

Es lassen sich auch alle Werte individuell belegen, wobei auch mehrere Konstanten den selben Wert besitzen dürfen (was aber selten sinvoll ist).
`enum lichtzeichenanlage {rot = 10, gelb = 20, gruen = 30};`

## 4.5 Arithmetische Operatoren

Das Wichtigste zuerst: Anders als bei vielen anderen Programmiersprachen stellt die Wertzuweisung gleichzeitig einen Operator dar. Sein Wert ist das Ergebnis der Wertzuweisung. Daher kann eine Wertzuweisung überall dort stehen, wo ein Operator stehen darf. Das fürt dann zu Konstruktionen wie:

    if (z = x + y) { .... }

z wird der Wert der Addition von x und y zugewiesen. Gleichzeitig ergibt der Zuweisungsoperator einen Wert, der von der if-Anweisung ausgewertet werden kann. Ist x + y != 0, dann ist das Ergenis WAHR und der Anweisungsblock wird ausgeführt. Ob die obige KOnstruktion umbedingt eleganter und lesbarer als

    z = x + y;
    if (z) { ... }

ist, mag dahingestellt sein.

Da die Zuweisung ein Operator ist, sind auch Mehrfachzuweisungen wie z. B. `a = b = c = 12` möglich. Die Zuweisung bindet schwächer als andere Operatoren (arithmetische oder Vergleichs-Operatoren). Daher sollte man Zuweisungen, die in Ausdrücken stehen, zur Sicherheit klammern. Erst durch den abschließenden Strichpunkt wird der Zuweisungsoperator zu einer Anweisung!

Es gibt sechs arithmetische Operatoren. Sie stehen für die vier Grundrechenarten (+ - * /), die Modulofunktion (Rest nach Division; % ) und die arithmetische Negation (das Vorzeichen).

- | Vorzeichen (Unär)
* | Multiplikation
/ | Division
% | Modulofunktion
+ | Addition
- | Subtraktion

Die Modulofunktion ist nicht auf float oder double Operanden anwendbar. Die Rangfolge ist wie gewohnt: *, / und % gleich und höher als + und -. Das Minuszeichen als unärer Operator (Vorzeichen) hat einen höheren Vorrang als *, / und %. Es gibt kein positives Vorzeichen.

#### Beispiele für die Anwendung arithmetischer Operatoren:

    int h, i=3, j=5, k=10;
    float x, y=5.0, z=3.0;
    h = i+j+k;              /* 18 */
    h = k-j-i;              /*  2 */
    h = i-k;                /* -7 */
    h = k/i;                /*  3 */
    h = k/i+j;              /*  8 */
    h = k/(i+j);            /*  1 */
    h = k*i+j;              /* 35 */
    h = k*(i+j);            /* 80 */
    h = k%i;                /*  1 */
    x = y+z;                /* 8.       */
    x = y/z;                /* 1.666... */
    x = y*z;                /*15.       */
    x = k/z;                /* 3.333... */
    x = k/z + y/z;          /* 5.       */
    x = k/i + y/i;          /* 4.666... */
    x = k%z;                /* verboten! */

Höhere Rechenoperationen wie Wurzelziehen, Potenzieren, Exponentiation, usw. fehlen in C als Operator ganz; sie sind über Bibliotheksfunktionen realisiert. Bei ihrer Verwendung muß in jedem Fall die Inlcude-Datei `math.h` mit eingebunden werden.

#### Inkrement- und Dekrementoperatoren

C kennt spezielle Operatoren zum Inkrementieren und Dekrementieren. Es sind dies:

++ | Inkrement
-- | Dekrement

Sie sind (wie der Vorzeichenoperator -) rechts assoziativ, werden also von rechts her zusammengefaßt. Sie können vor oder nach ihrem Operanden stehen. Im ersten Fall wird der Operand zunächst in- oder dekrementiert und dann weiterverwendet, im zweiten Fall wird der Operand erst verwendet und dann in- oder dekrementiert.

    int i;
    int k;
    i=1;
    if (++i > 1)
        k = 5;            /* wird durchlaufen */
    if (i++ > 2)
        k = 10;           /* wird nicht durchlaufen */

#### Logische Verknüpfungen

Unter die logischen Verknüpfungen für Wahrheitswerte fallen die Operatoren:

! | logische Negation
&& | logisches UND
|| | logisches ODER

Sie sind hier in ihrer Rangfolge geordnet aufgeführt, logisches UND hat also einen höheren Vorrang als das logische ODER. In Ausdrücken erfolgt die Abarbeitung von links nach rechts aber nur solange, bis das Ergebnis eindeutig feststeht. Das führt zu einer kürzeren Ausführungszeit, wenn man die häufigst verwendete Bedingung an den Anfang einer Bedingungsabfrage stellt. Bei Operationen, die Nebeneffekte haben können (z.B. Inkrementation), muß man allerdings vorsichtig sein.

    if (a<b         &&  (d=(a+b)) != c)   /* diese 2 Zeilen */
    if ((d=(a+b)) != c &&  a<b)           /* wirken nicht gleich */

Beispiel:

    #include <stdio.h>

    int main (void)
      {
      enum logik { false = 0, true = 1} a, b;

      printf ("| a b | a&&b | a||b |\n");
      printf ("|-----|------|------|\n");
      for (a = false; a <= true; a++)
        for (b = false; b <= true; b++)
          printf ("| %d %d |   %d  |   %d  |\n",a, b, (a && b), (a || b));
      return 0;
      }

Ergebnis:

         | a b | a&&b | a||b |
         |-----|------|------|
         | 0 0 |   0  |   0  |
         | 0 1 |   0  |   1  |
         | 1 0 |   0  |   1  |
         | 1 1 |   1  |   1  |

#### Bitmanipulationen

C stellt auch Operatoren für Bitmanipulationen zur Verfügung. Die Verknüpfung der beiden Operanden erfolgt bitweise. Sie können nicht auf Variablen oder Konstanten vom Typ float oder double angewendet werden. Die Operatoren sind:

~ | Komplement
<< | Linksshift
>> | Rechtsshift
& | bitweises UND
^ | bitweises EXKLUSIVES ODER
| | bitweises ODER

#### Beispiele:

    int i=7;
    int j=9;
    int k;
    k = i & j;        /* k=1   */
    k = i | j;        /* k=15  */
    k = i ^ j;        /* k=14  */
    k = ~0            /* k=max(unsigned int) */
    k = ~i;           /* k=max(unsigned int) -7 */
    k = i << 3;       /* k=56  */
    k = i >> 3;       /* k=0   */
    k = i & 0x03;     /* k=3; alle Bits bis auf die beiden */
                      /* unteren ausmaskiert */

Beispiel: Zählen der Anzahl der 1-Bits in n

    bitcount(unsigned int n)
      {
      int b;
      for (b=0; n!=0; n >>= 1)
      if (n & 01)
        b++;
      return (b);
      }

Zweite, schnellere Version:

    bitcount(unsigned int n)
      {
      int b;
      b=0;
      while (n != 0)
        {
        b++;
        n = n & (n-1);
        }
      return (b);
      }

Das folgende Beispiel ermittelt die Länge eines Maschinenwortes durch Zählen der Bits.

    #include <stdio.h>

    int wordlen()
      {
      int i;
      unsigned wort;
      i = wort = 0;
      wort = ~wort;  /* alle Bits auf 1 setzen */
      while (wort != 0)
        {
        wort = wort >> 1;  /* rechtsschieben */
        i++;
        }
      return(i);
      }
    main()
      {
       printf("Anzahl der Bits: %d\n",wordlen());
      }

Die folgenden "Einzeiler" manipulieren jeweils ein bestimmtes Bit in einem Integer-Wort:

    /* Teste, ob ein bestimmtes Bit 0 oder 1 ist */
    int tstbit( int word, int bit ) { return (word & (1 << bit) != 0); }

    /* Loesche ein bestimmtes Bit */
    int clrbit( int word, int bit ) { return (word & ~(1 << bit)); }

    /* Setze ein bestimmtes Bit */
    int setbit( int word, int bit ) { return (word | (1 << bit)); }

    /* Invertiere ein bestimmtes Bit */
    int xorbit( int word, int bit ) { return (word ^ (1 << bit)); }

#### Wichtig:

- es werden teilweise die gleichen Operatorzeichen für verschiedene Operatoren verwendet

  Operator- zeichen | 1. Bedeutung | 2. Bedeutung
  () | Klammerung | typecast
  * | Pointerdeklaration | Multiplikation
  - | Vorzeichen | Subtraktion
  & | Adresse von | bitweises UND

- Inkrement- und Dekrementoperatoren haben einen sehr hohen Vorrang
- die Arithmetikoperatoren haben einen Vorrang wie erwartet und gewohnt
- Shifts haben einen höheren Vorrang als Vergleiche, im Gegensatz zu den anderen Bitmanipulationen, die nach den Vergleichen stehen.
- bitweise logische Operationen stehen vor den logischen Operationen, die sich auf ganze Zahlen beziehen.
- die bedingte Bewertung hat einen sehr niedrigen Vorrang, trotzdem sollte man sich bei ihrer Anwendung eine Klammerung angewöhnen, damit die Anweisung leichter lesbar ist.
- außer durch den Vorrang ist die Reihenfolge der Bewertung undefiniert: `z = (a*2) + ++a` kann als `(a*2)+ ++a` aber auch als `++a + (a*2)` bewertet werden. Seiteneffekt!
- ebenso ist die Reihenfolge der Bewertung von Funktionsparametern nicht gewährleistet:

      i=1;
      printf("%d %d",++i,i*3)   /* kann 2,6 oder 4,3 ausgeben */

- ebenfalls nicht eindeutig definiert ist folgende Konstruktion: `a[i]=i++;`
- Dagegen bedeutet `x---y: x-- - y` und nicht `x - --y`. Der Compiler führt seine lexikalische Analyse immer von links nach rechts aus und nimmt dabei soviele Zeichen, wie er nur kann, um ein Token zu bilden.

## 4.6 Logische Datentypen und Operatoren, Vergleiche

Für Vergleiche stehen folgende Operatoren zur Verfügung:

< | kleiner
<= | kleiner gleich
> | größer
>= | größer gleich
== | gleich (identisch)
!= | ungleich

Die ersten vier haben untereinander gleichen Rang, stehen aber eine Rangstufe höher als die beiden letzten. Es gibt in C grundsätzlich keinen Datentyp BOOLEAN; WAHR oder FALSCH werden einfach über den numerischen Integer-Wert entschieden. Dabei gilt:

ungleich 0 | WAHR (erhält den Wert eins)
gleich 0 | FALSCH (Wert 0)

Arbeitet man in einem Programm viel mit diesen Werten, kann man folgende Konstantendefinitionen zu benutzen:

        #define FALSE  0
        #define TRUE   ! FALSE

#### Beispiele für die Verwendung dieser Operatoren:

    int a=5;
    int b=12;
    int c=7;
       a < b                /* WAHR   */
       b < c                /* FALSCH */
       a+c <= b             /* WAHR   */
       b-a >= c             /* WAHR   */
       a == b               /* FALSCH */
       a+c == b             /* WAHR   */
       a != b               /* WAHR   */
       a = b<c;             /* möglich: a=0 */
       a = c<b;             /*   -"-    a=1 */

Wie schon erwähnt, bindet die Zuweisung schwächer als andere Operatoren (arithmetische oder Vergleichs-Operatoren). Daher sollte man Zuweisungen, die in Ausdrücken stehen, zur Sicherheit klammern. Dazu einige Beispiele:

    if (x = y)
       tuwas();

Die Anweisung besagt nicht: "`Wenn x gleich y ist, dann tuwas();`", sondern "`x erhalte den Wert y; Wenn das Ergebnis der Operation (x = y) ungleich Null ist, dann tuwas();`". Es gibt also zwei Möglichkeiten:

1. Es soll wirklich x mit y verglichen werden. Dann darf nicht "=" stehen, sondern der Vergleichsoperator "==". Solche Flüchtigkeitsfehler kommen oft vor.

       if (x == y) /* Vergleich x == y (doppeltes Gleichheitszeichen) */
         tuwas();

2. Es soll tatsächlich die Zuweisung x = y erfolgen und abhängig vom Wert der Variablen x bzw. y verzweigt werden. Dann ist zwar `if (x = y)` richtig, aber der Deutlichkeit halber sollte man schreiben:

       if ((x = y) != 0)
         tuwas();

   Die Klammerung von `(x = y)` ist notwendig, da der Vergleichoperator stärker bindet als die Zuweisung. Fehlt die Klammer, wäre das schon wieder ein schwer zu findender Fehler. `x = y != 0` bedeutet: `x = (y !=0)`. x hätte nachher nicht den Wert von y, sondern 0 oder 1.

## 4.7 Zusammengesetzte Operatoren, implizite Typumwandlung

#### Zusammengesetzte Operatoren

Eine Spezialität von C ist die abgekürzte Schreibweise für bestimmte Zuweisungen.Diese abgekürzte Schreibweise ist vorteilhaft, wenn die linke Seite eine komplizierte Struktur hat (weniger Tippfehler, bessere Lesbarkeit). Sie ist aber für den Anfänger schwerer zu lesen. Bei der abgekürzten Schreibweise gilt folgendes:

    Ausdruck1 op= Ausdruck2

ist äquivalent zu (beachten Sie die Klammern!):

    Ausdruck1  =  (Ausdruck1) op (Ausdruck2)

op kann einer der Operatoren: + - * / % << >> & ^ oder | sein. Die Klammern sind sehr wichtig, damit keine unerwünschten Nebeneffekte auftreten:

    i *= k+1;      /* wird wie */
    i = i * (k+1); /* behandelt und nicht wie */
    i = i * k +1;

Der Vorteil der abgekürzten Schreibweise wird an folgendem Beispiel deutlich:

    feld[i*3+j-7] = feld[i*3+j-7] + 10;     /* normal */
    feld[i*3+j-7] += 10;                    /* abgekürzt */

Zusammengesetzte Zuweisungsoperatoren:

+= | x += ausdr | ← | x = x + ausdr
-= | x -= ausdr | ← | x = x - ausdr
*= | x *= ausdr | ← | x = x * ausdr
/= | x /= ausdr | ← | x = x / ausdr
%= | x %= ausdr | ← | x = x % ausdr
&= | x &= ausdr | ← | x = x & ausdr
|= | x |= ausdr | ← | x = x | ausdr
^= | x ^= ausdr | ← | x = x ^ ausdr
<<= | x <<= ausdr | ← | x = x << ausdr
= | x >>= ausdr | ← | x = x >> ausdr

#### Implizite Typumwandlung

Datentypwandlungen sind immer dann notwenig, wenn zwei Operanden in einem Ausdruck verschiedene Typen haben. Die Datentypwandlungen werden soweit notwendig implizit durchgeführt. Es gelten folgende Regeln:

- `char` wird bei Bewertungen immer in `int` gewandelt, dadurch sind `int` und `char` beliebig mischbar. Allerdings kann die `char-int` Wandlung rechnerabhängig sein, `char` kann auf die Zahlenwerte -128 bis +127 oder 0 bis +255 abgebildet sein, die "normalen" Zeichen liegen aber immer im positiven Bereich. Die `char-int` Wandlung ist vor allem bei der Abfrage eines eingelesenen Zeichens auf `EOF` wichtig (Eingelesen werden immer `int` Werte, die dann aber einfach `char` Werten zugewiesen werden können).
- die Wandlung von `int` nach `long` erfolgt durch Vorzeichenerweiterung (bei signed) oder Voranstellen von Nullen (unsigned), bei der umgekehrten Wandlung werden die höchstwertigen Bits einfach abgeschnitten.
- `float` wird bei Bewertungen immer in `double` gewandelt, alle Rechnungen erfolgen daher mit derselben großen Genauigkeit. Die Wandlung von `int` nach `float` ist wohldefiniert, die umgekehrte Richtung kann rechnerabhängig sein, insbesondere bei negativen Gleitkommazahlen.

Zusammenfassung:

Variable | = | Variable | durchgeführte Typumwandlung
int int | ← ← | float double | Weglassen des gebrochenen Anteils
int char char | ← ← ← | long int short | Weglassen der höherwertigen Bits
float | ← | double | Runden oder Abschneiden (implementierungsabhängig)
float double | ← ← | long, int, short,char | Wenn keine exakte Darstellung möglich Runden oder Abschneiden (implementierungsabhängig)

Bei Parameterübergabe an Funktionen erfolgt die Umwandlung des Typs des aktuellen Parameters in den Typ des formalen Parameters nach obigen Regeln.

Den genauen Ablauf der Wandlungen bei der Auswertung eines Ausdrucks oder einer Zuweisung beschreiben folgende Regeln (übliche arithmetische Umwandlungen):

- Zuerst werden `char` oder `short` Operanden in `int` Werte und `float` Operanden in `double` Werte umgewandelt.
- Hat jetzt ein Operand den Typ `double`, so wird der andere ebenfalls in `double` gewandelt und das Resultat ist auch `double`.
- Ist stattdesssen einer der Operanden `long`, so wird der andere auch in `long` gewandelt und das Resultat ist auch `long`.
- Ist andernfalls einer der Operanden `unsigned`, so wird auch der andere in `unsigned` gewandelt und das Resultat ist ebenfalls `unsigned`.
- Liegt keiner dieser Fälle vor, so handelt es sich um `int`-Werte, diesen Typ hat dann natürlich auch das Resultat.

Beispiel:

Vorsicht:

- Division zweier `int` ergibt wieder `int`, also den Ganzzahlanteil des mathematischen Wertes.
- Bei Umwandlung von höherwertigem zu niederwertigem Datentyp ist Informationsverlust möglich!
- Achtung bei Vergleich von `signed` und `unsigned` Typen!
  Beispielsweise könnte WAHR sein: `-1 > 1U`

#### Explizite Typumwandlung (type cast)

In manchen Fällen ist es erforderlich die Typkonversion gezielt vorzunehmen. Das sieht man am besten mit einem Beispiel:

    int j = 12, k = 18;
    float f;
       ...
    f = k/j;    /* in f steht 1.0 */
       ...

Beide Operanden der Division `k/j` sind `int`-Variablen. Also ist auch das Ergebnis der Division vom Typ `int` (18/12 = 1 Rest 6). Die Zuweisung erfolgt nach der Division. Mit der impliziten Typkonversion erfolgt dann die Expansion von 1 zu 1.0. Sollte das "echte" Ergebnis gewünscht werden, so wird dies durch eine explizite Typumwandlung möglich.

Die explizite Typumwandlung (type cast) erfolgt mittels des Cast-Operators, der unmittelbar vor den Ausdruck zu setzen ist, dessen Typ konvertiert werden soll. Der Cast-Operator ist ganz einfach eine in Klammern gesetzte Typangabe. Obiges Beispiel wird geändert zu:

    int j = 12, k = 18;
    float f;
       ...
    f = (float)k/j;
       ...

Die Variable `k` wird nun zu einer `float`-Variablen typkonvertiert (Neudeutsch: "gecastet"). Bei der Division von `float` durch `int`wird `j` implizit auf `float` typkonvertiert und so ergibt sich das korrekte Ergebnis 1.5.

Beispiel: Wandelt die nächsten x Ziffern aus dem String s in eine Integerzahl

    atoi(char s[])
      {
      int i,n;
      n=0;
      for (i=0; s[i]>='0' && s[i]<='9'; i++)
        n = 10*n + s[i]-'0';
      return (n);
      }

Beispiel: Wie atoi, aber für Hexadezimalzahlen.

    htoi(char s[])
      {
      int i,n;
      n=0;
      for (i=0; ; i++)
      if (s[i] >= '0' && s[i] <= '9')
        n = 16*n + s[i]-'0';
      else if (s[i] >= 'A' && s[i] <= 'F')
             n = 16*n + s[i]-'A'+10;
      else if (s[i] >= 'a' && s[i] <= 'f')
             n = 16*n + s[i]-'a'+10;
      else
        break;
      return (n);
      }

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © FH München, FB 04, Prof. Jürgen Plate