Das C-ABC: Stufenweise zum hohen C (1)

Die Sprache C ist maschinennah, übersichtlich, schnell - und wie Sie sehen werden, leicht zu erlernen.

So wichtig wie Vitamin C für die Gesundheit ist, so wichtig ist die Programmiersprache C für effektive Softwaretüftler. Denn C vereinigt alle Vorteile:

C ist eine strukturierte Sprache. Wie in Pascal oder Modula setzt sich ein fertiges Programm übersichtlich aus Einzelbausteinen zusammen. Deshalb sind C-Programme leicht zu verändern und zu erweitern.

C besitzt den Ruf, schwer erlernbar zu sein. Doch keine Angst: Unser Kurs geht Schritt für Schritt vor, und Sie werden sehen, daß Ihre Sorge unberechtigt ist. Werfen wir zunächst einen Blick auf die Geschichte von C. In den siebziger Jahren arbeitete ein Team namhafter Programmierer der amerikanischen Bell-Laboratorien an dem neuen sehr umfangreichen Betriebssystem UNIX. Keine leichte Aufgabe für die Programmierer, denn damals mußten sie noch auf primitivster Ebene, in Maschinensprache (Assembler), programmieren. Wer schon einmal in Maschinensprache programmiert hat, der weiß, wie unübersichtlich und umständlich das Entwickeln damit ist. Kurz und gut, Maschinensprache kam für die UNIX-Programmierer nicht in Frage. D. Ritchie, der Kopf des Teams, hätte zur Entwicklung gerne eine höhere Programmiersprache genutzt, die einerseits übersichtlich ist und andererseits die Mittel zur Systemprogrammierung besitzt. Da es so etwas noch nicht gab, machte er sich unverdrossen daran, eine eigene Sprache zu entwickeln. So entstand die Sprache C. Den Namen »C« leitete Ritchie von der Sprache B ab, von der er viele Ideen übernahm. B wiederrum lehnt sich stark an die Sprache BCPL an.

Im Gegensatz beispielsweise zu Basic ist C eine Compilersprache. Der Programmierer gibt den Quelltext zunächst mit einem Texteditor ein. Anschließend muß er den C-Quelltext »compilieren«. Dazu lädt er den Compiler, der das C-Listing in Maschinensprache wandelt. Ein Linker verbindet schließlich die nötigen Maschinensprachteile - Module genannt - zu einem startfertigen Programm.

Da es auf dem ST eine Vielzahl von C-Compilern gibt, und ich nicht voraussetzen will, daß Sie über einen bestimmten Compiler verfügen, ist der Kurs auf kein spezielles Produkt ausgerichtet. Alle Angaben lassen sich mit jedem C-Compiler nachvollziehen. Dies erfordert selbstverständlich, daß Sie sich anhand der dem C-Compiler beigelegten Handbücher in die Bedienung des Systems einarbeiten.

Das erste Programm

Nun gleich zu unserem ersten Programm (Listing 1). Besitzer des C-Compilers »Turbo-C« sollten sich an den auftretenden Warnmeldungen nicht stören. Das Programm tut zwar nichts, zeigt Ihnen aber, aus welchen Teilen ein C-Programm mindestens bestehen muß. Es hat einen Namen - in unserem Fall »main()« -, hinter dem in geschweiften Klammeren steht, was es zu tun hat. Oder anders ausgedrückt: Die geschweiften Klammern enthalten die Programmanweisungen, auch Anweisungsbereich genannt. In unserem Listing folgen die geschweiften Klammern direkt aufeinander. Das bedeutet, daß unser Listing keine Anweisungen enthält. Eine Übersetzung dieses Programms in Basic fällt nicht schwer: In Basic erreichen Sie denselben Effekt mit einer Leerzeile, beispielsweise »10«.

Anhand des Leer-Program ms erkennen Sie schon die Formatfreiheit der Sprache. Denn das Listing muß nicht in drei Programmzeilen aufgeteilt sein, wir können es auch problemlos in einer Zeile zusammenfassen: »main() {}« oder »main () {}«. Ganz frei sind wir allerdings nicht. Die Umformulierung des Programmnamens »main()« zu »ma in()« verwirrt den Compiler und erzeugt eine Fehlermeldung. Der auf den ersten Blick freizügige Umgang mit den Leerzeichen bzw. Tabulator-Einrückungen hat Grenzen. Der Programmierer darf zwar zwischen den verschiedenen Elementen wie »main« und »()« beliebig Platz lassen und auch die Elemente in verschiedene Zeilen setzen, aber er darf ein Element nicht auseinanderreissen (»main« in »ma in«).

»main«, die Hauptfunktion

Ich habe bisher den Programmnamen mit »main()« bezeichnet. Genaugenommen ist »main()« der Name einer Funktion. Was ist nun eine Funktion? Basic-Programmierer hatten damit schon Kontakt: Im Basic-Jar-gon werden Funktionen als Unterprogramme bezeichnet und meistens mit dem Befehl »gosub« aufgerufen und mit »return« beendet. Basic-Unkundige können sich Funktionen als Bausteine vorstellen. Wie durch Legoklötze größere Bauwerke entstehen, lassen sich durch Funktionen Programme zusammenstellen. Die Basis aller Funktionen ist in C die Funktion mit dem Namen »main()«. Der Name steht für »main program« und heißt im Deutschen »Hauptprogramm«.

Ein C-Programm besteht demzufolge mindestens aus der Hauptfunktion »main()« und kann weitere Funktionen enthalten. So läßt sich Listing 1 in zwei Funktionen aufteilen. Listing 2 unterscheidet sich in der Wirkung durch nichts von Listing 1. Es wurde jedoch eine weitere Funktion namens »tue_nichts« hinzugefügt.

Außerdem enthält der Anweisungsbereich der Hauptfunktion »main()« die Anweisung »tue_nichts();«. Daß die Anweisung nach rechts eingerückt ist, hat lediglich ästhetische Gründe: Es fällt leichter, Anweisungen von Funktionsnamen zu trennen.

Unsere Anweisung in der Hauptfunktion ruft die Funktion »tue_nichts« auf. Da diese wie uns der Name schon andeutet - keine Aufgabe verrichtet, kehrt das Programm zur Hauptfunktion zurück. Der Vorgang des Funktionsaufrufs läßt sich mit folgender Situation vergleichen: Sie (Funktion 1) wollen ein Eis und beauftragen (Anweisung) einen Freund (Funktion 2), Ihnen eins zu holen. Ihr Freund bringt Ihnen das Eis; er kehrt also zu Ihnen zurück. Und da Anweisungen im realen Leben eine gewisse Betonung erfordern, müssen Sie auch C-Befehle »betonen«. Dazu dient das Semikolon am Ende der Anweisung (»tue_nichts();«).

Escape-Sequenzen Bedeutung 
\b	Rückschritt (Backspace)
\f	Seitenvorschub (Formfeed)
\n	Zeilenvorschub (Linefeed)
\r	Carriage return
\t	Tabulator-Einrückung
\v	Vertikale Tabulator-Einrückung
\\	Backslash ("\")
\'	Hochkomma
\"  Anführungszeichen
\DDD	DDD = 1 bis 3 Oktal-Ziffern
\xHHH	HHH = 1 bis 3 Hex-Ziffern

Tabelle 1. Die Escape-Sequenzen von C

Nur verantwortungslose Programmierer liefern Programme ab, in denen nicht jeder Schritt erklärt ist. Erfahrene Programmierer wissen, daß man zwar während der Programmentwicklung tief in der Logik des Programms steckt, doch schon nach kurzer Zeit komplexere Zusammenhänge vergißt. Deswegen sollten Sie Anweisungen mit Bemerkungen versehen. Basic stellt dazu den »rem«-Befehl zur Verfügung, in C stehen Anmerkungen zwischen den beiden Zeichenfolgen »/« und »/«. Bemerkungen stehen normalerweise hinter den Anweisungen oder in separaten Zeilen. Sie dürfen auch mehrere Zeilen umfassen. Listing 3 entspricht der dokumentierten Fassung des Listing 2.

Einfache Ausgabe durch »printf«

Jetzt ist es aber Zeit, ein Programm zu schreiben, das etwas tut! Sie finden es in Listing 4. Auch hier ruft »main()« eine weitere Funktion auf. Diese heißt »printf«. Im Listing ist sie nicht vorhanden, trotzdem bemängelt Ihr C-Compiler keinen Fehler. Des Rätsels Lösung: Die Funktion »printf« ist dem Compiler, genauer gesagt dem Linker, schon bekannt Sie ist Bestandteil der sogenannten Standard-Bibliothek. Darin sind alle besonders wichtigen und elementaren Funktionen gesammelt.

Die »printf«-Funktion hat die Aufgabe, Text auf dem Bildschirm auszugeben (siehe Listing 4). Wie auch beim Aufruf der Funktion »tue_nichts« sind dem Funktionsnamen zwei Klammern angefügt. Doch bei »printf« ist der Bereich zwischen den Klammern nicht leer, er enthält vielmehr Informationen, die Sie der Funktion angeben. Anhand dieser sogenannten Argumente weiß die »printf«-Funktion, welche Zeichen auszugeben sind. Sind wie bei der Funktion »tue_nichts« keine Argumente vorhanden, so folgen die beiden Klammern unmittelbar aufeinander. Sie dürfen aber auf keinen Fall die Klammern vergessen. Der Text, der das Argument für die Funktion »printf« darstellt, muß in Anführungszeichen gesetzt sein (siehe Listing 4). Im Computer-Jargon heißen solche Zeichenketten »Strings«.

main()
{
}

Listing 1. Das leere Programm

main()
{
	tue_nichts();
}
tue_nichts ()
{
}

Listing 2. Wir haben Listing 1 in zwei Funktionen aufgeteilt

/* Unsere Hauptfunktion main */ main()
{
tue_nichts(); /* Aufruf der Funktion */
}
/* Die Tue-Nichts-Punlction */ tue__nichts()
{
}

Listing 3. Dokumentiert ist das Listing doppelt so gut lesbar

Wie schon erwähnt, dürfen Sie zwischen Elementen, wie dem Funktionsnamen »printf«, den Klammern und den Argumenten beliebig Leerzeichen, Tabulatoren und sogar auch Leerzeilen einfügen. So ist neben der im Listing benutzten Formulierung auch »printf ("Dieses Programm tut etwas!" );« erlaubt.

Wenn Sie mit der »printf«-Funktion herumexperimentieren, stellen Sie fest, daß »printf« den Text immer ab der Bildschirmstelle ausgibt, an der die letzte Ausgabe endete. Was aber, wenn wir in einer neuen Zeile beginnen wollen? Nun, Listing 5 bietet die Lösung.

Wie Sie sehen, ist im String die Zeichenfolge »\n« enthalten. Compilieren und starten Sie das Programm, so stellen Sie fest, daß die Zeichenfolge »\n« nicht erscheint. Denn diese beiden Zeichen stellen für die Funktion »printf« eine besondere Anweisung dar, die im C-Jargon als Escape-Sequenz bezeichnet wird: Aha, »\n« ist vorgekommen, dann muß ich den folgenden Text in eine neue Zeile setzen (Zeilenvorschub). Diese spezielle Anweisung dürfen Sie beliebig in Strings einsetzen. So lassen sich die beiden »printf«-Anweisungen aus Listing 5 auch in einer Anweisung formulieren: printf("\n Zeile 1\nZeile 2"); Genau genommen macht das erste Zeichen (»\«) nur auf die besondere Situation aufmerksam. Erst das zweite Zeichen (»n«) der Escape-Sequenz gibt dem Computer die Anweisung für einen Zeilenvorschub. In Tabelle 1 finden Sie alle Escape-Sequenzen. So weist beispielsweise die Zeichenfolge »\t« die »printf«-Ausga-be an, eine Tabulator-Einrückung vorzunehmen.

Erste Begegnung mit Variablen

Gehen wir nun einen Schritt weiter und betrachten Listing 6. Wie Sie sehen, hat sich in der »main«-Funktion einiges getan. Sie ist - wie auch die Kommentare schon andeuten - in drei Teile gegliedert.

Zunächst treffen wir im Anweisungsblock auf die Anweisung »int summe;«. Damit sagen wir dem C-Compiler, daß wir eine ganzzahlige Variable namens »summe« benötigen. Variablen sind dazu da, zugewiesene Werte aufzunehmen und dauerhaft zu speichern. So hält eine Variablen etwa das Ergebnis einer Rechenoperation fest. Anders als etwa in Basic müssen wir Variablen grundsätzlich vor ihrer ersten Benutzung deklarieren. Denn technisch gesehen sind Variablen nichts weiter als Speicheradressen. Bei der Deklaration weist der C-Compiler der Variablen eine bestimmte Speicheradresse zu. Über den Variablennamen greifen Sie jederzeit auf den Inhalt der Variablen bzw. auf den Inhalt der Speicheradresse zu.

Prinzipiell darf die Deklaration überall im Quelltext vor der ersten Verwendung der Variablen erfolgen. Es ist jedoch übersichtlicher, die in einer Funktion benutzten Variablen am Anfang des Anweisungsblocks zu deklarieren. Dazu geben wir zwei Informationen an. Wir benötigen zum einen die Art der Variable. In unserem Fall verwenden wir ganze Zahlen (Integer). C besitzt dazu den Variablentyp »int«. Die zweite Information ist der Namen der Variable, in unserem Fall also »summe«. Somit setzt sich die Anweisung zum Deklarieren einer Variable zu »int summe;« zusammen. Variablen- und auch Funktionsnamen dürfen sich aus Groß- und Kleinbuchstaben, Zahlen und dem Unterstrich »_« zusammensetzen. Das erste Zeichen muß allerdings ein Buchstabe oder der Unterstrich sein. Namen können prinzipiell beliebig lang sein. Für viele C-Compiler sind Namen allerdings nur bis zu einer gewissen Länge eindeutig. Auch darf ein Variablenname kein sogenanntes C-Schlüssel wort sein. Eines davon haben wir bereits kennengelernt: »int«.

Nachdem wir dem C-Compiler mitgeteilt haben, daß wir eine Variable namens »summe« verwenden, können wir im Anweisungsblock der Hauptfunktion »main« jederzeit darauf zugreifen. Um der Variablen einen bestimmten Wert zuzuordnen, benutzen wir die Anweisungsform »Variable = Wert;«. Auf der linken Seite der Anweisung plazieren Sie die Variable, der Sie einen Wert zuordnen wollen. Auf der rechten Seite steht der zuzuordnende Wert. Da wir bei der Deklaration von »summe« angaben, daß wir nur ganze Zahlen verwenden, sind bei der Wertzuweisung nur ganzzahlige Werte wie etwa -5, 0, und 6 erlaubt. Wollen Sie etwa den Wert 10 zuordnen, so verwenden Sie die Anweisung »summe = 10;«.

Einer Variablen lassen sich nicht nur feste Werte, sondern auch Variablen zuordnen. Die Anweisung »summe = zahl_1;« weist der Variablen »summe« den Wert der Variablen »zahl_1« zu. Außerdem können Sie einer Variablen das Ergebnis einer Rechenoperation zuordnen. Um zwei Werte zu addieren, stellt C den Rechenoperator »+«, zum Subtrahieren »-«, zum Multiplizieren »*« und zum Dividieren »/« zur Verfügung. Um beispielsweise der Variablen »summe« das Ergebnis der Addition zwischen den Zahlen 10 und 20 zuzuordnen, verwenden wir die Anweisung »summe = 10 + 20;«. Die Variable enthält nun den Wert 30. Durch die Anweisung »differenz = 30 - 10;« weisen wir der Variablen »differenz« den Wert 20 zu.

Bei Rechenoperationen dürfen wir feste Werte und Variablen mischen. Enthält beispielsweise die Variable »zahl_1« den Wert 5, so weist die Anweisung »summe = zahl_1 + 10;« der Variablen »summe« das Ergebnis der Addition zwischen dem Variablenwert 5 von »zahl_1« und dem Wert 10, also insgesamt 15 zu.

Die Funktion »printf« gibt nicht nur einfache Strings aus, sondern fügt auch Variablenwerte ein. In Listing 6 sehen Sie, daß »printf« nicht nur einen, sondern vier jeweils durch ein Komma getrennte Argumente besitzt. Das erste Argument ist der auszugebende String einschließlich der Escape-Sequenz »\n« und dreimal der Zeichenfolge »%d«. Die drei weiteren Argumente sind Variablennamen. Starten Sie Listing 6, so stellen Sie fest, daß bei der Ausgabe die drei »%d«-Zeichenfol-gen jeweils durch eine Zahl ersetzt wurden.

Das Prozentzeichen »%« ist für die »printf«-Funktion eine weitere Einleitung einer Escape-Sequenz. Wo dieses Zeichen steht, setzt der C-Compiler bei der Ausgabe den Wert einer Variablen ein. Auf das Prozentzeichen folgt ein Buchstabe, der angibt, von welcher Art die Variable ist. In unserem Fall folgt der Buchstabe »d«. Dieser gibt dem C-Compiler an, »%d« durch eine ganzzahlige Variable (»int«) zu ersetzen. Woher weiß der Compiler, welchen Variablenwert er einsetzen soll? Die Variablennamen folgen als Argumente der Funktion nach dem ersten Argument. Die erste im String gefundene Escape-Sequenz »%d« ersetzt der Compiler bei der Ausgabe durch den Wert der als zweites Argument aufgeführten Variable. Ein weiteres »%d« greift auf das dritte Argument zu usw. Sie müssen demnach genausoviele zusätzliche Argumente angeben, wie Sie »%d« verwenden. Im Falle von Listing 6 verwenden wir dreimal die Escape-Sequenz »%d« und geben demnach auch drei Variablennamen als zusätzliche Argumente für »printf« an.

Wollen Sie statt einer Variablen einen direkten Wert ausgeben, so benutzen Sie statt einem Variablennamen eine Zahl. Wir können die Ausgabe von Listing 6 auch durch eine einzige Anweisung der Form »printf("\n Die Summe von %d und %d ist %d.",10,20,10+20);« ersetzen.

Eingabe mit »scanf«

Wir wissen nun, wie sich mit Zahlen und Variablen rechnen und auch das Ergebnis auf dem Bildschirm darstellen läßt. Damit können wir schon fast einen einfachen Taschenrechner ersetzen. Allerdings müssen wir für jede neue Berechnung das Programm ändern, neu compilieren und starten. Sehr umständlich! In Basic lösen wir das Problem mit dem »input«-Be-fehl. Dabei gibt der Anwender einen Wert ein, der in einer Variablen gespeichert wird. Auch C verfügt über eine ähnliche Anweisung: Es stellt uns hierzu die Funktion »scanf« zur Verfügung.

Listing 7 stellt die überarbeitete Version des Listing 6 dar. Nun kann der Benutzer nach dem Programmstart die zu addierenden Werte eingeben. Wie »printf« benötigt auch die »scanf«-Funktion als erstes Argument einen String. Im Gegensatz zu dem von »printf« verwendeten String, enthält dieser nur Escape-Sequenzen als Variablen-Platzhalter. Deswegen nennen wir ihn im folgenden auch Formatstring.

main()
{
/* Textausgabe auf den Bildschirm */
printf("Dieses Programm tut etwas!");
}

Listing 4. Eine einfache Bildschirmausgabe in C

main()
{
printf("\nZeile 1"); /* Textausgabe */
printf("\nZeile 2"); /* auf 2 Zeilen */
}

Listing 5. Die Escape-Sequenz "\n" erwirkt einen Zeitenvorschub

/* Wir rechnen mit einfacher Arithmetik */ 

main()
{
int summe; /* Wir deklarieren drei */
int zahl_1; /* ganzzahlige (integer) */ 
int zahl_2; /* Variablen */

zahl_1 = 10; /* Wir weisen den Variab- */
zahl_2 ? 20; /# len ihre Verte zu */
summe = zahl_1 + zahl_2; /* Addition */

/* Ergebnis der Addition ausgeben */
printf("\nDie Summe von %d und %d ist %d.",
zahl_1, zahl_2, summe);
}

Listing 6. Wir rechnen mit Variablen und geben das Ergebnis aus

main() /* Addition von beliebigen Zahlen */
{
int summe; /* wir deklarieren #/
int zahl_1; /* drei ganzzahlige */
int zahl_2; /* Variablen */

/* Der Benutzer gibt die Werte für zahl_1
 und zahl_2 über die Tastatur ein */
 
printf("\nZahl 1: "); scanf("%d", &zahl_1);
printf("\nZahl 2: "); scanf("%d", &zahl_2);
summe = zahl_1 + zahl_2; /* Addition */

printf("\nDie Summe von %d und %d ist %d.", 
zahl_1 zahl_2, summe);

}

Listing 7. Das Programm fragt den Anwender nach den zwei Summanden

main()
{

float zahl_1; /# Deklaration von zwei */
float zahl_2; /* Fließkomma-Zahlen */
float quotient;

*/ Durch "%f" lesen wir Fließkomma-Zahlen 
mit "scanf" ein */

printf("\nZahl 1: ");  scanf("%f", &zahl_1);
printf("\nZahl 2: ");  scanf("%f", &zahl_2);
quotient = zahl_1 / zahl_2; 
printf("\n%f geteilt durch %f ist %f", 
zahl_1, zahl_2, quotient);

}

Listing 8. Wir rechnen mit Fließkomma Zahlen

Der Formatstring in Listing 7 enthält die Escape-Sequenz »%d«. Sie weist den C-Compiler an, einen ganzzahligen Wert vom Anwender zu fordern. Das nächste Argument ist der Variablename, dem der eingegebene Wert zuzuordnen ist. Vor dem Namen steht ein besonderes Zeichen, das »&«. Wie Sie bereits wissen, sind Variablen nichts weiter als mit Namen versehene Speicheradressen. Das »&« weist den Compiler an, der Funktion »scanf« nicht den Wert der Variablen bzw. der Speicheradresse zu übergeben, sondern die Speicheradresse der Variablen. Denn nur so weiß die »scanf«-Funkti-on, wohin sie den Wert schreiben soll. Liegt beispielsweise die Variable mit dem Wert 50 »summe« an der Speicheradresse 20000, so besagt das »&« vor dem Namen, daß statt dem Wert (50) die Adresse (20000) zu verwenden ist.

Die bisher benutzten Variablen nehmen nur ganzzahlige Werte auf. Damit können wir rechnen, doch um einen Taschenrechner zu ersetzen, benötigen wir die sogenannten Fließkomma-Zahlen. Fließkomma-Zahlen bestehen aus einem Vorkomma- und einem Nachkommawert und ggf. einem Exponenten. Der Vorkommawert ist durch einen Punkt - und nicht durch ein Komma, wie es der Namen vermuten läßt - vom Nachkommawert getrennt. Der Exponent folgt unmittelbar auf den Nachkommawert und wird durch das Zeichen »e« eingeleitet. Beispiele für Fließkomma-Zahlen sind 5.2, 3.1415 und 4.555el0.

C stellt für Fließkomma-Zahlen den Datentyp »float« zur Verfügung. Um etwa die Variable »ergebnis« als Fließkomma-Variable zu deklarieren, verwenden Sie die Anweisung »float ergebnis;«. Nun können Sie der Variablen beliebige Fließkomma-Zahlen zuordnen.

Um ganzzahlige Werte über die »printf«-Funktion auszugeben und über die »scanf«-Funktion einzulesen, benützen wir die Escape-Sequenz »%d«. Für Fließkomma-Werte verwenden wir »%f«. In Listing 8 finden Sie ein Beispiel für die Verwendung von Fließkomma-Zahlen. Sie geben zwei Werte, z.B. 9.33 und 3.124, ein und erhalten deren Quotienten.

Das wars. Versuchen Sie nun, das bisher Besprochene in eigenen Übungsprogrammen umzusetzen, damit wir im nächsten Teil gleich voll loslegen können.

Kursübersicht

Teil 1: Die Hauptfunktion, Bildschirm-Ein-/Ausgabe, einfache Datentypen
Teil 2: Operatoren, Schleifen, Datentypen, Funktionen
Teil 3: Arrays, Strukturen, ANSI-Standard
Teil 4: Module,Standard-C-Funktionen, Assembler und C, TOS-Funktionen
Teil 5: GEM-Programmierung in C
Teil 6: Programmprojekt: GEM-Anwendung


Martin Backschat
Aus: TOS 05 / 1990, Seite 74

Links

Copyright-Bestimmungen: siehe Über diese Seite