C-Tutorial (C oder C++, Vorwort, Installation, Kapitel 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)

Strukturen, Dateizugriff, Schlusswort

13.1. Strukturen definieren und darauf zugreifen

In C haben Sie die Möglichkeit, Daten, die logisch zusammengehören, zu einem eigenen Datentypen zusammenzufassen. Angenommen, Sie möchten die Daten eines Kunden speichern. Dazu gehören - in einer einfachen Variante - dessen Kundennummer, Name, Adresse und Telefonnummer.

Sie könnten dieses Beispiel mit Arrays lösen. Dazu legen Sie vier verschiedene Arrays an, um die Daten aufzunehmen. Damit Sie die richtigen Daten erhalten, speichern Sie die Werte so ab, dass ein Kunde in allen vier Arrays denselben Index hat. Also z.B. Kundennummer[3], Name[3], Adresse[3] und Telefonnummer[3] gehören zum selben Kunden.

Das lässt sich schöner und übersichlicher lösen, indem wir zusammenfassen, was zusammen gehört. Im nächsten Beispiel wird ein selbstdefinierter Datentyp Kundendaten erstellt:

struct Kundendaten
{
  int   Kundennummer;
  char  Name[150];
  char  Adresse[200];
  char  Telefonnummer[15];
};

Mit struct wird hier ein neuer Datentyp definiert. Achten Sie auf das abschließende Semikolon (;) nach dem Anweisungsblock! Sie können Strukturen aus beliebigen anderen Datentypen zusammensetzen. Auch verschachtelte Strukturen (Struktur in einer anderen Struktur) sind möglich. Nach der Strukturdefinition weiß der Compiler nur, wie die Struktur aufgebaut ist, quasi das Modell der Struktur. Damit Sie die Struktur tatsächlich verwenden können, müssen Sie eine Strukturvariable erstellen:

struct Kundendaten mueller;

Damit wird die Variable mueller vom Typ Kundendaten erstellt und Speicher reserviert. Nun kann die Strukturvariable mueller Daten speichern. Mit dem Zugriffsoperator . können Sie auf Komponenten der Struktur zugreifen. Z.B.:

mueller.Kundennummer = 1234;
strcpy (mueller.Name, "Peter Mueller");
strcpy (mueller.Adresse, "Waldweg 5");
strcpy (mueller.Telefonnummer, "1234567");

Sie können Strukturvariablen gleich bei ihrer Deklaration initialisieren. Gleichwertig zu oberem ist:

struct Kundendaten mueller =
  {
    1234,
    "Peter Mueller",
    "Waldweg 5",
    "1234567"
  };

Mit dieser Anweisung wird ebenfalls die Struktuvariable mueller erstellt, es werden aber gleich alle Daten gesetzt. Diese müssen Sie durch Beistriche getrennt, in der richtigen Reihenfolge angeben. Eine Strukturvariable können Sie auch gleich bei der Strukturdefinition erstellen:

struct
{
  int   Kundennummer;
  char  Name[150];
  char  Adresse[200];
  char  Telefonnummer[15];
} Kundendaten;

Die Strukturvariable steht nach dem }, aber noch vor dem Semikolon. Der Nachteil davon ist, dass Sie auf die Strukturdefinition später nicht mehr zurückgreifen können. Denn: Die Struktur hat hier keinen Namen!

Es lassen sich auch Arrays aus Strukturtypen und Zeiger auf Strukturen erstellen. Ersteres sei hier gezeigt:

struct Kundendaten kunden[10];
...
kunden[3].Kundennummer = 1234;
strcpy (kunden[3].Name, "Peter Mueller");

Im Beispiel wird das Array kunden mit 10 Elementen vom Typ Kundendaten erstellt. Es lassen sich damit also 10 Datensätze speichern. Ein Datensatz besteht aus Kundennummer, Name, Adresse und Telefonnummer. Dem 4. Datensatz (Index 3!) wird die Kundennummer und der Name zugewiesen.

Das nächste Programm kann die Daten von 10 Kunden einlesen und wieder ausgeben:

#include <stdio.h>
#include <string.h>
 
struct Kundendaten
{
  int   Kundennummer;
  char  Name[150];
  char  Adresse[200];
  char  Telefonnummer[15];
};
 
 
void loescheTastaturpuffer()
{
  int c;
  while( ((c = getchar()) != EOF) && (c != '\n') )
     ;
}
 
 
void setzeDatensatz (struct Kundendaten *k, int eingabe, int *datenGesetzt)
{
   int sizeName = 0, sizeAdr = 0, sizeTel = 0;   /* Groesse der Felder */
 
   sizeName = sizeof (k->Name);
   sizeAdr = sizeof (k->Adresse);
   sizeTel = sizeof (k->Telefonnummer); 
 
   printf ("Setze Eintrag %d:\n\n", eingabe);
 
   printf ("Kundennummer: ");
   scanf ("%d", &k->Kundennummer);
   loescheTastaturpuffer();   /* sonst bleibt \n im Buffer */
 
   printf ("Name: ");
   fgets (k->Name, sizeName-1, stdin);
 
   printf ("Adresse: ");
   fgets (k->Adresse, sizeAdr-1, stdin);
 
   printf ("Telefonnummer: ");
   fgets (k->Telefonnummer, sizeTel-1, stdin);
 
      /* Null-Terminierung und \n entfernen */
   k->Name[strlen(k->Name)-1] = '\0'; 
   k->Adresse[strlen(k->Adresse)-1] = '\0';
   k->Telefonnummer[strlen(k->Telefonnummer)-1] = '\0';
 
   *datenGesetzt = 1;   /* daran wird erkannt, dass schon Daten eingegeben wurden */
}
 
 
int main()
{
   struct Kundendaten kunden[10];
   unsigned int eingabe = 0,
                i = 0,
                datenGesetzt[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 
   do
   {
      printf ("Welchen Eintrag verwenden: (1-10; 0 = Programmende) ");
      scanf ("%u", &eingabe);
 
      if (eingabe == 0)
         return 0;   /* Programmende */
      else if (eingabe > 10)
         continue;   /* sonst wird der zulaessige Bereich ueberschritten */
 
      i = eingabe -1;   /* Array-Index geht von 0 bis 99, Benutzereingabe aber von 1 bis 100 */
 
         /* wenn noch keine Daten eingegeben wurden, einlesen */
      if (datenGesetzt[i] == 0)   /* false */
         setzeDatensatz (&kunden[i], eingabe, &datenGesetzt[i]);
      else   /* true */
      {   /* sonst Daten anzeigen */
         printf ("Kundennummer: %d\n", kunden[i].Kundennummer);
         printf ("Name: %s\n", kunden[i].Name);
         printf ("Adresse: %s\n", kunden[i].Adresse);
         printf ("Telefonnummer: %s\n\n", kunden[i].Telefonnummer);
      }
   }
   while (1);   /* Endlosschleife */
 
   return 0;
}

Das Beispiel ist etwas länger und enthält wieder ein paar neue Dinge. Daneben kennen Sie mittlerweile immer mehr C-Sprachelemente, und ich kann einiges kombinieren. Beginnen wir in main():

Am Anfang von main() stehen die Deklarationen und es wird ein Array von Kundendaten mit 10 Elementen angelegt. Soweit bekannt. Ich habe hier zur Vereinfachung 10 direkt angegeben. Wenn Sie stattdessen eine symbolische Konstante definieren, sind Sie flexibler. Danach folgt das Array datenGesetzt mit ebenfalls 10 Elementen. Dieses soll sich später "merken", welche Datensätze bereits eingegeben wurden. Diese werden dann angezeigt. Ist ein Datensatz hingegen leer, und das entsprechende Element in datenGesetzt enthält 0, werden Sie zur Eingabe aufgefordert.

Am Anfang der endlosen do-while-Schleife wählen Sie den entsprechenden Datensatz. Da nur positive Ganzzahlen (eingabe ist von unsigned int) erlaubt sind, wird das Formatelement %u verwendet. Bei 0 wird das Programm beendet, bei einer Falscheingabe (> 10) wird die Schleife wiederholt (und Sie "dürfen" erneut eingeben). Negative Eingaben brauchen wir hier nicht extra abzufangen, da mit %u ohnehin ein positiver Wert eingelesen wird.

Der Array-Index entspricht der Benutzereingabe minus 1. Es wird nun überprüft, ob der jeweilige Datensatz bereits gesetzt wurde. Falls ja, wenn datenGesetzt[i] ungleich 0 (effektiv: immer 1) ist, werden im else-Block die Daten ausgegeben und die Schleife beginnt von vorne.

Handelt es sich aber um einen "neuen" Datensatz, wird es interessant. Es wird die eigene Funktion setzeDatensatz() wie folgt aufgerufen:

setzeDatensatz (&kunden[i], eingabe, &datenGesetzt[i]);

Vergleichen wir das am besten mit dem Funktionskopf:

void setzeDatensatz (struct Kundendaten *k, int eingabe, int *datenGesetzt)

Ziel und Aufgabe von setzeDatensatz() ist, das EXTERNE Array kunden - bzw. genauer: ein Element davon - zu setzen. Wie externe Variablen gesetzt werden können, habe ich in Kapitel 11.2 erklärt: Indem Zeiger (Call-by-Reference) übergeben werden! Genau das geschieht beim ersten Parameter. Mit

struct Kundendaten *k

wird der Zeiger k auf Kundendaten deklariert. Indem dem Zeiger die Adresse des externen Array-Elements übergeben wird, können die Daten im Speicher an der richtigen Stelle gesetzt werden. Mit &kunden[i] wird die Adresse des Elements i übergeben. Auf dieses wird setzeDatensatz() dann immer zugreifen (siehe gleich).

Der zweite Parameter ist eingabe. Die Funktion könnte auch ohne diesen Parameter "leben". Er wird nur verwendet, um am Anfang anzuzeigen, welches Element gesetzt wird (Benutzerinformation). Wichtiger ist der dritte Parameter: Hier muss ein Zeiger auf einen Integer übergeben werden. Über diesen setzen wir das richtige Element in datenGesetzt[] auf 1, nachdem Daten eingegeben wurden.

Sehen wir uns nun setzeDatensatz() näher an:

Am Anfang werden die Größen der drei char-Arrays ermittelt. Also 150, 200 und 15 Zeichen/Bytes. Natürlich hätte man das auch direkt angegeben können, aber so steigern wir die Qualität des Quellcodes. :-)
Um ganz genau zu sein: Wäre char größer als 1 Byte, würde das so nicht funktionieren. Dann wäre die Stringlänge nicht gleich der Größe in Bytes (wie hier angenommen). Dann müssten Sie das mit sizeof(char) zusätzlich berücksichtigen.

Neu ist der (Bereichs-)Zugriffsoperator ->. Wie erwähnt, ist k ein Zeiger auf die Struktur. Um über den Zeiger auf die Daten zu kommen, muss dieser dereferenziert werden, also *k. Zum Beispiel:

(*k).Kundennummer = 12345;

Weil . eine höhere Priorität als * hat, muss das Dereferenzieren geklammert werden. Der Operator -> ist hier einfach eine Kurzform. Gleichbedeutend ist:

k->Kundennummer = 12345;

Mit

scanf ("%d", &k->Kundennummer);

wird im Anschluss die Kundennummer eingelesen. Da k->Kundennummer schon auf die Daten verweist (dereferenziert!), muss mit & die Adresse ermittelt werden. Aus Kapitel 3 kennen Sie das Problem, dass Daten im Buffer verbleiben können. Genau das passiert hier: Das Newline-Zeichen (\n) verbleibt im Buffer und muss aus diesem gelöscht werden. loescheTastaturpuffer() löscht den Tastaturpuffer (mehr dazu gleich).

Danach werden drei Strings mit fgets() eingelesen. fgets() kennen Sie schon. Da der Name eines (char-)Arrays (z.B. Adresse aus k->Adresse) bereits ein Zeiger auf das erste Element ist, brauchen Sie hier keinen &-Operator.

fgets() hat einen Nachteil: Der Zeilenumbruch (\n bzw. \r\n) wird mitgespeichert. Da das hier aber nicht erwünscht ist, wird mit den drei nachfolgenden Anweisungen das letzte gespeicherte Zeichen durch die String-Terminierung (\0) ersetzt. Damit wird zugleich das Ende des Strings markiert. Die Länge des Strings liefert strlen(). strlen() zählt so lange, bis es \n findet. Die Anzahl der gefundenen Zeichen abzgl. 1 entspricht dem Index des \n-Zeichens.

Nun muss nur mehr datenGesetzt dereferenziert und auf 1 gesetzt werden. Hier heißt der Funktionsparameter (datenGesetzt) nur zufällig gleich wie das Array in main(). Das ist natürlich nicht Pflicht. Die Funktion "sieht" immer ihre lokale Variable.

Eines fehlt noch: Die Funktion loescheTastaturpuffer():

void loescheTastaturpuffer()
{
  int c;
  while( ((c = getchar()) != EOF) && (c != '\n') )
     ;
}

getchar() liest ein Zeichen vom Tastaturbuffer. Damit getchar() beendet wird und einen Rückgabewert liefert, muss Enter gedrückt werden. Hier ist das nicht nötig, da das \n sowieso noch - ungewollt - im Tastaturbuffer steht. getchar() liest ein Zeichen ein und speichert es in c ab. Die Schleife (mit leerem Schleifenrumpf) läuft so lange, bis entweder \n oder EOF gefunden wird. EOF (= end of file) ist in stdio.h mit -1 definiert. Dass EOF negativ ist, erklärt auch, warum getchar() einen Integer und nicht char zurückliefert. Der Wertebereich von (unsigned) char geht von 0 bis 255 (erweiterter ASCII-Code), damit lässt sich -1 nicht darstellen.

EOF können Sie unter Windows mit Strg+Z, unter Linux/UNIX mit Strg+D simulieren. Dann wird die Eingabe übersprungen, da fgets() ebenfalls abbricht, wenn EOF gefunden wird.

13.2. Dateien öffnen und wieder schließen

Im letzten Kapitel dieses Tutorials möchte ich Ihnen zeigen, wie Sie auf Dateien zugreifen. Wir werden Funktionen verwenden, die die C-Standardbibliothek zur Verfügung stellt. Der Vorteil davon ist, dass diese auf allen Plattformen verfügbar sind, also unabhängig vom Betriebssystem und damit portabel. Diese Art bezeichnet man als High-Level-Dateizugriff.

Dem gegenüber steht der Low-Level-Dateizugriff, wo spezielle Funktionen des Betriebssystems verwendet werden. Low-Level-Funktionen können meistens mehr, da die Unterschiede des Betriebssystems Berücksichtigung finden. Da dieses Tutorial aber sowohl für Windows-, als auch für Linux-User interessant sein soll, hat das hier keine weitere Bedeutung. Daneben steht Portabilität im Vordergrund, und das bieten nur High-Level-Funktionen.

Zunächst muss eine Datei mit fopen() geöffnet werden. fopen() wird wie folgt verwendet:

dateizeiger = fopen(Dateiname, Modus);

fopen() liefert einen Zeiger auf FILE zurück. FILE ist eine spezielle Struktur und in stdio.h definiert. Entscheidend ist für uns vor allem: Der Dateizeiger "merkt" sich die aktuelle Position der Datei.

Als erster Parameter wird der Dateiname angegeben. Achten Sie, wenn Sie einen absoluten Pfad unter Windows angeben darauf, dass Sie den Backslash \ escapen. Geben Sie also \\ statt \ im String an! Der zweite Parameter, der Modus, gibt an, wie die Datei behandelt werden soll.

Sie können Datei nur zum Lesen ("r"), aber auch nur zum Schreiben ("w") öffnen. Daneben können Sie Daten auch an eine bestehende Datei anhängen ("a"). Weitere Modi wie "r+", "w+", "a+", "r+b", "w+b" sind auch möglich, was aber den Rahmen dieses Tutorials sprengt. "r+" werden Sie im Beispiel in 13.3 kennen lernen.

Die lokale (= aktuelles Verzeichnis) Datei test.txt kann folgendermaßen zum Lesen geöffnet werden:

dateizeiger = fopen("test.txt", "r");

Ich gehe davon aus, dass dateizeiger vorher vom Typ FILE* deklariert wurde. Existiert test.txt nicht oder tritt ein Fehler auf (z.B. Datei nicht lesbar, keine Rechte), wird NULL zurückgeliefert. Soll die Datei zum Schreiben geöffnet werden, muss "w" angegeben werden:

dateizeiger = fopen("test.txt", "w");

Wichtig, dabei zu wissen: Existiert die Datei bereits, wird ihr Inhalt ohne Vorwarnung überschrieben! Ansonsten wird eine leere Datei angelegt. Wenn das nicht möglich ist (z.B. keine Rechte, die Datei anzulegen), wird auch hier NULL Zurückgeliefert. Den Rückgabewert sollten Sie entsprechend auswerten.

In die geöffnete Datei können Sie schreiben bzw. aus ihr lesen, je nachdem, welchen Modus Sie gewählt haben. Wie das geht, beschreibe ich in Abschnitt 13.3 weiter unten. Zuvor aber noch ein wichtiger Punkt: Dateien müssen wieder geschlossen werden:

fclose (dateizeiger);

Es kann nämlich nur eine bestimmte Anzahl an Dateien gleichzeitig geöffnet werden. Wenn Sie alle offenen Dateien schließen möchten, können Sie fcloseall() verwenden:

fcloseall();

13.3. Dateien lesen und schreiben

Es gibt vier verschiedene Möglichkeiten, Dateien zu lesen und zu schreiben: Zeichenweise, stringweise, formatiert oder blockweise.

Beim zeichenweisen Lesen/Schreiben wird Zeichen für Zeichen vorgegangen (je ein char). Dazu gibt es die Funktionen fputc() und fgetc(). Beide können Sie bei Interesse in einer C-Referenz nachschlagen (bzw. siehe Link).

Formatiertes Lesen und Schreiben wird dann angewandt, wenn strukturierte Datentypen zum Einsatz kommen, etwa selbstdefinierte Strukturen wie in Kapitel 12. Dazu gibt es fprintf() und fscanf().

Blockweises Lesen/Schreiben ist interessant, wenn Sie mit größeren Datenmengen zu tun haben. Etwa, wenn Dateien kopiert werden sollen. Blockweise Funktionen sind fwrite() und fread().

Hier interessieren vor allem fputs() und fgets(), wovon Sie fgets() sogar schon kennen. Beide Funktionen werden verwendet, um Daten stringweise zu verarbeiten. Bei fgets() haben wir als dritten Parameter bisher immer stdin angegeben, um von Tastatur (Standardeingabe) zu lesen. Stattdessen kann bei Dateien einfach der Dateizeiger angegeben werden, was wir uns im folgenden Beispiel zunutze machen.

Das nachfolgende Beispiel liest Daten (Text) von der Tastatur ein und schreibt sie in die Datei testoutput.txt. Die Datei wird ohne Nachfrage (!!) im aktuellen Verzeichnis angelegt bzw. überschrieben, falls sie existiert. Indem Sie einen Punkt eingeben und Enter drücken, können Sie das Programm beenden. Wenn Sie das Programm erneut starten, wird der bisher gespeicherte Text ausgegeben. Ihre nachfolgenden Eingaben werden an die Datei angehängt.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFFER_IS_NEWLINE  ((buffer[1]=='\n') || (buffer[1]=='\r'))
 
void zeigeDateiinhalt (FILE *dateizeiger)
{
   char buffer[80] = "";
 
   printf ("Bisheriger Dateiinhalt:\n\n");
 
   while ( fgets(buffer,80,dateizeiger) != NULL )
      fputs (buffer, stdout);   /* Ausgabe nach stdout */
}
 
 
void TextSpeichern (FILE *dateizeiger)
{
   char buffer[80] = "";
   int erg = 0;
 
   printf ("\nBitte Text eingeben. Ende mit  (Punkt) und Enter.\n\n");
 
   while ( erg != EOF )
   {
      fgets (buffer, 80, stdin);   /* von der Tastatur lesen (stdin) */
 
      if ( (buffer[0]=='.') && BUFFER_IS_NEWLINE )   /* Punkt eingegeben, Ende */
         return;
 
      erg = fputs (buffer, dateizeiger);   /* in die Datei schreiben */
   }
}
 
 
int main()
{
   FILE *dateizeiger;
   const char DATEINAME[] = "testoutput.txt";
 
      /* Versuchen, die Datei mit r+ zum Lesen und Schreiben zu oeffnen */
   dateizeiger = fopen (DATEINAME, "r+");
 
   if (dateizeiger == NULL)   /* dann zum Schreiben oeffnen (neu anlegen) */
   {
      dateizeiger = fopen (DATEINAME, "w");
 
      if (dateizeiger == NULL)
      {
         fputs ("Fehler: Datei konnte nicht geoeffnet werden!\n", stderr);
         return EXIT_FAILURE;
      }
   }
 
      /* bisherigen Text ausgeben */
   zeigeDateiinhalt(dateizeiger);
 
      /* Text von der Tastatur einlesen und in die Datei schreiben */
   TextSpeichern(dateizeiger);
 
   fclose (dateizeiger);
 
   return EXIT_SUCCESS;
}

Ich beginne wie in den vorigen beiden längeren Beispielen, mit der Erklärung in main(). Ich beschränke mich auf die Zeilen, die erklärungsbedürftig sind. Zunächst wird versucht, die Datei mit dem Modus "r+" zu öffnen. "r+" heißt so viel wie: open for update. Das heißt, dass aus der Datei gelesen, aber auch in die Datei geschrieben werden kann. "r+" funktioniert aber nur, wenn die Datei bereits existiert. Tut sie das nicht, wird NULL zurückgeliefert und wir probieren es erneut mit dem Modus "w". Falls auch das nicht klappt (wieder NULL), wird dem Benutzer eine Fehlermeldung angezeigt. Dazu wird mit fputs() ein String an den Standard-Fehlerstrom (stderr) geschickt.

Natürlich könnten wir Fehlermeldungen auch normal mit printf() ausgeben. Hier explizit den Fehler-"Kanal" zu verwenden hat aber einen entscheidenen Vorteil: Sie können Fehlermeldungen anders verarbeiten als die normale Ausgabe des Programms. Gerade Linux-User kennen das, wo sich mit 2> auf der Shell Fehlermeldungen umleiten lassen (in eine Logdatei oder nach /dev/null, um sie zu unterdrücken).

Danach wird das Programm beendet. Stellt sich die Frage: Welchen Wert zurückliefern? Wenn 0 "alles ok" bedeutet, was ist dann ein Fehler? -1, 17, 500? Dazu gibt es EXIT_FAILURE, das in stdlib.h definiert ist. EXIT_FAILURE ist meist als 1 definiert, was auf anderen System aber abweichen kann. Je nachdem, wo Sie Ihr Programm compilieren, wird so automatisch der für das Betriebssystem richtige Fehlercode zurückgeliefert.

Für ein "normales" Programmende gibt es EXIT_SUCCESS. Verwenden Sie am besten statt return 0; immer return EXIT_SUCCESS;, um Ihr Programm portabel zu halten. Es ist ja noch nicht zu spät, das im letzten Kapitel zu erwähnen. :-)

zeigeDateiinhalt() ist schnell erklärt. Mit fgets() wird aus der Datei gelesen und mit fputs() wird in den Standard-Ausgabestrom (stdout) geschrieben. Eine normale Bildschirmausgabe also, gleichzusetzen mit der von printf().

In TextSpeichern() wird mit fgets() aus dem Standard-Eingabestrom (stdin) gelesen und mit fputs() in die Datei geschrieben. Um auf Nummer sicher zu gehen - und nicht einfach eine Endlosschleife zu machen mit while(1) - wird der Rückgabewert von fputs() in erg gespeichert. Sollte beim Schreiben etwas schief laufen (etwa kein Speicher mehr verfügbar sein), wird von fputs() EOF zurückgeliefert. Die Schleife (und somit das Programm) wird dann beendet.

BUFFER_IS_NEWLINE habe ich als Makro definiert, um die Bedingung in der if-Anweisung übersichtlicher zu gestalten. Notwendig ist das natürlich nicht.

13.4. Schlusswort, Fragen und Antworten

Zu jedem Vorwort gehört auch ein Schlusswort. Finde ich zumindest. Jedenfalls vermisse ich Schlussworte oft, wenn ich mir am Ende vieler Bücher denke: Und jetzt?

Wie lange brauche ich, bis ich (richtig) gut programmieren kann?

Diese Frage stellen vorwiegend Anfänger. Die Antwort kann eigentlich nur Jahre lauten. Wie viele, hängt von den eigenen Zielen und der Definition von gut ab. Was ist gut, mit welchem Ergebnis gebe ich mich zufrieden? Ziele: Was will ich - vorrangig - programmieren? Kleine Skripte für Websites, Shell-Skripte, Anwendungsprogramme, Hardware-Treiber, umfangreiche Spiele, das eigene Betriebssystem? Je schwieriger die Aufgabe, je mehr Kenntnisse nötig sind, umso länger werden Sie brauchen.

Aber auch lebenslang ist eine gute Antwort (Grüße an die Schüler, die nichts mehr von lebenslangem Lernen hören können! ;-) ). Ähnlich gelagert wäre wohl die Frage: Wie lange benötige ich, bis ich ein guter Chirurg bin?. Mit der Chirurgie gemeinsam hat die Programmierung übrigens, dass beide Tätigkeiten nicht durch Theorie allein erlernt werden können. Stattdessen hilft nur üben, anwenden und ausprobieren (Letzteres ist eher etwas für Programmierer). Aber einen Vorteil haben Sie als Programmierer: Programmieren können Sie auch autodidaktisch - aus Büchern, Tutorials und anderen Quellen - lernen. Das für den Lerneffekt wichtige Feedback erhalten Sie vom Compiler.

Wie kann ich meine C-Kenntnisse erweitern?

Am besten mit einem weiterführenden Buch zu C! Es gibt eine große Auswahl an deutschsprachigen C-Büchern. Wenn Sie englischsprachige oder C++-Bücher auch noch dazu nehmen, werden es noch einmal deutlich mehr. Die Auswahl wird dann bereits enorm.

Ganz besonders kann ich "C für PCs" von Ulla Kirch-Prinz und Peter Prinz empfehlen, das ich selbst in der 2. Auflage gelesen habe. Da das Buch nicht mehr erhältlich ist, rate ich zum Nachfolger des Buches derselben Autoren: C - Einführung und professionelle Anwendung .

Die bisherigen Kenntnisse werden Ihnen beim Verstehen eines C-Buches helfen. Alle wesentlichen und für Anfänger relevanten Themen wurden hier behandelt. Manche genauer, manche eher grob.

Daneben gibt natürlich noch andere Tutorials zu C. In der Link-Rubrik finden Sie Links zu C-Tutorials und anderen C-Themen. Vermissen Sie eine gute Seite in der Link-Rubrik, können Sie sie dort eintragen (vorschlagen).

Bücher bieten aber oft mehr Tiefgang und detailliertere Informationen, als im Internet zu finden sind. Bücher sind auch meist nicht so oberflächlich wie Tutorials wie dieses hier. :-)

Mein Programm funktioniert nicht! Was tun bei Fragen? Wo finde ich Hilfe?

Eines vorweg: Bitte schreiben Sie mir keine E-Mail! Ich leiste keinen E-Mail-Support. Ich habe leider nicht die Zeit, auf jede programmier-bezogene (inhaltliche) Frage persönlich eingehen, noch Fehler in fremden Programmen zu suchen.

Was tun wenn's nicht läuft? Finden Sie den Fehler selbst! Fehlersuchen gehört zum Programmieren dazu! Nur durch Programmieren lernt man Programmieren. Stundenlang einen Fehler suchen gehört dabei dazu (weiß ich aus eigener Erfahrung).

Es läuft immer noch nicht? Weitersuchen, ggf. nochmal die Grundlagen lesen und lernen. Auch eine kurze Pause wirkt oft wahre Wunder, um Fehler in der eigenen Denkweise zu erkennen ("So kann's ja gar nicht funktionieren ..."). Fremde Hilfe in Anspruch zu nehmen, sollte immer nur den letzten Ausweg darstellen.

Natürlich können Sie Fragen stellen, auch erhöht reger Austausch unter Programmierern den Lerneffekt. Mehr dazu im Forum. Nutzen Sie bitte, bevor Sie einen neuen Beitrag schreiben, die Suchfunktion. Fragen werden i.d.R. mehrfach gestellt, und Ihre Frage lässt sich so vielleicht bereits beantworten.

Ich freue mich natürlich über Feedback zu diesem Tutorial. Zum Beispiel: Was ist gut erklärt, was lässt sich noch verbessern? Gibt es Fehler? Schreiben Sie aber bitte alle inhaltlichen Fragen zur Programmierung, wenn Sie sie nicht selbst lösen konnten, immer ins Forum! Im Forum verteilen sich alle Fragen auf viele Programmierer. Es antwortet dann, wer gerade Zeit hat.


Weiterhin viel Spaß beim Programmieren wünscht der Autor dieses Tutorials,
Andreas Hammer

Vorheriges Kapitel