 |
Algorithmen & Datenstrukturen Programmieren 1
von Prof. Jürgen Plate |
Programmierübungen in "C"
Zur Vorbereitung der Aufgabe gehören:
- sofern nicht in der Angabe vorgeschrieben, geeignete Datenstrukturen
(mit Begründung),
- eine vernünftige Unterteilung in Teilprojekte (mit Beschreibung),
- sauber gezeichnete Struktogramme (sofern der Algorithmus sehr einfach
ist, genügt eine verbale Beschreibung),
- ein Terminplan für die Fertigstellung der einzelnen Teilprojekte und
- abschließend vollständige C-Quellprogramme (deren Lauffähigkeit
vorzuführen ist).
Je nach Komplexität der Aufgabe können einzelne Punkte stark gekürzt werden.
Erste Schritte
Potenzierung
Ein schnelles Potenzierungsverfahren für ganze Zahlen bietet der folgende
Algorithmus (WertExponente):
Potenz = 1
solange Exponent ungleich 0 ist wiederhole:
Wenn Eponent gerade dann: Exponent = Exponent/2, Wert = Wert * Wert
sonst: Exponent = Exponent-1, Potenz = Potenz * Wert
Setzen Sie das Verfahren in ein C-Programm um und testen Sie es mit verschiedenen
Werten.
Zahlentabelle
Schreiben Sie ein Programm, das in Tabellenform Zahlenwerte in dezimal, oktal und
hexadezimal (= sedezimal) darstellt. Der Wertebereich der Zahlen soll im Dialog mit
dem Benutzer eingegeben werden (untere Grenze, Obere Grenze). Denken Sie an eine
Überschriftszeile und die rechtsbündige Ausrichtung der Zahlen.
Anmerkung: Sie sollen nicht per Programm in hexadezimal oder oktal umrechnen,
sondern diese Arbeit der Funktion printf anvertrauen.
Ausgabe und Eingabe von Zeichen auf dem Bildschirm
- Schreiben Sie ein Programm, das ein Zeichen (z.B. '*') in einer Zeile
"laufen läßt". Benutzen Sie dazu nur einfache
Zeichenausgabe-Funktionen. Um den Vorgang am Bildschirm verfolgen zu
können, benutzen Sie als Verzögerungsfunktion den
Aufruf der Systemfunktion sleep(sec), die sec Sekunden
wartet (Headerdatei: unistd.h). Wenn Ihnen sleep() zu langsam
ist, können Sie auch usleep() verwenden. Das "u" steht für
"Mikro". Hier wird die Anzahl der Mikrosekunden als Parameter angegeben (max.
1000'000 Mikrosekunden = 1 Sekunde). Das Programm soll abbrechen, wenn das
Zeichen am rechten Rand angekommen ist (Bildschirm ist ca. 80 Zeichen breit).
Hinweis: Wie bei den meisten Betriebssystemen wird auch bei dem im Praktikum
verwendeten UNIX die Ausgabe gepuffert. Das heißt, es wird erst etwas
ausgegeben, wenn ein Newline kommt oder gar erst, wenn das Programm zuende ist.
Sie müssen also dafür sorgen, daß nach jedem Zeichen wirklich
auch eine Bildschirmausgabe erfolgt. Sie erreichen dies durch den Funktionsaufruf
fflush(stdout); nach jeder Ausgabe.
Erweitern Sie das Programm außerdem um die Fähigkeit, das Zeichen auch
links zu schieben, nämlich dann, wenn es am rechten Rand angekommen ist.
Programmende bei Rückkehr des Zeichens zum Zeilenanfang.
- Schreiben Sie nun das Programm so um, dass für das Schieben Funktionen
verwendet werden:
- Verwenden Sie zwei Funktionen: void movright(void) und
void movleft(void), die beim Aufruf das Zeichen jeweils um eine
Position nach rechts oder links bewegen. Stellen Sie Ihr Programm (genauer: die
Funktion main()) so um, daß nun anstelle der Ausgabeanweisungen
die FUnktionsaufrufe verwendet werden.
- Verwenden Sie danach eine universelle Funktion void movrl(int rl), die
abhängig vom Parameter nach rechts oder links schiebt (z.B.: rl == 0 schiebt rechts,
rl != 0 schiebt links).
- Erweitern Sie das Programm um die Fähigkeit, im Dialog mit dem Benutzer
die Schieberichtung zu steuern. So soll ein eingegebenes
Leerzeichen ein Rechtsschieben um eine Position bewirken, ein 'b' das
Linksschieben. Andere Zeichen sollen direkt wieder ausgegeben werden
und ein bestimmtes (z.B. 'e') soll das Programm beenden.
Hinweis: Auch die Eingabe wird wie die Ausgabe gepuffert. Das heißt,
es wird beim Aufruf von getchar() gewartet, bis nach dem Zeichen die
Enter-Taste gedrückt wird. Um das zu vermeiden, muß das System in den
ungepufferten Betrieb geschaltet werden. Verwenden Sie statt der Funktion
getchar() die Funktion getch(), die in der Headerdatei
adprakt.h definiert ist. Mit getch()
wird das eingegebene Zeichen auch nicht mehr automatisch auf dem Bildschirm
geechot, sondern erst durch die Ausgabe in Ihrem Programm.
Das Programmskelett sieht im Prinzip so aus:
#include <stdio.h>
#include <stdlib.h>
#include <adprakt.h>
int main()
{
char c = ' ';
while (c != 'e')
{
c = getch();
switch (c)
{
case ' ': moveright(); break; /* rechtsschieben */
case 'b': moveleft(); break; /* linksschieben */
case 'e': printf("\a"); break; /* fertig, Beep! */
default : putchar(c); break; /* anderes Zeichen */
}
fflush(stdout);
}
printf("\n"); /* Newline */
return 0;
}
Relais-Steuerung
Verwenden Sie die Unterprogramme zur Relais-Steuerung (siehe auch Praktikumsanleitung),
um eine Verkehrsampel zu programmieren. Beachten Sie dabei die Lichterfolge
(rot, rot-gelb, grün, gelb, rot, ...) und die Schaltzeiten
(kurze Zeit: 2s, lange Zeit: 10s). Welche Schaltzeiten sind lang, welche
kurz? Hinweis: Zur Zeitverzögerung dient die Funktion sleep(),
deren Parameter die Wartezeit in Sekunden angibt. Die Zuordnung der Lampen ist
R1: rot, R2: gelb, R3: grün.
Die Headerdatei adprakt.h stellt zwei Funktionen für das
Praktikum zur Verfügung:
int relais(int R1, int R2, int R3, int R4, int R5, int R6, int R7, int R8, char Result[200])
schaltet Relais in der Relaisbox des Rechners "blackhole", der
sich im Labor ganz hinten rechts befindet. Die Relaisbox wird
für das Praktikum mit dem schwarzen Hauptschalter
eingeschaltet (die roten Schalter bitte auf Stellung "ein" belassen).
Nach dem Praktikum abschalten nicht vergessen (die Box, nicht den Rechner).

Durch den Wert 1 werden die Relais R1 - R8 eingeschaltet, durch
den Wert 0 wieder ausgeschaltet. Als Rückgabewert erhält man
den Return-Value der Steuersoftware oder bei einem Fehler (z. B. Box
nicht eingeschaltet) -1. Im String "Result" wird die Klartextmeldung
der Steuersoftware zurückgegeben.
int relais_off(char Result[200])
Mit diesem Befehl werden alle Relais wieder ausgeschaltet.
Als Rückgabewert erhält man den Return-Value der
Steuersoftware (Link siehe unten) oder bei einem Fehler -1. Im String
"Result" wird die Klartextmeldung der Steuersoftware zurückgegeben.
Das folgende Programm schaltet die rote Lampe ein und aus.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <adprakt.h>
int main()
{
char Result[200];
int erg;
erg = relais(1,0,0,0,0,0,0,0,Result);
printf("Erg.: %d Res.: %s\n", erg,Result);
sleep(1);
erg = relais(0,0,0,0,0,0,0,0,Result);
printf("Erg.: %d Res.: %s\n", erg,Result);
return(0);
}
Beachten Sie, daß für die Funktion sleep() die Headerdatei
unistd.h zusätzlich benötigt wird.
Hinweis: Da nicht alle gleichzeitig auf die Relais-Fernsteuerung zugreifen
können, werden die beiden o. a. Funktionen für die Dauer der
Programmentwicklung simuliert, indem man sie zunächst im Programm selbst
definiert, z.B. als Ausgabefunktion mit printf für die einzelnen
Relais-Werte. Der Rückgabewert kann dann immer 0 sein.
Wenn das Programm dann lauffähig ist, kommentiert man die selbst geschriebenen
Funktionen aus und läßt; es auf die realen Relais zugreifen.
Stringausgabe
Entwickeln Sie ein C-Programm, das auf die Eingabe von Abkürzungsbezeichnungen
für die Wochentage (in deutsch) mit der Ausgabe
des vollen Namens der Wochentage (in englisch) reagiert.
- Die Abkürzungsbezeichnungen für die Wochentage sollen aus
drei Zeichen bestehen und folgendermaßen lauten:
MON, DIE, MIT, DON, FRE, SAM, SON.
Zwischen Groß- und Kleinbuchstaben soll dabei nicht unterschieden werden, d. h.
statt "MON" kann z.B. auch "mon" oder "Mon" oder
"moN" usw verwendet werden.
- Auf die Eingabe einer falschen - auch zu kurzen Abkürzungsbezeichnung
soll das Programm mit der Ausgabe von "No-day" reagieren.
- Das Programm soll in einer Schleife in jeweils einer neuen Zeile die
Eingabe einer neuen Abkürzungsbezeichnung anfordern und die
entsprechende Ausgabe in der nächsten Zeile erzeugen. Abschluß
jeder Eingabe mit <RETURN>.
- Werden mehr als drei Zeichen eingegeben, so sind nur die ersten drei Zeichen
zu berücksichtigen. Der Rest der Eingabezeile ist zu ignorieren.
- Das Programm soll enden, wenn nach einer Eingabeaufforderung als erstes
Zeichen ein Dateiende-Zeichen (<CTRL-D>) eingegeben wird.
Beispiel für einen Programmdialog:
Geben Sie die Abkürzung eines Wochentages ein
? mon
--> Monday
? mo
--> No_day
? Dienstag
-->Tuesday
? ^D
Hinweise zur Lösung:
- Gliedern Sie das Programm möglichst sinnvoll.
- Zur eigentlichen Ermittlung des vollen englischen Namens aus der deutschen
Abkürzung können Sie geeignete zweidimensionale char-Arrays zur
Speicherung der Abkürzungsbezeichnungen und der vollen Wochentags-Namen
oder eine if - else if-Kaskade verwenden.
- Die Einlesefunktion scanf() liefert als Rückgabewert
entweder die Anzahl der eingelesenen Elemente oder EOF (= -1), wenn
<>CTRL-D> gedrückt wurde.
Buchstabenhäufigkeit
Schreiben Sie ein Programm, das einen Text einliest und die Häufigkeiten der
eingegebenen Zeichen ermittelt (also die einzelnen ASCII-Zeichen zählt). Ist
der Text zuende, sollen die Häufigkeiten der Buchstaben 'A' bis 'Z', 'a' bis 'z'
und der Ziffern von '0' bis '9' absolut und prozentual ausgegeben werden.
Verwenden Sie zur Speicherung der Häufigkeiten ein Array mit 256 Elementen.
Das jeweils aktuelle Eingabezeichen wird als Feldindex verwendet. Zählen Sie
gleichzeitig alle eingegebenen Zeichen - dieser Wert wird für die Prozentangabe
benötigt.
Temperaturen umrechnen
Entwickeln Sie zwei Funktionen ToFahrenheit und ToCelsius, die eine
Temperatur von Fahrenheit nach Celsius und umgekehrt konvertieren. Die Umrechnung
erfolgt nach folgenden Formeln:
Celsius nach Fahrenheit: tF = 9 / 5 * tC + 32
Fahrenheit nach Celsius: tC = (tF - 32) * 5 / 9
Welchen Parametertyp und Rückgabewert haben die Funktionen?
Testen Sie die neuen Funktionen, indem Sie in einem Hauptprogramm eine Temperatur zuerst
von Celsius nach Fahrenheit und wieder zurück nach Celsius konvertieren und das Ergebnis
ausgeben.
Schreiben Sie anschliessend ein Programm, das eine Umrechnungstabelle Fahrenheit nach Celsius
für das Intervall von +32 bis +212 Fahrenheit sowie eine Umrechnungstabelle Celsius nach Fahrenheit
für das Intervall von -30 bis +50 Celsius ausgibt.
Widerstandswerte
Gemäß DIN 41429 wird der Wert eines elektrischen Widerstands durch
einen Farbcode dargestellt: Zwei Ziffern, gefolgt von 0 bis 6 Nullen. Die beiden
Ziffern und die Anzahl der Nullen wird durch folgende Farben dargestellt:
0: schwarz
1: braun
2: rot
3: orange
4: gelb
5: grün
6: blau
7: violett
8: grau
9: weiß
z. B. 1200 Ohm = braun-rot-rot. Schreiben Sie ein Programm, das für eine
eingegeben Zahlenwert den Farbcode ausgibt.
Es sollen nur Widerstandswerte zwischen 1 Ohm und 5 Megohm berücksichtigt
werden.
Tip: Ermitteln Sie die Anzahl der Nullen durch fortlaufende Division durch 10 und
zerlegen Sie die verbleibende zweistellige Zahl in Zehner- und Einerstelle.
Maße geometrischer Körper
Schreiben Sie ein Programm, das die Oberfläche und das Volumen
der geometrischen Körper Quader, Kugel und Zylinder berechnet.
Die Eingabe des Programms besteht aus einer Zeile, welche alle benötigten
Angaben enthält:
- Quader: Buchstabe "Q", Länge, Breite, Höhe
- Kugel: Buchstabe "K", Radius
- Zylinder: Buchstabe "Z", Radius, Höhe
Die Formeln für die Berechnung lauten (Hinweis: Die Konstante PI
heißt in C "M_PI" und ist in
math.h mit dem Zahlenwert definiert.):
- Quader:
Oberfläche = 2 * (L*B + L*H + B*H)
Volumen = L * B * H
- Kugel:
Oberfläche = 4 * R2 * PI
Volumen = 4/3 * R3 * PI
- Zylinder:
Oberfläche = 2 * PI * R *(R + H)
Volumen = H * R2 * PI
Kaufmännische Zinsrechnung
Ein Kapital K werde T Tage lang mit P% pro Jahr verzinst. Die Zinsen Z werden nach
der sogenannten kaufmännischen Zinsformel berechnet:
P T
Z = K * ----- * -----
100 360
Es ist ein Programm zu schreiben, das aus je drei der vier Größen die
vierte Größe berechnet. Das Programm fragt dazu per Menü ab,
welcher Wert gesucht wird (Eingabe durch einen Buchstaben: K, P, Z oder T).
Danach werden die anderen drei Größen abgefragt und der gewünschte
Wert berechnet und ausgegeben. Die Eingaben sollen auf Plausibilität geprüft
und gegebenenfalls neu abgefordert werden.
Zahlenraten
Variante 1:
Der erste Mitspieler gibt eine ganze Zahl im Bereich von 0..999 vor.
Aufgabe des zweiten Mitspielers ist es diese Zahl mit möglichst wenigen
Versuchen zu erraten. Entsprechend der Qualität des Rateversuchs
ist ein geeigneter Text mit den Hinweisen "zu klein", "zu groß" oder
"stimmt" auszugeben.
Algorithmus: Zerlegung in Einzelprobleme, z. B.:
- Ausgabe eines Textes als Aufforderung zur Eingabe der Geheimzahl,
- Einlesen des Zahlenwertes durch Funktionsaufruf,
- Löschen des Bildschirminhaltes,
- Aufforderung einen Rateversuch auszuführen,
- Vergleich der Geheimzahl und der geratenen Zahl,
- Ausgabe des Textes mit der Qualität des Ratens,
- Beenden falls die Zahl richtig war,
- Fortsetzen bei Punkt 4, falls die Zahl falsch war.
Bibliotheks-Funktionen: scanf(), printf(), clrscr()
Variante 2:
Die Geheimzahl wird durch den Rechner ermittelt, der eine Zufallszahl im Bereich
0 ... 999 ermittelt. Die Anzahl der Rateversuche wird auf 9 begrenzt.
Algorithmus: (ersetzt den vorhergehenden teilweise)
- Initialisierung des Zufallsgenerators durch Ihren Geburtstag. Sie
multiplizieren Tageszahl mit dem Geburtsmonat.
- Endekriterium ist erfüllt falls richtig geraten wurde oder
falls die maximale Anzahl von Rateversuchen erreicht wurden.
Bibliotheks-Funktionen: rand(), srand().
Variante 3:
Abhängig von der Nähe des Rateversuchs zur gesuchten Zahl werden Texte
ausgegeben (kalt, heiß, wärmer...), die auch von dem
vorhergehenden Rateversuch abhängen können.
Präzisieren
der Aufgabenstellung für den ersten Rateversuch:
- < 10 daneben --> "heiß"
- < 100 daneben --> "warm"
- > 100 daneben --> "kalt"
Für Folgeversuche, die im gleichen Intervall liegen:
- liegt der Versuch besser: "heißer", "wärmer",
"nicht mehr so kalt"
- liegt der Versuch schlechter: "weniger heiß", "weniger
warm", "kälter"
Algorithmus:
setzt bei (5) ein, der Vergleich muß bezüglich
weiterer Schwellen durchgeführt werden, basierend auf der
Differenz zwischen Geheimzahl und geratener Zahl;
- der Vergleich führt zu einem in einer Variablen festzuhaltenden
Ergebnis -> 0 richtig, 1 heiß zu groß, 2 heiß zu
klein ....;
- der Vergleich mit der vorhergehenden Differenz führt zu weiteren
Zuständen der Ergebnisvariablen
- für die Textausgabe kann nun die Mehrfachverzweigung basierend auf der
Ergebnisvariablen angewandt werden
- oder diese als Index für eine Texttabelle (Array von Strings)
verwendet werden.
Münzautomat
Erstellen Sie ein ANSI-C-Programm, das einen Münzautomaten simuliert. Dazu geben
Sie einen beliebigen Betrag zwischen 1 Euro und 10 DM vor (entweder durch Eingabe oder
als Zufallszahl). Der Betrag kann mit verschiedenen Münzen "abgezahlt" werden:
2 Euro
1 Euro
50 Cent
10 Cent
5 Cent
Die Münzen werden in beliebiger Reihenfolge "eingeworfen" (durch Eingabe im Programm)
und der Restbetrag jeweis angezeigt. Überzahlen soll nicht möglich sein.
Die Anzeige soll in großen Ziffern erfolgen und wie im folgenden Beispiel aussehen:
##### ##### #####
# # # #
##### # ####
# # # #
##### * # ####
Tipp: Rechnen Sie mit Cent (1 Euro wird dann als 100 und 2 Euro als 200 eingegeben).
Die Bildschirmausgabe soll als Funktion formuliert werden, die eine Integerzahl
als Eingabeparameter besitzt und diese in der oben angegebenen Form auf dem
Bildschirm ausgibt. Der Dezimalpunkt, dargestellt durch ein Sternchen, ist fester
Bestandteil der Ausgabe und nicht Kennzeichen einer float-Variablen.
Hinweise zur Lösung:
Die Vereinbarung der Ziffern ist unter /home/praktikum/ziff.h zu finden.
Sie lautet:
char * ziff[10][5] =
{ {" ### ",
"# #",
"# #",
"# #",
" ### " },
{" ##",
" #",
" #",
" #",
" #" },
{"#### ",
" #",
" ### ",
"# ",
"#####" },
{"#### ",
" #",
" ### ",
" #",
"#### " },
{"# # ",
"# # ",
"#####",
" # ",
" # " },
{"#####",
"# ",
"#### ",
" #",
"#### " },
{" ####",
"# ",
"#####",
"# #",
"#####" },
{"#####",
" #",
" # ",
" # ",
" # " },
{"#####",
"# #",
"#####",
"# #",
"#####" },
{"#####",
"# #",
"#####",
" #",
"#### " }};
(Sie können die Definition auch per "Cut-and-paste" von der Webseite übernehmen).
Die Ziffer j (j = 0...9) ist durch folgendes Programmstück zum Testen darstellbar:
for (i=0; i<5; i++)
printf("%s ",ziff[j][i]);
Arbeiten mit Binärdateien
Zum Speichern von Daten für das Telefonverzeichnis des Fachbereichs wird die
folgende Struktur definiert:
struct Adresse
{
char Name[30];
char Vorname[30];
int Raumnummer;
int Telefon;
};
Gegeben ist eine Binärdatei namens "teldat", die Datensätze vom Struktur-Typ
"Adresse" enthält.
1. Schreiben Sie eine Funktion tabelle, welche die Binärdatei zum Lesen öffnet,
und alle Datensätze nacheinander als formatierte Tabelle ausdruckt. Die Tabelle soll
folgendes Aussehen besitzen:
+-----------------------------------------+------+------+
| Name | Raum | Tel. |
+-----------------------------------------+------+------+
| Dr. Martin Bechteler | 4038 | 3418 |
| Dr. Gerd Becker | 4036 | 3416 |
| Dr. Maximilian Bleicher | 2051 | 3467 |
...
Die Funktion hat folgenden Prototyp:
int tabelle(char dateiname[]);
Sie gibt 0 zurück, wenn das öffen der Datei fehlschlägt, sonst 1.
2. Schreiben Sie eine Funktion kopiere, welche die Binärdatei mit den Adressen
(also die Quelldatei) zum Lesen öffnet und gleichzeitig eine weitere Binärdatei,
die Zieldatei, zum Schreiben öffnet.
Die zweite Binärdatei soll Datensätze enthalten, die folgender Struktur genügen:
struct NeueAdresse
{
char Name[30];
int Telefon;
};
Aufgabe der Funktion ist es, jeweils einen Datensatz aus der Quelldatei zu lesen,
die Inhalte in einen Datensatz der Zieldatei zu übertragen und diesen dann in
die Zieldatei zu schreiben.
Die Funktion hat folgenden Prototyp:
int kopiere(char quelldateiname[], char zieldateiname[]);
Sie gibt 0 zurück, wenn das öffen der Dateien fehlschlägt, sonst 1.
Entwerfen Sie ein geeignezes Hauptprogramm zum Testen der FUnktionen.
Fahrstuhl
In einem Hochhaus mit 20 Stockwerken fährt ein Aufzug. Im Aufzug
gibt es 20 Knöpfe zur Wahl des Fahrtzieles, und auf jedem Stock gibt
es einen Knopf zur Anforderung des Fahrstuhles.
Ohne auf die faire Behandlung der Aufzugsbenutzer zu achten, entwickeln
Sie einen einfachen Algorithmus für einen Fahrstuhl, der, solange es geht,
seine Fahrtrichtung beibehält, bis in seine augenblickliche Fahrtrichtung
keine Anforderungen mehr existieren, woraufhin er die Fahrtrichtung
wechselt, oder aber, falls gerade keine Anforderungen anliegen, einfach
auf dem augenblicklichen Stockwerk bleibt und auf die nächste
Anforderung wartet. Es wird angenommen, die Stockwerke seien von 1 bis
20 durchnummeriert.
Nehmen Sie folgende Grundaktionen an:
- fahre_oben: fahre nächsthöheres Stockwerk an
- fahre_unten: fahre nächsttieferes Stockwerk an
- lade: lade oder entlade auf dem aktuellen Stockwerk Fahrgäste
Stockwerk_ist_Fahrziel: ist aktuelles Stockwerk angefordert worden (j/n)?
- aktuelles_Stockwerk: liefert Nummer des aktuellen Stockwerkes
- max_Stockwerk: liefert das höchste Stockwerk (Stockwerknummer)
das augenblicklich angefordert ist. Falls keins angefordert
wurde, wird 0 geliefert.
- min_Stockwerk: liefert das tiefste Stockwerk (Stockwerknummer) das
augenblicklich angefordert ist. Falls keins angefordert
wurde, wird 0 geliefert.
- warte_Anforderung: bleibt (geschlossen) auf dem aktuellen
Stockwerk bis eine neue Anforderung eintritt
Simulieren Sie den Aufzug mit Hilfe von Bildschirm und Tastatur.
Iteration, Abfrage und Funktion
Sie sollen eine mathematische Funktion double mysin (double x);
entwerfen und programmieren, die mittels der Taylorreihenentwicklung für ein
hinreichend großes n:
für ein gegebenen Wert x den Sinus berechnet.
Die Genauigkeit des über die Reihenentwicklung gewonnen Sinuswertes hängt
von der Anzahl m der beteiligten Summanden [..] ab. Diese
Anzahl m ist abhängig vom Wert x und im voraus nur schwer
abzuschätzen.
Wir geben einen Wunschfehler vor und berechnen die Summe, durch Addition weiterer
Summanden [..], solange bis die gewünschte Genauigkeit erreicht ist.
D. h.: Ist der jeweils hinzuzufügende Betrag des Summanden kleiner oder
gleich dem Wunschfehler, so ist die gewünschte Genauigkeit erreicht.
Wunschfehler: 10 -10
Auffrischung:
Der Sinus ist eine periodische Funktion. Seine Funktionswerte kehren alle
x = x + 2*PI wieder. D. h. der relevante Definitionsbereich von sin x
liegt zwischen 0 und 2*PI. Ist der x-Wert größer als 2*PI,
so kann vor der Reihenberechnung, das überzählige Vielfache
von 2*PI von x abgezogen werden, bis der Wert wieder im o. g. relevanten
Definitionsbereich ist.
Häusliche Vorbereitung
Hinweis: #include <math.h> am Anfang des Hauptprogramms ist notwendig,
um auf die C-Bibliotheks-Funktion double sin(double x) zugreifen zu
können. Außerdem muß im Menü Projekt die Auswahl
Optionen angewählt werden. Im Reiter Linkeroptionen muß
im Bereich Bibliotheken die Bibliothek math ausgewählt werden.
Bringen Sie Ihr Programm zum Laufen. Die Differenzen zur C-Bibliotheksfunktion sollten
sich maximal in der Größenordnung 10-12 bewegen.
Magische Quadrate
Ein magisches Quadrat ist eine quadratische Anordnung mit n Zeilen
und n Spalten von Zahlen derart, dass die Summe der Zahlen in einer
beliebigen Zeile, Spalte, oder in der Hauptdiagonale alle gleich sind.
Wenn n ungerade ist, kann man ein magisches Quadrat folgendermaßen erstellen. Im Feld
Q[i][j] (i=1..n, j=1..n) wird eingetragen:
- Setzen Sie k = j - i + (n -1)/2 und m = 2*j - i.
- Wird k >= n, ersetzen Sie k durch k - n
und fahren Sie fort bei Schritt 4.
- Wird k < 0, ersetzen Sie k durch k + n.
- Wird m > n, so ersetzen Sie m durch m - n
und fahren Sie fort bei Schritt 6.
- Wird m <= 0, ersetzen Sie m durch m + n.
- Ergebnis: Q[i][j] = K*n + m.
Erstellen Sie ein Programm, das mit diesen Regeln bei
Benutzereingabe von ungeradem n ein magisches Quadrat erzeugt, das
in einem Array gespeichert und dann am Bildschirm schön formatiert
ausgegeben wird.
Textanalyse
Ein über die Standardeingabe einzugebender Text soll zeichenweise gelesen und
analysiert werden.
Erstellen Sie ein ANSI-C-Programm, mit dem
- die Anzahl der Zeichen,
- die Anzahl der Worte,
- die Anzahl der Zeilen,
- die Anzahl der Zeichen der längsten Zeile,
- die Länge des längsten Wortes
des Eingabetextes ermittelt werden kann. Unter einem Wort sei dabei jede
Zeichenfolge, die mit einem Buchstaben oder Underscore ('_') beginnt,
nur aus Buchstaben, Ziffern und den Underscore ('_') besteht und von
beliebigen Steuer- oder Sonder-Zeichen (nicht aber Ziffern) begrenzt
wird, verstanden. Beachten Sie, daß nach dieser Definition z. B.
die Zeichenfolge ;2abc% kein Wort enthält.
Hinweise zur Lösung:
- Der Text ist mit der Funktion getchar() einzulesen und darf nicht in Arrays
abgelegt werden. Achtung: Die Eingabe erfolgt gepuffert, d. h. wenn Sie etwas
eintippen, wird die Eingabe erst an das Programm übergeben, wenn Sie
die Enter-Taste gedrückt habe. Sie geben also immer zeilenweise ein.
Das Programm bearbeitet Ihre Eingabe aber trotzdem Zeichen für Zeichen.
- Aus Vereinfachungs- (u. Portabilitäts-) Gründen kann
angenommen werden, daß die deutsche Umlaute nicht im Text vorkommen.
- Berücksichtigen Sie, daß die letzte Zeile nicht unbedingt mit einem
Zeilenendezeichen abgeschlossen sein muß.
- Andererseits stellt ein Dateiendezeichen unmittelbar nach einem
Zeilenende keine neue Zeile dar.
- Zur Vereinfachung der Wortermittlung sind die folgenden symbolischen
Konstanten zu definieren und sinnvoll zu verwenden:
- symbolische Konstante (--> #define) zur Kennzeichnung der
jeweiligen Zeichenklasse
- BUCH (Groß- u. Kleinbuchstaben, Underscore)
- ZIFF (Ziffern)
- SPACE (Leerzeichen (' '), Tabulator ('\t') und Newline ('\n') )
- SONST (restliche Zeichen, d.h. Steuerzeichen und sonstige Sonderzeichen)
- symbolische Konstante zur Kennzeichnung des jeweils aktuellen Textzustands
- PREWORT (nächstes Zeichen kann Anfang eines Wortes sein)
- INWORT (innerhalb eines Wortes)
- NOWORT (innerhalb einer Zeichenfolge, die kein Wort ist, nächstes
Zeichen kann nicht Anfang eines Wortes sein)
- Überlegen Sie sich den durch ein Zeichen in Abhängigkeit von seiner
Klasse und dem alten Textzustand jeweils ausgelösten Zustandsübergang.
Betrachten Sie dazu das Zustandsübergangsdiagramm:
Ein solches Diagramm ist recht einfach aufgebaut:
- Alle Zustände werden als (beschrifteter) Kreis dargestellt.
- Der Übergang zu einem anderen Zustand wird durch einen Pfeil dargestellt.
Der Pfeil wird mit der Übergangsbedingung beschriftet.
- Beschreiben Sie den durch das Programm zu realisierenden Algorithmus vor der
Formulierung in C durch ein Struktogramm.
- Beginnen Sie mit der Programmschleife, die zeichenweise von der Standardeingabe
liest. Die Kommentare markieren später zu ergänzende Programmteile. In
der mit /* TEST */ gekennzeichneten Zeile steht eine Ausgabeanweisung,
mit der Sie die Funktion ihres Programmes testen können. Wenn das Programm
zufriedenstellend läuft, wird diese Anweisung gelöscht:
#include <stdio.h>
#include <ctype.h>
/* Textzustaende definieren */
/* Zeichenklassen definieren */
int main(void)
{
int c; /* Eingelesenes Zeichen */
int zustand; /* Textzustand */
int zklasse; /* Zeichenklasse von c */
/* Anfangswerte setzen */
while( (c=getchar())!=EOF )
{
/* ermittle Zeichenklasse */
/* fuehre Zustandswechsel durch */
/* TEST */ printf("%c %d %d\n",c,zklasse,zustand);
}
/* Ausgabe der Ergebnisse */
return(0);
}
- Schreiben Sie nun Ihr C-Programm in drei Etappen:
- Schreiben Sie den Programmteil zum Ermitteln der Zeichenklassse und testen Sie,
ob bei den Eingaben die Zeichenklasse richtig ermittelt wird.
- Schreiben Sie nun den "Automaten", der die Zustandsübergänge ermittelt und
testen Sie auch die Funktion dieses Programmteils. Erst wenn das funktioniert:
- Fügen Sie nun die Programmteile hinzu, die Zeichen, Worte und Zeilen zählen.
- Wenn das Programm einwandfrei funktioniert, er weitern Sie es so, daß von der
Tastatur ein Dateiname eingelesen wird und die Textanalyse mit dieser Datei
durchgeführt wird.
-
Änder Sie das Programm abschließend so, daß anstelle der mit
#define definierten Konstanten die folgenden beiden Aufzählungstypen
verwendet werden:
enum Zeichenklasse { BUCH, ZIFF, SPACE, SONST } zklasse;
enum Textzustand { PREWORT, INWORT, NOWORT } zustand;
Hotelzimmer-Verwaltung
Sie werden vom Besitzer des Gasthofs "Zum wilden Mann" damit beauftragt,
ein C-Programm zur Verwaltung der 20 Zimmer des Gasthofs zu schreiben.
Das Programm soll in einem Dialog mit dem Benutzer folgende Optionen anbieten:
- Zimmer Nummer N belegen für Gast mit Namen G
- Zimmer Nummer N wieder freigeben
- Alle freien/belegten Zimmer anzeigen
- Die Namen aller Gäste auflisten
- Herausfinden, welcher Gast sich in Zimmer N befindet
- Herausfinden, in welchem Zimmern sich Gast G befindet
- Hilfe (Auflisten aller Menüpunkte)
- Programm beenden
Als Datenstruktur wird ein Feld von Zeichenketten verwendet, dessen Index
mit der Zimmernummer übereinstimmt udn das den Namen des Gastes aufnimmt.
Vergessen Sie nicht für eine gute Fehlerbehandlung zu sorgen, z.B. wenn Zimmer
versehentlich mehrfach belegt werden sollen!
Telefonliste
Entwickeln Sie ein Programm zur Verwaltung von Telefondaten. An das Programm
werden folgende Forderungen gestellt:
- Die Teilnehmer werden in einem Array gespeichert.
- Jedes Element des Arrays besteht aus einem String, der die folgende Informationen
speichert:
- Name und Vorname des Teilnehmers (Zeichenkette, jeweils max. 30 Zeichen)
- Telefonnummer (Zeichenkette, max. 15 Zeichen)
- Das Programm soll über ein Menü gesteuert werden und folgende
Funktionen enthalten:
- Eintragen eines neuen Teilnehmers mit Namen und Telefonnummer in
alphabetisch richtiger Reihenfolge (d.h. der Eintrag wird an der
passenden Stelle im Array eingefügt und nachfolgende Einträge
ggf. nach hinten verschoben)
- Suchen eines Teilnehmers (nach Name oder Vorname)
- Löschen eines Teilnehmers
- Auflisten aller Teilnehmer
- Speichern der Telefonliste in einer Textdatei
- Laden der Telefonliste aus einer Textdatei
- Beenden des Programms
- Hilfe (Auflisten aller Menüpunkte)
Wählen Sie möglichst sinnvolle und leicht zu merkende Befehlsbuchstaben
für die einzelnem Menüpunkte, z. B. 'h' oder '?' für "Hilfe".
Histogramm
Erstellen Sie ein ANSI-C-Programm, mit dem ein Histogramm von
Prüfungsnoten ermittelt und in folgender Form auf dem
Bildschirm ausgegeben werden kann:
Histogramm der Noten
|
|
|
|
|
|
|
|
|
|
|
|
|
| *
| * * *
| * * * * *
| * * * * * *
| * * * * * * * * *
| * * * * * * * * * * * *
| * * * * * * * * * * * * *
--+--+--+--+--+--+--+--+--+--+--+--+--+
0 1 2 3 4 5 6 7 8 9 10 11 12
Die Noten werden bei der Eingabe in Kategorien (0 bis 12) eingeteilt und zwar
nach folgenden Schema:
Noten: 1.0 1.3 1.7 2.0 2.3 2.7 3.0 3.3 3.7 4.0 4.3 4.7 5.0
Kategorie: 0 1 2 3 4 5 6 7 8 9 10 11 12
Damit die gesamte Bildschirmausgabe sichtbar ist, ist bei Noten,
deren Auftritte die Anzahl 20 überschreitet die Ausgabe auf 19
Sternchen zu begrenzen und an der Position des 20. (obersten)
Sternchens die tatsächliche Anzahl der Auftritte auszugeben.
Die Noten stehen in einer Textdatei namens noten.dat, jeweils
eine Note pro Zeile. Das Programm öffnet die Datei und liest
alle Noten (bis EOF). Danach wird die Datei geschlossen und das Histogramm
ausgegeben.
Hinweise zur Lösung :
- Die Eingabe besteht aus drei Zeichen. Prüfen Sie die Eingabe
auf korrekte Noten gemäß obiger Skala und geben Sie eine Fehlermeldung
bei Falscheingaben aus (der Eingabewert wird dann ignoriert).
- Legen Sie zur Aufnahme der Auftritte der einzelnen Noten ein
int-Array an, dessen Komponenten zweckmäßigerweise über die
Noten-Kategorie indiziert werden.
- Die Bildschirmausgabe darf nicht über eine absolute
Cursor-Positionierung erfolgen.
- Zum Test erstellen Sie die Datei noten.dat im Projektverzeichnis
(dort, wo sich auch main.c befindet.
Ringpuffer mit Arrays
Ein Ringpuffer ist ein nach dem FIFO-Prinzip (first-in-first-out)
organisierter Speicher zur Pufferung von Daten, die z.B. von einem
Meßdatenerfassungsprogramm "angeliefert" werden, und
zeitversetzt von einem Auswertungsprogramm weiterverarbeitet werden.
Der Puffer muß groß genug sein, damit er auch bei maximalem
Zeitversatz nicht "überläuft".
Der Versuch, in einen vollen Puffer zu schreiben muss verhindert werden,
stattdessen soll eine Meldung "Puffer voll" ausgegeben
werden.
Ebenso muss dafür gesorgt werden, dass umgekehrt aus einem leeren
Puffer nicht mehr gelesen wird -> Meldung "Puffer leer".
Die Abbildung des Rings in einem linear adressierten Speicher geschieht
dadurch, dass ein am Pufferende angekommener Zeiger wieder an den
Anfang gesetzt wird.
- Beginnen Sie bei der Datenstruktur, skizzieren Sie den Puffer in
verschiedenen Betriebszuständen und tragen Sie die jeweiligen
Schreib- bzw. Lese-Indizes sowie die jeweils belegten Speicherzellen ein:
- ganz am Anfang
- nach einigen Schreibvorgängen
- nach einigen Leseoperationen
- wenn der Puffer voll wird
- wenn ein Zugriff auf das Ende des linearen Pufferbereichs erfolgt ist
- wenn der Puffer "leergelesen" wird.
- Überlegen Sie,
- welche Variablen benötigt werden,
- wie die Pufferzustände "leer" und "voll"
erkannt werden können,
- welche elementaren Funktionen gebraucht werden,
- welche der Variablen ggf. global (außerhalb aller Funktionen)
vereinbart werden müssen, damit sie auf einfache Weise von den
verschiedenen Funktionen benutzt werden können (möglichst wenige!),
- auf welche Weise Funktionsresultate an die aufrufende Funktion
übergeben werden sollen,
- welche Funktion welche Überprüfungen durchführen soll
(z.B. Puffer leer bzw. voll ?).
- Zeichnen Sie die Struktogramme der benötigten Funktionen zum
Initialisieren, Schreiben und Lesen des Puffers.
- Für Testzwecke eignet sich am besten ein Ringpuffer für ca.
20 char-Elementen. Zeichnen Sie jetzt das Struktogramm für
die Testfunktion (main), mit der per Tastatureingabe das Füllen
und Auslesen des Puffers - dargestellt als Zeile am Bildschirm -
verfolgt werden kann:
- jedes eingegebene Zeichen soll im Puffer an seiner Schreib-Position erscheinen;
- wird nur "return" (new line) eingegeben, so soll ein Zeichen aus dem
Puffer gelesen (entfernt) werden;
- beides soll auch in der Bildschirmdarstellung deutlich werden! Am einfachsten
ist es, wenn jede Veränderung im Puffer zu einer neuen Darstellung des
kompletten Pufferinhalts in einer neuen Zeile führt.
- Erst jetzt wird der C-Quellcode geschrieben und alles getestet.
Lineare Listen
Zeichenketten (strings) haben Sie in der Vorlesung als Arrays von Zeichen kennengelernt.
Man kann sie auch als Listen realisieren. Dabei machen wir das so, dass jedes
Listenelement jeweils nur ein einziges Zeichen speichert. Diese Zeichen werden
in Listenform miteinander verkettet.
Beispiele: Folgende Graphik zeigt zwei Zeichenketten, string1 und string2,
und eine leere Zeichenkette als Listen realisiert:
Eine Operation, die häufig für Zeichenketten benötigt wird, ist das
Zusammenhängen zweier Zeichenketten. Diese Operation nimmt als Eingabeparameter
zwei Zeichenketten - string1 und string2. Sie hängt string2
an string1 an. Dabei wird in string1 das Ergebnis zurückgegeben
(der Pointer string1 wird also verändert).
Die folgende Graphik zeigt das Ergebnis von concat für die beiden anfangs
gegebenen Zeichenketten
- Definieren Sie den graphisch dargestellten Zeichenkettendatentyp (wir nennen ihn
in der Folge ZK) in C als lineare Liste.
- Realisieren Sie die Funktionstruct ZK *genstring(char * s),
die aus der Zeichenkette s (in Standard-Arraydarstellung) eine Zeichenkettenliste
generiert (nicht vergessen: es muß auch Speicherpaltz reserviert werden).
Aufrufbeispiel:
string1 = genstring("Hallo Welt");
- Realisieren Sie die Funktion struct ZK *Concat (struct ZK *string1,
struct ZK *string2), die string2 an string1 anhängt
und einen Zeiger auf string1 zurückliefert. Aufrufbeispiel:
string1 = Concat(string1, string2);
- Zeichnen Sie die Struktogramme der benötigten Funktionen zum
Initialisieren, Schreiben, Lesen und Löschen der Puffereinträge.
- Erst jetzt wird der C-Quellcode geschrieben und alles getestet.
Auswerten einer Webserver-Statistik
Jeder Zugriff auf einen Webserver, d. h. der Abruf einer Webseite, wird
protokolliert. Die Protokolldatei ist eine Textdatei mit folgendem Format:
160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/b1-15.gif HTTP/1.1" 404 322
160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/b1-16.gif HTTP/1.1" 404 322
160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/netz1.gif HTTP/1.1" 404 322
82.174.14.6 - - [27/Apr/2004:09:24:30 +0200] "GET /mm-cd/sound/jet1.wav HTTP/1.1" 200 7148
82.174.14.6 - - [27/Apr/2004:09:24:31 +0200] "GET /mm-cd/sound/jet2.wav HTTP/1.1" 200 7148
Die einzelnem Felder sind jeweils durch Leerzeichen getrennt. Ihre Bedeutung:
| Nr. | Bedeutung |
| 1 | IP-Adresse des abrufenden Rechners |
| 2 | - |
| 3 | - |
| 4 | Datum und Uhrzeit des Abrufs (in eckige Klammern eingeschlossen) |
| 5 | Befehl zum Abruf der Seite (in "..." eingeschlossen) |
| 6 | Resultatcode |
| 7 | Anzahl übertragene Bytes |
Uns interessieren für die folgende Aufgabe nur Zeilen, bei denen das Feld 5 den
GET-Befehl enthält und bei denen die Resultatcode zwischen 100 und 399 liegt.
Aufgabe
Es ist ein Programm zu entwickeln, das die oben beschriebene Webserver-Protokolldatei
auswertet und für jede Datei (siehe Feld 5) feststellt, wieviele Zugriffe darauf erfolgten.
Dazu wird die Protokolldatei (Musterdatei unter /home/praktikum/access.log)
zeilenweise gelesen und aus jeder Zeile werden die Felder 5 und 6 extrahiert.
- Sofern Feld 6 nicht im erlaubten Bereich (100 bis 399) liegt, wird die
Zeile ignoriert (Hinweis: Es genügt das Untersuchen der ersten Ziffer).
Andernfalls wird aus Feld 5 (Achtung: Dieses Feld kann ebenfalls Leerzeichen
enthalten.) der Dateiname mit Pfad extrahiert. Aus dem Feldinhalt
"... GET /mm-cd/sound/jet1.wav HTTP/1.1 ..." ergibt sich beispielsweise
/mm-cd/sound/jet1.wav.
Formulieren Sie für diesen Aufgabenteil eine Funktion, der eine komplette
Zeile aus der Eingabedatei übergeben wird und die entweder den Dateinamen oder
eine leere Zeichenkette zurückliefert. Der Dateiname soll auf maximal 30
Zeichen begrenzt werden. Der Funktionsprototyp lautet:
char * extract(char * zeile);
Testen Sie diese Funktion mit einem einfachen Hauptprogramm.
- Der extrahierte Dateiname wird zusammen mit der Anzahl seiner Aufrufe in einer linearen
Liste gespeichert. Dazu verwenden Sie die unten angegebene Datenstruktur. Für jeden
Dateinamen wird der Name in der Liste gesucht.
- Ist der Name noch nicht in der Liste, wird ein neues Listenelement erzeugt und dieses
in die Liste (nach Namen) einsortiert sowie die Anzahl der Aufrufe auf 1 gesetzt.
- Ist der Name dagegen schon vorhanden, wird die Anzahl der Aufrufe bein entsprechenden
Listenelement um 1 erhöht.
- Anmerkung: Berücksichtigen Sie auch den Sonderfall, dass die Liste noch leer ist.
Datenstruktur:
struct liste
{
char name[31];
int anzahl;
struct liste * next;
};
Formulieren Sie für diesen Aufgabenteil eine Funktion, die das Enfügen übernimmt:
struct liste * insert(struct liste * root, char * name);
Die Funktion hat zwei Parameter, einen Zeiger auf den Listenanfang und das
einzufügende Wort. Sie liefert einen Zeiger auf den Listenanfang zurück.
Denken Sie auch an die Initialisierung der Zeiger im Hauptprogramm.
Testen Sie diese Funktion zusammen mit der folgenden.
- Ist die Eingabedatei vollständig bearbeitet, wird eine Aufrufstatistik erzeugt.
Für einen ersten Test geben Sie in einer Tabelle den Dateinamen und die
Anzahl der Abrufe aus.
Erweitern Sie dann die Ausgabe folgendermassen:
- Bestimmen Sie die Summe aller Aufrufe.
- Berechnen Sie für jedes Listenelement den prozentualen Wert der
Aufrufstatistik [Prozent = (Anzahl * 100)/Summe].
- Geben Sie in der Liste den Dateiname und den Prozentanteil aus, indem nach dem
Namen für je 2% ein '#' ausgedruckt wird (d.h. 100% = 50 mal '#'). Dabei wird
für den Namen immer eine Länge von 30 Zeichen genommen. Ganz hinten wird dann noch
die Anzahl dezimal ausgegeben.
Es ergibt sich dann in etwa folgendes Druckbild:
/mm-cd/sound/jet1.wav |##### 10
/mm-cd/sound/jet2.wav |## 4
/ad/uebungen.html |############# 26
/ad/variablenvereinbarung.gif |#### 8
...
(Anmerkung: Linksbündige Ausgabe von Strings bekommt man mit '... %-30s ...' in
der Formatdefinition von printf).
Die Funktion hat nur einen Parameter, den Zeiger auf den Listenanfang, und
gibt nichts zurück:
void print(struct liste * root);
Berücksichtigen Sie auch den Fall, dass die Liste noch leer ist.
Projekt: Crossreferenz-Tabelle
Es soll ein Programm entwickelt werden, das eine Textdatei einliest und daraus die
Worte extrahiert. Die Worte werden in einem Array sortiert gespeichert und zu jedem
Wort auch die Zeilen seines Auftretens erfasst. Die Zeilennummern werden separat für
jedes Wort in einer linearen Liste gespeichert.Auf diese Weise erhält man für jedes Wort
eine Liste der Zeilen, in denen das betreffende Wort auftaucht.
Ist die Datei vollständig gelesen, wird die Liste in eine Datei ausgegeben. Dazu werden
Formatierungszeichen der Web-Sprache HTML in die Datei geschrieben, die dann mit einem
handelsüblichen Browser betrachtet werden kann. Im Prinzip sieht das Ergebnis dann
folgendermassen aus:
| Adam | 2 4 12 13 14 54 123 234 |
| Birne | 7 12 33 43 44 45 46 78 321 433 434 542 |
| .... usw. |
Hinweise zur Programmierung:
Das Programm zerfällt in mehrere Teilaufgaben:
- Dateinamen erfragen und Text-Datei öffnen. Dann alle Worte aus der Datei extrahieren.
Betrachten Sie dazu die Aufgabe "Textanalyse". Dort wie hier wird ein Wort folgendermassen
definiert: Es beginnt mit einem Buchstaben und besteht aus Buchstaben und Ziffern. Im
Gegensatz zur Aufgabe "Textanalyse" soll hier aber nicht die Länge eines Wortes definiert,
sondern das Wort selbst in einer String-Variablen gespeichert werden. Schreiben Sie also
zuerst diesen Programmteil und geben Sie zur Kontrolle jedes gefundene Wort und die
Zeilennummer seines Auftretens am Bildschirm aus.
- Erweitern Sie nun das Programm dahingehend, dass die gefundenen Worte in einem Array
gespeichert werden. Da später noch die Zeilennummern als lineare Liste gesprechert werden
müssen, reicht ein String-Array nicht aus. Vielmehr sind die Arraykomponenten Elemente
der folgenden Struktur:
struct eintrag
{
char wort[100];
struct liste * zeilen;
};
struct liste * znum verweist dabei auf die (später zu ergänzende) Liste der
Zeilennummern. Damit der Compiler nicht meckert, muss diese Datenstruktur vor eintrag
definiert werden:
struct liste
{
int zeile;
struct liste * next;
};
Das Array wird dann definiert als
struct eintrag xref[1000];
so dass sich folgendes Bild ergibt:
Array Listen
+-----+ +-----+ +-----+ +-----+
|.....|--| |--| |--| |
| | +-----+ +-----+ +-----+
+-----+ +-----+
|.....|--| |
| | +-----+
+-----+ +-----+ +-----+
|.....|--| |--| |
| | +-----+ +-----+
+-----+ +-----+ +-----+ +-----+
|.....|--| |--| |--| |
| | +-----+ +-----+ +-----+
+-----+
...
Die gefundenen Worte sollen ins Array xref einsortiert werden, so dass
das Array immer eine aufsteigend sortierte Wortfolge enthält. Ist ein Wort
schon im Array, wird es nicht nochmals gespeichert. Lassen Sie sich
für diesen Aufgabenteil vom Skript anregen: "Ausgewählte Algoritmen" -->
"Sortieren durch Einfügen".
Geben Sie zur Kontrolle die Wortliste auf dem Bildschirm aus. Die Wörter müssen
aufsteigend sortiert sein und jedes Wort darf nur einmal vorkommen.
-
Nun erfolgt die dritte Erweiterung, die Speicherung der Zeilennummern.
- Tritt ein Wort das erste Mal auf, wird eine neue lineare Liste erzeugt und
das erste Element dieser Liste nimmt die Zeilennummer auf. Die Komponente
zeilen verweist auf den Anfang dieser Liste.
- Tritt ein Wort auf, das bereits in xref gespeichert ist, wird
für die Zeilennummer ein weiteres Element an die lineare Liste angehängt.
Geben Sie zur Kontrolle die Wortliste mit Zeilennummern auf dem Bildschirm aus.
- Der letzte Teil beinhaltet die Ausgabe in eine HTML-Datei. Dazu wird die
bereits existierende Kontrollausgabe erweitert und modifiziert.
- Der Name der HTML-Datei ergibt sich durch den Namen der Eingabedatei, an
den die Zeichenkette ".html" angehängt wird. Diese Datei wird zum Schreiben
eröffnet.
- Danach wird der folgende HTML-Vorspann in die Datei geschrieben:
<HTML>
<HEAD>
<TITLE>Cross-Referenzliste</TITLE>
</HEAD>
<BODY>
<H2>Cross-Referenz von xxxxxxxxx</H2>
<TABLE BORDER=1 CELLPADDING=3 CELLSPACING=0>
Für "xxxxxxxxx" soll der Name der Eingabendatei eingesetzt werden.
- Die Ausgabe für ein Wort bildet eine Zeile (Row) der HTML-Tabelle. Eine
Row wird durch die HTML-Sprachelemente <TR> ... </TR>
begrenzt. Innerhalb der Row gibt es zwei Spalten (columns), die durch die
HTML-Sprachelemente <TD> ... </TD> begrenzt werden.
In der ersten Spalte steht das Wort, in der zweiten Spalte alle Zeilennummern,
getrennt durch Leerzeichen. Eine komplette Tabelle-Zeile sieht dann z. B. so aus:
<TR><TD>Adam</TD><TD>2 4 12 13 14 54 123 234</TD></TR>
-
Nach Ausgabe der Wortliste wird die Datei mit den folgenden HTML-Sprachelemneten
abgeschlossen:
</TABLE>
</BODY>
</HTML>
und dann endgültig geschlossen.
Tipp: Als Eingabedatei kann man recht gut main.c verwenden.
Projekt: Terminkalender
Schreiben Sie ein Programm, das einen Kalender für das aktuelle Jahr produziert. Damit
steht auch der erste Wochentag (z.B. 1.1.2003 ist ein Mittwoch) fest. Der Kalendar
soll folgendes Aussehen haben:
Januar
---------------------
Mo 6 13 20 27 02 09:00 Frühstück bei Tiffany
Di 7 14 21 28 03 Wichtiger Termin
Mi 1 8 [15] 22 29 06* Epiphanias
Do 2 9 16 23 30 10 Alfred Geb. (23)
Fr 3 10 17 24 31 25 Opa Geb. (91)
Sa 4 11 18 25
So 5 12 19 26
---------------------
Der aktuelle Tag ist durch eckige Klammern hervorgehoben (im Beispiel der 15.).
Die Einträge, die im Kalendar rechts erscheinen sollen, stehen in einer
Datei mit Namen "kalender.dat". Pro Tag sind beliebig viele Einträge erlaubt.
Die Ausgabe soll nach Datum (gegebenfalls bei gleichem Datum nach Uhrzeit)
sortiert erfolgen. Jede Zeile dieser Datei hat folgenden Aufbau:
TT.MM[.JJJJ][-hh:mm] Eintrag
wobei TT für Tag, MM für Monat, JJJJ für Jahr, hh für Stunde und mm für
Minute steht. Optionale Teile sind in der Syntaxspezifikation
durch [] geklammert (sie dürfen also auch fehlen). Ist die Jahreszahl
angegeben, so bedeutet dies ein Geburtsdatum und im Kalender
soll automatisch in Klammern hinter dem Eintrag das Alter ausgegeben
werden.
Die Beispieldatei für das obige Blatt lautet:
10.01.1980 Alfred Geb.
02.01-09:00 Frühstück bei Tiffany
03.01 Wichtiger Termin
25.01.1912 Opa Geb.
Feiertage werden berechnet (siehe unten) und gegebenenfalls in die rechte Liste
mit aufgenommen. Beachten Sie, dass die Terminliste länger als der Monatskalender
links werden kann. Achten Sie auch darauf, dass der Text gegebenenfalls gekürzt
werden muss (max. Zeilenbreite 76 Zeichen).
Die Termineinträge eines jeden Monats werden in einer linearen Liste gespeichert
(Dynamische Datenstruktur).
Diese Listen werden wiederum mit einem 12-elementigen Pointerarray verknüpft,
so dass sich folgendes Bild ergibt:
Pointer-
Array Listen
+-----+ +-----+ +-----+ +-----+
| |__| |__| |__| |
| | +-----+ +-----+ +-----+
+-----+ +-----+
| |__| |
| | +-----+
+-----+ +-----+ +-----+
| |__| |__| |
| | +-----+ +-----+
+-----+ +-----+ +-----+ +-----+
| |__| |__| |__| |
| | +-----+ +-----+ +-----+
+-----+
...
Schreiben Sie ein Programm in C, das einen Kalender für das eingegebene
Jahr 2003 erzeugt. Das Programm liest - sofern angegeben - die Jahreszahl
von der Kommandozeile ein (ansonsten wird das aktuelle Jahr genommen) und
dann die Datei "kalender.dat". Es zeigt beim Start das Kalenderblatt für
Januar. Sehen Sie folgende Möglichkeiten der Steuerung vor:
- Monatsweise vor- und zurückblättern (Begrenzung auf den Zeitraum 1900
bis 2100).
- Löschen und Hinzufügen von Terminen (gleichzeitig soll die Datei
"kalender.dat" aktualisiert werden
- Ausdruck der angezeigten Kalenderseite auf dem Drucker
Hinweise zur Programmierung:
- Zuerst werden die Feiertage in dem entsprechenden Jahr berechnet.
Es gibt in Deutschland drei Typen von Feiertagen, die getrennt berechnet
werden müssen:
- Feste Feiertage: Diese sind immer am gleichen Tag im Jahr, aber an
unterschiedlichen Wochentagen. Dazu zählen Neujahr (1.1.),
Epiphanias (6.1.), Tag der Arbeit (1.5.), Mariä Himmelfahrt (15.8.),
Tag der deutschen Einheit (3.10.), Reformationsfest (31.10.),
Allerheiligen (1.11.), Weihnachten (25./26.12.).
- Osterabhängige Feiertage: Ostern wird immer am ersten Sonntag nach
dem ersten Frühlingsvollmond gefeiert. Die Feiertage dieser Gruppe
haben stets den gleichen Abstand in Tagen zum Ostersonntag. Diese
Feiertage liegen stets auf dem gleichen Wochentag, haben aber jeweils
ein anderes Datum, das um ca. 1 Monat schwanken kann. Zu dieser Gruppe
zählen Fastnacht (47 Tage vor Ostern), Karfreitag, Ostern, Christi
Himmelfahrt (Ostern+39), Pfingsten (Ostern+49/50), Fronleichnam (Ostern+60).
- Wenigschwankende Feiertage: Das sind Feiertage, die zwar auf einem festen
Wochentag liegen, aber nur innerhalb einer Woche schwanken
koennen. Diese sind: Buß- und Bettag (16.-22.11.), die Advente,
Erntedankfest (30.9.-6.10.), Muttertag (8.-14.5.).
- Das Osterdatum lässt sich mit einem Algorithmus von Gauß berechnen (siehe
Skript). Hat man dies einmal ermittelt, kann man die beweglichen Feiertage
leicht ausrechnen.
- Aus dem Wochentag, auf den der 1. Januar fällt ergibt sich die Wochentagsverteilung
für das ganze Jahr und ebenso die wenigschwankenden Feiertage.
Um dieses Problem zu lösen, soll eine Zerlegung in Teilprobleme erfolgen.
Diese Teilprobleme werden dann durch Struktogramme beschrieben und danach
als folgende Funktionsaufrufe in C kodiert:
- int istSchaltjahr(long jahr) soll den Wert 1 zurückliefern,
wenn jahr ein Schaltjahr ist, und sonst 0 (Null). Diese Funktion
gibt also an, um wieviele Tage jahr länger ist, als ein normales Jahr
mit 365 Tagen.
- int monatsLaenge(int monat, long jahr) liefert als Rückgabewert
die Länge eines gegebenen Monats in einem gegebenen Jahr, z.B. hat der
Monat 2 (Februar) im Schaltjahr 1996 eine Länge von 29 Tagen.
Prinzipiell ist die Lösung durch folgende Tabelle gegeben:
| Jan | Feb | Mar | Apr | Mai | Jun |
Jul | Aug | Sep | Okt | Nov | Dez |
| 31 | 28/29 | 31 | 30 | 31 | 30 |
31 | 31 | 30 | 31 | 30 | 31 |
- int wochenTag (int tag, int monat, long jahr) liefert für
ein konkretes Datum, z.B. den 24.12.1994, die Nummer (Montag = 0, ...,
Sonntag = 6) des zugehörigen Wochentages zurückliefert, in diesem Fall
eine 5 (Samstag).
- void printMonat(int monat, long jahr) erzielt für einen
gegebenen Monat in einem gegebenen Jahr die oben dargestellte Auflistung eines
kompletten Kalendermonats.
Schaltjahre sind alle Jahre, die eine der zwei folgenden Bedingungen erfüllen:
- Teilbar durch 4 und nicht teilbar durch 100.
- Teilbar durch 400.
Schreiben Sie zunächst das Programm für das Kalenderblatt (linke Seite) und fügen
Sie erst dann die Feiertagsfunktionen hinzu, wenn die Ausgabe des Kalenderblatts
einwandfrei funktioniert. Zum Schluß werden die Funktionen für die Anzeige der
Termine aus der Datei und die Terminänderungsfunktion programmiert.
Zum Inhaltsverzeichnis
Copyright © FH München, FB 04, Prof. Jürgen Plate
Letzte Aktualisierung: 15. Jun 2006