Adok's Way to C
Teil 1

+++ Vorwort +++

Ich schrieb diesen Kurs in den Jahren 1997/1998 fr die von mir herausgegebene
elektronische  Zeitschrift  "Hugi". Er erschien  in  Fortsetzungen. Den ersten
Teil  schrieb  ich bereits, als ich  das C-Programmieren erst selbst erlernte.
Inzwischen  ist  viel Zeit vergangen,  meine  Kenntnisse haben sich gefestigt.
Deshalb  liegt dieser erste Teil in komplett neuer Fassung vor. An den anderen
Teilen habe ich nur geringfgige nderungen vorgenommen.

Ursprnglich   richtete   sich   dieser  Kurs   an   Leute,   die  die  damals
weitverbreitete  Programmiersprache QBasic bereits beherrschten. So konnte ich
die  besonderen  Eigenschaften von C  erklren, ohne grundlegende Begriffe wie
Datentypen  zu erlutern. In der neuen Fassung  des ersten Teils gehe ich auch
auf diese Grundbegriffe ein.

+++ Einleitung +++

Die  Programmiersprache C wurde in den  70er Jahren von Kerningham und Ritchie
entwickelt.  Sie gehrt zu den  hheren Programmiersprachen, d.h. man schreibt
Programme  auf  einer hheren Abstraktionsebene,  die  dem menschlichen Denken
nher  steht  als  dem  "Denken" der  Maschine.  Es  ist aber dennoch mglich,
systemnah zu programmieren, z.B. mit Zeigern, Ansprechen einzelner Adressen im
Arbeitsspeicher usw. Deshalb wird C auch als "mittlere Sprache" bezeichnet.

Wegen  der  Kombination aus mchtigen  Konstrukten und maschinennahen Befehlen
erlangte C in professionellen Programmierkreisen rasch eine groe Verbreitung.
Viele   Betriebssysteme,   wie  UNIX,  Windows   und  OS/2,  und  die  meisten
Anwendungsprogramme fr diese Systeme wurden in C geschrieben.

C  bildet auerdem die Basis fr die objektorientierte Programmiersprache C++.
C++ ist die heute am meisten eingesetzte Programmiersprache fr professionelle
Anwendungen.  Auch  Java  baut  auf  C  auf,  auch  wenn  in  Java  nicht alle
C-Konstrukte mglich sind.

+++ Compiler +++

Es gibt sehr viele verschiedene C-Compiler fr Windows, UNIX und eine Vielzahl
anderer  Plattformen.  Einige sind kommerziell,  andere ber das Internet (zum
Teil sogar im Sourcecode) kostenlos erhltlich.

Bei  der  Wahl  eines  C-Compilers  sollte  man  darauf  achten,  da  er  zum
ANSI-C-Standard  kompatibel ist. Nur dann kann  man sich sicher sein, da alle
C-Konstrukte von ihm richtig bersetzt werden knnen.

Da C++ bis auf wenige Ausnahmen eine Obermenge von C ist, d.h. fast alles, was
man  in  C schreiben kann, auch  mit einem C++-Compiler bersetzt werden kann,
kann man natrlich als C-Programmierer auch mit einem C++-Compiler arbeiten.

Einige empfehlenswerte C- bzw. C++-Compiler sind:

- Watcom C++ (von Symantec):
  Gibt es in Universittsbuchhandlungen als vergnstigte Studentenversion. Die
  Studentenversion  ist voll einsatzfhig,  die Einschrnkungen sind lediglich
  rechtlicher  Natur  (z.B.  darf  man  mit  einer  Studentenversion  erzeugte
  Programme   nicht  kommerziell  vertreiben).  Fr  Fortgeschrittene  ist  im
  Internet  eine Open-Source-Version  auf http://www.openwatcom.org/ kostenlos
  erhltlich.

- Borland C++ (von Inprise):
  Eine  ltere, aber vollstndige Version des  Compilers fr DOS und Win32 ist
  auf    http://www.borland.com/    sowie    auf    Cover-CDs    verschiedener
  Computerzeitschriften erhltlich.

- Visual C++ (von Microsoft):
  Kommerziell. Der Windows-Compiler gilt als sehr effizient.

- GNU C++:
  V.a.  fr  UNIX-Systeme;  es existieren  aber  auch  Portierungen fr andere
  Plattformen  (z.B.  fr DOS: DJGPP).  Im  Internet kostenlos erhltlich. Das
  Programm   steht   unter  der  "GNU   Public   Licence"  (GPL).  Detail  auf
  http://www.gnu.org/.

Weiters mchte ich folgenden Compiler erwhnen:

- Quick C (von Microsoft):
  Befindet  sich  auf der "Turbo Toolbox"  CD.  Diesen Compiler verwendete ich
  anfangs,  als  ich  C  erlernte. Er ist  der  Vorgnger  von  Visual C++ und
  arbeitet   unter  MS-DOS.  Quick  C  hat  einen  guten  Programm-Editor  mit
  Online-Hilfe,   hnlich   der  Programmieroberflche   von  QBasic  und  dem
  MS-DOS-Texteditor  (edit.com).  Auerdem untersttzt  er einige zustzliche,
  systemnahe  Funktionen, die das C-Programmieren fr Leute, die bisher QBasic
  benutzten,   erleichtert.   Allerdings   hat   der   Quick-C-Compiler  einen
  gravierenden  Nachteil:  Er  ist fehlerhaft.  Ich  mchte  ihn deshalb nicht
  empfehlen.

+++ Los geht's: Etwas Theorie +++

C-Programme  bestehen  im  Wesentlichen aus  Deklarationen  von Funktionen und
evtl. Datentypen sowie Definitionen von Variablen und Konstanten.

Funktionen  sind Algorithmen, die  bestimmte Eingabe-Daten (Input) verarbeiten
und  in  der Regel auch ein  Resultat  ausgeben (Output). Funktionen, die kein
Resultat  ausgeben,  nennt  man in  anderen  Programmiersprachen Prozeduren. C
macht  allerdings  keinen  Unterschied  zwischen  Prozeduren  und  Funktionen;
Prozeduren  mssen  in  C als  void-Funktionen  (Nheres  wird spter erklrt)
deklariert werden.

Die   Hauptfunktion,  die  unmittelbar  nach  dem  Starten  eines  C-Programms
aufgerufen wird, heit main oder - in Windows-Programmen - WinMain.

Variablen   sind  in  imperativen  Programmiersprachen  wie  C  Behlter,  die
verschiedene  Werte  (Daten)  speichern knnen. Die  Art  der  Werte, die eine
Variable  speichern kann, nennt man Datentyp. Auf die verschiedenen Datentypen
werde ich in Teil 2 genauer eingehen.

Konstanten sind unvernderliche Werte.

Eine  Funktion  zu deklarieren, bedeutet,  festzulegen,  welche Datentypen ihr
Input (auch Parameter genannt) und ihr Output haben.

Eine Funktion zu definieren, bedeutet, ihren Algorithmus festzulegen.

Eine   Variable  zu  definieren,  bedeutet,   ihr  einen  Datentyp  und  damit
Speicherplatz zuzuweisen.

Die  Deklarationen von Funktionen knnen in C in der Regel entfallen, wenn man
die  Funktionen  in der richtigen Reihenfolge  definiert. Was ich damit meine,
werde ich spter an einem praktischen Beispiel erklren.

+++ Der grundlegende Aufbau einer Funktion +++

Am  Beispiel  der  Hauptfunktion  main mchte  ich  euch  zeigen, wie man eine
Funktion in C definiert.

Zuerst schreibt man den Funktionskopf:

 void main(void)

Das  erste  void  gibt hier an, da  main  keinen Output zurckgibt (und damit
eigentlich  eine  Prozedur  ist). Das zweite void  gibt  an, da man main auch
keine Parameter (keinen Input) zuweisen kann.

Als   nchstes   kommt  die  offene   geschwungene  Klammer.  Sie  leitet  den
Funktionsrumpf  ein.  Danach  folgen, wenn  bentigt,  Deklarationen und evtl.
Definitionen  von  lokalen  Variablen. Zuletzt  kommt  der Algorithmus. Dieser
besteht  aus  Anweisungen,  d.h.  Zuweisungen,  Rechenoperationen,  speziellen
Konstrukten   wie  z.B.  Schleifen  sowie  Aufrufen  anderer  Funktionen.  Der
Funktionsrumpf   wird   schlielich  mit   einer  geschwungenen  "Klammer  zu"
abgeschlossen.

So  etwa  knnte  die  Hauptfunktion  main  eines  ganz  einfachen  Programmes
aussehen:

 void main(void)
 {
   int i;                               // Variablendefinition
   i = 200;                             // Zuweisung
   printf("Hallo Welt!\n");             // Funktionsaufruf
   printf("Die Zahl i betraegt %d.",i); // Funktionsaufruf
   getch();                             // Funktionsaufruf
 }

Diese Hauptfunktion allein ist jedoch kein lauffhiges Programm. Es fehlt noch
die Deklaration der Funktion printf, die von main aufgerufen wird.

+++ Grundlagen des Prprozessors +++

printf   und   getch  sind  vordefinierte   Funktionen;  sie  gehren  zu  der
"Standardbibliothek" jedes C-Compilers. printf dient der Ausgabe von Daten auf
dem  Bildschirm oder auf anderen Ausgabegerten, getch dient der Eingabe eines
Zeichens  von  der Tastatur. Die Deklaration  von  printf befindet sich in der
"Headerdatei"  stdio.h,  die von getch in  der Headerdatei conio.h; auch diese
sind Bestandteil eines jeden C-Compilers.

Um  die  Deklarationen  von  printf und  getch  in  ein  Programm einzubinden,
schreibt man am Anfang dieses Programmes daher:

 #include <stdio.h>
 #include <conio.h>

#include  ist  eine "Prprozessorfunktion".  Der Prprozessor war ursprnglich
ein externes Programm, das die Aufgabe hatte, einen Sourcecode im "Rohzustand"
in  eine fr den C-Compiler lesbare Fassung  zu bringen, die der C-Compiler in
eine   ausfhrbare  Form  bersetzen  konnte.   Wenn  man  einen  C-Sourcecode
bersetzen  wollte,  mute man also zuerst  ihn  vom Prprozessor und dann das
Produkt vom Compiler verarbeiten lassen.

Heutzutage  ist  der Prprozessor in den  Compiler  integriert, d.h. bei jedem
Aufruf  des Compilers wird der Sourcecode zuerst "pr-prozessiert" und dann in
eine ausfhrbare Form bersetzt.

Die  Prprozessorbefehle bilden eine "Makrosprache",  die mit dem eigentlichen
Programmieren  in  C  nichts  zu tun  hat,  aber  dem  Programmierer das Leben
erleichtert,  weil er sich so einiges  an Schreibarbeit ersparen kann. Gbe es
den Prprozessor nicht, htten wir die Dateien stdio.h und conio.h ffnen, die
Deklarationen  von  printf  und getch suchen  und  in  unser Programm einfgen
mssen.

printf wird in stdio.h von Borland C++ 5.5 brigens folgendermaen deklariert:

int       _RTLENTRY _EXPFUNC printf(const char * __format, ...);

Ich  mchte  auf  die  Bedeutung  dieser  Deklaration  allerdings  nicht nher
eingehen, weil das im Moment zu weit fhren wrde.

+++ Unser erstes Programm! +++

Vervollstndigen  wir  also unser erstes Programm  und lassen wir es von einem
C-Compiler bersetzen. Hier ist der vollstndige Sourcecode:

// Das Hallo-Welt-Programm

#include <stdio.h>
#include <conio.h>

void main(void)
{
  int i;                               // Variablendefinition
  i = 200;                             // Zuweisung
  printf("Hallo Welt!\n");             // Funktionsaufruf
  printf("Die Zahl i betraegt %d.",i); // Funktionsaufruf
  getch();
}

Gebt  dieses  Programm  in  einem Texteditor  ein,  speichert  es ab (z.B. als
first.c)  und  compiliert  es;  wie Letzteres  geht,  hngt  von  dem von euch
benutzten  Compiler ab, also seht im Compilerhandbuch oder in der Online-Hilfe
nach.

Vorausgesetzt,  ihr  habt alles richtig abgetippt,  hat der Compiler jetzt aus
diesem  Sourcecode ein ausfhrbares Programm  erzeugt. Wenn ihr den Sourcecode
first.c  genannt  und  fr  DOS oder  Windows  compiliert  habt,  heit dieses
ausfhrbare  Programm  vermutlich first.exe. Startet  nun dieses Programm. Auf
dem Bildschirm erscheint jetzt Folgendes:

Hallo Welt!
Die Zahl i betraegt 200.

Das Programm wartet auf einen Tastendruck. Drckt eine beliebige Taste, und es
wird beendet.

Gehen wir den Sourcecode Zeile fr Zeile durch, um ihn im Detail zu verstehen:

Zeile 1:  // Das Hallo-Welt-Programm

          Dies  ist  ein Kommentar; er  dient  lediglich dem Programmierer als
          Orientierungshilfe. Vom Compiler wird er ignoriert.

          Strenggenommen  handelt  es  sich um  einen  C++-Kommentar, ist also
          nicht  im  ANSI-C-Standard enthalten. Er  wird  aber von den meisten
          heutigen C-Compilern auch bersetzt.

          Die  Zeichenfolge // leitet den C++-Kommentar ein. Sie signalisiert,
          da  der  Compiler den Rest  der  aktuellen Programmzeile ignorieren
          soll.

          Ein   echter  C-Kommentar  wird  dagegen  mit  der  Zeichenfolge  /*
          eingeleitet   und  mit  der  Zeichenfolge  */  abgeschlossen.  Alles
          zwischen  diesen  beiden Zeichenfolgen  wird vom Compiler ignoriert.
          Dadurch kann ein C-Kommentar auch ber mehrere Programmzeilen gehen.

          Statt

          // Das Hallo-Welt-Programm

          htten wir auch schreiben knnen:

          /* Das Hallo-Welt-Programm */

          oder:

          /*
              Das Hallo-Welt-Programm
          */

Zeile 2:

          Leerzeile.  Sie  dient nur der  bersichtlichkeit des Programms. Ich
          werde also knftig nicht mehr auf die Leerzeilen eingehen.

Zeile 3:  #include <stdio.h>

          Hier  ist  #include.  Das ist  der  Prprozessor-Befehl, mit dem man
          Headerdateien  in eigene Programme einbinden kann. Als Parameter mu
          der  Name  der  Headerdatei eingegeben  werden.  Dabei  gibt es zwei
          Mglichkeiten:  Die eine Mglichkeit ist,  den Namen der Headerdatei
          zwischen Kleiner-/Grerzeichen zu schreiben (wie im Beispiel). Dann
          sucht  der Compiler automatisch in bestimmten Verzeichnissen, die in
          der Regel in einer Konfigurationsdatei festgelegt sind oder ber die
          Kommandozeile angegeben wurden, nach der Headerdatei.

          Die  andere Mglichkeit ist, den Namen der Headerdatei zwischen zwei
          Anfhrungszeichen   zu   setzen.  Dann  lt   sich  auch  ein  Pfad
          mitangeben.  Allerdings  sucht  der Compiler  in  diesem  Fall nicht
          automatisch in den voreingestellten Verzeichnissen!

          Die Headerdatei, die in dieser Programmzeile eingebunden wird, heit
          stdio.h.  Sie ist die am meisten  benutzte Headerdatei und ebnet den
          Weg  fr die Verwendung der Standardbefehle fr Eingabe und Ausgabe.
          Auch  conio.h  (Zeile  4)  enthlt  Eingabe-/Ausgabe-Befehle. Andere
          wichtige  Headerdateien  sind z.B.  assert.h, graph.h, process.h und
          dos.h.

          Damit  es keine Miverstndnisse  gibt: Die Headerdateien beinhalten
          nicht die Funktionen selbst, sondern nur Funktions-Deklarationen und
          teilweise   auch  Konstanten-Definitionen.   Die  Funktionen  selbst
          befinden sich in der Standard-Bibliothek des Compilers.

Zeile 6:  void main(void)

          Ja,  hier  ist  sie wieder: Die  Funktion  main!  An diesem Exemplar
          dieser  hufig anzutreffenden Gattung ist  deutlich zu erkennen, wie
          main  aufgebaut  wird  (nett  ausgedrckt,  stimmt's?).  Zuerst  die
          Angaben,  da es keinen Rckgabewert  und auch keine Parameter gibt.
          In Zeile 7 dann die offene geschwungene Klammer, und in Zeile 13 die
          geschlossene  geschwungene Klammer. Dazwischen (also  in den Zeile 8
          bis 12) steht der Algorithmus.

Zeile 8:  int i;                               // Variablendefinition

          Hier  wird  die  lokale (d.h.  nur  fr  diese Funktion erreichbare)
          Variable  i  definiert.  int steht  fr  den Datentyp Integer (ganze
          Zahl). Im nchsten Teil werde ich auf die einzelnen Datentypen nher
          eingehen.

          Wie   man  sieht,  werden   Variablendefinitionen  stets  mit  einem
          Semikolon  (";")  abgeschlossen.  Dies gilt  auch  fr  alle anderen
          Anweisungen,   also   Zuweisungen,   Funktionsaufrufe   und   andere
          Konstrukte.

Zeile 9:  i = 200;                             // Variablendefinition

          Der  Variablen  i wird der Wert  200  zugewiesen. Anders gesagt: Dem
          Behlter i wird befohlen, den Wert 200 zu speichern.

Zeile 10: printf("Hallo Welt!\n");             // Funktionsaufruf

          Hier  ist unsere erste Funktion, printf! Wie bereits gesagt, ist sie
          in  stdio.h deklariert. Deshalb muten  wir diese Headerdatei in das
          Programm einbinden.

          Zwischen  den runden Klammern steht  der Parameter (der Input also).
          Man  merke  sich:  Funktionsparameter  mssen  stets zwischen runden
          Klammern geschrieben werden.

          In   diesem   speziellen  Fall  ist   der  Parameter  zustzlich  in
          Anfhrungszeichen  "verpackt",  weil  es  sich  um eine Zeichenkette
          (englisch "String") handelt.

          Wozu  dient  printf?  In  dieser einfachsten  Form  gibt  es den als
          Parameter  angegebenen Text auf das  Standardausgabegert - also auf
          den Bildschirm - aus. Zu beachten ist hierbei, da printf nicht
          automatisch einen Zeilenwechsel ausgibt. Schriebe man z.B.:

          printf("Zeile 1");
          printf("Zeile 2");

          so wrde man folgende Ausgabe erhalten:

          Zeile 1Zeile 2

          Fr  einen  Zeilenwechsel  mu  man  eine  "Escape-Sequenz"  in  die
          Zeichenkette  einbauen: \n. \n wird  also nicht als Text ausgegeben,
          sondern stellt den Befehl dar, die Zeile zu wechseln.

          Schreiben wir also:

          printf("Zeile 1\n");
          printf("Zeile 2");

          oder auch nur:

          printf("Zeile 1\nZeile 2");

          so lautet die Ausgabe:

          Zeile 1
          Zeile 2

          Es gibt noch mehr solcher Escape-Sequenzen. Hier die wichtigsten:

          \\    gibt Backslash (\) auf dem Bildschirm aus
          \a    lt einen Piepser ("Beep") ertnen
          \b    Rckschritt (entspricht einem Druck auf die Backspace-Taste)
          \"    gibt ein Anfhrungszeichen aus
          \'    gibt ein Hochkomma aus
          \f    Seitenvorschub

Zeile 11: printf("Die Zahl i betraegt %d.",i); // Funktionsaufruf

          Diese Zeile fhrt zur Ausgabe:

          Die Zahl i betraegt 200.

          Man  merkt, da %d ein Platzhalter fr  den Wert der Zahl i ist. Wie
          man mit solchen Platzhaltern arbeitet, wird in Teil 3 genau erklrt.

Zeile 12: getch();

          Eigentlich mte man schreiben:

          c = getch();

          (wobei  c  eine Variable des Typs  char wre). getch wartet nmlich,
          bis  der Benutzer ein beliebiges Zeichen  eingibt, und gibt dann den
          ASCII-Code  dieses  Zeichens  zurck. c  =  getch(); wrde also dazu
          fhren, da im Behlter c der ASCII-Code dieses Zeichens gespeichert
          wird.

          Allerdings  interessiert  uns der ASCII-Code  in diesem Programm gar
          nicht. Wir wollen nur, da das Programm auf einen Tastendruck wartet
          und   erst  dann  beendet  wird.  Deshalb  gengt  es,  getch();  zu
          schreiben. Der ASCII-Code der Eingabe wird damit verworfen.

+++ Strken von C +++

Tatschlich  liegt  eine der Strken von C  meiner Meinung nach darin, da man
Funktionen - wie im Fall von getch(); - auch prozedural aufrufen kann.

Ein  weiterer  Vorteil von C liegt darin,  da man in einer einzigen Anweisung
vieles  unterbringen  kann, wofr man  in anderen Sprachen mehrere Anweisungen
benutzen  mte.  Ein  praktisches  Beispiel  (aus  einer  Implementierung des
Quick-Sort-Algorithmus):

 swap(&dataspace[counter_min++], &dataspace[counter_max--]);

Damit   wird  zuerst  die  Funktion  swap   mit  den  Adressen  der  Variablen
dataspace[counter_min]  und  dataspace[counter_max]  aufgerufen.  Anschlieend
wird   counter_min  um  1  erhht   ("inkrementiert")  und  counter_max  um  1
verkleinert  ("dekrementiert"). In anderen Programmiersprachen htte man dafr
drei Anweisungen bentigt:

 swap(&dataspace[counter_min], &dataspace[counter_max]);
 counter_min = counter_min + 1;
 counter_max = counter_max - 1;

Allerdings   sollte  man  diese  Fhigkeit   von  C  auch  nicht  bertreiben.
Anweisungen wie etwa

 while(printf("Hallo!\n"),a=b++=++c,a!=c);

sind  einfach  nur  unbersichtlich und  fehleranfllig.  Da schreibt man doch
lieber:

 do
 {
   printf("Hallo!\n");
   c++;
   b = c;
   a = b;
   b++;
 } while (a!=c);

Dann  hat  C noch weitere Vorteile.  Zum  Beispiel werden kleinere ausfhrbare
Dateien  als  in manchen anderen  Programmiersprachen  erzeugt, weil durch die
Technik  der  Headerdateien nur die  Teile  der Laufzeitbibliothek eingebunden
werden,  die auch wirklich bentigt werden.

Auch  die Technik des Prprozessors hat so manche Vorteile. Einen haben wir ja
bereits  kennengelernt:  Man  erspart sich,  Deklarationen  von Funktionen der
Standardbibliothek   oder  anderer  Bibliotheken  zu  suchen.  Mehr  ber  den
Prprozessor findet ihr in Teil 3.

+++ Besonderheiten in der Syntax von C +++

Auf  folgende  Eigenschaften von C mt  ihr  aufpassen, insbesondere wenn ihr
eine laxere Programmiersprache (wie QBasic) gewhnt seid:

 - C  unterscheidet zwischen Gro- und Kleinschreibung. Schlsselwrter werden
   fr  gewhnlich  klein geschrieben. Wird  ein Schlsselwort, z.B. int, gro
   geschrieben, ist es fr den C-Compiler ein anderes Schlsselwort!
 - Alle Variablen mssen definiert werden.
 - Am Ende jeder Anweisung - Ausnahme: Blcke - steht ein Semikolon (;).
 - Wenn  man eine Funktion aufruft, aber  keine Parameter bergibt, mssen die
   Parameterklammern trotzdem geschrieben werden!
 - Arrays (siehe Teil 2) haben eckige Klammern.
 - Wenn  man einen Array mit mehreren Dimensionen hat, schreibt man nicht etwa
   a(3,1,4), sondern a[3][1][4]!

Also  dann:  Fleiig  das  bisher Gelernte  wiederholen  und  mit printf & Co.
experimentieren!
