vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 2

Die Komponenten eines C- Programms: Quellcode und Daten

Jedes C-Programm besteht aus verschiedenen Komponenten, die in bestimmter Weise kombiniert werden. Der größte Teil dieses Buches beschäftigt sich damit, diese Programmkomponenten zu erläutern und deren Einsatz zu zeigen. Für das Gesamtbild ist es hilfreich, wenn Sie sich zunächst ein vollständiges - wenn auch kleines - C- Programm ansehen, in dem alle Komponenten gekennzeichnet sind. Heute lernen Sie:

Ein kurzes C-Programm

Listing 2.1 zeigt den Quellcode für das Programm multiplizieren.c. Dieses sehr einfache Programm übernimmt zwei Zahlen, die der Benutzer über die Tastatur eingibt, und berechnet das Produkt der beiden Zahlen. Momentan brauchen Sie sich noch keine Gedanken darum zu machen, wie das Programm im Detail arbeitet. Es geht zunächst nur darum, dass Sie die Teile eines C-Programms kennen lernen, damit Sie die später in diesem Buch präsentierten Listings besser verstehen.

Bevor Sie sich das Beispielprogramm ansehen, müssen Sie wissen, was eine Funktion ist, da Funktionen eine zentrale Rolle in der C-Programmierung spielen. Unter einer Funktion versteht man einen unabhängigen Codeabschnitt, der eine bestimmte Aufgabe ausführt und dem ein Name zugeordnet ist. Ein Programm verweist auf den Funktionsnamen, um den Code in der Funktion auszuführen. Das Programm kann auch Informationen - so genannte Argumente - an die Funktion übergeben, und die Funktion kann Informationen an den Hauptteil des Programms zurückgeben. In C unterscheidet man Bibliotheksfunktionen, die unter Linux Teil des Betriebssystems sind, und benutzerdefinierte Funktionen, die der Programmierer erstellt. Im Verlauf dieses Buches werden Sie noch mehr über beide Arten von Funktionen erfahren.

Beachten Sie, dass die Zeilennummern in Listing 2.1 wie bei allen Listings in diesem Buch nicht zum Programm gehören und nur für Verweise im laufenden Text vorgesehen sind. Geben Sie die Zeilennummern also nicht mit ein.

Listing 2.1: Das Programm multiplizieren.c multipliziert zwei Zahlen.

1:  /* Berechnet das Produkt zweier Zahlen. */
2: #include <stdio.h>
3:
4: int a,b,c;
5:
6: int produkt(int x, int y);
7:
8: int main()
9: {
10: /* Erste Zahl einlesen */
11: printf("Geben Sie eine Zahl zwischen 1 und 100 ein: ");
12: scanf("%d", &a);
13:
14: /* Zweite Zahl einlesen */
15: printf("Geben Sie eine weitere Zahl zwischen 1 und 100 ein: ");
16: scanf("%d", &b);
17:
18: /* Produkt berechnen und anzeigen */
19: c = produkt(a, b);
20: printf ("%d mal %d = %d\n", a, b, c);
21:
22: return 0;
23: }
24:
25: /* Funktion gibt Produkt der beiden bereitgestellten Werte zurück */
26: int produkt(int x, int y)
27: {
28: return (x * y);
29: }

Geben Sie eine Zahl zwischen 1 und 100 ein: 35
Geben Sie eine weitere Zahl zwischen 1 und 100 ein: 23
35 mal 23 = 805

Die Komponenten eines Programms

Die folgenden Abschnitte beschreiben die verschiedenen Komponenten des Beispielprogramms aus Listing 2.1. Durch die angegebenen Zeilennummern können Sie die jeweiligen Stellen schnell finden.

Die Funktion main() (Zeilen 8 bis 23)

Die einzige Komponente, die in jedem ausführbaren C-Programm erforderlich ist, aber nur einmal vorhanden sein darf, ist die Funktion main(). In ihrer einfachsten Form besteht diese Funktion nur aus dem Namen main gefolgt von einem leeren Klammernpaar (()) und einem Paar geschweifter Klammern ({}). Innerhalb der geschweiften Klammern stehen die Anweisungen, die den Hauptrumpf des Programms bilden. Unter normalen Umständen beginnt die Programmausführung bei der ersten Anweisung in main() und endet mit der letzten Anweisung in dieser Funktion.

Die #include-Direktive (Zeile 2)

Die #include-Direktive weist den C-Compiler an, den Inhalt einer so genannten Include-Datei während der Kompilierung in das Programm einzubinden. Eine Include-Datei ist eine separate Datei mit Informationen, die das Programm oder der Compiler benötigt. Zum Lieferumfang des Compilers gehören mehrere dieser Dateien (man spricht auch von Header-Dateien). Diese Dateien müssen Sie nie modifizieren. Aus diesem Grund hält man sie auch vom Quellcode getrennt. Include-Dateien sollten die Erweiterung .h erhalten (zum Beispiel stdio.h).

In Listing 2.1 bedeutet die #include-Direktive: »Füge den Inhalt der Datei stdio.h in das Programm ein.« In den meisten C-Programmen sind eine oder mehrere Include- Dateien erforderlich. Mehr Informationen dazu bringt Tag 20, »Compiler für Fortgeschrittene«.

Die Variablendefinition (Zeile 4)

Eine Variable ist ein Name, der sich auf eine bestimmte Speicherstelle für Daten bezieht. Ein Programm verwendet Variablen, um verschiedene Arten von Daten während der Programmausführung zu speichern. In C muss man eine Variable zuerst definieren, bevor man sie verwenden kann. Die Variablendefinition informiert den Compiler über den Namen der Variablen und den Typ der Daten, die die Variable aufnehmen kann. Das Beispielprogramm aus Listing 2.1 definiert in Zeile 4 mit der Anweisung

int a,b,c;

drei Variablen mit den Namen a, b und c, die jeweils einen ganzzahligen Wert aufnehmen. Mehr zu Variablen und Variablendefinitionen erfahren Sie im Abschnitt »Daten speichern: Variablen und Konstanten« weiter hinten in diesem Kapitel.

Der Funktionsprototyp (Zeile 6)

Funktionsprototypen teilen dem C-Compiler den Namen und die Argumente der im Programm vorkommenden Funktionen mit. Bevor eine Funktion im Programm verwendet werden kann, muss der Funktionsprototyp im Programm bekannt gemacht worden sein. Ein Funktionsprototyp ist nicht mit der Funktionsdefinition zu verwechseln. Die Funktionsdefinition enthält die eigentlichen Anweisungen, die die Funktion ausmachen. (Auf Funktionsdefinitionen geht die heutige Lektion weiter hinten ein.)

Programmanweisungen (Zeilen 11, 12, 15, 16, 19, 20, 22 und 28)

Die eigentliche Arbeit eines C-Programms erledigen die Anweisungen. Mit C- Anweisungen zeigt man Informationen auf dem Bildschirm an, liest Tastatureingaben, führt mathematische Operationen aus, ruft Funktionen auf, liest Dateien - kurz gesagt, realisieren die Anweisungen alle Operationen, die ein Programm ausführen muss. Der größte Teil dieses Buches erläutert Ihnen die verschiedenen C-Anweisungen. Fürs Erste sollten Sie sich merken, dass man im Quellcode gewöhnlich eine Anweisung pro Zeile schreibt und eine Anweisung immer mit einem Semikolon abzuschließen ist. Die folgenden Abschnitte erläutern kurz die Anweisungen im Programm multiplizieren.c.

Die Anweisung printf()

Die Anweisung printf() in den Zeilen 11, 15 und 20 ist eine Bibliotheksfunktion, die Informationen auf dem Bildschirm ausgibt. Wie die Zeilen 11 und 15 zeigen, kann die Anweisung printf() eine einfache Textnachricht ausgeben oder - wie in Zeile 20 - die Werte von Programmvariablen gemeinsam mit Text.

Die Anweisung scanf()

Die Anweisung scanf() in den Zeilen 12 und 16 ist eine weitere Bibliotheksfunktion. Sie liest Daten von der Tastatur ein und weist diese Daten einer oder mehreren Programmvariablen zu.

Die Anweisung in Zeile 19 ruft die Funktion produkt() auf, d.h. sie führt die Programmanweisungen aus, die in der Funktion produkt() enthalten sind. Außerdem übergibt sie die Argumente a und b an die Funktion. Nachdem die Anweisungen in der Funktion produkt() abgearbeitet sind, gibt produkt() einen Wert an das Programm zurück. Diesen Wert speichert das Programm in der Variablen c.

Die return-Anweisung

Die Zeilen 22 und 28 enthalten return-Anweisungen. Die return-Anweisung in Zeile 28 gehört zur Funktion produkt(). Der Ausdruck in der return-Anweisung berechnet das Produkt der Werte in den Variablen x und y und gibt das Ergebnis an das Programm zurück, das die Funktion produkt() aufgerufen hat. Unmittelbar bevor das Programm endet, gibt die return-Anweisung in Zeile 22 den Wert 0 an das Betriebssystem zurück.

Die Funktionsdefinition (Zeilen 26 bis 29)

Eine Funktion ist ein unabhängiger und selbstständiger Codeabschnitt, der für eine bestimmte Aufgabe vorgesehen ist. Jede Funktion hat einen Namen. Um den Code in einer Funktion auszuführen, gibt man den Namen der Funktion in einer Programmanweisung an. Diese Ausführung bezeichnet man als Aufrufen der Funktion.

Die Funktion mit dem Namen produkt() in den Zeilen 26 bis 29 ist eine benutzerdefinierte Funktion, die der Programmierer (d.h. der Benutzer der Sprache C) während der Programmentwicklung erstellt. Die einfache Funktion in den Zeilen 26 bis 29 multipliziert lediglich zwei Werte und gibt das Ergebnis an das Programm zurück, das die Funktion aufgerufen hat. Am Tag 4, »Funktionen«, lernen Sie, dass die richtige Verwendung von Funktionen einen wichtigen Teil in der Programmierpraxis mit C ausmacht.

In einem »richtigen« C-Programm wird man kaum eine Funktion für eine so einfache Aufgabe wie die Multiplikation zweier Zahlen aufsetzen. Das Beispielprogramm multiplizieren.c soll lediglich das Prinzip verdeutlichen.

C umfasst auch Bibliotheksfunktionen, die Teil des Betriebssystems oder des C- Compilerpakets sind. Bibliotheksfunktionen führen vor allem die allgemeinen Aufgaben (wie die Ein-/Ausgabe mit Bildschirm, Tastatur und Festplatte) aus, die ein Programm benötigt. Im Beispielprogramm sind printf() und scanf() Bibliotheksfunktionen.

Programmkommentare (Zeilen 1, 10, 14, 18 und 25)

Jeder Teil eines Programms, der mit den Zeichen /* beginnt und mit den Zeichen */ endet, ist ein Kommentar. Da der Compiler alle Kommentare ignoriert, haben sie keinen Einfluss auf die Arbeitsweise des Programms. Man kann alles Mögliche in Kommentare schreiben, ohne dass es sich irgendwie im Programm bemerkbar machen würde. Ein Kommentar kann nur einen Teil einer Zeile, eine ganze Zeile oder auch mehrere Zeilen umfassen. Dazu drei Beispiele:

/* Ein einzeiliger Kommentar */

int a, b, c; /* Ein Kommentar, der nur einen Teil der Zeile betrifft */

/* Ein Kommentar,
der sich über mehrere
Zeilen erstreckt. */

Achten Sie darauf, keine verschachtelten Kommentare zu verwenden. Unter einem verschachtelten Kommentar versteht man einen Kommentar, der innerhalb der Begrenzungszeichen eines anderen Kommentars steht. Die meisten Compiler akzeptieren keine Konstruktionen wie:

/*
/* Verschachtelter Kommentar */
*/

Manche Compiler lassen verschachtelte Kommentare zu. Obwohl es verlockend erscheint, sollte man jedoch auf verschachtelte Kommentare generell verzichten. Einer der Vorteile von C ist bekanntlich die Portabilität, und Konstruktionen wie zum Beispiel verschachtelte Kommentare können die Portabilität Ihres Codes einschränken. Darüber hinaus führen derartige Kommentarkonstruktionen oftmals zu schwer auffindbaren Problemen.

Viele Programmieranfänger betrachten Kommentare als unnötig und als reine Zeitverschwendung. Das ist ein großer Irrtum! Die Arbeitsweise eines Programms mag noch vollkommen klar sein, wenn Sie den Code niederschreiben. Sobald aber Ihr Programm größer und komplexer wird oder wenn Sie Ihr Programm nach sechs Monaten verändern müssen, stellen Kommentare eine unschätzbare Hilfe dar. Spätestens dann dürften Sie erkennen, dass man Kommentare großzügig einsetzen sollte, um alle Programmstrukturen und Abläufe zu dokumentieren.

Viele Programmierer haben sich einen neueren Stil der Kommentare in ihren C-Programmen zu eigen gemacht. In C++ und Java kann man Kommentare mit doppelten Schrägstrichen kennzeichnen, wie es die folgenden Beispiele zeigen:

// Das ist ein Kommentar, der sich über eine Zeile erstreckt
int x; // Dieser Kommentar macht nur einen Teil der Zeile aus

Die Schrägstriche signalisieren, dass der Rest der Zeile ein Kommentar ist. Obwohl viele C-Compiler diese Form der Kommentare unterstützen, sollte man sie vermeiden, wenn die Portabilität des Programms zu wahren ist.

Was Sie tun sollten

Was nicht

Fügen Sie großzügig Kommentare in den Quellcode Ihres Programms ein, insbesondere bei Anweisungen oder Funktionen, die Ihnen oder einem anderen Programmierer, der den Code vielleicht modifizieren muss, später unklar erscheinen könnten.

Fügen Sie keine unnötigen Kommentare für Anweisungen hinzu, die bereits klar sind. Beispielsweise ist der folgende Kommentar überzogen und überflüssig, zumindest nachdem Sie sich mit der printf()-Anweisung auskennen:

Eignen Sie sich einen Stil an, der nützlich ist. Zu sparsame oder kryptische Kommentare bringen nichts. Bei zu umfangreichen Kommentaren verbringt man dagegen mehr Zeit mit dem Kommentieren als dem Programmieren.

/* Die folgende Anweisung gibt die Zeichenfolge Hallo Welt! auf dem Bildschirm aus */
printf("Hallo Welt!\n");

Geschweifte Klammern (Zeilen 9, 23, 27 und 29)

Mit den geschweiften Klammern { und } schließt man Programmzeilen ein, die eine C-Funktion bilden - das gilt auch für die Funktion main(). Eine Gruppe von einer oder mehreren Anweisungen innerhalb geschweifter Klammern bezeichnet man als Block. In den weiteren Lektionen werden Sie noch viele Einsatzfälle für Blöcke kennen lernen.

Das Programm ausführen

Nehmen Sie sich die Zeit, das Programm multiplizieren.c einzugeben, zu kompilieren und auszuführen. Es bringt Ihnen etwas mehr Praxis im Umgang mit Editor und Compiler. Zur Wiederholung seien hier noch einmal die Schritte analog zu Lektion 1, »Einführung in Linux und die Programmiersprache C«, genannt:

  1. Machen Sie Ihr Programmierverzeichnis zum aktuellen Verzeichnis.
  2. Starten Sie den Editor.
  3. Geben Sie den Quellcode für multiplizieren.c genau wie in Listing 2.1 gezeigt ein (außer den Zeilennummern mit Doppelpunkt).
  4. Speichern Sie die Programmdatei.
  5. Kompilieren und linken Sie das Programm mit dem entsprechenden Befehl Ihres Compilers. Wenn keine Fehlermeldungen erscheinen, können Sie das Programm durch Eingabe von ./multiplizieren an der Eingabeaufforderung ausführen.
  6. Sollte der Compiler Fehlermeldungen anzeigen, gehen Sie zurück zu Schritt 2 und korrigieren Sie die Fehler.

Eine Anmerkung zur Genauigkeit

Ein Computer arbeitet schnell und genau. Allerdings nimmt er alles wörtlich und er kann nicht einmal einfachste Fehler korrigieren. Er übernimmt daher alles genau so, wie Sie es eingegeben und nicht wie Sie es gemeint haben!

Das gilt ebenso für Ihren C-Quellcode. Ein einfacher Schreibfehler im Programm - schon beschwert sich der C-Compiler und bricht die Kompilierung ab. Auch wenn der Compiler Ihre Fehler nicht korrigieren kann (die auch Sie unweigerlich machen werden), so ist er doch zum Glück so intelligent, dass er Fehler erkennt und meldet. (Wie der Compiler Fehler meldet und wie man sie interpretiert, war Gegenstand der gestrigen Lektion.)

Die Teile eines Programms im Überblick

Nachdem diese Lektion alle Teile eines Programms erläutert hat, sollten Sie jedes beliebige Programm ansehen und Ähnlichkeiten feststellen können. Versuchen Sie, die verschiedenen Teile in Listing 2.2 zu erkennen.

Listing 2.2: Das Programm auflisten.c listet Codelistings auf

1:  /* auflisten.c Zeigt ein Listing mit Zeilennummern an */
2: #include <stdio.h>
3: #include <stdlib.h>
4:
5: void Verwendung_anzeigen(void);
6: int zeile;
7:
8: int main( int argc, char *argv[] )
9: {
10: char puffer[256];
11: FILE *fp;
12:
13: if( argc < 2 )
14: {
15: Verwendung_anzeigen();
16: return 1;
17: }
18:
19: if (( fp = fopen( argv[1], "r" )) == NULL )
20: {
21: fprintf( stderr, "Fehler beim Öffnen der Datei, %s!", argv[1] );
22: return 1;
23: }
24:
25: zeile = 1;
26:
27: while( fgets( puffer, 256, fp ) != NULL )
28: fprintf( stdout, "%4d:\t%s", zeile++, puffer );
29:
30: fclose(fp);
31: return 0;
32: }
33:
34: void Verwendung_anzeigen(void)
35: {
36: fprintf(stderr, "\nProgramm wie folgt starten: " );
37: fprintf(stderr, "\n\nauflisten Dateiname.ext\n" );
38: }

./auflisten auflisten.c
1: /* auflisten.c Zeigt ein Listing mit Zeilennummern an */
2: #include <stdio.h>
3: #include <stdlib.h>
4:
5: void Verwendung_anzeigen(void);
6: int zeile;
7:
8: int main( int argc, char *argv[] )
9: {
10: char puffer[256];
11: FILE *fp;
12:
13: if( argc < 2 )
14: {
15: Verwendung_anzeigen();
16: return;
17: }
18:
19: if (( fp = fopen( argv[1], "r" )) == NULL )
20: {
21: fprintf( stderr, "Fehler beim Öffnen der Datei, %s!", argv[1] );
22: return;
23: }
24:
25: zeile = 1;
26:
27: while( fgets( puffer, 256, fp ) != NULL )
28: fprintf( stdout, "%4d:\t%s", zeile++, buffer );
29:
30: fclose(fp);
31: return 0;
32: }
33:
34: void Verwendung_anzeigen(void)
35: {
36: fprintf(stderr, "\nProgramm wie folgt starten: " );
37: fprintf(stderr, "\n\nauflisten Dateiname.ext\n" );
38: }

Das Programm auflisten.c in Listing 2.2 zeigt C-Programmlistings an, die Sie gespeichert haben. Die Listings werden mit Zeilennummern auf dem Bildschirm ausgegeben.

Sicherlich sind Sie in der Lage, die verschiedenen Programmteile in Listing 2.2 wiederzuerkennen. Die obligatorische Funktion main() steht in den Zeilen 8 bis 32. Die Zeilen 2 und 3 enthalten #include-Direktiven. In den Zeilen 6, 10 und 11 finden Sie Variablendefinitionen. Zeile 5 enthält den Funktionsprototyp void Verwendung_anzeigen(void). Weiterhin gehören mehrere Anweisungen in den Zeilen 13, 15, 16, 19, 21, 22, 25, 27, 28, 30, 31, 36 und 37 zum Programm. Die Funktionsdefinition für Verwendung_anzeigen() erstreckt sich über die Zeilen 34 bis 38. Das gesamte Programm hindurch sind Blöcke in geschweiften Klammern eingeschlossen. Schließlich ist in Zeile 1 ein Kommentar angegeben. In den meisten Programmen sind wahrscheinlich weit mehr Kommentare angebracht.

Das Programm auflisten ruft mehrere Funktionen auf. Es enthält nur eine benutzerdefinierte Funktion - Verwendung_anzeigen(). Die Funktionen fopen() in Zeile 19, fprintf() in den Zeilen 21, 28, 36 und 37, fgets() in Zeile 27 und fclose() in Zeile 30 sind Bibliotheksfunktionen. Auf diese Bibliotheksfunktionen gehen die übrigen Lektionen näher ein.

Daten speichern: Variablen und Konstanten

In Listing 2.1 wurden, wie Sie sehen konnten, in Zeile 4 drei Variablen definiert. Computerprogramme arbeiten normalerweise mit unterschiedlichen Datentypen und benötigen eine Möglichkeit, die verwendeten Werte zu speichern. Bei diesen Werten kann es sich um Zahlen oder Zeichen handeln. In C gibt es zwei Möglichkeiten, Zahlenwerte zu speichern - Variablen und Konstanten. Und beide Möglichkeiten verfügen über eine Vielzahl von Optionen. Eine Variable ist eine Speicherstelle mit einem Wert, der sich im Laufe der Programmausführung ändern kann. Eine Konstante hingegen hat einen festen Wert, der nicht geändert wird. Bevor wir jedoch zu den Variablen kommen, sollten Sie erst ein wenig über die Funktionsweise des Speichers in Ihrem Computer erfahren.

Wenn Sie bereits wissen, wie der Speicher eines Computers funktioniert, können Sie diesen Abschnitt überspringen. Wenn Sie sich unsicher sind, lesen Sie einfach weiter. Die hier vermittelten Kenntnisse helfen Ihnen, bestimmte Aspekte der C- Programmierung besser zu verstehen.

Ein Computer legt Informationen in einem Speicher mit wahlfreiem Zugriff (RAM, Random Access Memory) ab. Der RAM - oder Hauptspeicher - ist in Form so genannter Chips realisiert. Der Inhalt dieser Chips ist flüchtig, d.h. die Informationen werden je nach Bedarf gelöscht und durch neue ersetzt. Es bedeutet aber auch, dass sich der RAM nur so lange an diese Informationen »erinnert«, solange der Computer läuft. Schaltet man den Computer aus, gehen auch die gespeicherten Informationen verloren.

In jeden Computer ist RAM eingebaut. Den Umfang des installierten Speichers gibt man in Megabyte (Mbyte) an, wie zum Beispiel 1 Mbyte, 8 Mbyte, 32 Mbyte, 64 Mbyte oder mehr. Ein Megabyte sind 1024 Kilobyte (Kbyte), und ein Kilobyte umfasst 1024 Byte. Ein System mit 4 Mbyte RAM hat also tatsächlich eine Größe von 4 * 1024 Kilobyte bzw. 4096 Kbyte. Das sind 4096 * 1024 Byte oder 4 194 304 Byte RAM.

Ein Byte ist die grundlegende Speichereinheit eines Computers. Näheres über Bytes erfahren Sie in Lektion 18, »Vom Umgang mit dem Speicher«. Tabelle 2.1 gibt einen Überblick, wie viele Byte für die Speicherung bestimmter Arten von Daten erforderlich sind.

Daten

Anzahl Byte

Der Buchstabe x

1

Die Zahl 500

2

Die Zahl 241105

4

Der Text C in 21 Tagen

14

Eine Schreibmaschinenseite

etwa 3000

Tabelle 2.1: Speicherbedarf für verschiedene Arten von Daten

Der Hauptspeicher ist fortlaufend organisiert, ein Byte folgt auf ein anderes. Jedes Byte im Speicher lässt sich durch eine eindeutige Adresse ansprechen - eine Adresse, die ein Byte auch von jedem anderen Byte unterscheidet. Die Adressen sind den Speicherstellen in fortlaufender Reihenfolge, beginnend bei 0 und wachsend bis zur maximalen Größe des Systems, zugeordnet. Momentan brauchen Sie sich noch keine Gedanken über Adressen zu machen, der C-Compiler kümmert sich für Sie darum.

Der RAM im Computer wird für mehrere Zwecke verwendet. Als Programmierer haben Sie es aber in erster Linie mit der Datenspeicherung zu tun. Daten sind die Informationen, mit denen ein C-Programm arbeitet. Ob ein Programm eine Adressenliste verwaltet, den Börsenmarkt überwacht, einen Haushaltsplan führt oder die Preise von Schweinefleisch verfolgt - die Informationen (Namen, Aktienkurse, Ausgaben oder zukünftige Preise für Schweinefleisch) werden im RAM gehalten, während das Programm läuft.

Nach diesem kurzen Ausflug in die Hardwarewelt des Computerspeichers geht es wieder zurück zur C-Programmierung und der Art und Weise, wie C im Hauptspeicher Informationen aufbewahrt.

Variablen

Eine Variable ist eine benannte Speicherstelle für Daten im Hauptspeicher des Computers. Wenn man den Variablennamen in einem Programm verwendet, bezieht man sich damit auf die Daten, die unter diesem Namen abgelegt sind.

Variablennamen

Um Variablen in C-Programmen zu verwenden, muss man wissen, wie Variablennamen zu erzeugen sind. In C müssen Variablennamen den folgenden Regeln genügen:

Hier einige Beispiele für zulässige und nicht zulässige C-Variablennamen:

Variablenname

Zulässigkeit

Prozent

erlaubt

y2x5__fg7h

erlaubt

gewinn_pro_jahr

erlaubt

_steuer1990

erlaubt, aber nicht empfohlen

sparkasse#konto

nicht zulässig: enthält das Zeichen #

double

nicht zulässig: ist ein C-Schlüsselwort

9winter

nicht zulässig: erstes Zeichen ist eine Ziffer

Da C die Groß-/Kleinschreibung von Namen beachtet, sind prozent, PROZENT und Prozent drei unterschiedliche Variablennamen. C-Programmierer verwenden oftmals nur Kleinbuchstaben in Variablennamen, obwohl dies keineswegs vorgeschrieben ist. Die durchgängige Großschreibung wird dagegen üblicherweise für Konstanten (siehe weiter hinten in dieser Lektion) verwendet.

Bei vielen Compilern kann ein Variablenname bis zu 31 Zeichen lang sein. (Tatsächlich kann er sogar länger sein, der Compiler betrachtet aber nur die ersten 31 Zeichen des Namens.) Damit lassen sich Namen erzeugen, die etwas über die gespeicherten Daten aussagen. Wenn zum Beispiel ein Programm Darlehenszahlungen berechnet, könnte es den Wert der ersten Zinsrate in einer Variablen namens zins_rate speichern. Aus dem Variablennamen geht die Verwendung klar hervor. Man hätte auch eine Variable namens x oder sogar uwe_seeler erzeugen können, für den Compiler spielt das keine Rolle. Falls sich aber ein anderer Programmierer Ihren Quelltext ansieht, bleibt ihm die Bedeutung derartiger Variablen völlig im Dunkeln. Auch wenn es etwas mehr Aufwand bedeutet, aussagekräftige Variablennamen einzutippen, der besser verständliche Quelltext ist diese Mühe allemal wert.

Es gibt zahlreiche Namenskonventionen für Variablennamen, die sich aus mehreren Wörtern zusammensetzen. Ein Beispiel haben Sie schon gesehen: zins_rate. Wenn man die Wörter durch einen Unterstrich voneinander absetzt, lässt sich der Variablenname leicht interpretieren. Der zweite Stil heißt Kamelnotation. Anstelle von Leerzeichen (die der Unterstrich verkörpern soll) schreibt man den ersten Buchstaben jedes Wortes groß und alle Wörter zusammen. Obige Variable trüge dann den Namen ZinsRate. Die Kamelnotation gewinnt immer mehr Anhänger, weil sich ein Großbuchstabe leichter eingeben lässt als der Unterstrich. Das Buch verwendet allerdings Variablennamen mit Unterstrichen, da derartige Namen besser zu erkennen sind. Entscheiden Sie selbst, welchem Stil Sie sich anschließen oder ob Sie einen eigenen entwickeln wollen.

Was Sie tun sollten

Was nicht

Verwenden Sie Variablennamen, die aussagekräftig sind.

Entscheiden Sie sich für eine Schreibweise der Variablennamen und behalten Sie diesen Stil dann durchgängig bei.

Beginnen Sie Variablennamen nicht mit einem Unterstrich, sofern es nicht erforderlich ist.

Verzichten Sie auf die durchgängige Großschreibung von Variablennamen. Diese Schreibweise hat sich für Konstanten eingebürgert.

Numerische Variablentypen

C bietet mehrere Datentypen für numerische Variablen. Diese unterschiedlichen Variablentypen sind erforderlich, da zum einen die verschiedenartigen numerischen Werte einen unterschiedlichen Speicherbedarf haben und zum anderen die ausführbaren mathematischen Operationen nicht für alle Typen gleich sind. Kleine Ganzzahlen (zum Beispiel 1, 199 und -8) erfordern weniger Speicher und der Computer kann mathematische Operationen mit derartigen Zahlen sehr schnell ausführen. Im Gegensatz dazu erfordern große Ganzzahlen und Fließkommazahlen (beispielsweise 123000000, 3.14 und 0.000000000871256) mehr Speicherplatz und auch wesentlich mehr Zeit bei mathematischen Operationen. Wenn man die jeweils passenden Variablentypen wählt, kann man ein Programm effizienter machen.

Die numerischen C-Variablen lassen sich in zwei Kategorien einteilen:

Innerhalb dieser Kategorien gibt es zwei oder mehrere Variablentypen.

Mit dem in Listing 2.3 vorgestellten Programm können Sie die Größe der Variablen für Ihren Computer ermitteln. Es gibt außerdem die Maximum- und Minimumwerte der Integer-Variablentypen an. Warum sollte man diese Art von Informationen mit einem Programm ermitteln? Wie bereits am Tag 1 kurz angesprochen wurde, läuft Linux auf vielen Rechnern. Dabei ist die Größe einiger Variablentypen auf einem Linux-Rechner mit einem Intel-Pentium-Prozessor (Teil der Intel-IA32-Familie) nicht die gleiche wie auf einem Linux-Rechner, der mit einem DEC-/Compaq-Alpha-Prozessor läuft.

Listing 2.3: groessevon.c - Ein Programm, das die Größe einiger Variablentypen auf Ihrem Computer in Byte anzeigt.

1 : /* Ein Programm, das die Grösse der unterschiedlichen */
2 : /* C-Variablen auf Ihrem Rechner ausgibt. */
3 : #include <stdio.h>
4 : #include <limits.h>
5 :
6 : int main(void)
7 : {
8 : printf ("Signed : Groesse %20s %22s\n", "Min", "Max") ;
9 : printf ("char : %d %22d %22d\n",
10: (int) sizeof (char), CHAR_MIN,CHAR_MAX);
11: printf ("short : %d %22d %22d\n",
12: (int) sizeof (short), SHRT_MIN,SHRT_MAX);
13: printf ("int : %d %22d %22d\n",
14: (int) sizeof (int), INT_MIN,INT_MAX);
15: printf ("long : %d %22ld %22ld\n",
16: (int) sizeof (long), LONG_MIN,LONG_MAX);
17: printf ("\n") ;
18:
19: printf ("Unsigned : Groesse %20s %22s\n", "Min", "Max") ;
20: printf ("char : %d %22d %22u\n",
21: (int) sizeof (unsigned char),0,UCHAR_MAX);
22: printf ("short : %d %22d %22u\n",
23: (int) sizeof (unsigned short),0,USHRT_MAX);
24: printf ("int : %d %22d %22u\n",
25: (int) sizeof (unsigned int),0,UINT_MAX);
26: printf ("long : %d %22d %22lu\n",
27: (int) sizeof (unsigned long),0,ULONG_MAX);
28: printf ("\n") ;
29:
30: printf ("single prec. float : %d\n", (int) sizeof (float));
31: printf ("double prec. float : %d\n", (int) sizeof (double));
32:
33: return 0 ;
34: }

Es soll Sie nicht bekümmern, dass Sie die Funktionsweise des Programms nicht verstehen. Auch wenn einige Elemente wie zum Beispiel sizeof neu sind, sind Ihnen andere mit Sicherheit bekannt. Die Zeilen 1 und 2 sind Kommentare, die den Namen des Programms und eine kurze Beschreibung enthalten. Die Zeilen 3 und 4 binden zwei Header-Dateien ein, die standardmäßig von allen ANSI/ISO-C-Compilern verstanden werden. In diesem einfachen Beispielprogramm gibt es nur eine einzige Funktion, nämlich main() in den Zeilen 6 bis 34. Die Zeilen 9 bis 31 bilden den Kern des Programms. Jede dieser Zeilen gibt eine verbale Beschreibung mit der Größe jedes Variablentyps aus, wobei das Programm die Größe der Variablen mit dem Operator sizeof ermittelt. In Kapitel 17, »Die Bibliothek der C-Funktionen«, erfahren Sie Näheres zu diesem Operator. Zeile 33 gibt den Wert 0 an das Betriebssystem zurück, bevor das Programm endet.

Und so sieht die Ausgabe des Programms groessevon.c aus, das auf einem Linux- Rechner mit einem Intel-x86-Prozessor kompiliert und ausgeführt wurde.

./groessevon
Signed : Groesse Min Max
char : 1 -128 127
short : 2 -32768 32767
int : 4 -2147483648 2147483647
long : 4 -2147483648 2147483647
Unsigned : Groesse Min Max
char : 1 0 255
short : 2 0 65535
int : 4 0 4294967295
long : 4 0 4294967295
single prec. float : 4
double prec. float : 8

Und so sieht die Ausgabe des Programms groessevon.c aus, das auf einem Linux- Rechner mit einem DEC/Compaq Alpha-Prozessor kompiliert und ausgeführt wurde.

Ausgabe

./groessevon
Signed : Groesse Min Max
char : 1 -128 127
short : 2 -32768 32767
int : 4 -2147483648 2147483647
long : 8 -9223372036854775808 9223372036854775807
Unsigned : Groesse Min Max
char : 1 0 255
short : 2 0 65535
int : 4 0 4294967295
long : 8 0 18446744073709551615
single prec. float : 4
double prec. float : 8

Beachten Sie, dass Integer-Variablen standardmäßig vorzeichenbehaftet (signed) sind, das heißt, es bedarf keines besonderen Schlüsselwortes, um Integer-Variablen mit Vorzeichen zu versehen. Auffallen sollte Ihnen auch, dass die Ergebnisse beider Prozessoren für char, short und int identisch sind, aber für die größeren Datentypen stark abweichen. Dies liegt daran, dass der Alpha-Prozessor ein 64-Bit-Prozessor ist und Integer-Werte bis 2^64-1 darstellen kann, während Prozessoren der Intel- Pentium-Familie 32-Bit-Prozessoren sind und nur Zahlen bis 2^32-1 darstellen können. Programmierer, deren Code zwischen Alpha- und Pentium-Prozessoren portierbar sein soll, sollten sich diesen Unterschied merken.

Auch wenn sich die Größe von Datentypen je nach Computerplattform unterscheiden kann, gibt C Dank des ANSI-Standards einige Garantien. Auf die folgenden fünf Dinge können Sie sich verlassen:

Die Werte in Fließkomma-Variablen entsprechen der wissenschaftlichen Notation, die Ihnen noch von der Schule her bekannt sein dürfte. Fließkommazahlen bestehen aus drei Teilen: einem Vorzeichen (+ oder - für die Darstellung positiver und negativer Zahlen), einer Mantisse (ein Wert zwischen 0 und 1) und einem Exponenten. Wenn man diese Notation zugrunde legt, würde eine Zahl wie 23.85 als +0.2385E2 dargestellt: eine positive Zahl gleich 0.2385 mal 10 hoch 2 (das heißt mal 100). Der Exponent kann sowohl positiv als auch negativ sein, so dass damit sehr große sowie sehr kleine Zahlen dargestellt werden können.

Diese Darstellung von Fließkommazahlen ist jedoch nicht für alle Zahlen absolut genau. Viele Zahlen, wie zum Beispiel 1/3 können mit Fließkommazahlen nur annähernd beschrieben werden. 1/3 wird korrekt als 0.333333..... mit einer unendlichen Folge von Dreien wiedergegeben. Fließkommazahlen können jedoch nur eine begrenzte Anzahl dieser sich wiederholenden Dreien abspeichern. Bei einer Darstellung von 1/3 mit einfacher Genauigkeit (single precision) werden sieben dieser Dreien und bei einer Darstellung mit doppelter Genauigkeit (double precision) 19 dieser Dreien abgespeichert.

Die Darstellungen der Fließkommazahlen in einfacher und doppelter Genauigkeit unterscheiden sich auch in dem Wertebereich, den sie speichern können. Variablen doppelter Genauigkeit können wesentlich größer und wesentlich kleiner sein als Variablen einfacher Genauigkeit. Der Grund dafür liegt darin, dass der Exponent der Darstellung mit doppelter Genauigkeit einen größeren Zahlenbereich umfasst als der Exponent bei einfacher Genauigkeit. In Tabelle 2.2 werden der Wertebereich und die Genauigkeit der Fließkommazahlen mit doppelter und einfacher Genauigkeit miteinander verglichen.

Wertebereich

Genauigkeit

einfache Genauigkeit

1. 2E-38 bis 3.4E38

7 Ziffern

doppelte Genauigkeit

2. 2E-308 bis 1.8E308

19 Ziffern

Tabelle 2.2: Wertebereich und Genauigkeit von Fließkommazahlen

Variablendeklarationen

Bevor man eine Variable in einem C-Programm verwenden kann, muss man sie deklarieren. Eine Variablendeklaration teilt dem Compiler den Namen und den Typ der Variablen mit. Die Deklaration kann die Variable auch mit einem bestimmten Wert initialisieren. Wenn ein Programm versucht, eine vorher nicht deklarierte Variable zu verwenden, liefert der Compiler eine Fehlermeldung. Eine Variablendeklaration hat die folgende Form:

typbezeichner variablenname;

Der typbezeichner gibt den Variablentyp an und muss einem der in Listing 2.3 verwendeten Schlüsselwörter entsprechen. Der variablenname gibt den Namen der Variablen an und muss den weiter vorn angegebenen Regeln genügen. In ein und derselben Zeile kann man mehrere Variablen desselben Typs deklarieren, wobei die einzelnen Variablennamen durch Kommata zu trennen sind:

int zaehler, zahl, start;       /* Drei Integer-Variablen */
float prozent, gesamt; /* Zwei Fließkommavariablen */

Wie Tag 11, »Gültigkeitsbereiche von Variablen«, zeigen wird, ist der Ort der Variablendeklaration im Quellcode wichtig, weil er die Art und Weise beeinflusst, in der ein Programm die Variablen verwenden kann. Fürs Erste können Sie aber alle Variablendeklarationen zusammen unmittelbar vor der Funktion main() angeben.

Das Schlüsselwort typedef

Mit dem Schlüsselwort typedef lässt sich ein neuer Name für einen vorhandenen Datentyp erzeugen. Im Grunde erzeugt typedef ein Synonym. Beispielsweise definiert die Anweisung

typedef int integer;

den Bezeichner integer als Synonym für int. Von nun an können Sie Variablen vom Typ int mit dem Synonym integer wie im folgenden Beispiel definieren:

integer zaehler;

Beachten Sie, dass typedef keinen neuen Datentyp erstellt, sondern lediglich die Verwendung eines anderen Namens für einen vordefinierten Datentyp erlaubt. Das Schlüsselwort typedef verwendet man vor allem in Verbindung mit zusammengesetzten Datentypen, wie am Tag 10 zum Thema Strukturen erläutert. Ein zusammengesetzter Datentyp besteht aus einer Kombination der in der heutigen Lektion vorgestellten Datentypen.

Variablen initialisieren

Wenn man eine Variable deklariert, weist man den Compiler an, einen bestimmten Speicherbereich für die Variable zu reservieren. Allerdings legt man dabei nicht fest, welcher Wert - d.h. der Wert der Variablen - in diesem Bereich zu speichern ist. Dies kann der Wert 0 sein, aber auch irgendein zufälliger Wert. Bevor Sie eine Variable verwenden, sollten Sie ihr immer einen bekannten Anfangswert zuweisen. Dies können Sie unabhängig von der Variablendeklaration mit einer Zuweisung wie im folgenden Beispiel erreichen:

int zaehler;      /* Speicherbereich für die Variable zaehler reservieren */
zaehler = 0; /* Den Wert 0 in der Variablen zaehler speichern */

Das Gleichheitszeichen in dieser Anweisung ist der Zuweisungsoperator der Sprache C. Auf diesen und andere Operatoren gehe ich am Tag 3, »Anweisungen, Ausdrücke und Operatoren«, näher ein. Hier sei lediglich erwähnt, dass das Gleichheitszeichen in der Programmierung nicht die gleiche Bedeutung hat wie in der Mathematik. Wenn man zum Beispiel

x = 12

als algebraischen Ausdruck betrachtet, bedeutet das: »x ist gleich 12«. In C dagegen drückt das Gleichheitszeichen den folgenden Sachverhalt aus: »Weise den Wert 12 der Variablen x zu.«

Variablen kann man auch im Zuge der Deklaration initialisieren. Dazu schreibt man in der Deklarationsanweisung nach dem Variablennamen ein Gleichheitszeichen und den gewünschten Anfangswert:

int zaehler = 0;
double prozent = 0.01, steuersatz = 28.5;

Achten Sie darauf, eine Variable nicht mit einem Wert außerhalb des zulässigen Bereichs zu initialisieren. Zum Beispiel sind folgende Initialisierungen fehlerhaft:

int gewicht = 10000000000000;
unsigned int wert = -2500;

Zum Glück gibt der GNU-C-Compiler eine Warnung aus, wenn er einen solchen Code kompilieren soll. Es ist jedoch nur eine Warnung. Sie können das Programm trotzdem kompilieren und linken, erhalten aber unerwartete Ergebnisse, wenn das Programm läuft.

Was Sie tun sollten

Was nicht

Stellen Sie fest, wie viele Byte die einzelnen Variablentypen auf Ihrem Computer belegen.

Verwenden Sie typedef, um Ihre Programme verständlicher zu machen.

Initialisieren Sie Variable, wenn möglich, bereits bei ihrer Deklaration.

Verwenden Sie keine Variable, die noch nicht initialisiert ist. Die Ergebnisse sind nicht vorhersagbar.

Verwenden Sie keine Variablen der Typen float oder double, wenn Sie lediglich Ganzzahlen speichern. Es funktioniert zwar, ist aber nicht effizient.

Versuchen Sie nicht, Zahlen in Variablen zu speichern, deren Typ für die Größe der Zahl nicht ausreicht.

Schreiben Sie keine negativen Zahlen in Variablen, die einen unsigned Typ haben.

Konstanten

Wie eine Variable ist auch eine Konstante ein Speicherbereich für Daten, mit dem ein Programm arbeiten kann. Im Gegensatz zu einer Variablen lässt sich der in einer Konstanten gespeicherte Wert während der Programmausführung nicht ändern. C kennt zwei Arten von Konstanten für unterschiedliche Einsatzgebiete:

Literale Konstanten

Eine literale Konstante ist ein Wert, den man direkt im Quellcode angibt. D.h. man schreibt den Wert an allen Stellen, wo er vorkommt, »wörtlich« (literal) aus:

int zaehler = 20;
float steuer_satz = 0.28;

Die Zahlen 20 und 0.28 sind literale Konstanten. Die obigen Anweisungen speichern diese Werte in den Variablen zaehler und steuer_satz. Eine der beiden Konstanten enthält einen Dezimalpunkt, die andere nicht. Ein vorhandener bzw. nicht vorhandener Dezimalpunkt unterscheidet Fließkommakonstanten von Integer- Konstanten.

In C sind Fließkommazahlen mit einem Punkt zu schreiben, d.h. nicht mit einem Komma, wie es in deutschsprachigen Ländern üblich ist.

Enthält eine literale Konstante einen Dezimalpunkt, gilt sie als Fließkommakonstante, die der C-Compiler als eine Zahl vom Typ double auffasst. Fließkommakonstanten lassen sich in der gewohnten Dezimalschreibweise wie in den folgenden Beispielen schreiben:

123.456
0.019
100.

Beachten Sie, dass in der dritten Konstanten nach der Zahl 100 ein Dezimalpunkt steht, auch wenn es sich um eine ganze Zahl handelt (d.h. eine Zahl ohne gebrochenen Anteil). Der Dezimalpunkt bewirkt, dass der C-Compiler die Konstante wie eine Fließkommazahl doppelter Genauigkeit behandelt. Ohne den Dezimalpunkt nimmt der Compiler eine Integer-Konstante an.

Fließkommakonstanten können Sie auch in wissenschaftlicher Notation angeben. Vielleicht erinnern Sie sich noch aus Schultagen, dass die wissenschaftliche Notation eine Zahl als Dezimalteil mal 10 hoch einer positiven oder negativen Zahl darstellt. Diese Notation bietet sich vor allem für sehr große und sehr kleine Zahlen an. In C schreibt man Zahlen in wissenschaftlicher Notation als Dezimalzahl mit einem nachfolgenden E oder e und dem Exponenten:

Zahl in wissenschaftlicher Notation

Zu lesen als

1.23E2

1.23 mal 10 hoch 2 oder 123

4.08e6

4.08 mal 10 hoch 6 oder 4080000

0.85e-4

0.85 mal 10 hoch minus 4 oder 0.000085

Eine Konstante ohne Dezimalpunkt fasst der Compiler als Integer-Zahl auf. Integer- Zahlen kann man in drei verschiedenen Notationen schreiben:

Symbolische Konstanten

Eine symbolische Konstante ist eine Konstante, die durch einen Namen (Symbol) im Programm dargestellt wird. Wie literale Konstanten kann sich auch der Wert von symbolischen Konstanten nicht ändern. Wenn Sie in einem Programm auf den Wert einer symbolischen Konstanten zugreifen wollen, verwenden Sie den Namen dieser Konstanten genau wie bei einer Variablen. Den eigentlichen Wert der symbolischen Konstanten muss man nur einmal eingeben, wenn man die Konstante definiert.

Symbolische Konstanten haben gegenüber literalen Konstanten zwei wesentliche Vorteile, wie es die folgenden Beispiele verdeutlichen. Nehmen wir an, dass Sie in einem Programm eine Vielzahl von geometrischen Berechnungen durchführen. Dafür benötigt das Programm häufig den Wert für die Kreiszahl (~3.14). Um zum Beispiel den Umfang und die Fläche eines Kreises bei gegebenem Radius zu berechnen, schreibt man:

umfang = 3.14 * (2 * radius);
flaeche = 3.14 * (radius) * (radius);

Das Sternchen (*) stellt den Multiplikationsoperator von C dar. (Operatoren sind Gegenstand von Tag 3.) Die erste Anweisung bedeutet: »Multipliziere den in der Variablen radius gespeicherten Wert mit 2 und multipliziere dieses Ergebnis mit 3.14. Weise dann das Ergebnis an die Variable umfang zu.«

Wenn Sie allerdings eine symbolische Konstante mit dem Namen PI und dem Wert 3.14 definieren, können Sie die obigen Anweisungen wie folgt formulieren:

umfang = PI * (2 * radius);
flaeche = PI * (radius) * (radius);

Der Code lässt sich dadurch besser verstehen. Statt darüber zu grübeln, ob mit 3.14 tatsächlich die Kreiszahl gemeint ist, erkennt man diese Tatsache unmittelbar aus dem Namen der symbolischen Konstanten.

Der zweite Vorteil von symbolischen Konstanten zeigt sich, wenn man eine Konstante ändern muss. Angenommen, Sie wollen in den obigen Beispielen mit einer größeren Genauigkeit rechnen. Dazu geben Sie den Wert PI mit mehr Dezimalstellen an: 3.14159 statt 3.14. Wenn Sie literale Konstanten im Quelltext geschrieben haben, müssen Sie den gesamten Quelltext durchsuchen und jedes Vorkommen des Wertes 3.14 in 3.14159 ändern. Mit einer symbolischen Konstanten ist diese Änderung nur ein einziges Mal erforderlich, und zwar in der Definition der Konstanten.

Symbolische Konstanten definieren

In C lassen sich symbolische Konstanten nach zwei Verfahren definieren: mit der Direktive #define und mit dem Schlüsselwort const. Die #define-Direktive verwendet man wie folgt:

#define KONSTANTENNAME Wert

Damit erzeugt man eine Konstante mit dem Namen KONSTANTENNAME und einem Wert, der in Wert als literale Konstante angegeben ist. Der Bezeichner KONSTANTENNAME folgt den gleichen Regeln wie sie weiter vorn für Variablennamen genannt wurden. Per Konvention schreibt man Namen von Konstanten durchgängig in Großbuchstaben. Damit lassen sie sich leicht von Variablen unterscheiden, deren Namen man per Konvention in Kleinbuchstaben oder in gemischter Schreibweise schreibt. Für das obige Beispiel sieht die #define-Direktive für eine Konstante PI wie folgt aus:

#define PI 3.14159

Beachten Sie, dass Zeilen mit #define-Direktiven nicht mit einem Semikolon enden. Man kann zwar #define-Direktiven an beliebigen Stellen im Quellcode angeben, allerdings wirken sie nur auf die Teile des Quellcodes, die unter der #define-Direktive stehen. In der Regel gruppiert man alle #define-Direktiven an einer zentralen Stelle am Beginn der Datei und vor dem Start der Funktion main().

Arbeitsweise von #define

Eine #define-Direktive teilt dem Compiler Folgendes mit: »Ersetze im Quellcode die Zeichenfolge KONSTANTENNAME durch wert.« Die Wirkung ist genau die gleiche, als wenn man mit dem Editor den Quellcode durchsucht und jede Ersetzung manuell vornimmt. Beachten Sie, dass #define keine Zeichenfolge ersetzt, wenn diese Bestandteil eines längeren Namens, Teil eines Kommentars oder in Anführungszeichen eingeschlossen ist. Zum Beispiel wird das Vorkommen von PI in der zweiten und dritten Zeile nicht ersetzt:

#define PI 3.14159
/* Sie haben eine Konstante für PI definiert. */
#define PIPETTE 100

Die #define-Direktive gehört zu den Präprozessoranweisungen von C, auf die am Tag 20, »Compiler für Fortgeschrittene«, umfassend eingegangen wird.

Konstanten mit dem Schlüsselwort const definieren

Eine symbolische Konstante kann man auch mit dem Schlüsselwort const definieren. Das Schlüsselwort const ist ein Modifizierer, der sich auf jede Variablendeklaration anwenden lässt. Eine als const deklarierte Variable lässt sich während der Programmausführung nicht modifizieren, sondern nur zum Zeitpunkt der Deklaration initialisieren. Dazu einige Beispiele:

const int zaehler = 100;
const float pi = 3.14159;
const long schulden = 12000000, float steuer_satz = 0.21;

Das Schlüsselwort const bezieht sich auf alle Variablen der Deklarationszeile. In der letzten Zeile sind schulden und steuer_satz symbolische Konstanten. Wenn ein Programm versucht, eine als const deklarierte Variable zu verändern, erzeugt der Compiler eine Fehlermeldung, wie es beispielsweise bei folgendem Code der Fall ist:

const int zaehler = 100;
zaehler = 200; /* Wird nicht kompiliert! Der Wert von Konstanten kann */
/* weder neu zugewiesen noch geändert werden. */

Welche praktischen Unterschiede bestehen zwischen symbolischen Konstanten, die man mit der #define-Direktive erzeugt, und denjenigen mit dem Schlüsselwort const? Das Ganze hat mit Zeigern und dem Gültigkeitsbereich von Variablen zu tun. Hierbei handelt es sich um zwei sehr wichtige Aspekte der C-Programmierung, auf die wir an den Tagen 8, »Zeiger«, und 11, »Gültigkeitsbereiche von Variablen«, näher eingehen werden.

Sehen Sie sich jetzt ein Programm an, das demonstriert, wie man Variablen deklariert und literale und symbolische Konstanten verwendet. Das in Listing 2.4 wiedergegebene Programm fragt den Benutzer nach seinem Gewicht und Geburtsjahr. Dann rechnet es das Gewicht in Gramm um und berechnet das Alter für das Jahr 2010. Das Programm können Sie entsprechend den in Kapitel 1 vorgestellten Schritten eingeben, kompilieren und ausführen.

Listing 2.4: Ein Programm, das zeigt, wie man Variablen und Konstanten verwendet.

1:    /* Demonstriert die Verwendung von Variablen und Konstanten */
2: #include <stdio.h>
3:
4: /* Konstante zur Umrechnung von Pfund in Gramm definieren */
5: #define GRAMM_PRO_PFUND 500
6:
7: /* Konstante für Beginn des nächsten Jahrzehnts definieren */
8: const int ZIEL_JAHR = 2010;
9:
10: /* Erforderliche Variablen deklarieren */
11: int gewicht_in_gramm, gewicht_in_pfund;
12 int jahr_der_geburt, alter_in_2010;
13:
14: int main()
15: {
16: /* Daten vom Benutzer einlesen */
17:
18: printf("Bitte Ihr Gewicht in Pfund eingeben: ");
19: scanf("%d", &gewicht_in_pfund);
20: printf("Bitte Ihr Geburtsjahr eingeben: ");
21: scanf("%d", &jahr_der_geburt);
22:
23: /* Umrechnungen durchführen */
24:
25: gewicht_in_gramm = gewicht_in_pfund * GRAMM_PRO_PFUND;
26: alter_in_2010 = ZIEL_JAHR - jahr_der_geburt;
27:
28: /* Ergebnisse auf Bildschirm ausgeben */
29:
30: printf("\nIhr Gewicht in Gramm = %d", gewicht_in_gramm);
31: printf("\nIm Jahr 2010 sind Sie %d Jahre alt.\n", alter_in_2010);
32:
33: return 0;
34: }

Bitte Ihr Gewicht in Pfund eingeben: 175
Bitte Ihr Geburtsjahr eingeben: 1960

Ihr Gewicht in Gramm = 87500
Im Jahr 2010 sind Sie 50 Jahre alt.

Das Programm deklariert in den Zeilen 5 und 8 zwei Arten von symbolischen Konstanten. Die in Zeile 5 deklarierte Konstante dient dazu, die Umrechnung von Pfund in Gramm (das heißt den Wert 500) verständlicher zu formulieren, wie es in Zeile 25 geschieht. Die Zeilen 11 und 12 deklarieren Variablen, die in anderen Teilen des Programms zum Einsatz kommen. Aus den beschreibenden Namen wie gewicht_in_gramm lässt sich die Bedeutung einer Berechnung leichter nachvollziehen. Die Zeilen 18 und 20 geben die Texte für die Eingabeaufforderungen auf den Bildschirm aus. Die Funktion printf() wird später in diesem Buch noch im Detail behandelt. Damit der Benutzer auf die Aufforderungen reagieren kann, verwenden die Zeilen 19 und 21 eine weitere Bibliotheksfunktion, scanf(), mit der sich Eingaben über die Tastatur entgegennehmen lassen. Auch auf diese Funktion geht das Buch später näher ein. Die Zeilen 25 und 26 berechnen das Gewicht des Benutzers in Gramm und sein Alter für das Jahr 2010. Diese und andere Anweisungen kommen in der morgigen Lektion zur Sprache. Am Ende des Programms zeigen die Zeilen 30 und 31 die Ergebnisse für den Benutzer an.

Was Sie tun sollten

Was nicht

Verwenden Sie Konstanten, um Ihre Programme verständlicher zu machen.

Versuchen Sie nicht, einer Konstanten einen Wert zuzuweisen, nachdem diese bereits initialisiert wurde.

Zusammenfassung

Diese Lektion hat Ihnen die Hauptkomponenten eines typischen C-Programms vorgestellt. Ausführlich haben wir uns mit den numerischen Variablen beschäftigt, die in C-Programmen dazu dienen, während der Programmausführung Daten zu speichern. Sie haben gelernt, dass der einzig erforderliche Teil jedes C-Programms die Funktion main() ist. Die eigentliche Arbeit erledigen die Programmanweisungen, die den Computer instruieren, die gewünschten Aktionen auszuführen. Weiterhin haben Sie Variablen und Variablendefinitionen kennen gelernt und erfahren, wie man Kommentare im Quellcode verwendet.

Neben der Funktion main() kann ein C-Programm zwei Arten von Funktionen enthalten: Bibliotheksfunktionen, die zum Lieferumfang des Compilers gehören, und benutzerdefinierte Funktionen, die der Programmierer erstellt.

Sie haben gelernt, dass es zwei Kategorien von numerischen Variablen gibt - Integer und Fließkomma. Innerhalb dieser Kategorien gibt es verschiedene spezifische Variablentypen. Welchen Variablentyp - int, long, float oder double - man für eine bestimmte Anwendung einsetzt, hängt von der Natur der Daten ab, die in der Variablen zu speichern sind. Es wurde auch gezeigt, dass man in einem C-Programm eine Variable zuerst deklarieren muss, bevor man sie verwenden kann. Eine Variablendefinition informiert den Compiler über den Namen und den Typ der Variablen.

Ein weiteres Thema dieser Lektion waren Konstanten. Dabei haben Sie die beiden Konstantentypen von C - literale und symbolische Konstanten - kennen gelernt. Im Gegensatz zu den Variablen lässt sich der Wert einer Konstanten während der Programmausführung nicht verändern. Literale Konstanten geben Sie direkt in den Quelltext ein, wann immer der entsprechende Wert erforderlich ist. Symbolischen Konstanten ist ein Name zugewiesen, und unter diesem Namen beziehen Sie sich im Quelltext auf den Wert der Konstanten. Symbolische Konstanten erzeugt man mit der #define-Direktive oder mit dem Schlüsselwort const.

Fragen und Antworten

Frage:
Welche Wirkung haben Kommentare auf ein Programm?

Antwort:
Kommentare sind für den Programmierer gedacht. Wenn der Compiler den Quellcode in Objektcode überführt, ignoriert er Kommentare sowie Leerzeichen, Tabulatoren etc., die nur der Gliederung des Quelltextes dienen (so genannter Whitespace). Das bedeutet, dass Kommentare keinen Einfluss auf das ausführbare Programm haben. Ein Programm mit zahlreichen Kommentaren läuft genauso schnell wie ein Programm, das überhaupt keine oder nur wenige Kommentare hat. Kommentare vergrößern zwar die Quelldatei, was aber gewöhnlich von untergeordneter Bedeutung ist. Fazit: Verwenden Sie Kommentare und Whitespace, um den Quellcode so verständlich wie möglich zu gestalten.

Frage:
Worin besteht der Unterschied zwischen einer Anweisung und einem Block?

Antwort:
Ein Block ist eine Gruppe von Anweisungen, die in geschweiften Klammern ({}) eingeschlossen sind. Einen Block kann man an allen Stellen verwenden, wo auch eine Anweisung stehen kann.

Frage:
Wie kann ich herausfinden, welche Bibliotheksfunktionen verfügbar sind?

Antwort:
Zum Standardumfang von Linux gehören Hunderte von Bibliotheken. Einige von ihnen können Sie anzeigen, indem Sie die Verzeichnisse /lib und /usr/lib auflisten lassen. Am wichtigsten ist die Standard-C-Bibliothek, die /lib/libc- 2.1.1.so oder so ähnlich heißt. Diese Bibliothek enthält eine riesige Auswahl an vordefinierten Funktionen für Tastatureingabe/Bildschirmausgabe, Dateiein-/-ausgabe, Stringmanipulation, Mathematik, Speicherallokation und Fehlerbehandlung. All diese vordefinierten Funktionen sind vollständig in dem libc-Abschnitt der GNU-Infoseiten dokumentiert und nach ihrer Funktionalität kategorisiert. Sie können die Dokumentation mit einem der Informations-Leseprogramme, die bereits am Tag 1 erwähnt wurden, einsehen. Mit einem der beiden folgenden Befehle können Sie direkt zu dem libc-Abschnitt springen:

  1. gnome-help-browser info:libc
    info libc

Mit kdehelp gibt es keinen direkten Weg, die libc-Dokumentation anzusteuern, aber es ist relativ einfach, dort die GNU-Informationsseiten und den darin enthaltenen libc-Abschnitt zu finden.

Frage:
Was passiert, wenn ich eine Zahl mit gebrochenem Anteil einer Integer-Variablen zuweise?

Zahlen mit gebrochenem Anteil kann man durchaus einer Variablen vom Typ int zuweisen. Wenn Sie eine konstante Variable verwenden, gibt der Compiler möglicherweise eine Warnung aus. Der zugewiesene Wert wird am Dezimalpunkt abgeschnitten. Wenn Sie einer Integer-Variablen namens pi zum Beispiel 3.14 zuweisen, enthält pi den Wert 3. Der gebrochene Anteil .14 geht schlicht und einfach verloren.

Frage:
Was passiert, wenn ich eine Zahl einer Variablen zuweise, deren Typ für die Zahl nicht groß genug ist?

Antwort:
Viele Compiler erlauben solche Zuweisungen, ohne einen Fehler dafür auszugeben. Die Zahl wird dabei in der Art eines Kilometerzählers angepasst, d.h. wenn der Maximalwert überschritten ist, beginnt die Zählung wieder von vorn. Wenn Sie einer vorzeichenbehafteten Integer-Variablen (Typ signed int) zum Beispiel 32768 zuweisen, enthält die Variable am Ende den Wert -32768. Und wenn Sie dieser Integer-Variablen den Wert 65535 zuweisen, steht tatsächlich der Wert -1 in der Variablen. Ziehen Sie den Maximalwert, den die Variable aufnehmen kann, vom zugewiesenen Wert ab. Damit erhalten Sie den Wert, der tatsächlich gespeichert wird.

Frage:
Was passiert, wenn ich eine negative Zahl in eine vorzeichenlose Variable schreibe?

Antwort:
Wie in der vorherigen Antwort bereits erwähnt, bringt der Compiler wahrscheinlich keine Fehlermeldung. Er behandelt die Zahl genauso wie bei der Zuweisung einer zu großen Zahl. Wenn Sie zum Beispiel einer Variablen vom Typ unsigned int, die zwei Bytes lang ist, die Zahl -1 zuweisen, nimmt der Compiler den größtmöglichen Wert, der sich in der Variablen speichern lässt (in diesem Fall 65535).

Frage:
Welche praktischen Unterschiede bestehen zwischen symbolischen Konstanten, die man mit der Direktive #define erzeugt, und Konstanten, die man mit dem Schlüsselwort const deklariert?

Antwort:
Die Unterschiede haben mit Zeigern und dem Gültigkeitsbereich von Variablen zu tun. Hierbei handelt es sich um zwei sehr wichtige Aspekte der C-Programmierung, auf die die Tage 8 und 11 eingehen. Fürs Erste sollten Sie sich merken, dass sich ein Programm leichter verstehen lässt, wenn man Konstanten mit #define erzeugt.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, sowie Übungen, die Sie anregen sollen, das Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Die Lösungen zu den Fragen und den Übungen finden Sie in Anhang C.

Quiz

  1. Wie nennt man eine Gruppe von einer oder mehreren C-Anweisungen innerhalb geschweifter Klammern?
  2. Welche Komponente muss in jedem C-Programm vorhanden sein?
  3. Wie fügt man Programmkommentare ein und wozu verwendet man sie?
  4. Was ist eine Funktion?
  5. C kennt zwei Arten von Funktionen. Wie nennt man sie und worin unterscheiden sie sich?
  6. Welche Aufgabe erfüllt die #include-Direktive?
  7. Lassen sich Kommentare verschachteln?
  8. Dürfen Kommentare länger als eine Zeile sein?
  9. Wie nennt man eine Include-Datei noch?
  10. Was ist eine Include-Datei?

Übungen

  1. Schreiben Sie das kleinstmögliche Programm.
  2. Sehen Sie sich das folgende Programm an:
    1:  /* Ueb2-2.c */
    2: #include <stdio.h>
    3:
    4: void anzeigen_zeile(void);
    5:
    6: int main()
    7: {
    8: anzeigen_zeile();
    9: printf("\n C in 21 Tagen\n");
    10: anzeigen_zeile();
    11: printf("\n\n");
    12: return 0;
    13: }
    14:
    15: /* Zeile mit Sternchen ausgeben */
    16: void anzeigen_zeile(void)
    17: {
    18: int zaehler;
    19:
    20: for( zaehler = 0; zaehler < 21; zaehler++ )
    21: printf("*" );
    22: }
    23: /* Programmende */
  3. a. Welche Zeilen enthalten Anweisungen?
  4. b. Welche Zeilen enthalten Variablendefinitionen?
  5. c. Welche Zeilen enthalten Funktionsprototypen?
  6. d. Welche Zeilen enthalten Funktionsdefinitionen?
  7. e. Welche Zeilen enthalten Kommentare?
  8. Schreiben Sie einen Beispielkommentar.
  9. Was bewirkt das folgende Programm? (Geben Sie es ein und starten Sie es.)
    1:  /* Ueb2-4.c */
    2: #include <stdio.h>
    3:
    4: int main()
    5: {
    6: int ctr;
    7:
    8: for( ctr = 65; ctr < 91; ctr++ )
    9: printf("%c", ctr );
    10:
    11: return 0;
    12: }
    13: /* Programmende */
  10. Was bewirkt das folgende Programm? (Geben Sie es ein und starten Sie es.)
    1:  /* Ueb2-5.c */
    2: #include <stdio.h>
    3: #include <string.h>
    4: int main()
    5: {
    6: char puffer[256];
    7:
    8: printf( "Bitte Name eingeben und <Eingabe> druecken:\n");
    9: fgets( puffer,256,stdin );
    10:
    11: printf( "\nIhr Name enthält %d Zeichen (inkl. Leerzeichen).",
    12 strlen( puffer ));
    13:
    14: return 0;
    15: }


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH