4 Grundlagen


4.1 Elemente des Quellencodes
4.2 Datentypen
4.3 Deklarationen
4.4 Konstanten
4.4.1 Ganzzahlkonstanten
4.4.2 Gleitkommakonstanten
4.4.3 Zeichenkonstanten
4.4.4 Zeichenkettenkonstanten
4.5 Operatoren
4.5.1 Arithmetikoperatoren
4.5.2 Vergleiche und logische Verknüpfungen
4.5.3 Inkrement- und Dekrementoperatoren
4.5.4 Bitmanipulationen
4.5.5 Bedingte Bewertung
4.6 Ausdrücke und Zuweisungen
4.6.1 Operatorrangfolge
4.6.2 Zuweisungen
4.6.3 Datentypwandlungen
4.6.4 Zusammengesetzte Operatoren


4.1 Elemente des Quellencodes

C Programme bestehen aus "Worten": Schlüsselwörtern, (Objekt-)Namen, Konstanten, Strings, Operatoren und anderen Trenn- und Sonderzeichen. Trennzeichen sind BLANK, TAB und Zeilenende. Kommentare wirken auch als Trennzeichen. Der Quellentext ist (fast) formatfrei.

Der Zeichenvorrat beinhaltet folgende Zeichen:


  die Klein-und Großbuchstaben: a-z, A-Z 

  die Ziffern: 0-9 

  die Sonderzeichen: + - * / \ = , . ; ? " # % & _ '

  < > ( ) [ ] { } | ^ ~ @ : 

- die Umlaute (ä, ö, ü, Ä, Ö und Ü) sowie das "ß" gehören nicht zu den erlaubten Buchstaben

- Klein- und Großschreibung wird strikt unterschieden

- Schlüsselwörter müssen kleingeschrieben werden

- weiterhin ist folgende Konvention üblich:


  Variablennamen:         Kleinbuchstaben
  Symbolische Konstanten: Großbuchstaben

- Anweisungen werden mit ; (Semikolon) abgeschlossen (Achtung: Das Vergessen des ; ist einer der häufigsten Anfängerfehler!!!)

- Schlüsselwörter sind:


  auto      break     case      char      continue  const
  default   do        double    else      enum      extern
  float     for       goto      if        int       long
  register  return    short     signed    sizeof    static
  struct    switch    typedef   union     unsigned  void
  while     volatile

- oft werden auch noch:


  asm       fortran   near      far       huge      pascal

- verwendet

- Bezeichner sind Namen von Objekten (z.B. Variablen- oder Funktionsnamen). Sie dürfen aus Buchstaben, Ziffern und dem Zeichen Underline "_" bestehen, sie müssen mit einem Buchstaben oder Underline anfangen (Hinweis: Namen, die mit Underline anfangen, werden oft auf Systemebene verwendet, daher eigene Namen besser ohne führendes Underline.)

- Variablennamen dürfen je nach C-Compiler unterschiedlich lang sein, man sollte sich aber bei Namen, die als Querbezug zu Programmen in anderen Quellendateien dienen, auf maximal 6 Zeichen beschränken. (Der Linker ist hier die beschränkende Komponente.) Namen, die nur programmintern auftreten haben meist 31 signifikante Stellen.

- zum Zwecke der internen Dokumentation sollten Variablennamen ihren Zweck und ihre Bedeutung erkennen lassen

- Beispiele für gültige Namen (links) und ungültige bzw. nicht zu empfehlende Namen (rechts):


  gültig          ungültig        Kommentar
                                                        
  
  prog1           1_label         /* falsch */
  was_nun         heutegehenwir   /* ungünstig bei 
				     externen Bezügen */
  punkte          wer.ist         /* falsch */
  vorsicht        _vorsicht       /* ungünstig */
		  if              /* falsch */

- Kommentar kann überall dort stehen, wo auch sonst ein Trennzeichen stehen darf, er muß mit der Zeichenkombination /* eingeleitet und mit */ abgeschlossen werden (Natürlich darf Kommentar über mehrere Zeilen gehen.)

- sinnvollerweise schreibt man Kommentar an das Ende einer Zeile nach einer Anweisung oder auf eine eigene Zeile

- ein # in der 1.Spalte dient als Kennung für eine nachfolgende Preprozessoranweisung, diese werden vor der eigentlichen Kompilation ausgewertet, besonders wichtig sind hier:

a) das Einfügen von (Header)Dateien mittels:


  #include "stdio.h"  /* Suche beginnt im Homeverzeichnis */
  #include <stdio.h>  /* Suche beginnt im Systemverzeichnis */

b) und die Definition von symbolischen Konstanten, die dann vor der eigentlichen Kompilation mittels Textersatz eingesetzt werden:


  #define MAXIMUM 100

(das Wort "MAXIMUM" wird durch den Text "100" ersetzt

- Preprozessoranweisungen werden nicht mit Semikolon abgeschlossen, da dieses sonst beim Textersatz mit eingefügt wird


4.2 Datentypen

In C gibt es 4 Grunddatentypen; diese sind:


  char    ein Zeichen    (meist 1 Byte, z.B. ASCII-Code)
  int     ganze Zahl     (2 oder 4 Byte je nach Rechner)
  float   Gleitpunktzahl (meist 4 Byte je nach Rechner)
  double  Gleitpunktzahl (meist 8 Byte je nach Rechner)

Der Wertebereich für diese Grunddatentypen ist rechnerabhängig.

Diese Grunddatentypen können durch Voranstellen von "Eigenschaften" noch qualifiziert werden. Wird nur eine Eigenschaft angegeben, so wird als Typ int angenommen. Die möglichen Eigenschaften sind:


  unsigned                     für char
  unsigned, long und short     für int 
  long                         für float

Beispiele:

  
  unsigned int
  long int
  short int
  unsigned char
  unsigned short int
  long
  unsigned
  long float


4.3 Deklarationen

Alle Variablen müssen vor ihrer Verwendung deklariert werden. Die Deklaration legt den Namen, den Typ und die Speicherklasse der Variablen fest. Deklarationen müssen am Anfang eines Blockes, am Anfang einer Funktion (Parameter) oder außerhalb von Funktionen (globale Variablen) stehen.

Variablentypen haben wir gerade in Punkt 4.2 kennengelernt. Speicherklassen legen fest, wie die Variable behandelt werden soll, insbesondere in welchem Speicherbereich sie angelegt werden soll. Es gibt 4 Speicherklassen:


  auto
  static
  extern
  register

auto ist sozusagen die Normalbehandlung und folglich auch der Weglasswert für Variablen, die in Blöcken deklariert werden. Der Weglasswert außerhalb von Funktionen ist extern zur Erzeugung globaler Variablen. static Variablen in Funktionen behalten ihren Wert zwischen 2 Funktionsaufrufen bei, auto Variablen werden jeweils neu angelegt.

Eine Besonderheit in C ist die Speicherklasse register. Damit kann man erreichen, daß Variablen (sofern es die Hardware des Rechners erlaubt) in CPU-Registern gehalten werden, was zu einer schnelleren Programmausführung führt.

Beispiele für Deklarationen:


  auto int i;
  static int i;
  auto punkte;
  auto char c;
  char c;
  register j;
  register int i, j;

Gleichzeitig mit der Deklaration kann man die Variablen auch initialisieren:


  auto int i = 1;
  static int i = 0;
  register int i = 1, j = 100;

Hinweis: Eine eingehendere Besprechung der 4 Speicherklassen wird im Zusammenhang mit Funktionen im Kapitel 6.4 vorgenommen.


4.4 Konstanten

4.4.1 Ganzzahlkonstanten

Ganzzahlkonstanten bestehen aus einer Ziffernkette. Sie werden mormalerweise dezimal interpretiert. Eine führende 0 führt zu einer oktalen Interpretation und die führende Zeichenkombination 0x oder 0X gilt als Kennung für Hexadezimalzahlen. Dezimalzahlen werden als normale int-Typen behandelt, Oktal- und Hexadezimalzahlen als unsigned.

Die folgenden Zahlenangaben sind zeilenweise jeweils gleich:


  1       01      0x1
  10      012     0xa
  4368    010420  0x1110

Konstanten vom Typ long werden implizit gebildet, wenn die Zahl für int zu groß ist. Durch Anhängen von l oder L an die Konstante kann man eine long-Konstante erzwingen.


  35000   0104270   0x8868
  35000l  0104270l  0x8868l
  10l     012l      0xal

4.4.2 Gleitkommakonstanten

Sie haben immer den Typ double. Der Dezimalpunkt und/oder die Exponentenangabe e oder E muß vorhanden sein, so wie man das gewohnt ist:


  .1234
  0.1234
  1.345e-10
  3E+3

4.4.3 Zeichenkonstanten

Sie haben den Typ char und werden durch Angabe eines Zeichens eingeschlossen in (einfache) Anführungszeichen erzeugt. Ihr Wert ist der numerische Wert des Zeichens im gültigen Zeichensatz (z.B. ASCII).


  'a'     /* numerischer Wert 97  (0x61) */
  'F'     /* numerischer Wert 70  (0x46) */
  '1'     /* numerischer Wert 49  (0x31) */

Nicht druckbare Zeichen können mit Hilfe des Fluchtsymbols \ (Backslash) nach folgender Tabelle angebeben werden:


  Backspace              BS    \b
  Tabulator Zeichen      HT    \t
  Zeilentrenner          LF    \n
  Formfeed               FF    \f
  Carriage Return        CR    \r
  Fluchtsymbol           \     \\
  Anführungszeichen      '     \'
  Null Zeichen           NUL   \0
  Bitmuster              ddd   \ddd

ddd steht dabei für maximal 3 Oktalziffern, damit ist jedes beliebige Zeichen des Zeichensatzes darstellbar. Auch die Fluchtsymbolfolgen müssen natürlich in Anführungszeichen eingeschlossen werden.

4.4.4 Zeichenkettenkonstanten

Zeichenkettenkonstanten bestehen aus einer linearen Folge von Einzelzeichen mit einem wichtigen Unterschied: Am Ende einer Zeichenkette wird immer ein NUL-Zeichen ('\0') als Endekennung automatisch angehängt. Zeichenkettenkonstanten werden in doppelte Anführungszeichen eingeschlossen. Es gelten die gleichen Fluchtsymbolfolgen wie bei Zeichenkonstanten; zusätzlich gibt es noch die Fluchtsymbolfolge \". Zeichenketten können über Zeilengrenzen gehen, wenn unmittelbar vor dem Zeilentrenner (im Quellencode) das Fluchtsymbol \ steht.


  'A'                  /* das Zeichen A */
  "A"                  /* die Zeichenkette A\0 */
  "Dies ist eine Zeichenkette"
  "\"Hahaha\" lachte er"
  "Diese Zeichenkette piepst \07"


4.5 Operatoren

4.5.1 Arithmetikoperatoren

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


  Vorzeichen             -
  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 i = 3, j = 5, k = 10;
  int m;
  float r = 3., s = 5.;
  float u;
  
  m = i + j + k;          /* 18 */
  m = k - j - i;          /*  2 */
  m = i - k;              /* -7 */
  m = k / i;              /*  3 */
  m = k / i + j;          /*  8 */
  m = k / (i + j);        /*  1 */
  m = k * i + j;          /* 35 */
  m = k * (i + j);        /* 80 */
  m = k % i;              /*  1 */
  u = s + r;              /* 8.       */
  u = s / r;              /* 1.666... */
  u = s * r;              /*15.       */
  u = k / r;              /* 3.333... */
  u = k / r + s / r;      /* 5.       */
  u = k / i + s / i;      /* 4.666... */
  u = k % r;              /* nicht erlaubt */

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 Include-Datei math.h mit eingebunden werden.

Beispielprogramm p4-1.c


  /*
   *      p4-1.c
   *      Beispielprogramm 1, Abschnitt 4
   */

  #define LAUFZEIT 10
  #define ANF_KAP 1000.
  #define ZI_SATZ 2.5
  
  void main(void)
  {
    int i;
    float anf_kap, end_kap;
  
    printf("\nGuthabenzinsenberechnung\n");
    printf("Jahr Anfangskapital Endkapital\n");
    anf_kap = ANF_KAP;
    for (i = 1; i <= LAUFZEIT; i++) {
      end_kap = anf_kap + ZI_SATZ / 100 * anf_kap;
      printf("%2d    %8.2f      %8.2f\n", i, anf_kap, end_kap);
      anf_kap = end_kap;
    }

  } /* main() */

4.5.2 Vergleiche und logische Verknüpfungen

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


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

Die ersten 4 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 Wert entschieden. Dabei gilt:


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

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 */

Unter die logischen Verknüpfungen 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)
  if ((d = (a + b)) != c &&  a < b)

Diese zwei Zeilen wirken nicht gleich!

4.5.3 Inkrement- und Dekrementoperatoren

C kennt spezielle Operatoren zum In- 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 */
  printf ("\nk hat den Wert %d\n",k);    /* Ausgabe: 5 */

  
  int i, k;
  int j;
  
  i = 2;
  k = 3;
  j = i+++k;            /* i+ (++k); i=2,k=4,j=6  */
  j = (i++)+k;          /*           i=3,k=4,j=6  */
  j = i++ +k;           /*           i=4,k=4,j=7  */
  j = (i+k)++;          /* ist verboten           */

Beispielprogramm p4-2.c

 
  /*
   *      p4-2.c
   *      Beispielprogramm 2, Abschnitt 4
   *      Anhaengen von String t an String s
   *
   */
 
  char *strcat(char s[], char t[])
  {
    char *sp;
    int i, j;

    sp = s;  
    i = j = 0;
    while (s[i] != '\0')
      i++;
    while ((s[i++] = t[j++]) != '\0')
      ;
    return (sp);    

  } /* strcat() */

Beispielprogramm p4-3.c


  /*
   *      p4-3.c
   *      Beispielprogramm 3, Abschnitt 2
   *      Entfernt aus String s1 alle Zeichen,
   *      die im String s2 vorkommen
   */

  int squeeze(char s1[], char s2[])
  {
    int i, j, k;
   
    for (i = j = 0; s1[i] != '\0'; i++) {
      s1[j++] = s1[i];
      for (k = 0; s2[k] != '\0'; k++) {
	if (s1[i] == s2[k]) {
	  j--;
	  break
	}
      }
    }
    s1[j] = '\0';
    return (j);           /* Anzahl Zeichen im String */
 
  } /* squeeze() */

Beispielprogramm p4-4.c


  /*
   *      p4-4.c
   *      Beispielprogramm 4, Abschnitt 4 
   *      Gibt die Position des ersten Zeichens 
   *      in String s1 an, das in String s2 enthalten ist
   */
 
  int any(char s1[], char s2[])
  {
    int i, j, n;
  
    for (i = 0, n = -1; s1[i] != '\0' && n == -1; i++) {
      for (j = 0; s2[j] != '\0'; j++) {
	if (s1[i] == s2[j]) {
	  n = i;
	  break;
	}
      }
    }
    return (n);

  } /* any() */

4.5.4 Bitmanipulationen

C stellt auch Operatoren für Bitmanipulationen zur Verfügung. Sie können nicht auf Variablen oder Konstanten vom Typ float oder double angewendet werden. Die Operatoren sind wie folgt:


  Komplement                  ~
  Linksshift                  <<
  Rechtsshift                 >>
  bitweises UND               &
  bitweises EXCLUSIVES 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 */

Beispielprogramm p4-5.c

  /* 
   *      p4-5.c
   *      Beispielprogramm 5, Abschnitt 4
   *      n Bits ab Position p rechtsbuendig
   *      ... 7 6 5 4 3 2 1 0
   */
 
  int getbits(unsigned x, int p, int n)
  {
    return ((x >> (p + 1 - n)) & ~(~0 << n));

  } /* getbits() */

Beispielprogramm p4-6.c


  /*
   *      p4-6.c
   *      Beispielprogramm 6, Abschnitt 4
   *      Bestimmt die Bitlaenge von int 
   */
 
  int wordlength()
  {
    int i, j;     /* oder: unsigned i; int j; */
  
    i = ~0;
    j = 0;
    while (i) {
      j++;
      i = i << 1;
    }
    return (j);

  } /* wordlength() */

Beispielprogramm p4-7.c


  /* 
   *      p4-7.c
   *      Beispielprogramm 7, Abschnitt 4
   *      Zaehlt die Anzahl der 1-Bits in n
   */

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

  } /* bitcount() */

und hier eine schnellere Variante:

Beispielprogramm p4-8.c


  /* 
   *      p4-8.c
   *      Beispielprogramm 8, Abschnitt 4
   *      Zaehlt die Anzahl der 1-Bits in n,
   *      schnellere Variante
   */

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

  } /* bitcount() */

4.5.5 Bedingte Bewertung

C kennt einen Operator, der eigentlich aus einem Operatorenpaar (? :), besteht. Es handelt sich dabei um die bedingte Bewertung. Sie hat die Form:


  Ausdruck1 ? Ausdruck2 : Ausdruck3

Ist Ausdruck1 WAHR, dann wird Ausdruck2 ausgeführt, ansonsten Ausdruck3. Die bedingte Bewertung entspricht daher folgender if-else Konstruktion:


  if (Ausdruck1)
    Ausdruck2;
  else   
    Ausdruck3;

Beispiele:


  /* Maximum von a und b */
  z = (a > b) ? a : b;
  
  /* Ausgabe eines Feldes, wobei immer nach 10 Zahlen
     eine neue Zeile angefangen werden soll */
  for (i = 0; i < N; i++)
    printf ("%6d%c", a[i], (i % 10 == 9 || i == N - 1) ? '\n' : ' ');


4.6 Ausdrücke und Zuweisungen

Ein Ausdruck besteht aus Operanden und Operatoren oder einem Funktionsaufruf. Ein Ausdruck gefolgt von einem Semikolon ist eine Anweisung. Zur Konstruktion gültiger Ausdrücke bzw. Anweisungen sind noch einige Dinge zu klären, die bisher noch nicht besprochen wurden.

4.6.1 Operatorrangfolge

Bis zu diesem Zeitpunkt haben wir die meisten Operatoren besprochen. Zu ihrer richtigen Anwendung gehört nicht nur das Wissen um ihre Funktion, sondern auch das um ihre Rangfolge und ihre Assoziativität. Diese Angaben sind in der folgenden Tabelle für alle Operatoren zusammengestellt, auch für die bisher noch nicht besprochenen. Die Operatoren in der Tabelle sind ihrer Rangfolge nach von oben (höchster Rang) nach unten (niedrigster Rang) angeordnet. Alle Operatoren, die auf einer Zeile stehen haben gleiche Rangfolge und können bei einem gemeinsamen Auftreten in einem Ausdruck vom Compiler in beliebiger Reihenfolge bewertet werden.


  Operator                            Zusammenfassung
                                      (Assoziativität)
  
  ()  []  ->  .                       von links her
  !  ~  ++ --  - (typ)  *  &  sizeof   von rechts her
  *  /  %                             von links her
  +  -                                von links her
  <<  >>                              von links her
  <  <=  >  >=                        von links her
  ==  !=                              von links her
  &                                   von links her
  ^                                   von links her
  |                                   von links her
  &&                                  von links her
  ||                                  von links her
  ?:                                  von rechts her
  =  +=  -=  += (etc.)                von rechts her
  ,                                   von links her

Auf folgende Dinge wird hier noch einmal besonders hingewiesen:

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


  ()   1. Klammerung    2. type cast
  *    1. Pointerdekl.  2. Multiplikation
  -    1. Vorzeichen    2. Subtraktion
  &    1. Adresse von   2. bitweises UND

- In- 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. Man nennt dies einen Nebeneffekt. Diese müssen aus naheliegenden Gründen vermieden werden.

- 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++;

4.6.2 Zuweisungen

- die linke Seite einer Zuweisung muß ein einfacher Ausdruck und ein sogenannter L-Wert sein

- einfache Ausdrücke bezeichnen ein einzelnes Objekt, dieses Objekt kann eine Variable, eine Konstante, ein Funktionsaufruf, oder ein Feld-, Struktur-, Aufzählungs- oder Variantenelement sein.

- L-Werte sind Objekte, die veränderbar sind z.B. Variablen

- keine L-Werte sind: Konstanten, Feldnamen ohne folgende Indexangabe, Funktionsnamen, Namen aus einer Aufzählung, Objekte, die explizit mit const als konstant vereinbart wurden

- Zuweisungen können in Ausdrücken an der Stelle eines Operanden stehen (z.B.: while ((c = getchar()) != EOF)

- Zuweisungen in einer Zuweisungskette werden von rechts nach links ausgeführt:


  a = b = c = 1;     /* alle 1 */

- mit Hilfe des Kommaoperators können Listen von Anweisungen gebildet werden, sie werden von links nach rechts abgearbeitet


  a = b, b = c, c = 1;    /* nur c = 1, andere unbestimmt */
  c = 1, b = c, a = b;    /* alle 1 */

- Ausdruckslisten werden oft im Zusammenhang mit einer for-Schleife verwendet, um z.B. in der Reinitialisierung 2 verschiedene Operationen durchzuführen:


  for (i = 0, j = 10;i < N; i++, j++)

- Vorsicht ist allerdings dann geboten, wenn das Komma wie z.B. in einer Funktionsparameterliste noch eine andere Funktion hat, durch eine Klammerung kann man aber jederzeit das Gewünschte erreichen:


  printf("Was wird hier %d ausgeben ?", (i = 1, i = i + 10));

4.6.3 Datentypwandlungen

- Datentypwandlungen sind immer dann notwenig, wenn 2 Operanden in einem Ausdruck verschiedene Typen haben

- Datentypwandlungen werden soweit notwendig implizit durchgeführt

- 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

- 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

- durch eine sogenannte type cast Operation kann man in einem Ausdruck den Typ eines Operanden auf den geünschten Typ abändern, dies Operation ändert den Typ nur für diese eine Operation:


  int n;
  float r;
  r = sqrt((double)n);    /* sqrt benötigt double Parameter */

- einige Beispiele für implizite Typwandlungen haben wir schon in Kapitel 4.5.1 bei den Arithmetikoperatoren kennengelernt, hier sind noch einige mehr, insbesondere zur char-int Wandlung

Beispielprogramm p4-9.c


  /* 
   *      p4-9.c
   *      Beispielprogramm 9, Abschnitt 4
   *      wandelt die naechsten x Ziffern aus dem
   *      String s in eine Integerzahl 
   */
 
  int 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);

  } /* atoi() */

Beispielprogramm p4-10.c


  /* 
   *      p4-10.c
   *      Beispielprogramm 10, Abschnitt 4
   *      wandelt die naechsten x Hexziffern aus dem
   *      String s in eine Integerzahl 
   */
 
  int 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);

  } /* htoi() */

4.6.4 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)

- 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;                      /* abgekuerzt */


zurück zum Inhaltsverzeichnis

 

11. November 1999, Peter Klingebiel, DVZ