Selbst das längste Leben lässt sich in Sekunden ausdrücken.— Robert Merle: Madrapur
Idee aus: [Hartmut Ring: Texteditoren in C. McGraw-Hill (1989), S. 10-17.]
Wie alt bist du? In Jahren weiß das jeder sofort. Aber in Tagen gemessen? Es sind die einfachen Fragen, die schwierig zu beantworten sind.
Schreibe ein Programm in C++, welches mit dem Aufruf
tageseit 1967-03-22
(Datum gemäß EN) die entsprechende Zahl auf den Bildschirm schreibt. Es spricht nichts dagegen, den gefundenen Algorithmus für ein abgewandeltes Programm einzusetzen:
tagebis 2002-12-24
Für den Anfang genügt auch ein Programm, bei dem beide Daten einzugeben sind:
tageseit Anfangsdatum (Jahr Monat Tag): 1967 3 22 Enddatum (Jahr Monat Tag): 2002 12 24 Tage = ????
Anstelle der Fragezeichen sollte die Anzahl der Tage vom Anfangs- bis zum Enddatum stehen. Zur Vereinfachung kann vorerst angenommen werden, dass der Nutzer sich nicht vertippt und die Daten in der richtigen Folge eingegeben werden.
A Lebenstage im Geburtsjahr bestimmen B Tage der Zwischenjahre hinzurechnen C Tage im aktuellen Jahr (bis heute) hinzuzählen Beispiel (22.03.1967 bis 24.12.2002): A) verbleibende Tage im März 31-22 = 9 Tage im April ........... +30 = 39 Tage im Mai ............. +31 = 70 Tage im Juni ............ +30 = 100 Tage im Juli ............ +31 = 131 Tage im August .......... +31 = 162 Tage im September ....... +30 = 192 Tage im Oktober ......... +31 = 223 Tage im November ........ +30 = 253 Tage im Dezember ........ +31 = 284 Wäre das Startjahr Schaltjahr und der Startmonat Januar oder Februar, wäre noch der Schalttag zu addieren. B) für alle Zwischenjahre 1968 bis 2001 die Jahreslängen hinzuzählen, zusammengefasst: 33 Jahre je 365 Tage ... +10945= 11229 bei Schaltjahr je ein Tag +9 = 11238 (1968,72,76,80,84,88,92,96,2000) C) für alle vollen Monate im aktuellen Jahr Tage im Januar .......... +31 = 11308 Tage im Februar ......... +30 = 11338 Tage im März ............ +31 = 11369 Tage im April ........... +30 = 11399 Tage im Mai ............. +31 = 11430 Tage im Juni ............ +30 = 11460 Tage im Juli ............ +31 = 11491 Tage im August .......... +31 = 11522 Tage im September ....... +30 = 11552 Tage im Oktober ......... +31 = 11583 Tage im November ........ +30 = 11613 Wäre das aktuelle Jahr Schaltjahr und aktueller Monat mindestens März, wäre noch der Schalttag zu addieren. Tage im aktuellen Monat.. +24 = 11637 =====
Die ganze Rechnung klappt nicht, wenn Anfangs- und Endjahr das gleiche sind. Dann wäre genau die Länge des Jahres abzuziehen:
D Falls Anfangs- gleich Endjahr: - (365+evtl. Schalttag) = Ergebnis
Diese erste Version ließe sich wie folgt implementieren (als Funktion). Würden Programmierer nach Quelltextzeilen bezahlt, gäbe das ein hübsches Sümmchen. (Vielleicht würden wir noch ein bisschen mehr hinzuzaubern
int anzahltage(int startjahr, int startmonat, int starttag, int endjahr, int endmonat, int endtag) { int tage = -starttag; // Teil A: Rest des Startjahres if (startmonat==1) tage += 31; if (startmonat<=2) { tage += 28; if (startjahr%4==0) tage++; // Schaltjahr: Julianische Regel if (startjahr%100==0) tage--; // doch keins: Gregorianische Regel if (startjahr%400==0) tage++; // trotzdem eins: Gregorianische Ausnahme } if (startmonat<=3) tage += 31; if (startmonat<=4) tage += 30; if (startmonat<=5) tage += 31; if (startmonat<=6) tage += 30; if (startmonat<=7) tage += 31; if (startmonat<=8) tage += 31; if (startmonat<=9) tage += 30; if (startmonat<=10) tage += 31; if (startmonat<=11) tage += 30; if (startmonat<=12) tage += 31; for (int jahr=startjahr+1; jahr<endjahr; jahr++) // Teil B: Zwischenjahre { tage += 365; if (jahr%4==0) tage++; if (jahr%100==0) tage--; if (jahr%400==0) tage++; } if (endmonat>1) tage += 31; // Teil C: Anfang des Endjahres if (endmonat>2) { tage += 28); if (endjahr%4==0) tage++; if (endjahr%100==0) tage--; if (endjahr%400==0) tage++; } if (endmonat>3) tage += 31; if (endmonat>4) tage += 30; if (endmonat>5) tage += 31; if (endmonat>6) tage += 30; if (endmonat>7) tage += 31; if (endmonat>8) tage += 31; if (endmonat>9) tage += 30; if (endmonat>10) tage += 31; if (endmonat>11) tage += 30; tage += endtag; // Teil D: Korrektur if (startjahr == endjahr) { tage -= 365; if (startjahr%4==0) tage++; if (startjahr%100==0) tage--; if (startjahr%400==0) tage++; } return tage; // endlich! richtig?? }
Für C-Fremdlinge seien einige komische Zeichen erklärt:
+=
erhöht die links stehende Variable um den rechten Wert,
-=
zieht ab,
++
und –
zählen die Variable eins rauf bzw. runter,
a%b
wird a modulo b gelesen und berechnet den Rest der Division von a durch b.
Die Berechnung mag zwar richtig sein, aber beim Lesen befallen einen doch Zweifel. Aufwändig (frühere Schreibung: aufwendig) ist die Lösung. (Man könnte die Wand hoch gehen, wie langsam die Kiste ist.) Und besonders elegant ist sie auch nicht, höchstens als Kunstschmiedearbeit. Programmieren ist schließlich (auch) eine Kunst. Die Kunst beim Programmieren besteht jedoch im Streben nach Einfachheit und Wiederverwendbarkeit.
Als erstes sticht die viermalige Verwendung der Schaltjahr-Regel hervor. Wiederverwendung wurde zwar auch betrieben, aber nur durch Kopieren, Einfügen und Ändern von Quelltext. Kein Grund, stolz zu sein. Dieser Schaltjahr-"Eiertanz" lässt sich herausziehen:
bool schaltjahr(int jahr) { if (jahr%400 == 0) return 1; // besondere Schaltjahre: 1600, 2000, 2400 if (jahr%100 == 0) return 0; // andere Jahrhunderte nicht if (jahr%4 == 0) return 1; // gewoehnliche Schaltjahre /* else */ return 0; // gemeine Jahre }
oder unter Ausnutzung logischer Verknüpfungen UND (&&) und ODER (||):
//: tageseit.cpp : Berechnung der Tagesdifferenz - R.Richter 2002-10-06 /////////////////////////////////////////////////////////////////////// bool schaltjahr(int jahr) { return (jahr%4==0 && jahr%100!=0) || jahr%400==0; } //~
Ganze 20 Zeilen lassen sich einsparen, z.B. im Teil B stünde dann nur
tage += 365 + schaltjahr(jahr);
Schaut man etwas genauer hin, lassen sich der Teil A und der (unschöne) Teil D zusammenfassen. Das Vertauschen der Reihenfolge ist möglich, da nur Additionen der Tage stattfinden. Eigentlich wird da weiter nichts betrieben, als auf den Anfang des Geburtsjahres zurückzurechnen oder vom Anfang des Geburtsjahres bis zum Geburtstag zu zählen. Dies geschieht im wesentlichen mit dem gleichen Quelltext wie im Teil C. Diese Zahl ist als negativer Anfangswert zu nehmen (Teil A'). Danach kann von Beginn des Geburtsjahres in Jahresschritten vorwärtsgegangen werden (Teil B'). Teil C bleibt, wie er war.
int anzahltage(int startjahr, int startmonat, int starttag, int endjahr, int endmonat, int endtag) { int tage = 0; // Teil A': zurueck zum Anfang des Startjahres if (startmonat>1) tage -= 31; if (startmonat>2) tage -= 28 + schaltjahr(startjahr); if (startmonat>3) tage -= 31; if (startmonat>4) tage -= 30; if (startmonat>5) tage -= 31; if (startmonat>6) tage -= 30; if (startmonat>7) tage -= 31; if (startmonat>8) tage -= 31; if (startmonat>9) tage -= 30; if (startmonat>10) tage -= 31; if (startmonat>11) tage -= 30; tage -= starttag; for (int jahr=startjahr; jahr<endjahr; jahr++) // Teil B': ganze Jahre { tage += 365 + schaltjahr(jahr); } if (endmonat>1) tage += 31; // Teil C: Anfang des Endjahres if (endmonat>2) tage += 28 + schaltjahr(endjahr); if (endmonat>3) tage += 31; if (endmonat>4) tage += 30; if (endmonat>5) tage += 31; if (endmonat>6) tage += 30; if (endmonat>7) tage += 31; if (endmonat>8) tage += 31; if (endmonat>9) tage += 30; if (endmonat>10) tage += 31; if (endmonat>11) tage += 30; tage += endtag; return tage; }
Wir sind zwar noch nicht schneller, aber der Formulierung ist übersichtlicher geworden.
Wenn zwei Wege zum Ergebnis führen, ist wahrscheinlich der einfachere auch der richtige.
sagt Roger Penrose. Vermutlich geht es noch einfacher.
Die fortlaufenden Tests kosten einen Haufen Rechenzeit. Hier wäre ein direkter Sprungbefehl wünschenswert. Aber wir sollen goto ja nicht verwenden. Der am 07.08.2002 verstorbene Edsger Wybe Dijkstra hätte sonst keine gesegnete Ruhe:
Wenn Sie unsauber programmieren und dabei denken, Dijkstra hätte das nicht gemocht,
so ist dies genug Unsterblichkeit für mich.
Nun, es geht ohne goto. Es gibt noch genügend andere "schlimme" Konstruktionen in C. Lernen wir etwas C-Syntax hinzu:
switch(endmonat) { case 12: tage += 30; // immer die Vormonate drauf rechnen case 11: tage += 31; case 10: tage += 30; case 9: tage += 31; case 8: tage += 31; case 7: tage += 30; case 6: tage += 31; case 5: tage += 30; case 4: tage += 31; case 3: tage += 28 + schaltjahr(endjahr); case 2: tage += 31; break; case 1: break; };
Die Mehrfachverzweigung springt sofort an die richtige Stelle und wurstelt sich bis zum nächsten Abbruch (break) durch. Auch eine Art goto, ohne es so zu nennen. Java-Fans brüsten sich damit, dass in Java goto nicht erlaubt ist, dafür kann break ein Sprungziel angeben. Wie originell: goto ohne goto!
Die fortlaufende Addition macht auch noch zu viel (unnütze) Arbeit. Wir können das ändern, in dem wir dem Compiler diese Arbeit abnehmen. (Natürlich erfordert das geistige Anstrengung. Nichts ist kostenlos!)
switch(endmonat) { case 1: break; case 2: tage += 31; break; case 3: tage += 59; break; case 4: tage += 90; break; case 5: tage += 120; break; case 6: tage += 151; break; case 7: tage += 181; break; case 8: tage += 212; break; case 9: tage += 243; break; case 10: tage += 273; break; case 11: tage += 304; break; case 12: tage += 334; break; }; if (endmonat > 2) tage=+ schaltjahr(endjahr);
Jetzt wird nur noch eine Addition ausgeführt. Gut, nicht? Es geht noch besser.
Solche Mehrfachverzweigungen, die nur einzelne Werte nachschlagen, sind wiederum Ungetüme, die durch ein anderes Konzept aus dem Rennen geschlagen werden, durch eine Tabelle oder Liste (Feld oder indizierte Variable):
int anzahltage(int startjahr, int startmonat, int starttag, int endjahr, int endmonat, int endtag) { int const monatstage[] = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 //[0] Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez }; int tage = 0; tage += monatstage[startmonat] + starttag; // Teil A' if (startmonat > 2) tage += schaltjahr(startjahr); tage = -tage; // Tage bis einschliesslich Starttag werden abgezogen for (int jahr=startjahr; jahr<endjahr; jahr++) // Teil B' { tage += 365 + schaltjahr(jahr); } tage += monatstage[endmonat] + endtag; // Teil C' if (endmonat > 2) tage+= schaltjahr(endjahr); return tage; }
Die Anfangswertbelegung mit 0 und die Umkehr der Tageszahl ist weitschweifig, verdeutlicht aber die inhaltliche Gleichheit der Teile A' und C'. Ein Bild sagt mehr als Tausend Worte. (Frei übersetzt ins Computer-Englisch: A picture counts more than … Kwords.) Machen wir uns ein Bild von dem, was geschieht:
Anfang Startjahr A' |-------------------Start C' |------------------------------|------------Ende B' Anfang Ende
Das soll uns auf die nächste Idee bringen, mit der die zeitraubende Schleife (Teil B') aufgelöst wird. Lässt sich der Abstand von Start bis Ende ohne die Schleife bestimmen? Natürlich geht das. Wir müssen nur eine einheitliche Skalenbeschriftung anbringen, an der wir den Wert ablesen. Dann können wir einfach die Differenz der Skalenwerte als Abstand nehmen!
Die neue Lösung basiert auf dem Zusammenhang
Anzahl der Tage = TagesNummer(Enddatum) - TagesNummer(Startdatum)
Für die TagesNummer-Umrechnung können wir die Teil B' und C' zusammenfassen. Dabei fällt allerdings der Anfang des Startjahres als Bezugspunkt weg. Was nehmen wir stattdessen?
Das Nächstliegende ist, einfach alle Jahre seit Anfang der (unseren) Zeitrechung zu nehmen. Es ist (für unsere Zwecke) auch gar nicht so wichtig, ob es ein Jahr Null gab oder nicht.
(Es gab keins. Auch vorher haben schon Menschen gelebt, vermutlich, ohne das Gefühl gehabt zu haben, vorzeitliche Wesen zu sein. Man fühlt sich eher am Ende einer Epoche: "fin de siècle"-Stimmung. Vorher rechnete man "ad urbe condita", ab dem sagenhaften Gründungsdatum der "ewigen" Stadt (Rom) 753 v.u.Z. durch die Wolfskinder Romulus und Remus. Tatsächlich ist Rom wohl um 575 v.u.Z. unter etruskischer Herrschaft entstanden.
Noch eher, ab 776 v.u.Z., rechneten die Griechen in Olympiaden. Israeliten setzten die Erschaffung der Welt als Anfangspunkt fest, nach ihren Vorstellungen 3761 v.u.Z.; die Ägypter 4713 v.u.Z. Für Mohammedaner beginnt die Zeitrechnung im Jahr 622 u.Z. mit der Hedschra, der Flucht Mohammeds. Buddhisten rechnen immer in Jahreszyklen, die nach Tieren benannt sind: Tiger, Drache usw. Japaner rechnen in Regierungsjahren ihres jeweiligen Kaisers, z.Z. Akihito, eine Methode, die lange Regierungszeiten voraussetzt, um praktisch zu sein.
Unsere Zeitrechnung (nach Christi Geburt) wurde auch nicht im Jahre 1 eingeführt, sondern erst im Jahre 532 u.Z. durch den Mönch Dionysius Exiguus [Schlag nach Natur, S.88]. Christi Geburt zu Weihnachten kurz vor Beginn des Jahres 1 u.Z. ist so genau nicht überliefert. Es gibt Theorien, dass dies um mehrere Jahre danebenliegt. So könnte der Stern von Bethlehem eine Konjunktion, d.h. ein Vorbeigang, von Jupiter und Saturn im Jahre 7 v.u.Z. gewesen sein, der Astronomen aus der Fremde, uns als die drei Könige dem Morgenland bekannt, in Bewegung brachte zum besten Beobachtungsplatz. Es gibt Menschen, die die Gestalt Jesus für eine Erfindung halten. Und es gibt Menschen, die ungefähr 3 Jahrhunderte für eine Erfindung spät-mittelalterlicher Urkundenfälscher halten [Illig: Das erfundene Mittelalter]. Klammer zu.)
Rechnen wir also so, als ob es ein Jahr Null gegeben hätte, in dem Maria schwanger war.
int tagesNr(int jahr, int monat, int tag) { int const monatstage[] = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 //[0] Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez }; int tage = 365*jahr // Normaljahre + jahr/4 // Anzahl gewoehnliche Schaltjahre - jahr/100 // ohne Jahrhunderte + jahr/400 // aber mit 400er Schaltjahren + monatstage[monat] // Vormonate + tag; if (monat<=2 && schaltjahr(jahr)) tage--; // vor dem Schalttag return tage; } int anzahltage(int startjahr, int startmonat, int starttag, int endjahr, int endmonat, int endtag) { return tagesNr(endjahr, endmonat, endtag) -tagesNr(startjahr, startmonat, starttag); }
Für weit zurückliegende Datumsangaben ist die Anwendung der Differenzberechnung mit Vorsicht zu genießen. In den katholischen Ländern verordnete Papst Gregor XIII., dass der 15. Oktober unmittelbar auf den 4. Oktober 1582 folgte, um die Fehler des Julianischen Kalenders auszugleichen, und künftig die Hundertjahr- sowie die Vierhundertjahr-Schaltregel zu befolgen. Das protestantische Deutschland vollzog diesen Schritt am 1. März 1700, England im September 1752, Schweden 1753, Japan 1873, die Türkei 1916, Russland 1918, Rumänien 1919, Griechenland erst 1923.
Die Festlegung, wann das Jahr beginnt, ist willkürlich. Erst im Spätmittelalter setzte sich der Januar als Jahresanfang durch. Die alten Römer begannen ihr Jahr mit dem Monat, der nach dem Kriegsgott Mars benannt ist (Martius = März). Mit der Öffnung des Mars-Tempels konnte wieder ein Feldzug beginnen, um neue Provinzen zu unterwerfen. Die Monatsnamen September (septem = der siebte) bis Dezember (decem = der zehnte) künden noch von dieser Zählweise. Der etwas kürzer geratene Februar beschloss das Jahr. Der Schalttag befand sich also ursprünglich am Jahresende. Er wurde auf Julius Caesars Vorschlag eingeführt (Julianischer Kalender 46 v.u.Z.), um den Fehler zwischen Kalenderjahr (365 Tage) und Sonnenjahr (365,2422 Tage) zu mindern.
Lassen wir das Jahr ab März beginnen und schlagen Januar und Februar dem Vorjahr zu, können wir die Schaltjahrprozedur einsparen. Allerdings müssen wir die Monatstabelle neu berechnen:
int tagesNr(int jahr, int monat, int tag) { if (monat > 2) monat -= 3; // Maerz: monat==0 else { jahr--; monat += 9; } int const monatstage[] = { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 // Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez Jan Feb // 0 1 2 3 4 5 6 7 8 9 10 11 }; return 365*jahr // Normaljahre + jahr/4 - jahr/100 + jahr/400 // Schalttage + monatstage[monat] // Vormonate + tag; }
Jetzt erst zahlt sich auch die Umstellung aus! Die mit März beginnende Monatstabelle enthält eine Regelmäßigkeit. Sie ist nicht ganz einfach zu entdecken, unter Zeitdruck schon gleich gar nicht. Hier brauchen wir eine Erleuchtung. Eine genaue (!) Zeichnung auf Millimeterpapier kann auf die Sprünge helfen: Es ist (fast) eine Gerade. Durch Experimentieren (Tabellenkalkulation!) findet man den linearen Ausdruck, der die Tabelle überflüssig macht:
monatstage[monat] = (306*monat+5)/10
Die Fünf in der Klammer erzwingt die korrekte Rundung zur nächstliegenden Ganzzahl. Und noch eine letzte Überlegung: Vier Jahre haben 4*365+1 = 1461 Tage, von Jahrhunderten abgesehen.
int tagesNr(int jahr, int monat, int tag) { if(monat>2) monat -= 3; // Maerz: monat==0 else { jahr--; monat += 9; } return 1461*jahr/4 - jahr/100 + jahr/400 // ganze Jahre + (306*monat+5)/10 // Vormonate + tag; }
Jetzt ist die Tagesnummerberechnung kompakt. Die Ideen, die zu dieser Formel geführt haben, sind am Quelltext nicht mehr nachvollziehbar. Die "magischen" Zahlen 1461 und 306 scheinen "vom Himmel gefallen" zu sein. Unter diesem Blickwinkel gesehen, wurde hier evtl. schon zu viel optimiert, auf Kosten der Verständlichkeit.
Werden Datum-Angaben häufig benutzt, kann es lästig werden, jeweils drei Parameter an die Funktionen übergeben zu müssen. Aus diesem Grund lassen sich solche Dinge zu einem zusammengesetzten Typ, Datensatz, Verbund oder Struktur genannt, bündeln.
//> tageseit.cpp struct Datum { int jahr, monat, tag; }; int tagesNr(Datum d) { if (d.monat > 2) d.monat -= 3; // Maerz: d.monat==0 else { d.jahr--; d.monat += 9; } return 1461*d.jahr/4 - d.jahr/100 + d.jahr/400 // ganze Jahre + (306 * d.monat + 5)/10 // Vormonate + d.tag; } int anzahltage(Datum start, Datum ende) { return tagesNr(ende) - tagesNr(start); } //~
Computerprogramme, die Nutzereingaben abfragen, müssen damit rechnen, dass falsche, sogar offensichtlich unsinnige Eingaben getätigt werden. Einige davon lassen sich durch eine Gültigkeitsprüfung erkennen:
//> tageseit.cpp bool istgueltig(Datum d) { if(d.tag<1 || d.monat<1 || d.monat>12 || d.jahr<0) return 0; int const tage[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez }; return d.tag <= tage[d.monat] + (d.monat==2 && schaltjahr(d.jahr)); } //~
Da brauchen wir auch die Schaltjahrbestimmung wieder. Nichts war umsonst!
In Programmen gibt es viele seichte, triviale Stellen. Nicht zu einfache Programme sind jedoch an einigen Stellen auch tief oder knifflig. Eine solche Frage ist die nach dem aktuellen Datum. Der Nutzer soll dieses nicht eingeben müssen.
Hier werden Kenntnisse der Systemschnittstelle benötigt.
Da es um Zeitangaben geht,
sollten wir zuerst in der <ctime>
-Bibliothek nachsehen.
Dort gibt es eine Funktion time()
, die die aktuelle Zeit liefert,
allerdings in einer sehr unhandlichen Form:
die Anzahl der Sekunden seit dem 1. Januar 1970, 0.00 Uhr Weltzeit (GMT).
Diese Zeitangabe lässt sich mit einer Funktion localtime()
in Ortszeit umrechnen und aufbrechen in Jahr, Monat, Tag, Stunde usw.
Zu beachten ist,
dass Monate ab 0 (Januar) beginnend zählen und 1900 als Jahr Null angegeben wird.
//> tageseit.cpp #include <ctime> Datum heute() { using namespace std; time_t jetzt = time(nullptr); struct tm* t = localtime(&jetzt); Datum d; d.jahr = t->tm_year+1900; d.monat = t->tm_mon +1; d.tag = t->tm_mday; return d; } //~
Die Funktion localtime()
übernimmt die Adresse (&
)
einer time_t
-Typ-Variable und liefert einen Zeiger (*
),
über den mit →
auf die Komponenten einer
systeminternen Variable vom Typ tm
zugegriffen werden kann.
Nun fehlt noch das Hauptprogramm. Das Einfachste wäre
#include <iostream> int main() { Datum start, ende; std::cout << "Anfangsdatum (Jahr Monat Tag): "; std::cin >> start.jahr >> start.monat >> start.tag; std::cout << "Enddatum (Jahr Monat Tag): "; std::cin >> ende.jahr >> ende.monat >> ende.tag; std::cout << "Tage = " << anzahltage(start, ende) << '\n'; return 0; }
Es soll aber mit einem Datum als Parameter aufrufbar sein:
tageseit 1967-03-22
Das Datum ist nach EN anzugeben. Wird kein Datum angegeben, wird das Geburtsdatum im Nutzerdialog erfragt. Das Programm berechnet die Anzahl der Tage bis zum aktuellen Datum, das nicht eingegeben wird. Ungültige Eingaben sind zurückzuweisen.
//> tageseit.cpp #include <iostream> #include <cstdlib> int main(int argc, char* argv[]) { Datum geburt; if (argc == 1) // Aufruf ohne Parameter { std::cout << "seit Datum (Jahr-Monat-Tag): "; char puffer[20]; std::cin.width(20); std::cin >> puffer; std::cin.ignore(200,'\n'); geburt.jahr = atoi(puffer); geburt.monat = atoi(puffer+5); geburt.tag = atoi(puffer+8); } else if (argc == 2) // Datum als Argument { geburt.jahr = atoi(argv[1]); geburt.monat = atoi(argv[1]+5); geburt.tag = atoi(argv[1]+8); } if (!istgueltig(geburt)) { std::cerr << "Kein gueltiges Datum.\n"; return 1; } std::cout << "Tage = " << anzahltage(geburt, heute()) << '\n'; return 0; } //~
Damit erfüllt das Programm die ihm gestellte Aufgabe.
In diesem Text ist vorgesehen,
dass ab den mit //:
beginnenden Zeilen
bzw. mit //>
fortsetzend
bis zur mit //~
beginnenden Zeile stehender Quelltext
in Dateien mit dem dahinterstehenden Namen zu schreiben ist.
Auf diese Weise werden Quelltext und Dokumentation
nach der Idee des Literate Programming von Donald E. Knuth zusammengefasst.
Prinzip one source:
Halte Quelle und Dokumentation stets konsistent, am besten an einem Ort.
Das Herausziehen der Quellen (aus einer ASCII-Klartextdatei) kann ein Hilfsprogramm erledigen, eine schöne Übungsaufgabe in Sachen Dateiarbeit. Die Idee dafür habe ich aus [Bruce Eckel: Thinking in C++].
Das endgültige Programm umfasst nur 95 Zeilen! Hier ist noch ein Testrahmen:
//# make.bat call cc tageseit.cpp tageseit //~
Auf dem Weg zur Lösung haben wir einen weiten Bogen geschlagen. Angefangen haben wir bei den Problemen der Algorithmierung, mit denen Programmierer unabhängig von der Sprache zu tun haben, in der sie Algorithmen niederschreiben.
Mit der einfachen Frage am Anfang wurde ein großer Bereich der Programmiergrundlagen berührt. Dabei haben wir aber keine Dateien benutzt, schließlich muss das Programm keine Daten speichern.
Man könnte den Typ Datum
auch in objektorientierter Weise weiterentwickeln,
aber dieses Programmierbeispiel ist schon lang genug. Schluss jetzt!