C-Programmierung: Variablen und Konstanten
Was sind Variablen?
[Bearbeiten]Als nächstes wollen wir ein Programm entwickeln, das die Oberfläche eines Quaders ermittelt. Bezeichnet man die Länge des Quaders mit , die Breite mit und die Höhe mit , so gilt die Formel
- .
Eine einmal eingeführte Variable, hier also , und auch , ist in der Mathematik im weiteren Gang der Argumentation fest: sie ändert weder ihren Wert noch ihre Bedeutung.
Auch bei der Programmierung gibt es Variablen, diese werden dort allerdings anders verwendet als in der Mathematik: Eine Variable repräsentiert eine Speicherstelle, deren Inhalt während der gesamten Lebensdauer der Variable jederzeit verändert werden kann. Es ist so beispielsweise möglich, beliebig viele Quader nacheinander zu berechnen, ohne jedesmal neue Variablen einführen zu müssen.
Eine Variable kann bei der Programmierung also ihren Wert ändern. Jedoch zeugt es von schlechtem Programmierstil, im Verlauf des Quelltextes die Bedeutung einer Variablen zu ändern. Hat man also in einem Programm zur Kreisberechnung beispielsweise eine Variable namens radius, in der der Radius eines Kreises abgespeichert ist, so hüte man sich davor, in ihr etwa den Flächeninhalt desselben Kreises oder etwas völlig Anderes abzulegen. Der Quelltext würde dadurch erheblich weniger verständlich.
Weiteres zur Benennung von Variablen lese man im Abschnitt Namensgebung nach.
Das Programm zur Berechnung einer Quaderoberfläche könnte etwa wie folgt aussehen:
#include <stdio.h>
int main(void)
{
int a,b,c;
printf("Bitte Länge des Quaders eingeben:\n");
scanf("%d",&a);
printf("Bitte Breite des Quaders eingeben:\n");
scanf("%d",&b);
printf("Bitte Höhe des Quaders eingeben:\n");
scanf("%d",&c);
printf("Quaderoberfläche:\n%d\n", 2 * (a * b + a * c + b * c));
return 0;
}
- Bevor eine Variable in C benutzt werden kann, muss sie definiert werden (Zeile 5). Das bedeutet, Bezeichner (Name der Variable) und (Daten-)Typ (hier
int
) müssen vom Programmierer festgelegt werden, dann kann der Computer entsprechenden Speicherplatz vergeben und die Variable auch adressieren (siehe später: C-Programmierung: Zeiger). Im Beispielprogramm werden die Variablen a, b, und c als Integer (Ganzzahl) definiert. - Mit der Bibliotheksfunktion
scanf
können wir einen Wert von der Tastatur einlesen und in einer Variable speichern (mehr zur Anweisungscanf
im nächsten Kapitel). - Dieses Programm enthält keinen Code zur Fehlererkennung; d. h., wenn man hier statt der ganzen Zahlen etwas anderes oder auch gar nichts eingibt, passieren sehr komische Dinge. Hier geht es zunächst nur darum, die Funktionen zur Ein- und Ausgabe kennenzulernen. Wenn Sie eigene Programme schreiben, sollten Sie darauf achten, solche Fehler zu behandeln.
Deklaration, Definition und Initialisierung von Variablen
[Bearbeiten]Bekanntlich werden im Arbeitsspeicher alle Daten über Adressen angesprochen. Man kann sich dies wie Hausnummern vorstellen: Jede Speicherzelle hat eine eindeutige Nummer, die zum Auffinden von gespeicherten Daten dient. Ein Programm wäre jedoch sehr unübersichtlich, wenn jede Variable mit der Adresse angesprochen werden würde. Deshalb werden anstelle von Adressen Bezeichner (Namen) verwendet. Der Compiler wandelt diese dann in die jeweilige Adresse um.
Neben dem Bezeichner einer Variable, muss der Typ mit angegeben werden. Über den Typ kann der Compiler ermitteln, wieviel Speicher eine Variable im Arbeitsspeicher benötigt.
Der Typ sagt dem Compiler auch, wie er einen Wert im Speicher interpretieren muss. Bspw. unterscheidet sich in der Regel die interne Darstellung von Fließkommazahlen (Zahlen mit Nachkommastellen) und Ganzzahlen (Zahlen ohne Nachkommastellen), auch wenn der ANSI-C-Standard nichts darüber aussagt, wie diese implementiert sein müssen. Werden allerdings zwei Zahlen beispielsweise addiert, so unterscheidet sich dieser Vorgang bei Fließkommazahlen und Ganzzahlen aufgrund der unterschiedlichen internen Darstellung.
Bevor eine Variable benutzt werden kann, müssen dem Compiler der Typ und der Bezeichner mitgeteilt werden. Diesen Vorgang bezeichnet man als Deklaration.
Darüber hinaus muss Speicherplatz für die Variablen reserviert werden. Dies geschieht bei der Definition der Variable. Es werden dabei sowohl die Eigenschaften definiert als auch Speicherplatz reserviert. Während eine Deklaration mehrmals im Code vorkommen kann, darf eine Definition nur einmal im ganzen Programm vorkommen.
Merke
|
Die Literatur unterscheidet häufig nicht zwischen den Begriffen Definition und Deklaration und bezeichnet beides als Deklaration. Dies ist insofern richtig, da jede Definition gleichzeitig eine Deklaration ist (umgekehrt trifft dies allerdings nicht zu).
Beispiel:
int i;
Damit wird eine Variable mit dem Bezeichner i
und dem Typ int
(Integer) definiert. Es wird eine Variable des Typs Integer und dem Bezeichner i vereinbart sowie Speicherplatz reserviert. Mit der Definition ist die Variable auch deklariert. Mit
extern char a;
wird eine Variable deklariert. Das Schlüsselwort extern in obigem Beispiel besagt, dass die Definition der Variablen a irgendwo in einem anderen Modul des Programms liegt. So deklariert man Variablen, die später beim Binden (Linken) aufgelöst werden. Da in diesem Fall kein Speicherplatz reserviert wurde, handelt es sich um keine Definition. Der Speicherplatz wird erst über
char a;
reserviert, was in irgendeinem anderen Quelltextmodul erfolgen muss.
Noch ein Hinweis: Die Trennung von Definition und Deklaration wird hauptsächlich dazu verwendet, Quellcode in verschiedene Module unterzubringen. Bei Programmen, die nur aus einer Quelldatei bestehen, ist es in der Regel nicht erforderlich, Definition und Deklaration voneinander zu trennen. Vielmehr werden die Variablen einmalig vor Gebrauch definiert, wie Sie es im Beispiel aus dem letzten Kapitel gesehen haben.
Für die Vereinbarung von Variablen müssen Sie folgende Regeln beachten:
Variablen mit unterschiedlichen Namen, aber gleichen Typs können in derselben Zeile deklariert werden. Beispiel:
int a,b,c;
Definiert die Variablen int a
, int b
und int c
.
Nicht erlaubt ist aber die Vereinbarung von Variablen unterschiedlichen Typs und Namens in einer Anweisung wie etwa im folgenden:
float a, int b; /* Falsch */
Diese Beispieldefinition erzeugt einen Fehler. Richtig dagegen ist, die Definitionen von float
und int
mit einem Semikolon zu trennen, wobei man jedoch zur besseren Lesbarkeit für jeden Typen eine neue Zeile nehmen sollte:
float a;
int b;
Auch bei Bezeichnern unterscheidet C zwischen Groß- und Kleinschreibung. So können die Bezeichner name
, Name
und NAME
für unterschiedliche Variablen oder Funktionen stehen. Üblicherweise werden Variablenbezeichner klein geschrieben, woran sich auch dieses Wikibuch hält.
Für vom Programmierer vereinbarte Bezeichner gelten außerdem folgende Regeln:
- Sie müssen mit einem Buchstaben oder einem Unterstrich beginnen; falsch wäre z. B.
1_Breite
. - Sie dürfen nur Buchstaben des englischen Alphabets (also keine Umlaute oder 'ß'), Zahlen und den Unterstrich enthalten.
- Sie dürfen nicht einem C-Schlüsselwort wie z. B.
int
oderextern
entsprechen.
Nachdem eine Variable definiert wurde, hat sie keinen bestimmten Wert (außer bei globalen Variablen oder Variablen mit Speicherklasse static
), sondern besitzt lediglich den Inhalt, der sich zufällig in der Speicherzelle befunden hat (auch als "Speichermüll" bezeichnet). Einen Wert erhält sie erst, wenn dieser ihr zugewiesen wird, z. B: mit der Eingabeanweisung scanf
. Man kann der Variablen auch direkt einen Wert zuweisen.
Beispiel:
a = 'b';
oder
summe = summe + zahl;
Verwechseln Sie nicht den Zuweisungsoperator in C mit dem Gleichheitszeichen in der Mathematik. Das Gleichheitszeichen sagt aus, dass auf der rechten Seite das Gleiche steht wie auf der linken Seite. Der Zuweisungsoperator dient hingegen dazu, der linksstehenden Variablen den Wert des rechtsstehenden Ausdrucks zuzuweisen.
Die zweite Zuweisung kann auch wesentlich kürzer wie folgt geschrieben werden:
summe += zahl;
Diese Schreibweise lässt sich auch auf die Subtraktion (-=), die Multiplikation (*=), die Division (/=) und den Modulooperator (%=) und weitere Operatoren übertragen.
Einer Variablen kann aber auch unmittelbar bei ihrer Definition ein Wert zugewiesen werden. Man bezeichnet dies als Initialisierung. Im folgenden Beispiel wird eine Variable mit dem Bezeichner a
des Typs char
(character) deklariert und ihr der Wert 'b' zugewiesen:
char a = 'b';
Ganzzahlen
[Bearbeiten]Ganzzahlen sind Zahlen ohne Nachkommastellen. In C gibt es folgende Typen für Ganzzahlen:
char
(character): 1 Byte [1] bzw. 1 Zeichen (kann zur Darstellung von Ganzzahlen oder Zeichen genutzt werden)short int
(integer): ganzzahliger Wertint
(integer): ganzzahliger Wertlong int
(integer): ganzzahliger Wertlong long int
(integer): ganzzahliger Wert, ab C99
Ist ein Typ-Spezifizierer (long
oder short
) vorhanden, ist die int
Typangabe redundant, d.h.
short int a;
long int b;
ist äquivalent zu
short a;
long b;
Bei der Vereinbarung wird auch festgelegt, ob eine ganzzahlige Variable vorzeichenbehaftet sein soll. Wenn eine Variable ohne Vorzeichen vereinbart werden soll, so muss ihr das Schlüsselwort unsigned
vorangestellt werden. Beispielsweise wird über
unsigned short int a;
eine vorzeichenlose Variable des Typs unsigned short int
definiert. Der Typ signed short int
liefert Werte von mindestens -32.768 bis 32.767. Variablen des Typs unsigned short int
können nur nicht-negative Werte speichern. Der Wertebereich wird dabei nicht kleiner, sondern bleibt gleich groß und verschiebt sich in den Bereich von 0 bis 65.535. [2]
Wenn eine Integervariable nicht explizit als vorzeichenbehaftet oder vorzeichenlos vereinbart wurde, ist sie immer vorzeichenbehaftet. So entspricht beispielsweise
int a;
der Definition
signed int a;
Leider ist die Vorzeichenregel beim Datentyp char
etwas komplizierter:
- Wird
char
dazu verwendet einen numerischen Wert zu speichern und die Variable nicht explizit als vorzeichenbehaftet oder vorzeichenlos vereinbart, dann ist es implementierungsabhängig, obchar
vorzeichenbehaftet ist oder nicht. - Wenn ein Zeichen gespeichert wird, so garantiert der Standard, dass der gespeicherte Wert der nichtnegativen Codierung im Zeichensatz entspricht.
Was versteht man unter dem letzten Punkt? Ein Zeichensatz hat die Aufgabe, einem Zeichen einen bestimmten Wert zuzuordnen, da der Rechner selbst nur in der Lage ist, Dualzahlen zu speichern. Im ASCII-Zeichensatz wird beispielsweise das Zeichen 'M' als 77 Dezimal bzw. 1001101 Dual gespeichert. Man könnte nun auch auf die Idee kommen, anstelle von
char c = 'M';
besser
char c = 77;
zu benutzen. Allerdings sagt der C-Standard nichts über den verwendeten Zeichensatz aus. Wird nun beispielsweise der EBCDIC-Zeichensatz verwendet, so wird aus 'M' auf einmal eine öffnende Klammer (siehe Ausschnitt aus der ASCII- und EBCDIC-Zeichensatztabelle rechts).
ASCII | EBCDIC | Dezimal | Binär |
---|---|---|---|
L | < | 76 | 1001100 |
M | ( | 77 | 1001101 |
N | + | 78 | 1001110 |
… | … | … | … |
Man mag dem entgegnen, dass heute hauptsächlich der ASCII-Zeichensatz verwendet wird. Allerdings werden es die meisten Programmierer dennoch als schlechten Stil ansehen, den codierten Wert anstelle des Zeichens der Variable zuzuweisen, da nicht erkennbar ist, um welches Zeichen es sich handelt, und man vermutet, dass im nachfolgenden Programm mit der Variablen c
gerechnet werden soll.
Für Berechnungen werden Variablen des Typs Character sowieso nur selten benutzt, da dieser nur einen sehr kleinen Wertebereich besitzt: Er kann nur Werte zwischen -128 und +127 (vorzeichenbehaftet) bzw. 0 bis 255 (vorzeichenlos) annehmen (auf einigen Implementierungen aber auch größere Werte). Für die Speicherung von Ganzzahlen wird deshalb der Typ Integer (zu deutsch: Ganzzahl) verwendet. Es existieren zwei Varianten dieses Typs: Der Typ short int
ist mindestens 16 Bit breit, der Typ long int
mindestens 32 Bit. Eine Variable kann auch als int
(also ohne ein vorangestelltes short
oder long
) deklariert werden. In diesem Fall schreibt der Standard vor, dass der Typ int
eine "natürliche Größe" besitzen soll. Eine solche natürliche Größe ist beispielsweise bei einem IA-32 PC (Intel-Architektur mit 32 Bit) mit Windows XP oder Linux 32 Bit. Auf einem 16-Bit-Betriebssystem wie etwa MS-DOS beträgt die Größe 16 Bit. Auf anderen Systemen kann int
aber auch eine andere Größe annehmen. Das Stichwort hierzu lautet Wortbreite.
Mit dem C99-Standard wurde außerdem der Typ long long int
eingeführt. Er ist mindestens 64 Bit breit. Allerdings wird er noch nicht von allen Compilern unterstützt.
Eine Übersicht der Datentypen befindet sich in: C-Programmierung: Datentypen
Erweiterte Zeichensätze
[Bearbeiten]Wie man sich leicht vorstellen kann, ist der "Platz" für verschiedene Zeichen mit einem einzelnen Byte sehr begrenzt, wenn man bedenkt, dass sich die Zeichensätze verschiedener Sprachen unterscheiden. Reicht der Platz für die europäischen Schriftarten noch aus, gibt es für asiatische Schriften wie Chinesisch oder Japanisch keine Möglichkeit mehr, die vielen Zeichen mit einem Byte darzustellen. Bei der Überarbeitung des C-Standards 1994 wurde deshalb das Konzept eines breiten Zeichens (engl. wide character) eingeführt, das auch Zeichensätze aufnehmen kann, die mehr als 1 Byte für die Codierung eines Zeichen benötigen (beispielsweise Unicode-Zeichen). Ein solches "breites Zeichen" wird in einer Variable des Typs wchar_t
gespeichert.
Soll ein Zeichen oder eine Zeichenkette (mit denen wir uns später noch intensiver beschäftigen werden) einer Variablen vom Typ char
zugewiesen werden, so sieht dies wie folgt aus:
char c = 'M';
char s[] = "Eine kurze Zeichenkette";
Wenn wir allerdings ein Zeichen oder eine Zeichenkette zuweisen oder initialisieren wollen, die aus breiten Zeichen besteht, so müssen wir dies dem Compiler mitteilen, indem wir das Präfix L
benutzen:
wchar_t c = L'M';
wchar_t s[] = L"Eine kurze Zeichenkette" ;
Leider hat die Benutzung von wchar_t
noch einen weiteren Haken: Alle Bibliotheksfunktionen, die mit Zeichenketten arbeiten, können nicht mehr weiterverwendet werden. Allerdings besitzt die Standardbibliothek für jede Zeichenkettenfunktion entsprechende äquivalente Funktionen, die mit wchar_t
zusammenarbeiten: Im Fall von printf
ist dies beispielsweise wprintf
.
Kodierung von Zeichenketten
[Bearbeiten]Eine Zeichenkette kann mit normalen ASCII-Zeichen des Editors gefüllt werden. Z. B. : char s []="Hallo Welt";
. Häufig möchte man Zeichen in die Zeichenkette einfügen, die nicht mit dem Editor darstellbar sind. Am häufigsten ist das wohl die Nächste Zeile (engl. linefeed) und der Wagenrücklauf (engl. carriage return). Für diese Zeichen gibt es keine Buchstaben, wohl aber ASCII-Codes. Hierfür gibt es bei C-Compilern spezielle Schreibweisen:
Schreibweise | ASCII-Nr. | Beschreibung |
---|---|---|
\n | 10 | Zeilenvorschub (new line) |
\r | 13 | Wagenrücklauf (carriage return) |
\t | 09 | Tabulator |
\b | 08 | Backspace |
\a | 07 | Alarmton |
\' | 39 | Apostroph |
\" | 34 | Anführungszeichen |
\\ | 92 | Backslash-Zeichen |
\nnn | 1..3 Zeichen mit Oktalcode (0..7) | |
\xhh | 1..2 Zeichen im Hexadezimalcode mit (0..9A..F) |
Fließkommazahlen
[Bearbeiten]Fließkommazahlen (auch als Gleitkomma- oder Gleitpunktzahlen bezeichnet) sind Zahlen mit Nachkommastellen. Der C-Standard kennt die folgenden drei Fließkommatypen:
- Den Typ
float
für Zahlen mit einfacher Genauigkeit. - Den Typ
double
für Fließkommazahlen mit doppelter Genauigkeit. - Den Typ
long double
für zusätzliche Genauigkeit.
Wie die Fließkommazahlen intern im Rechner dargestellt werden, darüber sagt der C-Standard nichts aus. Welchen Wertebereich ein Fließkommazahltyp auf einer Implementierung einnimmt, kann allerdings über die Headerdatei float.h
ermittelt werden.
Im Gegensatz zu Ganzzahlen gibt es bei den Fließkommazahlen keinen Unterschied zwischen vorzeichenbehafteten und vorzeichenlosen Zahlen. Alle Fließkommazahlen sind in C immer vorzeichenbehaftet.
Beachten Sie, dass Zahlen mit Nachkommastellen in US-amerikanischer Schreibweise dargestellt werden müssen. So muss beispielsweise für die Zahl 5,353
die Schreibweise 5.353
benutzt werden.
Speicherbedarf einer Variable ermitteln
[Bearbeiten]Mit dem sizeof
-Operator kann die Länge eines Typs auf einem System ermittelt werden. Im folgenden Beispiel soll der Speicherbedarf in Byte des Typs int
ausgegeben werden:
#include <stdio.h>
int main(void)
{
int x;
printf("Der Typ int hat auf diesem System die Groesse %lu Byte.\n", (unsigned long)sizeof(int));
printf("Die Variable x hat auf diesem System die Groesse %lu Byte.\n", (unsigned long)sizeof x);
return 0;
}
Nach dem Ausführen des Programms erhält man die folgende Ausgabe:
Der Typ int hat auf diesem System die Groesse 4 Byte. Die Variable x hat auf diesem System die Groesse 4 Byte.
Die Ausgabe kann sich auf einem anderen System unterscheiden, je nachdem, wie breit der Typ int
ist. In diesem Fall ist der Typ 4 Byte lang. Wie viel Speicherplatz ein Variablentyp besitzt, ist implementierungsabhängig. Der Standard legt nur fest, dass sizeof(char)
immer den Wert 1 ergeben muss.
Beachten Sie, dass es sich bei sizeof
um keine Funktion, sondern tatsächlich um einen Operator handelt. Dies hat unter anderem zur Folge, dass keine Headerdatei eingebunden werden muss, wie dies bei einer Funktion der Fall wäre. Die in das Beispielprogramm eingebundene Headerdatei <stdio.h>
wird nur für die Bibliotheksfunktion printf
benötigt.
Der sizeof
-Operator wird häufig dazu verwendet, um Programme zu schreiben, die auf andere Plattformen portierbar sind. Beispiele werden Sie im Rahmen dieses Wikibuches noch kennenlernen.
Das Ergebnis des sizeof
-Operators ist ein Wert vom Datentyp size_t
. Es handelt sich um einen vorzeichenlosen Ganzzahl-Datentyp, seine Bitbreite ist implementierungsabhängig. Der C-Standard schreibt keine feste Zuordnung zu unsigned
, unsigned long
oder einem anderen Datentyp vor.
Will man einen size_t
-Wert mit einer Funktion der printf
-Familie ausgeben, sollte man den Wert explizit in den vorzeichenlosen Ganzzahl-Datentyp konvertieren, der dem verwendeten Platzhalter entspricht.
Konstanten
[Bearbeiten]Symbolische Konstanten
[Bearbeiten]Im Gegensatz zu Variablen, können sich konstante Werte während ihrer gesamten Lebensdauer nicht ändern. Dies kann etwa dann sinnvoll sein, wenn Konstanten am Anfang des Programms definiert werden, um sie dann nur an einer Stelle im Quellcode anpassen zu müssen.
Ein Beispiel hierfür ist etwa die Mehrwertsteuer. Wird sie erhöht oder gesenkt, so muss sie nur an einer Stelle des Programms geändert werden. Um einen bewussten oder unbewussten Fehler des Programmierers zu vermeiden, verhindert der Compiler, dass der Konstante ein neuer Wert zugewiesen werden kann.
In der ursprünglichen Sprachdefinition von Dennis Ritchie und Brian Kernighan (K&R) gab es nur die Möglichkeit, mit Hilfe des Präprozessors symbolische Konstanten zu definieren. Dazu dient die Präprozessoranweisung #define
. Sie hat die folgende Syntax:
#define IDENTIFIER token-sequence
Bitte beachten Sie, dass Präprozessoranweisungen nicht mit einem Semikolon abgeschlossen werden.
Durch die Anweisung
#define MWST 19
wird jede vorkommende Zeichenkette MWST durch die Zahl 19 ersetzt. Eine Ausnahme besteht lediglich bei Zeichenketten, die durch Anführungszeichen oder Hochkommata eingeschlossen sind, wie etwa der Ausdruck
"Die aktuelle MWST"
Hierbei wird die Zeichenkette MWST nicht ersetzt.
Die Großschreibung ist nicht vom Standard vorgeschrieben. Es ist kein Fehler, anstelle von MWST
die Konstante MwSt
oder mwst
zu benennen. Allerdings benutzen die meisten Programmierer Großbuchstaben für symbolische Konstanten. Dieses Wikibuch hält sich ebenfalls an diese Konvention (auch die symbolischen Konstanten der Standardbibliothek werden in Großbuchstaben geschrieben).
ACHTUNG: Das Arbeiten mit define
kann auch fehlschlagen: Da define
lediglich ein einfaches Suchen-und-Ersetzen durch den Präprozessor bewirkt, wird folgender Code nicht das gewünschte Ergebnis liefern:
#include <stdio.h>
#define quadrat(x) x*x // fehlerhaftes Quadrat implementiert
int main (int argc, char *argv [])
{
printf ("Das Quadrat von 2+3 ist %d\n", quadrat(2+3));
return 0;
}
Wenn Sie dieses Programm laufen lassen, wird es Ihnen sagen, dass das Quadrat von 2+3 = 11 sei. Die Ursache dafür liegt darin, dass der Präprozessor quadrat(2+3)
durch 2+3 * 2+3
ersetzt.
Da sich der Compiler an die Regel Punkt-vor-Strich-Rechnung hält, ist das Ergebnis falsch. In diesen Fall kann man das Programm wie folgt modifizieren damit es richtig rechnet:
#include <stdio.h>
#define quadrat(x) ((x)*(x)) // richtige Quadrat-Implementierung
int main(int argc,char *argv[])
{
printf("Das Quadrat von 2+3 ist %d\n",quadrat(2+3));
return 0;
}
Konstanten mit const
definieren
[Bearbeiten]Der Nachteil der Definition von Konstanten mit define
ist, dass dem Compiler der Typ der Konstante nicht bekannt ist. Dies kann zu Fehlern führen, die erst zur Laufzeit des Programms entdeckt werden. Mit dem ANSI-Standard wurde deshalb die Möglichkeit von C++ übernommen, eine Konstante mit dem Schlüsselwort const
zu deklarieren. Im Unterschied zu einer Konstante, die über define
definiert wurde, kann eine Konstante, die mit const
deklariert wurde, bei älteren Compilern genau wie eine Variable Speicherplatz verbrauchen. Bei neueren Compilern wie GCC 4.3 ist die Variante mit const
immer vorzuziehen, da sie dem Compiler ein besseres Optimieren des Codes erlaubt und die Kompiliergeschwindigkeit erhöht. Beispiel:
#include <stdio.h>
int main()
{
const double pi = 3.14159;
double d;
printf("Bitte geben Sie den Durchmesser ein:\n");
scanf("%lf", &d);
printf("Umfang des Kreises: %lf\n", d * pi);
pi = 5; /* Fehler! */
return 0;
}
In Zeile 5 wird die Konstante pi
deklariert. Ihr muss sofort ein Wert zugewiesen werden, ansonsten gibt der Compiler eine Fehlermeldung aus.
Damit das Programm richtig übersetzt wird, muss Zeile 11 entfernt werden, da dort versucht wird, der Konstanten einen neuen Wert zuzuweisen. Durch das Schlüsselwort const
wird allerdings der Compiler damit beauftragt, genau dies zu verhindern.
Sichtbarkeit und Lebensdauer von Variablen
[Bearbeiten]In früheren Standards von C musste eine Variable immer am Anfang eines Anweisungsblocks vereinbart werden. Seit dem C99-Standard ist dies nicht mehr unbedingt notwendig: Es reicht aus, die Variable unmittelbar vor der ersten Benutzung zu vereinbaren.[3]
Ein Anweisungsblock kann eine Funktion, eine Schleife oder einfach nur ein durch geschwungene Klammern begrenzter Block von Anweisungen sein. Eine Variable lebt immer bis zum Ende des Anweisungsblocks, in dem sie deklariert wurde.
Wird eine Variable/Konstante z. B. im Kopf einer Schleife vereinbart, gehört sie laut C99-Standard zu dem Block, in dem auch der Code der Schleife steht. Folgender Codeausschnitt soll das verdeutlichen:
for (int i = 0; i < 10; i++)
{
printf("i: %d\n", i); // Ausgabe von lokal deklarierter Schleifenvariable
}
printf("i: %d\n", i); // Compilerfehler: hier ist i nicht mehr gültig!
Existiert in einem Block eine Variable mit einem Namen, der auch im umgebenden Block verwendet wird, so greift man im inneren Block über den Namen auf die Variable des inneren Blocks zu, die äußere wird überdeckt.
#include <stdio.h>
int main()
{
int v = 1;
int w = 5;
{
int v;
v = 2;
printf("%d\n", v);
printf("%d\n", w);
}
printf("%d\n", v);
return 0;
}
Nach der Kompilierung und Ausführung des Programms erhält man die folgende Ausgabe:
2 5 1
Erklärung: Am Anfang des neuen Anweisungsblocks in Zeile 8, wird eine neue Variable v
definiert und ihr der Wert 2 zugewiesen. Die innere Variable v
"überdeckt" nun den Wert der Variable v
des äußeren Blocks. Aus diesem Grund wird in Zeile 10 auch der Wert 2 ausgegeben. Nachdem der Gültigkeitsbereich der inneren Variable v
in Zeile 12 verlassen wurde, existiert sie nicht mehr, so dass sie nicht mehr die äußere Variable überdecken kann. In Zeile 13 wird deshalb der Wert 1 ausgeben.
Sollte es in geschachtelten Anweisungblöcken nicht zu solchen Überschneidungen von Namen kommen, kann in einem inneren Block auf die Variablen des äußeren zugegriffen werden. In Zeile 11 kann deshalb die in Zeile 6 definierte Zahl w
ausgegeben werden.
- ↑ Der C-Standard legt die Breite eines Bytes über die Konstante CHAR_BIT als implementierungsabhängig fest, die die Anzahl der Bits festlegt. Vorgeschrieben sind >= 8, üblich ist CHAR_BIT == 8. Allerdings ist dies nur von Interesse, wenn Sie Programme entwickeln wollen, die wirklich auf jedem auch noch so exotischen Rechner laufen sollen.
- ↑ Wenn Sie nachgerechnet haben, ist Ihnen vermutlich aufgefallen, dass 32.768 + 32.767 nur 65.534 ergibt, und nicht 65.535, wie man vielleicht vermuten könnte. Das liegt daran, dass der Standard nichts darüber aussagt, wie negative Zahlen intern im Rechner dargestellt werden. Werden negative Zahlen beispielsweise im Einerkomplement gespeichert, gibt es zwei Möglichkeiten, die 0 darzustellen, und der Wertebereich verringert sich damit um eins. Verwendet die Maschine (etwa der PC) das Zweierkomplement zur Darstellung von negativen Zahlen, liegt der Wertebereich zwischen –32.768 und +32.767.
- ↑ Beim verbreiteten Compiler GCC muss man hierfür explizit Parameter
-std=c99
übergeben