![]() |
Algorithmen & Datenstrukturen
|
| "C retains the basic philosophy that programmers know what they are doing". |
Genereller Charakter: Der sehr kleine Sprachumfang ist fast immer ungenügend (z.B. keine I/O-Möglichkeit), deshalb finden Routinen aus definierten Standard-Bibliotheken Anwendung.
In einigen Fällen werden zur Beschreibung der Syntax von C sogenannte Syntaxdiagramme verwendet. Dies sind grafische Darstellungen der Abfolge von Elementen der Sprache C. Sie können mit den Diagrammen sozusagen "mit dem Zeigefinger" nachprüfen, ob Ihre Programmsyntax mit der Sprachdefinition übereinstimmt. Jedes Diagramm stellt eine Produktionsregel dar, was nichts weiter bedeutet als eine erlaubte Folge von Symbolen. Dabei gibt es zwei Formen von Symbolen:

Ein Programm ist dann syntaktisch richtig, wenn es durch eine Folge von Terminalsymbolen dargestellt werden kann (wobei die Meta-Variablen nach und nach durch Terminalsymbole aufgelöst werden).

| Form | Zeichen |
|---|---|
| Buchstaben | A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k 1 m n o p q r s t u v w x y z |
| Ziffern | 0 1 2 3 4 5 6 7 8 9 |
| Unterstrich | _ |
| Sonderzeichen | ! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ { | } ~ |
Neben den sichtbaren Zeichen verfügt C noch über weitere Zeichen:
| Zeichen | Bedeutung |
|---|---|
| Leertaste | Space, Leerzeichen |
| Warnung | BEL, Klingel, Signalton |
| Backspace | BS, Rückschritt |
| Formfeed | FF, Sprung zum nächsten Seitenanfang |
| Newline | NL, Zeilenende, "Line Feed", Zeilenvorschub |
| Return | CR, "Carriage Return", Sprung zum Anfang der aktuellen Zeile |
| Tab | HT, Horizontaler Tabulator |
Sie sehen, daß die nationalen Sonderzeichen, wie z. B. Umlaute, nicht zum Zeichensatz von C gehören. Diese können jedoch in Literalen (Strings) auftauchen. Die oben vorgestellten weiteren Zeichen des Zeichensatzes in C werden durch Escape-Sequenzen realisiert. Diese Technik wird weiter unten vorgestellt.
Namen bestehen in C aus Buchstaben, Ziffern und dem Unterstrich. Sie dürfen nicht mit einer Ziffer beginnen.
Das \ ist eine logische Zeile
#include <stdio.h>Durch diese Compilerdirektive wird der Inhalt der Datei stdio.h dem Quelltext hinzugefügt. Die Lage der Headerdateien im Dateisystem des Rechners ist über Compiler-Voreinstellungen festgelegt. Meist heißt das Verzeichnis auch "include". Eine Weitere Form der include-Anweisung ist:
#include "myfile.h"Hier wird normalerweise in aktuellen Verzeichnis gesucht. Man kann bei der Headerdatei auch einen Dateipfad angeben.
Anweisungsblöcke werden durch geschweifte Klammern zusammengefaßt.
main() wird auch im Programm nur definiert, aber nicht aufgerufen. Sie werden niemals einen Befehl main(); in einem C-Programm finden. main() hat eine Sonderstellung: Es handelt sich um die Funktion, die beim Programmstart auf jeden Fall ausgeführt wird.
Die Funktion main() liefert bei einem erfolgreichen Programmlauf den Wert Null als Ergebnis. Manche Compiler verlangen, daß am Ende von main() ausdrücklich ein Rückgabewert angegeben wird. Ergänzen Sie dann das Programm nach der letzten Anweisung, also vor der letzten geschweiften Klammer, um die Anweisung:
return (0)
oder
return 0
Mit dieser Angabe kann das Betriebssystem, das das Programm gestartet hat, überprüfen, ob das aufgerufene Programm ordnungsgemäß beendet worden ist. Im Fehlerfall wird ein Wert ungleich Null zurückgegeben.
| Bibliothek | Aufgabenbereiche |
|---|---|
| assert.h | Überprüfung von Bedingungen |
| ctype.h | Typkonvertierungen und Typtests |
| errno.h | Behandlung von Systemfehlern |
| float.h | Fließkomma-Bibliothek |
| limits.h | Grenzen für Datentypen |
| locale.h | Verwaltung der lokalen Struktur |
| math.h | mathematische Funktionen |
| signal.h | Prozeßsteuerung |
| stddef.h | Standardkonstanten |
| stdio.h | Standardeingabe und -ausgabe |
| stdlib.h | Standardbibliotheksfunktionen |
| string.h | Funktionen zur Stringverarbeitung |
| time.h | Zeitmanagement |
Beim Programmieren in C verwenden Sie entweder die oben angebotenen Standardfunktionen oder selbstdefinierte Funktionen oder Sie beziehen vollständige Funktionsbibliotheken für alle nur denkbaren Anwendungsgebiete. Damit ist C so leistungsfähig, daß sowohl Betriebssysteme als auch beliebige Anwendungen programmiert werden können. Eine Beschreibung der Bibliotheken befindet sich im Anhang.
| auto | double | int | struct |
|---|---|---|---|
| break | else | long | switch |
| case | enum | register | typedef |
| char | extern | return | union |
| const | float | short | unsigned |
| continue | for | signed | void |
| default | goto | sizeof | volatile |
| do | if | static | while |
Für C ist der Unterschied zwischen Groß- und Kleinschreibung wichtig.
Die Schlüsselwörter müssen also genauso geschrieben werden, wie sie vorgestellt worden sind. Diese Schlüsselwörter bilden den Kern der Programmiersprache C.
printf("Hello World!\n");
finden Sie die Escape-Sequenz \n, die den Cursor zum Anfang der nächsten Zeile bewegt (bzw. auf dem Drucker eine neue Zeile beginnt). Nichtdruckbare Zeichen werden über solche Escape-Sequenzen, eingeleitet durch "\" in den Programmtext eingefügt (meist, für die Ein- und Ausgabe oder in Strings). Hier eine Übersicht über einige Escape-Sequenzen:
| Zeichen | Escape-Sequenz | Bedeutung |
|---|---|---|
| " | \" | Anführungszeichen |
| ' | \' | Apostroph |
| ? | \? | Fragezeichen |
| \ | \\ | Backslash |
| BEL | \a | Bell |
| BS | \b | Backspace |
| FF | \f | Formfeed |
| NL | \n | Newline |
| CR | \r | Carriage Return |
| HT | \t | Horizontal-Tabulator |
| VT | \v | Vertical-Tabulator |
#include <stdio.h> /* header file '... .h' */
int main(void) /* Hauptprogramm 'main()' */
{ /* Beginn ... */
printf("Hello world!\n"); /* schreibt "..." auf stdout */
return 0; /* alles okay ... */
} /* ... Ende Block */
Anmerkungen:
#include <stdio.h>
int main(void)
{
int n, m; /* Deklaration der Variablen
'n' und 'm' als integer */
printf("2 ganze Zahlen eingeben: ");
scanf("%d%d", &n, &m); /* Werte einlesen von stdin */
printf("\nSumme von %d und %d: %d\n",
n, m, n+m); /*Ergebnis*/
return 0;
}
Anmerkungen:
Damit haben Sie auch schon eine wichtige Kontrollstruktur kennengelernt. Als Block wird bezeichnet, was in einem Klammerpaar { } eingeschlossen ist. Ein solcher Block faßt die in ihm enthaltenen Statements zu einem sog. "Compound-Statement" zusammen; dies ist syntaktisch äquivalent zu einem einzelnen Statement. Blöcke verfügen über bemerkenswerte Eigenständigkeit: innerhalb jedes Blockes können lokale Variablen deklariert werden. Beispiel:
int i=3;
{
int i=4;
printf("%d", i); /* output: 4 */
}
printf("%d", i); /* output: 3 */
Noch ein Beispiel: Vorstellung einfacher Datentypen und ihrer Ausgabe mit
printf(); Verwendung der Zuweisung und der arithmetischen
Operatoren +, -, *, / und %.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* Beachten Sie: Innerhalb eines Paares geschweifter Klammern */
/* immer zuerst alle benoetigten lokalen Variablen deklarieren */
/* und dann die Anweisungen schreiben. */
/* Deklaration der (lokalen) Variablen */
int i,ii; /* printf-Format: %d */
short int h,hh; /* printf-Format: %hd */
long int l,ll; /* printf-Format: %ld */
char c,cc; /* printf-Format: %c */
float f,ff; /* printf-Format: %f | %e | %g */
double d,dd; /* printf-Format: %lf | %le | %lg */
long double q,qq; /* printf-Format: %Lf | %Le | %Lg */
/* Addition zweier int-Zahlen */
i=1;
ii=-2;
printf("int: i=%d, ii=%d, i+ii=%d\n",i,ii,i+ii);
/* Ganzzahl-Division zweier short-int-Zahlen */
h=7;
hh=2;
printf("short: h=%hd, hh=%hd, h/hh=%hd\n",h,hh,h/hh);
/* Divisionsrest zweier long-int-Zahlen */
l=7l;
ll=2l;
/* Achtung beim Prozent: Zur Ausgabe doppelt schreiben */
printf("long: l=%ld, ll=%ld, l%%ll=%ld\n",l,ll,l%ll);
/* Ausgabe zweier Zeichen */
c='A';
cc='B';
printf("char: c=%c, cc=%c\n",c,cc);
/* Probieren Sie auch die Zeichen '\n', '\t', '\b' aus */
/* Multiplikation zweier float-Zahlen */
f=1.3E4f;
ff=-5.7E3f;
printf("float: f=%f, ff=%f, f*ff=%f\n",f,ff,f*ff);
/* Division zweier double-Zahlen (Gleitkomma-Division) */
d=1.3E4;
dd=-5.7E3;
printf("double: d=%lf, dd=%lf, d/dd=%lf\n",d,dd,d/dd);
/* Subtraktion zweier long-double-Zahlen */
q=1.3E4l;
qq=1.299999999E4l;
printf("long double: q=%Lf, qq=%Lf, q-qq=%Lf\n",q,qq,q-qq);
/* Ergebnis eines Vergleichs ist 0 oder 1 als int-Zahl */
c='A';
cc='B';
printf("Vergleich von %c und %c ergibt %d.\n",c,cc,c==cc);
/* Mehrere Zuweisungen in einem Ausdruck ausführen */
c=cc='X';
printf("Zwei Ixe: %c%c\n",c,cc);
/* Man kann Gleitkommazahlen in verschiedenen Formaten
ausgeben. Die 20 hinter dem % füllt links mit Leerzeichen
auf 20 Zeichen auf. */
dd=4E0/3.0;
printf("Gleit- > %20lf %20le %20lg\n",dd,dd,dd);
dd=4E9/3.0;
printf("komma- > %20lf %20le %20lg\n",dd,dd,dd);
dd=4E20/3.0;
printf("formate> %20lf %20le %20lg\n",dd,dd,dd);
/* Funktion main() und damit Programm beenden */
return(0);
}
a = 7den Wert 7.
a = b;steht der Ausdruck auf der rechten Seite für einen Wert, während der Ausdruck auf der linken Seite die Stelle angibt, an der der Wert zu speichern ist. Wenn wir das Beispiel noch etwas modifizieren, wird der Unterschied noch deutlicher:
a = a + 42;Der Name a, der ja auch einen einfachen Ausdruck darstellt, wird hier in unterschiedlicher Bedeutung verwendet. Rechts vom Zuweisungsoperator ist der Wert gemeint, der in der Speicherzelle a gespeichert ist, und links ist die Adresse der Speicherzelle a gemeint, in der der Wert des Gesamtausdrucks auf der rechten Seite gespeichert werden soll. Aus dieser Stellung links oder rechts des Zuweisungsoperators wurden auch die Begriffe L-Wert (L-Value) und R-Wert (R-Value) abgeleitet.
Ein Ausdruck stellt einen L-Wert dar, wenn er sich auf ein Speicherobjekt bezieht. Ein solcher Ausdruck kann links und rechts des Zuweisungsoperators stehen.Ein Ausdruck, der keinen L-Wert repräsentiert, stellt einen R-Wert dar. Er darf nur rechts des Zuweisungsoperators stehen. Einem R-Wert kann man also nichts zuweisen.
Ein Ausdruck, der einen L-Wert darstellt, darf auch rechts vom Zuweisungsoperator
stehen, er hat dann aber, wie oben erwähnt, eine andere Bedeutung. Steht ein L-Wert
rechts neben dem Zuweisungsoperator, so wird dessen Namen bzw. Adresse
benötigt, um an der entsprechenden Speicherstelle den Wert der Variablen
abzuholen. Dieser Wert wird dann zugewiesen. Links des Zuweisungsoperators
muss immer ein L-Wert stehen, da man den Namen bzw. die Adresse einer
Variablen braucht, um an der entsprechenden Speicherstelle den zugewiesenen
Wert abzulegen.
Des Weiteren wird zwischen modifizierbarem und nicht modifizierbarem L-Wert
unterschieden. Ein nicht modifizierbarer L-Wert ist z.B. der Name eines Arrays. Dem
Namen entspricht zwar eine Adresse. Diese ist jedoch konstant und kann nicht
modifiziert werden. Auf der linken Seite einer Zuweisung darf also nur ein
modifizierbarer L-Wert stehen, jedoch kein R-Wert oder ein nicht modifizierbarer
L-Wert. Nicht modifizierbare L-Werte liegen dann vor, wenn es sich bei dem L-Wert
um einen Arraytyp, einen unvollständigen Typ, einen mit dem Typ-Attribut
const versehenen Typ oder um einen structure- oder
union-Typ handelt, von dem eine seiner Komponenten einen mit dem Attribut
const versehenen Typ hat.
Bestimmte Operatoren können nur auf L-Werte angewandt werden. So kann man den Inkrementoperator ++ oder den Adressoperator & nur auf L-Werte anwenden. 5++ ist falsch, i++ ist korrekt (falls i eine Variable darstellt). Der Inhaltssoperator * kann auf L- und R-Werte angewandt werden.
Ein L-Wert ist also ein Ausdruck, der ein Datenobjekt bezeichnet. Außer dem schon besprochenen Fall eines L-Wertes auf der rechten Seite einer Zuweisung gibt es viele weitere Fälle bei denen ein L-Wert in den Wert umgewandelt wird, der in dem entsprechenden Objekt gespeichert ist. Ein L-Wert, der nicht von einem Array-Typ ist, wird stehts in den Wert gewandelt, der in dem entsprechenden Objekt gespeichert ist und ist damit kein L-Wert mehr, es sei denn das Objekt ist:

if (Bedingungsausdruck) Anweisung;
if (Bedingungsausdruck) Anweisung1; else Anweisung2;Pascal-Programmierer sollten beachten, daß hier vor dem "else" sehr wohl ein Semikolon steht. Beispiel 1:
if (a > b) max = a; else max = b;Beispiel 2:
main()
{
int x, y, z;
x = 3; z = 2;
if ( z != 0 ) /* auch: if ( z ) */
y = x/z;
else
printf("Division durch Null\n");
printf("y = %d\n",y);
}
Wichtig: else gehört im Zweifel immer zum letzten if.
Gegebenenfalls mit { ... } klarstellen, was wohin gehört. Beispiel:
if(i==1)
if(j==2)
printf("i = 1, j = 2.\n");
else
printf("i = 1, j unbekannt.\n");
else
if(j==2)
printf("i unbekannt, j = 2.\n");
else
printf("i und j unbekannt.\n");
Ketten: if (a) b; else if (c) d; else if (e) f; else g;. Beispiel:
if(i==1)
printf("i ist eins.\n");
else if(i==2)
printf("i ist zwei.\n");
else if(i==3)
printf("i ist drei.\n");
else if(i==4)
printf("i ist vier.\n");
else
printf("i ist eine Zahl.\n");
Vorsicht: Nicht if (...) { ... } ; else { ... }!Beispiel: Simulation eines Taschenrechners
#include <stdio.h>
void main(void)
{
char operator;
int wert1, wert2, erg ;
if (scanf("%d %c %d",&wert1, &operator, &wert2) == 3)
{
if (operator== '+')
erg = wert1 + wert2;
else if (operator == '-')
erg = wert1- wert2;
else if (operator=='*')
erg = wert1 * wert2;
else if (operator=='/')
erg = wert1 / wert2;
else printf("Falsche Eingabe\n");
printf("%d %c %d=%d\n",wert1,operator,wert2,erg);
}
else
printf("zu wenig Eingabewerte\n");
Vorsicht! Die Bedingungen werden nur solange ausgewertet, bis das Ergebnis feststeht. Beispiel:
if(x <= 0 || ((c = getchar()) != EOF))
printf("%d\n", c);
Das Zeichen wird nur gelesen, wenn x > 0.
(EOF ist eine symbolische Konstante, die in stdio.h definiert ist.
Sie hat bei Dateiende den Wert -1.)
Beispiel: Im EStG, Par. 32, ist folgendes Verfahren zur Berechnung der
Einkommenssteuer festgelegt:
Die Einkommenssteuer beträgt in deutsche Mark
#include<stdio.h>
#include<math.h>
int main(void)
{
double steuer, einkommen, y;
scanf(Daten,"%lf",&einkommen);
if (einkommen < 0)
printf("Einkommen sollte positiv sein\n");
else
{
/* Zahl muss abgerundet werden */
einkommen = floor(einkommen / 54) * 54;
if (einkommen < 4753)
steuer = 0;
else if (einkommen < 18036)
steuer = 0.22 * einkommen - 1045;
else if (einkommen < 80028)
{
y = (einkommen - 17982)/10000.0;
steuer = (((0.34*y - 21.58)*y + 392)*y + 2200)*y + 2911;
}
else if (einkommen < 130032)
{
y = (einkommen-79974)/10000.0;
steuer = (70*y + 4900)*y + 26974;
}
else steuer = 0.56 * einkommen - 19561;
}
printf("%10g %10g\n",einkommen,steuer);
}
Bedingungsausdruck ? Ausdruck1 : Ausdruck2

Ein Ausdruck mit dem Bedingungsoperator kann nicht alleine stehen (wie if ...),
sondern innerhalb eines Ausdrucks (z. B. einer Wertzuweisung). Ist das Ergebnis des
Bedingungsausdrucks wahr (!= 0), wird Ausdruck1 verwendet, sonst Ausdruck2.
Beispiele: Vorzeichenoperator (Zahl negativ: vz = -1, Zahl positiv oder Null: vz = +1)
und Maximum wie oben bei "if...":
vz = (Zahl < 0) ? -1 : +1
max = (a > b) ? a : b
switch (Ausdruck)
{
case W1: Anweisung1;
case W2: Anweisung2;
...;
case Wn: Anweisungn;
default: Anweisungdef;
}

Wenn der Ausdruck den Wert Wi besitzt, wird die Anweisung(sfolge) Anweisungi und alle folgenden ausgeführt. Will man das vermeiden, muß der jeweilige switch-Zweig durch break; abgeschlossen werden. Das sieht dann so aus:
switch (Ausdruck)
{
case W1: Anweisung1; break;
case W2: Anweisung2; break;
...; break;
case Wn: Anweisungn; break;
default: Anweisungdef;
}
Den Unterschied machen auch die beiden folgenden Ablaufdiagramme sichtbar:

Beispielprogramm:
/* Beispiel fuer switch: Einlesen einer Zahl ohne Benutzung von scanf */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* Lokale Variablen - teilweise mit Anfangswert */
unsigned long zahl=0;
enum boolean {false,true} ende = false;
int zeichen;
printf("Geben Sie eine Zahl ein.\n");
while(!ende)
{
/* Lesen eines Zeichens aus der Eingabe */
zeichen=getchar();
switch(zeichen)
/* oder kürzer: switch(zeichen=getchar()) */
{
case '0':
zahl=zahl*10;
break;
case '1':
zahl=zahl*10+1;
break;
case '2':
zahl=zahl*10+2;
break;
case '3':
zahl=zahl*10+3;
break;
case '4':
zahl=zahl*10+4;
break;
case '5':
zahl=zahl*10+5;
break;
case '6':
zahl=zahl*10+6;
break;
case '7':
zahl=zahl*10+7;
break;
case '8':
zahl=zahl*10+8;
break;
case '9':
zahl=zahl*10+9;
break;
default:
ende=true;
break;
}
}
printf("Zahl %lu eingelesen, danach folgte '%c'.", zahl, zeichen);
return(0);
}
Zweites Beispiel. Berechnung der Datumsdifferenz zweier Tage.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void main()
{
int tag1, mon1, jahr, tag2, mon2, tage, i;
printf("Anzahl der Tage zwischen zwei Tagen\n");
printf("Jahr:");
scanf("%i",&jahr);
printf("1. Datum: Tag:");
scanf("%i",&tag1);
printf("Monat:");
scanf("%i",&mon1);
printf("\n2. Datum: Tag:");
scanf("%i",&tag2);
printf("Monat:");
scanf("%i",&mon2);
tage = tag2 - tag1;
for (i=mon1; i<=mon2-1; i++)
switch (i)
{
case 2: if (jahr%4 == 0 && jahr%100 != 0 || jahr%400 == 0)
tage = tage + 29;
else
tage = tage + 28;
break;
case 4:
case 6:
case 9:
case 11: tage = tage + 30; break;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12: tage = tage + 31; break;
};
printf("\n Es liegen %3i Tage dazwischen.",tage);
}
Ein ganz einfacher Rechner:
#include <stdio.h>
#include <stdlib.h>
main()
{
double op1, op2;
char op[2];
while(scanf("%lf %1s %lf", &op1, op, &op2) != EOF) {
switch(op[0])
{
case '+': op1 = op1 + op2; break;
case '-': op1 = op1 - op2; break;
case '*': op1 = op1 * op2; break;
case '/': op1 = op1 / op2; break;
default: printf("illegal operator\n"); continue;
}
printf(" = %g\n", op1);
}
}
while (Bedingungsausdruck)
Anweisung;

Es folgen nun einige Beispiele für while-Schleifen, zuerst die Berechnung des größten gemeinsamen Teilers:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int i,j;
i = 2*2*3*3*5*17;
j = 2*2*3*5*5*13;
/* ggt = 2*2*3*5 = 60 */
if(i <= 0 || j <= 0)
{
printf("Unzulaessige Werte\n");
}
else
{
while(i != j)
{
i = i%j; if (i == 0) i = j;
j = j%i; if (j == 0) j = i;
}
printf("ggT(i,j) = %d\n",i);
}
Das folgende Programm berechnet Quadratzahlen zwischen zwei Grenzen:
main() /* Tabelle der Quadratzahlen */
{
int anf, ende, spanz;
printf("Berechnung der Quadratzahlen von: ");
scanf("%d", &anf);
printf("Berechnung der Quadratzahlen bis: ");
scanf("%d", &ende);
printf("Anzahl der Spalten fuer Ausgabe : ");
scanf("%d", &spanz);
putchar('\n');
while(anf <= ende)
{
printf("%3d x %3d = %6d ", anf, anf, anf*anf);
if (anf % spanz)
printf(" ");
else
putchar('\n');
anf = anf + 1;
}
putchar('\n');
}
for (Ausdruck1; Ausdruck2; Ausdruck3)
Anweisung;

for(init; bedingung; inkrement) anweisung;entspricht
init;
while(bedingung)
{
anweisung;
inkrement;
}
Ein paar Beispiele für die Anwendung des for-Statements:
| Programmcode | Ergebnis |
|---|---|
for(i=0; i<4; i++){
for(j=0; j<4; j++){
printf("*");
}
printf("\n");
}
|
**** **** **** **** |
for(i=1; i<=7; i++){
for(j=1; j<= 4 - (abs(i-4)); j++){
printf("*");
}
printf("\n");
}
|
* ** *** **** *** ** * |
for(i=1; i<=7; i++){
for(j=1; j<= abs(i-4); j++){
printf(" ");
}
for(j=1; j<= 4 - (abs(i-4)); j++){
printf("*");
}
printf("\n");
}
|
* ** *** **** *** ** * |
for(i=1; i<=7; i++){
int num1 = 3 - abs(4-i),
num2 = 5 - 2 * num1;
for(j=0; j< num1; j++){
printf(" ");
}
printf("*");
for(j=0; j< num2; j++){
printf(" ");
}
if( num2 > 0)
printf("*");
printf("\n");
}
|
* * * * * * * * * * * * * |
Das folgende komplette Programm erzeugt eine Multiplikationstabelle:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int i,j;
printf(" * | 1 2 3 4 5 6 7 8 9 10\n");
printf("---+----------------------------------------\n");
for(i=1; i<=10; i++)
{
printf("%2d |",i);
for(j=1; j<=10; j++)
{
printf(" %3d",i*j);
}
printf("\n");
}
}
do
Anweisung;
while (Bedingungsausdruck);

Schleifenkontrolle am Ende, Berechnung von 5!:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int i,j;
i = j = 1;
do
{
j = j*i;
i = i+1;
} while (i <= 5);
printf("5! = %d\n",j);
}
Marke:
Diese Marke kann mit dem goto-Befehl angesprungen werden.
goto Marke;
Daneben gibt es noch zwei weitere unstrukturierte Verzweigungsbefehle:
break
bricht die Bearbeitung der aktuellen strukturierten Anweisung ab (siehe
switch).
continue
führt einen Sprung zum Schleifenende aus, um dann mit dem nächsten
Schleifendurchlauf fortzufahren.

Assertions
An bestimmten Punkten im Programm werden die Zusicherungen festgeschrieben und zwar als logischer Ausdruck in der assert-Anweisung ("Zusicherung"):
assert(expression);expression ist ein logischer Ausdruck. assert() ust in der Include-Datei assert.h definiert. der Ausdruck wird jedesmal ausgewertet, wenn assert-Anweisung ausgeführt wird. Das Ergebnis kann sein:
| != 0: | keine Aktion |
| 0: | Programm wird sofort abgebrochen unter Angabe von Dateiname, Zeilennummer und Klartext der verletzten Zusicherung. |
Beispiel: Offenkundig fehlerhaftes Quelltextfragment (statt <= müßte < stehen):
// Werte von 0...9 verarbeiten
for(int i = 0; i <= 10; i++)
{
assert(i >= 0 && i < 10);
... i verarbeiten ...
}
Liefert bei Ausführung die Meldung
main.C:8: failed assertion `i >= 0 && i < 10'Alle assert-Anweisungen eines Quelltextes lassen sich abschalten durch Compilerschalter -DNDEBUG. Der Code wird dann übersetzt, als ob keine assert-Anweisung existierte. Daher werden assert-Anweisungen während Entwicklung und Test eines Programms aktiviert und vor der Auslieferung der Software abgeschaltet.
Der Einsatz von Zusicherungen erfordert gesunden Menschenverstand (!). Es muß ein goldener Mittelweg zwischen Trivialitäten und Unwägbarkeiten gefunden werden. Die korrekte Abwicklung einzelner Sprachkonstrukte kann vorausgesetzt werden und muß nicht geprüft werden. Bedingungen, die an äußere Einflüsse (Benutzereingaben, Dateiinhalte, ...) gebunden sind, muß die Programmlogik behandeln, sie lassen sich nicht mit Assertions abhandeln.
Beim folgenden Beispiel wird mittels assert überprüft, ob der stream auch wirklich ungleich NULL ist. Ist er NULL, dann ist er mit Sicherheit nicht gültig, unser Ausdruck liefert einen Wert von 0 (Falsch) und das Programm wird angehalten. Schreiben Sie ein kleines Programm, das diese Funktion enthält und übergeben Sie einmal einen gültigen Dateizeiger und einmal NULL. Dann versuchen Sie beides nochmals, nachdem Sie vor der Includeanweisung für assert.h die Zeile#define NDEBUG eingefügt haben.
int WriteInFile(FILE *fd)
{
assert(fd != NULL);
fputs("Hallo Welt",fd);
}
Zusicherungen verwendet man für Bedingungen, die aus der Entwurfslogik stammen. Passende Punkte für Zusicherungen sind:
Wie bereits erwähnt, werden assert-Anweisungen nur dann ausgewertet, wenn die Prüfung von Zusicherungen ausdrücklich aktiviert ist. Standardmäßig sind Zusicherungen deaktiviert. Aufgrund dieser Tatsache sollten die Ausdrücke in assert-Anweisungen keinerlei Seiteneffekte enthalten, da sonst das Programmverhalten davon abhängt, ob Zusicherungen aktiviert sind oder nicht, wie das folgende Beispiel zeigt:
assert (i++ < limit);In dieser Zeile wird i nur dann inkrementiert, wenn Zusicherungen aktiviert sind. Andernfalls wird die Anweisung nicht ausgeführt und der Wert bleibt unverändert. Somit hängt das Verhalten des Programms davon ab, ob Zusicherungen aktiviert sind oder nicht.
#define CMULT(x) (3.8*(x)) #define SQ(x) ((x)*(x)) #define MAX(a,b) ((a) > (b) ? (a) : (b))Vorteil: Makroersetzungen funktionieren für alle Typen.
#if MAX < 1000 #define MIN 100 #else #define MIN 200 #endifAllgemein wertet #if einen konstanten Integerausdruck aus; statt '<' kann irgendein Vergleichsoperator stehen. Weiterhin läßt sich mit #ifdef name verzweigen, wenn dieses Token definiert, bzw. mit #ifndef name, wenn es nicht definiert ist, Beispiel:
#ifdef DEBUG
printf("a hat hier den Wert: %f\n", a);
#endif
#ifndef AFLAG #error "AFLAG" nicht definiert! #endif
Zum Inhaltsverzeichnis |
Zum nächsten Abschnitt |