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.