The Real Adok's Way to C
Folge 3

Begleit-Dateien:

Seid  gegrüßt, meine Freunde! Mit neuem Eifer  stürzen wir uns in eine weitere
Schlacht,   in  einen  neuen  Teil   unseres  C-Kurses!  Diesmal  geht's,  wie
angekündigt,  wieder  um  Variablen.  Dieses  Thema  ist  einfach  riesig! Wir
beschäftigen  uns  mit der Eingabe und  Ausgabe von Zahlenvariablen und lernen
dabei  einige  neue Varianten von printf  kennen. Und als Draufgabe werden wir
den  if-Befehl  & Co. kennenlernen, mit  dem wir Verzweigungen realisieren und
etwas  nützlichere  Programme erstellen können!  Also, worauf wartet ihr noch?
Laßt uns beginnen!

+++ Los geht's! +++

Jau,  wie  man Zeichen und Strings ausgibt, wißt ihr ja schon. Na, wie? Genau,
mit putchar und puts! Jetzt geht's darum, auch Zahlen ausgeben zu können. Dazu
bedienen wir uns einer altbekannten Funktion  aus  stdio.h,  printf.  Wie  wir
wissen, kann der Parameter Text nicht nur aus druckbaren Zeichen, sondern auch
aus  Steuerzeichen  bestehen!  Diese  werden  durch  einen  Backslash (also \)
gekennzeichnet.

Jedoch   sind  nicht  nur  Steuerzeichen  nicht  druckbar,  sondern  auch  die
sogenannten   PLATZHALTER.  Wie  immer  gilt's  auch  hier:  Nomen  est  Omen!
Platzhalter  halten  den Platz für eine  Variable  frei. An dieser Stelle wird
dann  eine  Variable  auf  dem  Bildschirm  ausgegeben.  Ein jeder Platzhalter
beginnt  mit  einem  Prozentzeichen. Danach  folgt  eine  Formatangabe. Einige
Beispiele für Formatangaben:

       Datentyp(en):
 %d    short int und char
 %ld   long int
 %u    unsigned short und unsigned char
 %lu   unsigned long
 %f    float
 %lf   double

%f  und  %lf  haben übrigens eine Besonderheit:  Man  kann auch die Anzahl der
Nachkommastellen,  die ausgegeben werden sollen,  bestimmen! Dazu muß man nach
dem  Prozentzeichen  einen  Punkt und  danach  die Anzahl der Nachkommastellen
hinschreiben.  Beispielsweise muß man %.2f schreiben,  um einen float mit zwei
Nachkommastellen  anzuzeigen.  Außerdem gibt es  noch einige Formatangaben für
Zeichen und Strings. Mit ihnen lassen sich Zeichen und Strings genauso wie mit
putchar  und  puts ausgeben, nur wird  im Gegensatz zu puts kein automatischer
Zeilenvorschub ausgegeben.

 %c    gibt eine char-Variable als Zeichen aus
 %s    gibt einen char-Array als String aus
 %%    gibt das Prozentzeichen aus

So,  aber  mit diesen Formatangaben allein  läßt sich absolut nichts anfangen!
Jau,  ihr  habt  recht:  Wir müssen  natürlich  noch  angeben, welche Variable
ausgegeben  werden  soll!  Und  das  geschieht  z.B.  so:

 printf("Eine Zahl: %d\n",intvar);

oder, mit mehreren Variablen:

 printf("%d %s %lf",intvar,chararray,doublevar);

+++ Beispielprogramm: printf +++

// C-Kurs 3: Die erweiterten Funktionen von printf

#include <stdio.h>

void main(void)
{
  int summand[2],
      summe;

  summand[0]=-20;
  summand[1]=40;
  summe=summand[0]+summand[1];

  printf("Erster Summand: %d\n"
         "Zweiter Summand: %d\n"
         "Summe: %d\n",
          summand[0],summand[1],summe);

  /* Ein String läßt sich in C auch über mehrere Zeilen verteilen, wenn man
     wie oben vorgeht. */
}

+++ Die vier Grundrechnungsarten +++

Back to kindergarten! Also, liebe Kinder, da gibt es das Plus, damit könnt ihr
zwei  Äpfel  und  vier Äpfel zu  sechs  Äpfeln  machen... Scherz beiseite, ich
hoffe, jeder weiß, wie die vier Grundrechnungsarten funktionieren. In C werden
sie durch folgende Symbole dargestellt:

 +    Plus
 -    Minus
 *    Ma(h)l(zeit)
 /    Dividiert

Besonderheiten gibt's nur bei der Division. Denn was glaubt ihr, kommt bei der
folgenden Zuweisung raus:

 float floatvar;
 floatvar=5/2;

"Zwei komma fünf!!!"

Falsch,  ihr  Schlaumeier,  zu früh gefreut!  Es  kommt 2 heraus! Wenn nämlich
sowohl  der  Dividend  als  auch der  Divisor  ganze  Zahlen  sind, also keine
Nachkommastellen  enthalten, wird immer eine Integerdivision durchgeführt. Die
Nachkommastellen  werden  also  abgeschnitten. Um  das  zu verhindern, muß man
einfach  eine der beiden Zahlen  als Fließkommazahlen schreiben. Also schreibt
man z.B.:

 floaty=5/2.0;

Oder man schreibt das:

 floaty=5/(float)2;

WAS SOLL DENN DAS SEIN? DA STEHT JA EIN DATENTYP IN DER KLAMMER!

Richtig, das gibt es in C auch! Und zwar nennt sich diese Eigenschaft

+++ Typecasting +++

Mit  Typecasting  wird  schlicht und einfach  eine  Zahl oder eine Variable in
einen  anderen  Datentyp umgewandelt. Aber  nur temporär (für die Berechnung)!
Mit  Typecasting  läßt  sich  nicht  eine  Integervariable  dauerhaft  in eine
Fließkommazahl  verwandeln!  Der Datentyp, in  den verwandelt werden soll, muß
dabei in zwei runde Klammern eingeschlossen werden.

+++ Division durch Null +++

Und dann gibt es noch einige Besonderheiten. Zum einen darf man nie durch Null
dividieren,  wenn man nicht absichtlich den  Interrupt 0 auslösen will (der im
Normalfall  das  Programm  abbricht,  wenn er  nicht  auf  eine eigene Routine
verbogen ist).

+++ Überlauf und Unterlauf +++

Weiters  muß  man  das Phänomen des  Überlaufs  beachten. Addiert man zu einem
unsigned  char,  der  gleich 255 (der Maximalwert)  ist,  1,  so wird er auf 0
zurückgesetzt.  Wenn  ihr euch das Bitmuster  der Zahl anseht, wird euch alles
klar: 255d = 11111111b. Addieren wir dazu 1, so müßte 100000000b entstehen. Da
ein  Byte  jedoch  nur 8 Bit hat,  wird  Bit 8 abgeschnitten, und übrig bleibt
Null.  Daher muß man eine int-Variable als Schleifenzähler verwenden, wenn man
eine Schleife (s.u.) von 0 bis 255 machen will. Bei einer char-Variablen würde
die Schleife endlos laufen! Genauso gibt's natürlich auch Unterläufe (Zahl ist
0, und wir ziehen immer noch was ab).

Solche Überlaufphänoneme treten natürlich auch bei den anderen Datentypen auf,
aber dort erst ab 65535 (unsigned short), 2^32-1 (unsigned long) usw. Aber nun
zu etwas anderem!

+++ scanf +++

Der  Name sagt's: scanf aus stdio.h ist der Eingabebefehl für Zahlenvariablen.
ABER: scanf ist sehr fehleranfällig! Deshalb würde ich an eurer Stelle nur mit
großer Sorgfalt mit ihm umgehen.

Die Syntax lautet:

 scanf(formatstring,adresse_der_variablen);

Im   Grunde  genommen  geht's  also  wie   printf!  Auch  die  Platzhalter  im
Formatstring  sind  die gleichen (bis auf  die Anzahl der Nachkommastellen bei
floats  und doubles, die gibt's hier  nicht). Nur beim Variablennamen müßt ihr
aufpassen:  Vor dem Namen muß IMMER ein &-Zeichen stehen. Der &-Operator dient
dazu,  die  Speicheradresse  der  Variablen  herauszufinden.  In  Wirklichkeit
arbeitet scanf nämlich mit Zeigern (siehe Teil 4).

+++ Beispielprogramm: scanf +++

// C-Kurs 3: scanf

#include <stdio.h>

void main(void)
{
  float zahl[2];

  printf("Zahl 1 eingeben: ");
  scanf("%f",&zahl[0]);
  printf("Zahl 2 eingeben: ");
  scanf("%f",&zahl[1]);

  printf("\n%.2f + %.2f = %.2f\n",zahl[0],zahl[1],zahl[0]+zahl[1]);
  printf("%.2f - %.2f = %.2f\n",zahl[0],zahl[1],zahl[0]-zahl[1]);
  printf("%.2f * %.2f = %.2f\n",zahl[0],zahl[1],zahl[0]*zahl[1]);
  printf("%.2f / %.2f = %.2f\n",zahl[0],zahl[1],zahl[0]/zahl[1]);
}

+++ Weitere Operatoren +++

Richtig,  das waren noch nicht alle Operatoren,  die es in C gibt! Zunächst zu
den  kombinierten  Operatoren. Sie vereinfachen Zuweisungen wie:

 a=a+3;

Man kann stattdessen nämlich die Operatoren + und = kombinieren und schreiben:

 a+=3;

Analoges gilt für -, * und /.

Daneben gibt es noch Inkrement und Dekrement:

 a++;

steht für

 a=a+1;

und

 a--;

steht für

 a=a-1;

Es  ist  auch möglich, ++a; bzw. --a;  zu schreiben. Steht der Inkrement- bzw.
Dekrementoperator  isoliert,  also  als  eigene  Anweisung,  macht  das keinen
Unterschied. Sehr wohl aber, wenn man den Inkrement- oder Dekrementoperator in
komplexeren Anweisungen einbaut. So wird

 a=b++;

mit

 a=b;
 b=b+1;

übersetzt. Die Anweisung

 a=++b;

dagegen steht für

 b=b+1;
 a=b;

Auf  den  Wert  von  b haben beide  Anweisungen  also  denselben Effekt, für a
liefern sie dagegen verschiedene Ergebnisse. Ein Beispielprogramm demonstriert
dies.

+++ Beispielprogramm: Zwei verschiedene Arten des Inkrement-Operators +++

// C-Kurs 3: Der Unterschied zwischen a++ und ++a

#include <stdio.h>

void main(void)
{
  char a=5,
       b=0;

  // Erste Möglichkeit
  b=a--;
  printf("b=a-- ergibt:\na=%d\nb=%d\n\n",a,b);

  // Variablen zurücksetzen
  a=5;
  b=0;

  // Zweite Möglichkeit
  b=--a;
  printf("b=--a ergibt:\na=%d\nb=%d\n",a,b);
}

+++ Binäre Operatoren +++

Eine weitere Art von Operatoren in C sind die binären Operatoren:

 ~      bitweises NOT                             NOT 0101b = 1010b
 &      bitweises AND                       0101b AND 0011b = 0001b
 ^      bitweises XOR                       0101b XOR 0011b = 0110b
 |      bitweises OR                        0101b  OR 0011b = 0111b

Eine  genaue  Erkläuterung dieser Operatoren findet  ihr in Artikeln zum Thema
Boolesche Algebra.

+++ Bitschiebeoperatoren +++

Die  Bitschiebeoperatoren  entsprechen  einer  Multiplikation  mit  oder einer
Division durch eine Zweierpotenz.

 a<<x   entspricht:   a mal (2 hoch x)
 a>>x   entspricht: a durch (2 hoch x)

+++ Verzweigungen +++

Oft  müssen in Programmen Entscheidungen nach dem Muster "Wenn diese Bedingung
erfüllt  ist, dann tu' dies, andernfalls tu' das" getroffen werden. Dazu dient
der if-else-Block:

 if(boolescher_ausdruck) {
   ...
 }
 else {
   ...
 }

Der else-Zweig kann weggelassen werden.

Die im booleschen Ausdruck zulässigen Vergleichsoperatoren sind:

 ==   Test auf Gleichheit
 !=   Test auf Ungleichheit
 <    Test, ob der linke Wert kleiner ist als der rechte
 >    Test, ob der linke Wert größer ist als der rechte
 <=   Test, ob der linke Wert nicht größer ist als der rechte
 >=   Test, ob der linke Wert nicht kleiner ist als der rechte

Außerdem kann man folgende logische Verknüpfungen verwenden:

 !    Boolesches NOT: negiert den Wahrheitswert des Ausdrucks
 &&   Boolesches  AND:  liefert nur dann  TRUE,  wenn beide Teilausdrücke wahr
      sind, ansonsten FALSE
 ||   Boolesches  OR: liefert nur dann  FALSE, wenn beide Teilausdrücke falsch
      sind, ansonsten TRUE

FALSE ist gleich 0; jeder andere Wert ist TRUE. Die Vergleichsoperatoren geben
für TRUE in der Regel -1 zurück (da dies aber nicht definiert ist, kann es bei
einzelnen  Compilern  abweichen,  weshalb  man  sich  nicht  darauf  verlassen
sollte). Aus diesem Grund muß der Ausdruck nicht unbedingt aus einem Vergleich
bestehen.  Schreiben  wir z.B. if(var) und die  Variable  var ist gleich 0, so
ergibt  der Test automatisch FALSE. Die  Anweisungen innerhalb if werden nicht
ausgeführt.  Falls vorhanden, wird zum  else-Zweig gesprungen. Ist var dagegen
ungleich 0, so ist die Bedingung erfüllt.

+++ Beispielprogramm: if +++

// C-Kurs 3: Verzweigungen mit if

#include <stdio.h>

void main(void)
{
  long int zahl[2];

  printf("Gib die erste Zahl ein!  ");
  scanf("%ld",&zahl[0]);
  printf("Gib die zweite Zahl ein! ");
  scanf("%ld",&zahl[1]);

  printf("\nDie beiden Zahlen erfüllen folgende Bedingungen:\n");
  if(zahl[0]==zahl[1]) printf("Zahl 1 ist gleich Zahl 2.\n");
  if(zahl[0]<=zahl[1]) printf("Zahl 1 ist kleiner oder gleich Zahl 2.\n");
  if(zahl[0]>=zahl[1]) printf("Zahl 1 ist größer oder gleich Zahl 2.\n");
  if(zahl[0]<zahl[1])  printf("Zahl 1 ist kleiner als Zahl 2.\n");
  if(zahl[0]>zahl[1])  printf("Zahl 1 ist größer als Zahl 2.\n");
  if(zahl[0]!=zahl[1]) printf("Zahl 1 ist ungleich Zahl 2.\n");
}

+++ String-Vergleiche +++

Wenn  wir  testen  wollen,  ob  der  String  str1  dem String str2 entspricht,
schreiben wir:

if(!strcmp(str1,str2)) { ... }

Die   Verneinung   (!)  ist  deshalb   wichtig,   weil  strcmp  aus  internen,
algorithmischen Gründen den Wert 0 zurückliefert, wenn beide Strings identisch
sind, obwohl 0 FALSE bedeutet.

+++ Beispielprogramm: strcmp, strlen +++

// C-Kurs 3: strcmp und strlen

#include <stdio.h>
#include <string.h>

void main(void)
{
  char vor[21],
       nach[21],
       name[42];

  printf("Hallo, Typ!\nWie lautet dein Vorname? ");
  scanf("%s",vor);
  fflush(stdin);
  printf("Und dein Nachname? ");
  scanf("%s",nach);
  fflush(stdin);

  if(!strcmp(vor,nach))
  {
    printf("\nHe, du Schlingel! Dein Vorname ist ja genauso wie dein ");
    printf("Nachname!\nAber wie dem auch sei...\n");
  }

  strcpy(name,vor);
  strcat(name," ");
  strcat(name,nach);

  printf("\nDu heißt also %s!\n"
         "Wußtest du eigentlich, daß dein Name "
         "genau %d Zeichen lang ist?",name,strlen(name));
}

Die  Funktion fflush, aufgerufen mit dem Parameter stdin (Standard-Input, also
Tastatur), dient hierbei zum Leeren des Tastaturpuffers.

+++ switch +++

Ein unübersichtliches Konstrukt wie

if(ausdruck=1)
{
   ....
}
else
{
  if(ausdruck=2)
  {
     ....
  }
  else
  {
     ....
  }
}

läßt sich mit switch vereinfachen:

switch(ausdruck)
{
  case 1:    //Jetzt kommen die Befehle
             break;
  case 2:    //Wieder Befehle
             break;
  default:   //usw.
}

Wichtig  ist es, break nicht zu vergessen. Ansonsten werden alle nachfolgenden
Anweisungen  im  switch-Block abgearbeitet. Wenn  ausdruck  also gleich 1 sein
sollte,  werden in diesem Fall auch die Anweisungen für den Fall ausdruck == 2
und ausdruck == ein anderer Wert abgearbeitet!

Ein   Beispiel,   zuerst   richtig,   danach   schlampig.   Links   steht  die
switch-Anweisung, rechts immer das Äquivalent als if-Block.

Richtig:

switch(var>1)                          if(!(var>1))
{                                        printf("Wert <= 1\n");
  case 0:    printf("Wert <= 1\n");    else
             break;                      printf("Wert > 1\n");
  default:   printf("Wert > 1\n");
}

(Ihr  seht:  Da  TRUE und FALSE  Zahlenwerte  sind, kann auch ein switch-Block
Entscheidungen treffen, obgleich if hierfür wesentlich besser geeignet ist.)

Schlampig:

switch(vary>1)                         if(!(vary>1))
{                                      {
  case 0:    printf("Wert <= 1\n");      printf("Wert <= 1\n");
  default:   printf("Wert > 1\n");       printf("Wert > 1\n");
}                                      }
                                       else
                                         printf("Wert > 1\n");

Ihr  seht  also,  was da entstehen kann. Am besten, ihr probiert das Ganze mal
live aus, um euch mit dieser  Eigenheit  vertraut  zu  machen.  In  bestimmten
Fällen kann sie auch ganz nützlich sein, um Speicherplatz zu sparen.

+++ for +++

Die  for-Schleife existiert in vielen  Programmiersprachen, doch in keiner ist
sie  so  flexibel wie in C. Wir  möchten zuerst auf die "klassische" Benutzung
der  for-Schleife  eingehen; dann werden  wir uns die besonderen Möglichkeiten
ansehen, die C bietet.

Normalerweise  stellt  die for-Schleife eine  Zählschleife  dar. Eine Variable
wird  zuerst auf einen bestimmten Anfangswert gesetzt. Nun werden, solange sie
einen   bestimmten  Endwert  noch  nicht  überschritten  hat,  ein  bestimmter
Anweisungsblock  abgearbeitet und die Variable um einen bestimmten Wert erhöht
oder verringert.

Diese "klassische" for-Schleife sieht in C z.B. so aus:

for(zaehler=startwert;zaehler<=endwert;zaehler++)
{
  ...
}

Tatsächlich  verlangt  die  for-Schleife  von  C  drei  Parameter,  die  durch
Semikolons  getrennt werden. Der erste ist die Anweisung, mit der die Schleife
initialisiert  wird.  Normalerweise ist das  die Zuweisung des Anfangswerts an
die Schleifenzählvariable.

Der   zweite  Parameter  ist  eine  Bedingung;  jedesmal,  wenn  der  Computer
unmittelbar  vor  der  Ausführung  des  Inneren  der  for-Schleife steht, wird
geprüft,  ob  sie  erfüllt  (TRUE) ist.  Falls  ja,  wird  das Schleifeninnere
ausgeführt;  falls  nein,  wird die Ausführung  der  Schleife beendet, und die
nächste Anweisung außerhalb der for-Schleife wird ausgeführt.

Der  dritte  Parameter schließlich ist eine  Operation,  die jedesmal nach dem
Abarbeiten  des  Schleifeninneren  ausgeführt  wird -  in  der  Regel also das
Erhöhen oder Verringern des Schleifenzählers.

+++ Beispielprogramm: "klassisches" for +++

// C-Kurs 3: Fakultätsberechnung mit for

#include <stdio.h>

void main(void)
{
  unsigned char zahl,
                i;
  unsigned long ergebnis=1;

  printf("Bitte 'ne Zahl zwischen 2 und 30 eingeben: ");
  scanf("%ud",&zahl);

  switch( (zahl>=2) && (zahl<=30) )
  {
    case 0:    printf("\nZwischen 2 und 30 habe ich gesagt. Örks.\n");
               break;
    default:   for(i=2; i<=zahl; i++) ergebnis*=(unsigned long)i;
               printf("\nFakultät ist: %lu\n",ergebnis);
  }
}

+++ Das Besondere von for in C +++

Das  Besondere  der  for-Schleife  in C  ist,  daß  sie eine Verallgemeinerung
darstellt.  Oben  habe  ich  bereits  die  Funktionen  der einzelnen Parameter
erklärt: Initialisation, Bedingung, bei deren Nichterfüllung abgebrochen wird,
und  eine  am Ende jeder for-Schleife  ausgeführte  Operation. Muß es sich nun
etwa  bei  der  Bedingung um einen  Test,  ob  die Schleifenzählvariable einen
bestimmten  Wert noch nicht überschritten hat, handeln? Nein - wir können eine
beliebige  Bedingung verwenden, je nachdem,  was wir benötigen. Eine Schleife,
die im Quick-Sort-Algorithmus verwendet wird, könnte man bspw. so ausdrücken:

 for(; dataspace[counter_min]<central; counter_min++);

Im  Prinzip  handelt es sich dabei  aber  um syntaktische Spielereien. C kennt
noch  weitere,  ähnliche Schleifen, mit  denen sich solche Aufgaben treffender
ausdrücken lassen.

+++ while +++

Eine davon ist while. Syntax:

while(ausdruck)
{
  ...
}

Zuerst  wird überprüft, ob der angegebene Ausdruck  wahr ist; nur wenn er wahr
ist,  werden die Anweisungen im  Schleifeninneren ausgeführt, andernfalls wird
direkt  zur  nächsten Anweisung außerhalb  der while-Schleife gesprungen. Nach
einmaliger   Ausführung  der  Anweisungen   im  Schleifeninneren  wird  erneut
überprüft, ob der Ausdruck wahr ist, usw.

Man  hätte  obige  for-Schleife aus  dem  Quick-Sort-Algorithmus also auch als
while-Schleife formulieren können:

 while(dataspace[counter_min]<central) counter_min++;

Das ist vielleicht sogar besser lesbar.

Auch  die  klassische for-Schleife ließe  sich  mit Hilfe einer while-Schleife
realisieren:

 zaehler=startwert;
 while(zaehler<=endwert)
 {
   ...
   zaehler++;
 }

+++ do ... while +++

Eng  verwandt  mit  while ist do ...  while.  Hier erfolgt die Überprüfung des
Ausdrucks allerdings erst nach einmaliger Ausführung des Schleifenrumpfs.

Syntax:

do
{
  ...
} while(ausdruck)

+++ Ternärer Operator +++

Darf's  noch ein Operator mehr sein? Das if-Konstrukt läßt sich abkürzen, wenn
es  darum  geht,  je  nach Erfüllung  einer  Bedingung  einer  Variablen einen
bestimmten  Wert  zuzuweisen. In C gibt  es  einen eigenen Operator dafür, den
"ternären  Operator". Ein Beispiel soll  seine Funktionsweise erklären. Nehmen
wir folgendes, eher umgangssprachliche Konstrukt:

if(marius_sagt_richtiges)
  marius_ist_klug = -1;
else
  marius_ist_klug = 0;

Statt diesem schreibt man kürzer:

marius_ist_klug = marius_sagt_richtiges ? -1 : 0

+++ Vergleiche als Operatoren +++

Das letzte Beispiel ließe sich noch kürzer formulieren:

marius_ist_klug = ( marius_sagt_richtiges != 0 );

Auch ein Vergleich ist ein Operator. Mit obiger Anweisung wird zuerst geprüft,
ob  die Variable marius_sagt_richtiges ungleich FALSE ist. Das Ergebnis dieses
Vergleichs  wird  der  Variablen  marius_ist_klug  zugewiesen.  Wer  sich  die
Zahlenwerte  von  TRUE  und FALSE nicht merken  kann,  dem  kann mit Hilfe des
Präprozessors Abhilfe geschaffen werden.

+++ Der Präprozessor +++

Bisher  tauchte  der  PP  nur  im  Zusammenhang mit dem Einbinden von externen
Headerdateien auf. Der PP kann jedoch noch viel mehr!  Da stellt sich zunächst
die  Frage:  Was ist der PP überhaupt? Er ist eine Art "Textübersetzer". Bevor
ein Quellcode compiliert werden kann,  muß  es  zuerst  in  den  PP  geschickt
werden.  Dieser  sucht  nun nach "seinen" Befehlen wie #include, #define etc.,
welche natürlich immer mit einem amerikanischen Numerus beginnen.  Die Befehle
veranlassen  den  PP,  den  Quellcode  des  Programms  zu ändern. Stößt er auf
#include, so wird die danach angegebene Headerdatei geöffnet und  komplett  in
das  Proggy  eingebunden.  Ebenso  veranlassen  die  anderen  Befehle wie etwa
#define den PP, etwas im Programm zu ändern.

Da  sind  wir  beim springenden Punkt:  Was  macht  #define? #define dient zum
Erstellen von Konstanten und Makros. Die Anweisung

 #define TRUE -1

veranlaßt  den  PP, überall dort, wo im  Quellcode das Wörtchen TRUE steht, es
durch -1 zu ersetzen. Ebenso kann man mit der kurzen Anweisung

 #define HW printf("Hello World!\n");

den angegeben langen Funktionsaufruf abkürzen.

Es lassen sich auch Makros mit Parametern definieren. Ein Beispiel lautet:

 #define CUBE(x) x*x

Nun kann man etwa schreiben:

 y = CUBE(10);

und y wird 100.

Hier liegt jedoch der Hund begraben! Denn was passiert, wenn man

 y = CUBE(5+5);

schreibt? Der PP ersetzt dies durch

 y = 5+5*5+5;

was  35 ergäbe. Wir täten also besser  daran, das Makro CUBE(x) mit (x)*(x) zu
definieren:

 #define CUBE(x) (x)*(x)

und

 y = CUBE (5+5);

wird durch

 y = (5+5)*(5+5);

ersetzt - wir erhalten das richtige Ergebnis.

Eine weitere Fehlerquelle liegt darin, daß sich eine Makrodefinition jederzeit
überschreiben oder mit dem Befehl #undef rückgängig machen läßt. Ebenso könnte
man Variablen mit Makros/Konstanten verwechseln. Aus diesem Grund hat man sich
angewöhnt,  MAKROS  GROSS  zu schreiben,  während  variablen klein geschrieben
werden.

Nachdem nach dem PP-Lauf der Quellcode in eine für den Compiler  lesbare  Form
umgewandelt worden ist, wird der Compiler aufgerufen, und weiter geht's.

Weitere  nützliche  PP-Befehle sind #if, #endif,  #else  und #elif. Damit läßt
sich eine bedingte Übersetzung realisieren! Schreiben wir nämlich:

#if defined(WIN95)
  printf("Windows-95-Version des Programms wird geladen...\n");
#elif defined(LINUX)
  printf("Linux-Version loading...\n");
#else
  printf("Starte DOS-Version...\n");
#endif

...so wird Anweisung Nummer 1 nur dann compiliert, wenn das Makro WIN95 vorher
definiert  wurde.  Ist  dagegen  LINUX  definiert,  wird  die zweite Anweisung
compiliert.  Und  wenn weder WIN95 noch  LINUX  definiert sind, wird Anweisung
Nummer  3  compiliert. So ließe sich  der Quellcode von mehreren verschiedenen
Versionen  des  Programms für verschiedene  Betriebssysteme  in einem einzigen
Quellcode  unterbringen. Dieser muß nur  minimal verändert werden (nämlich die
Makrodefinitionen),  danach wird er neu compiliert,  schon hat man sein Proggy
für  sein  System! Soviel zu der  Portabilität  von C-Programmen, wenn man sie
richtig anwendet.

Statt  #if  defined(...) geht es auch  kürzer,  indem man #ifdef ... schreibt.
Analog  gibt  es  auf  #ifndef.  Wenn  man  jedoch  #if  verwendet,  sind auch
komplexere Varianten möglich, etwa:

 #if KONSTANTE == 1

Naja,  das  war  viel  auf einmal, stimmt's?  Aber  wenn  wir  in diesem Tempo
weitermachen,  sind wir in Teil 5  mit den Grundkenntnissen der Programmierung
in C fertig.