Allgemeine Hinweise ------------------- Folgende Datentypen gibt es in C: bool Wahrheitswert (false, true) int Integer (ganzzahliger Typ) double Fließkomma (einfache Genauigkeit) float Fließkomma (doppelte Genauigkeit) char Zeichen [] Feld (Array), z.B. int[] * Zeiger (Pointer), z.B. char* struct Struktur if...else, while, do...while: Beispiele: if (x) {...} Falls x wahr ist, wird das im Rumpf stehende ausgeführt. while (x>0) {...} Solange x>0 ist, wird der Rumpf ausgeführt. do {...} (while x>0); Das Gleiche wie "while" nur wird der Rumpf mindestens einmal ausgeführt. "=" und "==": In der Mathematik macht man keinen Unterschied zwischen einer Zuweisung und einem "ist gleich". In Programmiersprachen ist dies jedoch essentiell! Eine Verwechslung von "=" mit "==" kann fatale Folgen haben. Beispiel: if (x=0) {...} Dies ist ein häufig, selbst von erfahrenen Programmierern gemachter Fehler. "x=0" ist eine Zuweisung, der Wahrheitswert ist also falsch, wodurch der Rumpf nie ausgeführt wird! Richtig ist: if (x==0) {...} Wenn x gleich 0 ist, dann ... Ganz normal wie in der Mathematik geht folgendes: int a, b, c; a = 1; b = 2; c = a + b; c = c + 1; c = c + 1 kann man auch so schreiben c += 1 bzw. c++ (inkr.) Stunde 1: --------- Einen einfachen Text ausgeben. Dies ist nicht besonders schwer; benötigt wird nur die Funktion printf(). Lasst euch nicht durch das #include verwirren, nehmt es einfach als gegeben hin.         #include                 int main()                 {                         printf("Hallo Welt");                         return (0);                 } Kurze Erklärung: Mittels #include werden die notwendigen Header eingebunden. stdio.h ist das gängigste und wird (fast) immer gebraucht. In den Headerdateien stehen die Funktionsbeschreibungen, die der Compiler benötigt. int main(): Jedes Programm muss eine main-Funktion haben, da der Compiler sonst das Programm nicht compiliert. Als Rueckgabewert wird hier ein Integer erwartet, d.h. eine ganze Zahl. Wenn 0 zurückgegeben wird, ist alles ok. Dies passiert hier mit return(0). Compiliert wird das Programm mittels gcc DATEINAME -o test. Stunde 2: --------- Erweiterung des Programms aus Stunde 1:         #include                 int main()                 {                         char *text = "Hallo Welt";                         printf("%s", text);                         return (0);                 } Im Laufe eurer Karriere werdet ihr noch viel mit Zeigern (Pointern) arbeiten müssen. Dies sind quasi "Relikte" aus der Urzeit der Programmierung, werden aber heute immer noch verwendet, da viele Programme entweder in C oder C++ geschrieben sind. JAVA und C# haben keine Zeiger mehr, bzw. sind sie dort versteckt. Was bedeutet "char *text"? text ist ein Typ, in diesem Fall vom Typ "character pointer". Anschaulich kann man sich das Ganze so vorstellen.                     -------------- text -------------> | Hallo Welt |                     -------------- Wobei der Zeiger auf das erste Zeichen des Textes "Hallo Welt" zeigt, also auf das "H". Das Ende einer Zeichenkette wird durch eine abschlie- ßende "\0" erkannt. Also benötigt "Hallo Welt" insgesamt 11 Stellen! Das muss man sich unbedingt merken! (Der Backslash maskiert die 0!) Man hätte hier genauso gut auch "char text[]" schreiben können. Dies hat in C die gleiche Bedeutung. "[]" bedeutet Feld (Array). Mehr dazu in der nächsten Stunde. printf("%s", text): %s ist hier ein Platzhalter, und zwar fuer einen Zeichenkette, in diesem Fall fuer "text" (das "Hallo Welt" beinhaltet). Stunde 3: --------- Felder in C. Wie bereits erwähnt bedeutet in C "char *text" das Gleiche wie "char text[]". Ein Beispiel dazu:         #include                 int main()                 {                         char textfeld[11];                         textfeld = "Hallo Welt";                         char *text = textfeld;                         printf("%s", text);                         return (0);                 } Was wurde hier gemacht? Mittels "char textfeld[11]" wurde ein Feld von 11 Elementen in denen nur Zeichen (char) vorkommen reserviert (auch die "\0" braucht Platz!) Nun ergibt sich C folgendes Problem: Wenn man mehr Zeichen in ein reserviertes Feld hineinschreibt als initialisiert wurde, kann es zu einem Speicherzugriffsfehler kommen (Ihr kennt bestimmt die Segmention Faults von Windows ;-) ). Wie kommt es dazu? Ganz einfach, es wurde in nicht reservierten Speicher geschrieben und ein anderes Programm hat diesen Speicher überschrieben; evtl. hat man sogar einen Speicherbereich, dem einem anderen Programm zugeordnet war überschrieben. Dies ist eine der Krankheiten von C mit denen man zurechtkommen muss. Der Compiler wird zwar meckern:         c.c:6: incompatible types in assignment Aber ich wollte einerseits demonstrieren, wie nah Pointer und Arrays in C zusammenliegen und andererseite die "Reservierung" zeigen. Ein guter Programmierer hätte natürlich folgendes geschrieben:         char textfeld[] = "Hallo Welt"; und sich im weiteren Verlauf der Array-Problematik zugewandt. Stunde 4: --------- Funktionsaufrufe: Folgendes Beispiel:         #include                 int funktion(int a, int b)                 {                         return (a*b);                 }                 int main()                 {                         int i = 5;                         int j = 6;                         int ergebnis = funktion(i, j);                         printf("Das Ergebnis lautet %d\n", ergebnis);                         return (0);                 } Funktionsaufrufe in C sind ziemlich einfach. In main wird die Funktion "funktion" mit 2 Parametern aufgerufen. Diese zwei Parameter sind vom Typ "int". Wenn man versucht, die Funktion mit anderen Typen aufzurufen, gibt der Compiler eine Fehlermeldung aus. (Mittels cast, d.h Typumwandlung, kann man nicht passende Typen "zurechtstutzen"; siehe nächste Stunde.) Da die Funktion ein "int" zurückgibt, muss auch dies einer Variablen (ergebnis) vom Typ "int" zugeordnet werden. printf() gibt nun wie gewohnt eine Zeichenkette aus. "%d" steht fuer "digit/decimal". "\n" bedeutet einen Zeilenumbruch. Stunde 5: --------- Casting/Typumwandlung: Wenn ein Typ mal nicht passt kann man ihn in C einfach umwandeln; dies hat folgende Syntax: (TYP IN DEN UMGEWANDELT WERDEN SOLL)UMZUWANDELNDER TYP siehe folgendes Beispiel:         #include                 int funktion(int a, int b)                 {                         return (a*b);                 }                 int main()                 {                         int i, j;                         float f, g;                         i = j = 5;                         f = g = 5.2525; int x = (int)f; int y = (int)g;                         int int_ergebnis   = funktion(i, j);                         int float_ergebnis = funktion(x, y);                         printf("int-Ergebnis und float-Ergebnis sind "); printf("%d\t%d", int_ergebnis, float_ergebnis);                         return (0);                 } Hier wurde ein Fliesskomma-Wert auf einen Integer-Wert "gecastet". "\t" bei der 2. printf() Funktion ist ein Tabulator, wie man ihn von Textverarbeitungsprogrammen kennt, nur dass die Tabulatoren hier eine feste Breite haben. Stunde 6: --------- Eine etwas andere main-Funktion... Man betrachte das folgende Beispiel:         #include                 int main(int argc, char **argv)                 {                         if (argc == 1) {                                 printf("Kein Parameter uebergeben!\n");                         } else if (argc == 2) {                                 printf("Ein Parameter uebergeben!\t");                                 printf("Name: %s\n", argv[1]);                         } else {                                 printf("Mehr als ein Parameter!\n");                         }                         return (0);                 } Das gilt es erst einmal zu verdauen. Was wurde hier gemacht? Im Grunde nichts schwieriges. "argc": Heißt "argument count", also die Anzahl der Argumente. "argv": Heißt "argument vector", also der Argumentvektor (besser wäre wohl Feld hier). Im Grunde ist ein Vektor wie ein Feld, nur ist dieser dynamisch ver- größerbar (siehe auch Vector-, bzw. ArrayList-Klasse in JAVA). Dies aber nur am Rande! Wieso heisst "argc == 1", dass kein Argument übergeben wurde? Bereits der Programmname wird als erstes Argument betrachtet. Ziemlich verwirrend für den Anfang, ich verzähle mich selbst immer wieder, also "DON'T PANIC!" Was hat es mit "char **argv" auf sich? Das ist der schwierigere Teil. Einfach ausgedrückt bedeutet es: "argv zeigt auf ein Feld von char-Zeiger". Jetzt werden einige ins Schwitzen geraten. Vielleicht ist folgende Schreibweise einleuchtender:         int main(int argc, char *argv[]) Das ist doch schon viel schöner, und man kann es endlich lesen :-) Zu beachten ist, dass Felder "null-initialisiert sind", d.h. man fängt mit "0" zum Zählen an. Somit ist "argv[0]" der Name des Programmes. Probiert es einfach mal aus, indem ihr das vorgegebene Programm abändert! Stunde 7: --------- Speicherallozierung: Manchmal ist es wichtig fuer gewisse Variablen Speicher zu reservieren/allozieren. Dies ist in C nicht gerade schön, ist aber Schema F, wie folgendes kurzes Codeschnipsel demonstrieren soll:         #define anzahl 100         ...         float *f;         f = (float *)malloc(anzahl * sizeof(float));         ...         for (int i = 0; i < anzahl; i++) {                 f[i] = 1.234;         }         ... Das ist nun für den geübten Anfänger das ziemlich Schrecklichste, was er wohl je gesehen hat. Aber keine Angst, es ist nur halb so schlimm. Erklärung der einzelnen Zeilen: #define anzahl 100: Anstatt überall 100 im Code scheiben zu müssen, reicht es nun "anzahl" zu schreiben. Dies hat außerdem den Vorteil, dass man den Wert, falls man eine Änderung wünscht, nur an einer Stelle modifizieren muss und nicht an x Stellen im Code. float *f: Variable vom Typ Fließkomma-Zeiger (sollte klar sein) f = (float *)malloc(anzahl * sizeof(float)): "malloc" heißt so viel wie "Reserviere Speicher (memory allocation). "sizeof": Größe von (das hat sicherlich jeder erraten) Wenn "malloc" fertig ist, gibt es uns einen Zeiger auf den reservierten Speicherbereich zurück. Wir reservieren also Speicher für "100 mal der Größe, die ein Fließkommawert im Speicher annimmt", casten den zurückgegebenen Zeiger dann auf einen float-Zeiger und weisen das der Variablen f zu. Nicht so schwer wie es aussieht?! (HINWEIS: Es gibt Architekturen, bei denen eine Fließkommazahl evtl. größer ist, als auf der x86-Archtektur. Diese Schreibweise stellt sicher, dass ausreichend Speicher reserviert wird.) In C++ bzw. JAVA ist das Reservieren von Speicher deutlich einfacher:         Float f = new Float(); Hier wird Platz für einen "Float" reserviert. Natürlich geht das auch mit Feldern. (Wobei in C++ *f benötigt wird, das Schlüsselwort "new" ist aber bei beiden Sprachen gleich!) Statt "malloc" hätte man auch "calloc" verwenden können. Einziger Unterschied ist, dass bei "calloc" alles mit "0" vorinitialisiert wird. Bsp.: f = (float *)calloc(1, anzahl * sizeof(float)); Falls einem der Speicher zu eng wird, kann er diesen mittels "realloc" erweitern. Dies gehört aber nicht hier her; alle die daran interessiert sind, sollten die man-Page (man realloc) oder ein gutes C-Buch lesen. I.A. wird gerne vergessen in C Speicher "dynamisch" zu vergrössern und damit treten dann immer wieder Fehler auf. Stunde 8: --------- File-Handling: Äußerst wichtig ist auch der Umgang mit Dateien in einem Programm. Linux bzw. Unix stellt dafür einige Funktionen zur Verfügung, die einem das Leben erleichtern sollen:         #include                 int main(int argc, char **agrv)                 {                         FILE *datei = NULL;                         char *text = "Hallo Welt!";                         datei = fopen("text.txt", "w+");                         fprintf(datei, "Gespeicherter Text:\n%s", text);                         fclose(datei);                 } Erklärung: Wir initialisieren zunächst einmal einen FILE-Pointer mit NULL; d.h. es ist jetzt erstmal ein sog. Nullpointer (was ganz Schreckliches eigentlich!). Man könnte aber auch "FILE *datei" schreiben, das würde vollkommen reichen. ACHTUNG: "FILE" muss groß geschrieben werden, sonst klappt es nicht!! Die nächste Zeile sollte jedem klar sein. Mittels fopen() wird eine Datei geöffnet, bzw. mit dem richtigen Parameter neu erzeugt ("w+" bedeutet: Öffne die Datei und lösche deren Inhalt, falls die Datei nicht existiert, lege eine neue an! Es gibt noch weitere Parameter: r, r+, a, a+, w (siehe man fopen)) Neu hinzugekommen ist "fprintf"; diese Funktion ähnelt "printf", schreibt aber Werte in einen Stream (erstes Argument, also eine Datei). Zum Schluss muss die zuvor geöffnete Datei wieder geschlossen werden, was mittels "fclose" passiert. Vergisst man dies, kann es zu unerwünschten Nebeneffekten kommen! Stunde 9: --------- Fehler beim Öffnen bzw. Speichern von Dateien abfangen. Im Gegensatz zu C++/C#/JAVA ist es in C nicht möglich, Fehler mittels throw, try, catch abzufangen. Dies kann aber mit ganz einfach "nachimplementiert" werden, auch wenn es nicht so flexibel wie in den genannten Sprachen ist. Beispiel von Stunde 8 wird ergänzt:         #include         int main(int argc, char **agrv)         {                 FILE *datei = NULL;                 char *text = "Hallo Welt";                 datei = fopen("text.txt", "w+");                 if (datei == NULL) {                         perror("!!FEHLER!!");                         printf("Datei konnte nicht erzeugt werden!\n");                 } else { printf("Datei wurde erfolgreich angelegt!\n"); }                 fprintf(datei, "Gespeicherter Text:\n%s", text);                 fclose(datei);         } "if (!datei)" ergibt wahr, wenn die Datei nicht angelegt werden konnte. Es wird eine Fehlermeldung mit Hilfe von "perror" ausgegeben, die, in einem schreibgeschützten Verzeichnis so aussieht:         !!FEHLER!!: Permission denied Auf diese Weise kann man schnell feststellen, wieso die Datei nicht angelegt werden konnte. Ich finde dies eine nette Sache! Stunde 10 (letzte Stunde): -------------------------- So, wir haben es fast geschafft, was gibt es noch Wichtiges, das man wissen sollte?! Ich gebe hier ein paar zusätzliche Infos, die einem das Leben erleichtern: extern void funktion(); Eine Funktion wird irgendwo anders noch implementiert, d.h. sie muss es! static-Funktionen, Beispiel: static void foo(void) { ... } Statische Funktionen sind nur innerhalb der Datei sichtbar und können somit von außen aus nicht aufgerufen bzw. zugegriffen werden. static-Variablen, Beispiel: void foo(void) { static int a = 1; a++; return a; } Die Variable a der Funktion wird nicht bei jedem Aufruf wieder auf 1 gesetzt, sondern erhält nach dem x. Aufruf den Wert "x". Ein bisschen Pointer müssen sein: int *a; int b = 1; float *f; char *c; a = &b; f = (float *)a; c = (char *)f; Preisfrage: Welche Werte haben a, b, c und f? Um es vorweg zu nehmen. Ihr könnt die Werte nicht wissen (ausser b). Das ist eines der Tücken von C. C ist nicht typensicher (wie bsp.weise JAVA) und genau hier liegt der Hund begraben. Die Variable a zeigt irgendwo im Speicher hin, nur wo? b wird 1 zugewiesen. Ein float- und char-pointer werden deklariert. Nun wird a die Adresse von b zugewiesen (nur was ist die Adresse von b??). a wird auf float-pointer gecastet, jetzt haben wir auch hier das Problem: wo zeigt a hin und welchen Wert hat damit f?? Zum Schluss noch das Sahnehäubchen: der Cast auf char-pointer. Wie soll man denn erraten welchen Wert dann c hat. Im ungünstigsten Fall kann das Programm sogar segfault'en, nämlich dann, wenn man mittels "fprintf" c auszugeben versucht, es aber keine abschließende "\0" gefunden wird. Dies ist der Fehlerteufel schlechthin bei C und höchst- problematisch! Wenn man in C nicht höllisch aufpasst was man macht, ist man ziemlich schnell in einer Sackgasse, wo man so schnell nicht mehr herausfindet. Selbst bei diesem kleinen Programm ist es für den Anfänger und auch teilweise für C-Kenner nicht einfach herauszufinden, wo der Fehler liegt. Also seid gewarnt: C ist eine Sprache, in der man schnell mal etwas schreiben kann. Für größere Projekte mag es, wenn man nicht alle Tücken und Details kennt, ungeeignet sein. Andererseits sind Projekte wie bsp.weise GNOME in C geschrieben. (AIX, HP-UX, IRIX, Solaris, *BSD, Linux sind ebenfalls in C geschrieben!) Anfänger tun sich meist damit leichter, als gleich in C++ einzusteigen und erzielen damit auch schnell erste Erfolge. Es gibt immer Pros und Contras für Sprachen; man muss genau abwägen, welche für sein Projekt am besten geeignet ist! Zusammenfassung: ---------------- Vorteile von C: - kleiner, kompakter Sprachumfang - sehr schnell, weil hardware-nah (gute Compiler verfügbar) - geringer Speicherverbrauch - sehr portabel - gutes Toolkit verfügbar (gtk+) - für kleinere Projekte gut geeignet Nachteile von C: - mag für größere Projekte ungeeignet sein - Zeiger sind für den Anfänger nicht leicht AUTOR: Christian Meyer