Blockkonstanten in C
Eine Konstante kann auch in einem Block definiert werden. Solch eine Konstante wird auch als eine „(zu einem Block) lokale Konstante“ bezeichnet.
constant.c
#include <stdio.h> // printf
int main( void )
{ printf( "%d\n", 12 );
int const zwoelf = 12; printf( "%d\n", zwoelf ); }stdout
12
12
Der Name der Konstante kann verändert werden, ohne daß dies die Funktion des Programmes verändert. Ein irreführender Name, wie in dem folgenden Beispiel, sollte natürlich vermieden werden. Doch für den Computer ist der Name ohne Bedeutung: Er verknüpft nur die verschiedenen Stellen seiner Verwendung.
Bei der Ersetzung eines Namens durch einen anderen, muß dieser überall ersetzt werden, wenn sich die Bedeutung nicht ändern soll.
constant1.c
#include <stdio.h> // printf
int main( void )
{ printf( "%d\n", 12 );
int const zehn = 12; printf( "%d\n", zehn ); }stdout
12
12
Typwandlung bei Initialisierung
Die Festlegung des Wertes der Konstante beim Anlegen wird auch als Initialisierung bezeichnet. Bei der Initialisierung einer Konstanten kann ein Ausdruck verwendet werden, der nicht denselben Typ hat wie die Konstante, wenn C für diesen Fall eine Umwandlung vorsieht. Dies ist ähnlich wie beim Aufruf einer Funktion, die ein Argument vom Typ der Konstanten erwartet mit einem Argument vom Typ des zur Initialisierung verwendeten Ausdrucks. Das folgende Programmbeispiel zeigt, wie von solchen Umwandlungen Gebrauch gemacht wird.
conversion.c
#include <stdio.h> // printf
#include <locale.h> // setlocale, LC_ALL
int main( void )
{ double const d = 300;
int const i = 273.15;
setlocale( LC_ALL, "" );
printf( "%.2f; %d\n", d, i ); }stdout
300,00; 273
Das folgende Programm errechnet aus einem benannten Wert "300", der eine Temperatur in Kelvin darstellen soll, einen anderen benannten Wert: die entsprechende Celsius-Temperatur.
celsius.c
#include <stdio.h> // printf
#include <locale.h> // setlocale, LC_ALL
int main( void )
{ double const kelvin = 300;
double const versatz = 273.15;
double const celsius = kelvin - versatz;
setlocale( LC_ALL, "" );
printf( "%.2f\n", celsius ); }stdout
26,85
Eine Initialisierung einer Konstante kann in vielen Fällen erst zur Laufzeit erfolgen, während ein Ausdruck, der nur Literale und Operatoren enthält, möglicherweise bereits bei der Übersetzung vollständig ausgewertet werden kann. Daher kann die Verwendung von Konstanten anstelle von Literalen möglicherweise etwas Laufzeit kosten. Als Alternative verbleibt noch die Benennung eines Literals durch ein Makro, die sich aber weniger gut mit der Programmiersprache verträgt.
Bereiche in C
Die Zuordnung von Bezeichnern zu ihren Werten ist auf den Textbereich (Gültigkeitsbereich, scope ) der Bezeichner eingeschränkt. Dieser ist zunächst durch den Block des Bezeichners gegeben. Dieser Block spielt die Rolle eines Namensraumes.
Eindeutigkeit von Bezeichnern
Die Namen eines Bereiches bilden eine Menge: Das bedeutet, daß jeder Namen nur einmal darin vorkommen kann. Daher ist der folgende Text keine korrekte C -Übersetzungseinheit.
block.txt
#include <stdio.h> // printf
int main( void )
{ int const c = 1;
int const c = 2;
printf( "%d\n", c ); }Diagnose
"block.txt", line 4: error: "c" has already been declared in the current scope
int const c = 2;
^
"block.txt", line 3: warning: variable "c" was declared but never referenced
{ int const c = 1;
^
Ende der Bereiche
Außerhalb ihres Blocks ist eine lokale Konstante im allgemeinen nicht bekannt.
block0.txt
#include <stdio.h> // printf
int main( void )
{ { int const c = 1; }
printf( "%d\n", c ); }Diagnose
"block0.txt", line 3: warning: variable "c" was declared but never referenced
{ { int const c = 1; }
^
"block0.txt", line 4: error: identifier "c" is undefined
printf( "%d\n", c ); }
^
Verschachtelte Bereiche
Blöcke können ineinander verschachtelt werden, das heißt: ein Block kann einen anderen Block enthalten.
Der innere Block ist dann wieder ein eigener Namensraum. Er „erbt“ alle Definitionen umgebender Namensräume, kann aber alle geerbten Bezeichner in eigenen Definitionen „überschreiben“ (umdefinieren). (Diese Vererbung von Definitionen wird auch bei Klassen verwendet.) Wenn ein innerer Block einen Bezeichner definiert, dann gilt in dem inneren Block auch die Definition des darin definierten Bezeichners ab der Stelle ihres Auftretens. Endet der innere Block, so enden auch die im inneren Block definierten Zuordnungen der Werte zu den Namen.
block1.c
#include <stdio.h> // printf
int main( void )
{ int const c = 9;
int const d = 9;
printf( "0%d%d", c, d );
{ printf( "1%d%d", c, d ); int const c = 8;
printf( "2%d%d", c, d ); }
printf( "3%d%d\n", c, d ); }stdout
099199289399
Der Leser kann jetzt vorhersagen, was das folgende Programm ausgibt.
block2.c
#include <stdio.h> // printf
int main( void )
{ int const c = 3;
{ int const c = 4;
{ int const c = 5;
printf( "%d", c ); }
printf( "%d", c ); }
printf( "%d\n", c ); }
Aufeinanderfolgende Bereiche
Blöcke können einander folgen. Jeder Block ist ein eigener Namensraum und kann daher eigene lokale Konstanten enthalten, deren Namen sich nicht voneinander unterscheiden müssen.
Man könnte einwenden die Konstante "i" sei hier gar keine richtige Konstante, da sie zuerst 3 und dann 7 sei. Tatsächlich handelt es sich hierbei aber um zwei verschiedene Konstanten, die nur den gleichen Bezeichner "i" miteinander teilen. Sie sind aber unterschiedliche Konstanten, da sie in zwei unterschiedlichen, nichtüberlappenden Gültigkeitsbereichen existieren.
block4.cpp
#include <stdio.h> // printf
int main( void )
{ { int const i = 3; printf( "%d\n", i ); }
{ int const i = 7; printf( "%d\n", i ); }}System.out
3
7
Blockeinträge
Obwohl eine Deklaration in einem Block an Stellen stehen darf, an denen auch eine Anweisung stehen darf, ist eine Deklaration doch keine Anweisung. Ein Block enthält Blockeinträge, und ein Blockeintrag ist eine Deklaration oder eine Anweisung.
- 〈compound-statement 〉 ::=
- "{" [〈block-item-list 〉] "}".
- 〈block-item-list 〉 ::=
- 〈block-item 〉 | 〈block-item-list 〉 〈block-item 〉.
- 〈block-item 〉 ::=
- 〈declaration 〉 | 〈statement 〉.
Beispiele
Möbelkauf
Wenn Büromöbel von einem Endverbraucher erworben werden, sind im Jahre 2003 noch 16 % Mehrwertsteuer zu zahlen. Es werden zwei Tische für je 420 Euro und eine Lampe für 145 Euro Warenwert erworben und 3 % Rabatt vereinbart. Wieviel Euro sind zu zahlen?
In dem folgenden Programm werden Konstanten verwendet, um die Ausdrücke möglichst verständlich und lesbar zu machen.
Rechnung.c
#include <stdio.h> // printf
#include <locale.h> // setlocale, LC_ALL
int main( void )
{ double const prozent = 0.01;
double const euro = 100.; // intern wird in Cent gerechnet
{ // aktuelle Konstanten
double const mwst = 16. * prozent;
{ // Warenwerte (Preisliste)
double const tisch = 420 * euro;
double const lampe = 145 * euro;
{ // Dieser Einkauf
double const rabatt = 3. * prozent;
double const warenwert = 2 * tisch + 1 * lampe;
double const netto = warenwert * ( 100 * prozent - rabatt );
double const brutto = netto * ( 100 * prozent + mwst );
double const gerundet = ( int )( brutto + 0.5 );
double const forderung = gerundet;
setlocale( LC_ALL, "" );
printf( "Zu zahlen sind %.2f Euro.\n", forderung / euro ); }}}}stdout
Zu zahlen sind 1108,32 Euro.
Übungsaufgaben
- A Übungsfrage
pluseins.c
#include <stdio.h> // printf int main( void )
{ int const a = 1;
printf( "%d\n", a + 1 );
// Welchen Wert hat die Konstante "a" hier?
}- Welchen Wert hat die Konstante "a" nach der Ausgabe?
- A Übungsaufgabe
- Schreiben Sie in das folgende Programm an die Stelle von "/* ... */" einen Ausdruck, so daß der Wert der Konstanten "inch" die Länge einer Strecke in Zoll angibt, deren Zentimeterlänge durch den Zahlenwert in der Konstanten "cm" angegeben ist. Die ersten drei Zeilen und die letzte Zeile dürfen nicht verändert werden!
umwandlung.c
#include <stdio.h> // printf int main( void )
{ double const cm = 1.29; /* Laenge in cm (Zentimeter) */
double const inch = /* ... */ ;
printf( "%g\n", inch ); /* Laenge in Zoll (ca 0,51 Zoll) */ }- A Übungsaufgabe
sinus1.c
#include <stdio.h> // printf
#include <locale.h> // setlocale, LC_ALL
#include <math.h> // sin, sqrt, pow int main( void )
{ double const x = 1;
double const y = sin( x );
double const z = sqrt( y );
double const u = pow( y, 0.51 );
setlocale( LC_ALL, "" );
printf( "%f\n", z );
printf( "%f\n", u ); }stdout
0,917317
0,915735- Schreiben Sie die Einheit "sinus1.c" so um, daß dieselben Werte ausgegeben werden und dieselben Numerale und Funktionen zur Berechnung verwendet werden, aber keine Konstanten mehr in dem Programm vorkommen. Ersetzen Sie zunächst die Konstante "x" überall durch ihre Definition, also durch den Wert "1" und entfernen Sie die Definition der Konstante "x". Nach diesem Schema können dann alle anderen Definitionen eliminiert werden, bis nur noch die beiden Ausgabeausdrücke übrigbleiben.
- A Übungsaufgabe
sinus2.c
#include <stdio.h> // printf
#include <locale.h> // setlocale, LC_ALL
#include <math.h> // sin, cos, log int main( void )
{ setlocale( LC_ALL, "" );
printf( "%f\n", sin( 1 + log( 2.178 )));
printf( "%f\n", cos( 1 + log( 2.178 ))); }stdout
0,978526
-0,206122- Schreiben Sie die Einheit "sinus2.c" so um, daß derzeit wiederholt vorkommende gleiche Teilausdrücke nur einmal vorkommen. Definieren Sie diese dazu als Konstante oder Konstanten und verwenden Sie die Konstante oder Konstanten dann an deren Stellen. Verwenden Sie aber die in der Einheit "sinus.c" vorkommenden Literale und Operatoren, ohne die Werte von Teilausdrücken dabei auszurechnen. Die Ausgabe des Programmes darf sich dadurch nicht ändern.