C / C++ Sprachelemente
Da C eine sehr komplexe und umfangreiche Sprache ist, möchte ich versuchen, soviel wie möglich zu erklären und zu beschreiben. Die Tatsache, dass C sehr weit verbreitet ist, trägt dazu bei, dass es sehr viele verschiedene Abarten und Versionen dieser Sprache gibt. Um jedoch nicht zu sehr abzuschweifen, werde ich mich nur auf eine Version festlegen. C ist eine Compiler - Sprache, was bedeutet, dass aus dem Quellcode ein Bytecode erzeugt wird, der direkt Anweisungen für den Prozessor enthält. Dieses Übersetzen (to compile - engl.: übersetzen, auflesen) in den Bytecode übernimmt der sogenannte Compiler. Abhängig von der Herstellerfirma übersetzen verschiedene Compiler den selben Code sehr unterschiedlich, was durchaus zu Problemen führen kann. Aus diesem Grund möchte ich den Quellcode, den ich in diesem Referat benutze, nur anhand des Borland Turbo C++ Compiler 3.0 beschreiben.
C ist eine genormte Sprache, was dazu führt, dass die Art und Weise, wie diese Sprache auszusehen hat, die Syntax, einheitlich sein muss. All diese Regeln sind in ANSI - C festgehalten und müssen erfüllt werden. Standard C ist eine ablauforientierte, modulare Sprache, ohne objektorientierte Elemente.
Wie in jeder anderen Sprache gibt es auch in C die drei grundlegenden Sprachelemente:
a) Sequenz - z.B.: Summe = a + b;
b) Iteration - z.B.: for(x=0; x<=10; x=x+1);
c) Selektion - z.B.: if(x == 10)
Hier wird zu jedem Element zwar nur ein Beispiel angegeben, tatsächlich gibt es jedoch sehr viele Variationen. Wie bereits ersichtlich ist, steht hinter jeder Anweisung ein Semikolon (Strichpunkt). Dieser wird in C dazu benützt, um eine Sequenz als abgeschlossen zu markieren. In anderen Sprachen ist es auch üblich, eine Zeile als eine Anweisung zu sehen. Durch dieses Abschlusszeichen ist es in C aber möglich, den gesamten Code eines Programmes (abgesehen von den Präprozessorbefehlen) in eine Zeile zu schreiben. Nicht sehr übersichtlich aber möglich.
Was C besonders auszeichnet, ist die Vielfalt an Datentypen. Standardtypen gibt es zwar nur einige wenige, jedoch können diese zu den komplexesten Konstrukten zusammengelegt werden. Variablen müssen in C vor allen anderen Operationen deklariert werden (diese Beschränkung existiert in C++ nicht mehr). Eine Deklaration in C sieht folgend aus:
[Schlüsselwörter] [Schlüsselwörter] Variablenname [=Wert];
unsigned int iWert = 0;
In das Feld für 'Datentyp' kann jeder in C vorhandene oder selbst definierte Datentyp geschrieben werden. Für 'Variablenname' kann jede beliebige mit einem Buchstaben (A - Z, a - z, _) beginnende Zeichenfolge stehen, die jedoch nicht länger als 255 Zeichen sein darf. Zahlen dürfen nicht am Beginn des Variablennamens, sehr wohl jedoch in der Mitte oder am Ende stehen. Sonderzeichen dürfen (außer dem '_' ) überhaupt nicht vorkommen, da sie vom Compiler entweder als Operatoren identifiziert werden oder im Quellcode nicht erlaubt sind. Optional kann man der deklarierten Variable einen Wert zuweisen, der sich selbstverständlich im Verlauf des Programmes noch ändern kann. Die in eckigen Klammern ( [ ] ) stehenden 'Schlüsselwörter' sind auch optional und können wie folgt aussehen:
Schlüsselwörter (engl.: keywords) können bei der Deklaration von Variablen verschiedene Funktionen ausüben und den Standarddatentyp damit erheblich verändern. Einige der wichtigsten Schlüsselwörter:
unsigned - Steht vor dem Datentyp und legt fest, dass die folgende Variable kein Vorzeichen hat, das heißt, dass sie nur positive Werte annehmen kann.
signed - Das Gegenstück zu unsigned. Eine Variable, die mit diesem Schlüsselwort deklariert wurde, kann sowohl positive als auch negative Zahlen aufnehmen. Zu beachten ist hier jedoch, dass diese Variable dann nur noch die Hälfte ihrer bisherigen Kapazität an positiven Zahlen annehmen kann. Die andere Hälfte wird eben für die negativen Zahlen verwendet. (Zusatz: 0 gilt als die erste positive Zahl)
register - Weißt den Compiler an, dass die folgende Variable, falls möglich, in ein Prozessor-register geschrieben werden soll, um die kürzeste Zugriffszeit zu erreichen.
long - Eigentlich ein eigener Datentyp, der jedoch als Ersatz von long int zu sehen ist. Er weist den Compiler an, eine 32 Bit Variable zu erzeugen.
short - Mit short verhält es sich ähnlich wie mit long, nur dass der Compiler angewiesen wird eine 16 Bit Variable zu erzeugen.
static - Das Schlüsselwort static lässt eine Variable auch über einen Block hinaus am Leben.
volatile - Dem Compiler wird hierbei mitgeteilt, dass eine Variable auch außerhalb des Programmes verändert werden kann. Diese ist z.B. bei Interrupts notwendig.
Der oft gehasste Begriff des Zeigers (engl. Pointer) ist in C so wichtig, wie das Amen im Gebet. Ein Pointer zeigt auf eine Variable im Speicher. Ein Pointer wird ähnlich deklariert, wie eine normale Variable, mit einigen Unterschieden:
[Datentyp]* Variablenname;
int *pnWert;
Einem Pointer sollte nicht direkt eine Adresse zugewiesen werden. Diese sollte von einer anderen Variable belegt werden. Ein Pointer, der mit einem bestimmten Variablentyp deklariert wurde, kann nur auf eine Variable des selben Typs zeigen. Dies ist wichtig, da der Typ des Zeigers die Länge der Variable angibt, auf die gezeigt wird. Ein Zeiger enthält in der Regel weniger Information als die Variable auf die er zeigt, was bei den später folgenden Funktionsaufrufen von Vorteil ist. Auch ist die Rückgabe von komplexen Datenstrukturen nur mit Hilfe ihrer Zeiger möglich. Abbildung 1 veranschaulicht den Begriff des Zeigers:
Abb. 1
Ein Zeiger muss nur so groß sein, wie die Adresse, auf die er zeigt. Die Adresse, auf die der Pointer hier zeigt, enthält eine Variable, deren Größe auch im MByte Bereich liegen könnte. Der Pointer bleibt trotzdem gleich groß sein, auch wenn er auf eine 4 Byte große Variable zeigen würde. Dies eröffnet nun die Möglichkeit, dass Variablen, die in verschiedenen Funktionen gebraucht werden, nicht aufwendig durch Kopieren des gesamten Inhalts an Unterfunktionen übergeben werden müssen, sondern lediglich durch Kopieren des Zeigers.
Felder sind in C Aneinanderreihungen von Variablen gleichen Typs. Man unterscheidet Felder nach ihrer Dimension. Diese Dimensionen entsprechen auch den Dimensionen von geometrischen Objekten. Demnach entspricht eine normale Variable einem punktförmigen Objekt, ein eindimensionales Feld einer Linie, ein zweidimensionales Feld einer Fläche und ein dreidimensionales Feld einem Körper. Felder werden in C folgendermaßen deklariert:
[Datentyp] Feldname[feste Anzahl]; <-- eine Dimension
[Datentyp] Feldname[feste Anzahl][feste Anzahl]; <-- zwei
int anFeld[10][10];
Felder können von jedem beliebigen Datentyp erstellt werden. Die Größe des Feldes muss bereits zur Übersetzungszeit bekannt sein, da der Compiler hierfür Speicherplatz reservieren muss. Ist es aus programmtechnischen Gründen jedoch erforderlich, dass die Größe des Feldes erst zur Laufzeit feststehen darf, so kann man ein Feld dynamisch erzeugen. Dieser Punkt wird erst später genauer behandelt.
In der Sprache C gibt es keine Unterschied zwischen einer Prozedur und einer Funktion, wie zum Beispiel in Basic. Jede Routine wird gleich behandelt. Grundsätzlich ist es in C zwingend erforderlich, dass Funktionen vor ihrem Aufruf zuerst angekündigt werden. Das heißt, dass der Kopf einer jeden Funktion vor besagtem Aufruf stehen muss. Danach folgt der Programmeinstiegspunkt main und danach die Definition der Unterprogramme. Prinzipiell sieht eine Funktion in C folgend aus:
[Datentyp] Funktionsname( )
int Funktion(int x1, int x2)
Auch bei Funktionen kann das Feld 'Datentyp' jeden beliebigen Datentyp annehmen. Hier ist jedoch darauf zu achten, dass bei komplexen, eigenen Datentypen ein Zeiger zurückgegeben werden muss.
Die Parameterliste kann beliebig viele Parameter enthalten, die syntaktisch gleich zu schreiben sind, wie Variablendeklarationen. Die Parameter in der Liste müssen durch Beistriche getrennt werden und können ebenfalls jeden Datentyp darstellen. Bei der Übergabe von Parametern gelten folgende Richtlinien:
a) Zwischen den beiden runden Klammern können beliebig viele Parameter stehen, die durch einen Beistrich getrennt werden müssen.
b) Zusätzlich zum Namen der Variablen muss auch deren Typ angegeben werden.
c) Es können Zeiger auf Variablen oder auch Variablen selbst übergeben werden.
d) Es ist möglich, die Parameterliste variabel zu gestalten (Ellipse )
Wird an die Funktion ein Feld übergeben, so geschieht dies über einen Zeiger. Dieser zeigt auf das erste Element des Feldes.
Soll die aufgerufene Funktion ein Resultat zurückliefern, so ist die Art, wie die Variable zurückgeliefert wird, abhängig von deren Typ. Wird ein Feld zurückgeliefert, muss ein Zeiger auf das Feld zurückgeliefert werden. Bei normalen Variablen kann der Wert oder der Zeiger zurückgeliefert werden. Weitere Information: siehe 4.6
Die Auflösung des Quellcodes durch den Compiler erfolgt von rechts nach links und von innen nach außen. Wie stark eine C - Sequenz verschachtelt sein darf, ist von der Mächtigkeit des Compilers abhängig, der den Code übersetzt. Ein sehr großer Vorteil von C ist die Fähigkeit, sehr komplexe Konstrukte erzeugen zu können, was allerdings oftmals sehr zu Lasten der Lesbarkeit geht. So können in einer Sequenz Zuweisungen, Abfragen und Funktionsaufrufe auftreten. Um hier die Übersicht und vor allem die Richtigkeit zu gewährleisten, besitzen die einzelnen Operatoren in C eine Wertigkeit. Ahnlich wie die 'Klammer-vor-Punkt-vor-Strich' Regel in der Mathematik, haben auch hier einige Operatoren eine höhere Priorität als andere.
Die Prioritäten können aus der Tabelle entnommen werden:
Operatoren |
Bezeichnung |
|
Operatoren |
Bezeichnung |
Operatoren |
Erster Rang |
|
Relational |
Siebter Rang |
|
Funktionsaufruf |
|
< |
Kleiner |
|
Array-Subscript |
|
<= |
Kleiner Gleich |
-> |
Indirekte Komponentenauswahl (nur C++) |
|
> |
Größer |
|
Gültigkeitsbereichszugriff und -auflösung (nur C++) |
|
>= |
Größer Gleich |
|
Direkte Komponentenauswahl (nur C++) |
|
Gleichheit |
Achter Rang |
Unäre |
Zweiter Rang |
|
|
Gleich |
|
Logische Negation (NOT) |
|
|
Ungleich |
|
Bitweises Komplement |
|
UND |
Neunter Rang |
|
Unäres Plus |
|
& |
Binäres Und |
|
Unäres Minus |
|
EXOR |
Zehnter Rang |
|
Prä- oder Postinkrementierung |
|
|
Bitweises Exklusiv-Oder |
|
Prä- oder Postdekrementierung |
|
Oder |
Elfter Rang |
& |
Adressermittlung |
|
|
Bitweises Oder |
|
Umleitung |
|
Und |
Zwölfter Rang |
sizeof |
Größe des Operanden in Bytes |
|
&& |
Logisches Und |
new |
Dynamische Speicherzuweisung (nur C++) |
|
Oder |
Dreizehnter Rand |
Delete |
Dynamische Speicherfreigabe (nur C++) |
|
|
Logisches Oder |
Multiplikative |
Dritter Rang |
|
Bedingung |
Vierzehnter Rang |
|
Multiplikation |
|
|
Ternärer Operator |
|
Division |
|
Zuweisung |
Fünfzehnter Rang |
|
Modulo |
|
|
Einfache Zuweisung |
Klassen |
Vierter Rang |
|
|
Produkt Zuweisen |
|
Dereferenzierung (nur C++) |
|
|
Quotient Zuweisen |
->* |
Dereferenzierung (nur C++) |
|
|
Modulo Zuweisen |
Additiv |
Fünfter Rang |
|
|
Summe zuweisen |
|
Binäres Plus |
|
|
Differenz zuweisen |
|
Binäres Minus |
|
&= |
Bitweises Und zuweisen |
Shift |
Sechster Rang |
|
|
Bitweises ExOr zuweisen |
<< |
Shift nach links |
|
|
Bitweises Oder zuweisen |
>> |
Schift nach rechts |
|
<<= |
Linksschieben zuweisen |
|
|
|
>>= |
Rechtsschieben zuweisen |
|
|
|
Komma |
Sechzehnter Rang |
|
|
|
|
Auswerten |
Das wichtigste beim Programmieren überhaupt sind die Kommentare. Bei kleineren Programmen eher lästig entscheiden sie bei größeren Projekten zwischen Gelingen und nicht Gelingen. Aufwendige und unübersichtliche Programmteile sollten ausführlich kommentiert werden, um die Lesbarkeit auch nach längerer Abwesenheit vom Code noch gewährleisten zu können.
Der Compiler ignoriert alle Zeichen, die nach den Kommentarzeichen kommen. Kommentare können in C wie folgt geschrieben werden:
Einfacher Kommentar (erst in C++) - kommentiert nur eine Zeile
Kommentiert bis zu den Abschlusszeichen, welche auch in einer anderen Zeile liegen können */ (C und C++)
Wie in jeder anderen Programmiersprache auch, werden die Datentypen in C in verschiedene Kategorien unterteilt. Je nach Verwendungsart, Größe und Funktion. Grob gesehen gibt es nur drei große Klassen an Datentypen, die sich wie folgt einteilen lassen:
Dieser Gruppe gehören vier Datentypen an, die sich ausschließlich durch ihre Größe unterscheiden. Beginnend mit dem kleinsten möchte ich nun die Fähigkeiten und die Einsatzgebiete dieser Typen erklären.
Eine acht Bit lange Ganzzahl, deren Abkürzung für 'Charakter ' steht. Eine Variable, die vom Typ char ist, kann Zahlen im Bereich von 0 bis 255 oder von -128 bis +127 annehmen. Durch die bereits erwähnten Schlüsselwörter signed bzw. unsigned kann zwischen den beiden Wertebereichen gewechselt werden. Wird eine Variable nur mit char deklariert, so ist sie standardmäßig signed.
Bsp.:
char cChar = 100; //enthält 100
char cChar = -10; //enthält -10
signed char cChar = -10; //wie Vorzeile
char cChar = 200; //Würde zwar keinen Fehler während des Kompilieren //erzeugen, ist jedoch ein Fehlergrund, da cChar in //diesem Fall signed ist und deshalb nur bis +127 //reicht.
Je nach Betriebssystem ist dieser Datentyp 16 oder 32 Bit lang. Die Abkürzung int steht für 'Integer' (integere - lat.: ganz). Ausgehend vom 16 Bit Typ reicht der Wertebereich dieses Typs von 0 bis 65535 oder von - 32768 bis +32767. Auch hier kann man die Bereiche mit signed bzw. unsigned ändern.
Bsp.:
int iValue = 100; //enthält 100
int iValue = -10; //enthält -10
signed int iValue = -10; //wie Vorzeile
Eigentlich nur eine Abart von int, ist short immer 16 Bit lang und sollte deshalb anstelle von int verwendet werden. Wertebereich und Beispiel siehe 3.1.2.
Long ist int mit 32 Bit. Long stellt in C den größten Datentyp für Festkommazahlen dar.
Die Verwendung von Fließkommazahlen ist in den meisten Programmen erforderlich, da es nur selten Berechnungen gibt, die ohne Komma auskommen. Die Darstellung solcher Zahlen, welche nach IEEE 754 floating-point standard genormt ist, ist wesentlich komplizierter als bei Festkommazahlen. Der Wert einer Fließkommazahl entspricht nämlich nicht direkt ihrem binären Wert, sondern besteht aus drei Teilen. Eine Fließkommazahl sieht deshalb folgend aus:
Die obige Darstellung entspricht einem sogenannten short real Datentyp, der in C als float bekannt ist. Insgesamt gibt es nur zwei verschiedene Grundtypen von Fließkommazahlen, die ähnlich wie die Festkommazahlen, in C durch voranstellen von Schlüsselwörter verändert werden können (signed und unsigend haben hier jedoch keine Bedeutung!).
Die Darstellung von speziellen Zahlen ist in der Fließkommarechnung wie folgt gelöst:
Nulldarstellung (zero): Exponent ist 0, Mantisse ist 0
Unendlich (infinity): Exponent alles 1, Mantisse ist 0
Keine Zahl (not a number): Exponent alles 1, Mantisse alles 0
Ein 32 Bit Datentyp, dessen Abkürzung für 'floating' (engl. fließend) steht. Eine Variable, die als float deklariert wurde, besitzt ein Vorzeichenbit, acht Exponentenbits und 23 Bits für die Mantisse. Der Wertebereich liegt zwischen 3.4*10-38 und 3.4*1038. Eine float - Zahl ist eine Fließkommazahl einfacher Genauigkeit. Einfache Genauigkeit, da bei jeder Fließkommazahl der Wert nur angenähert werden kann. Diese Annäherung ist besser, je länger die Mantisse ist. Dies ist vor allem bei Anwendungen, die mit Geldbeträgen rechnen wichtig, da hier Rundungsfehler auftreten.
Bsp.:
float fValue = 1.5; //Zuweisung von 1,5
float fValue = -10.3; //Zuweisung von -10,3
Der Datentyp double ist 64 Bit lang und ist vom Prinzip her float mit doppelter Genauigkeit. Dieser Typ besteht aus einem Vorzeichenbit, elf Exponentenbits und 52 (doppelte Anzahl) Bits für die Mantisse. Der Wertebereich liegt zwischen 1.7 * 10-308 bis 1.7 * 10308. Double bietet nun eine doppelt so hohe Genauigkeit wie float, ist aber unter Umständen immer noch nicht für den Umgang mit Geld geeignet. Der letzte und höchste Typ an Zahl, den C zur Verfügung stellt, ist long double. Mit 80 Bit Länge und dem damit größten Wertebereich in C von 3.4 * 10-4932 bis 1.1 * 104932 die größte darstellbare Zahl.
Bsp.:
double dValue = 1.5; //Zuweisung von 1,5
long double dValue = -10.3; //Zuweisung von -10,3
Bevor die nächste Gruppe von Datentypen vorgestellt werden soll, noch eine abschließende Übersicht:
Typ |
Länge |
Wertebereich |
unsigned char |
8 Bits |
0 bis 255 |
char |
8 Bits |
-128 bis 127 |
enum |
16 Bits |
-32768 bis 32767 |
unsigned int |
16 Bits |
0 bis 65535 |
short int |
16 Bits |
-32768 bis 32767 |
int* |
16 Bits |
-32768 bis 32767 |
unsigned long |
32 Bits |
0 bis 4294967295 |
long |
32 Bits |
-2147483648 bis 2147483647 |
float |
32 Bits |
3,4 * 10-38 bis 3,4 * 1038 |
double |
64 Bits |
1.7 * 10-308 bis 1.7 * 10308 |
long double |
80 Bits |
3.4 * 10-4932 bis 1.1 * 104932 |
Das Faktum, das C so hoch angerechnet wird, ist die Flexibilität in der Gestaltung eigener Datentypen. Es gibt hier eine Unzahl an Möglichkeiten und Variationen, die an das jeweilige Anwendungsgebiet angepasst werden können.
Ein String (engl.:Schnur) ist eine aufeinanderfolgende Anordnung von ASCII - Zeichen, die in einem char- Feld stehen. Eine Zeichenkette sieht in C folgendermaßen aus:
Jedes Kästchen repräsentiert eine Variable vom Typ char, die mit einer Zahl aus der ASCII Tabelle gefüllt ist. Um das Ende einer solchen Kette zu markieren, wird in das letzte Feld eine binäre Null geschrieben. Dieses Zeichen, darf also nicht mitten im String auftauchen, da es ihn sonst bereits in der Mitte abschließt. Bei der Übergabe an Unterprogramme ist gleich zu verfahren, wie bei jedem anderen Feld. Auch die Rückgabe muss gleich erfolgen.
Bsp.:
char *String = 'Text'; //Zulässig, da der Compiler selbständig eine Null anhängt
char *String;
String = 'Text'; //Nicht zulässig
Die obigen Beispiele zeigen Zeichenketten ohne festgelegtes Ende.
char String[80]; //Eine Zeichenkette, die auf 79 Zeichen beschränkt ist
Um Zeichenketten zu verbinden, ihre Länge zu ermitteln, sie abzuschneiden oder sonstiges mit ihnen zu machen, bietet C eine Bibliothek, in der solche Funktionen vorhanden ist: string.h.
Folgende Funktionen zur Stringbehandlung sind dort verfügbar:
Name |
Bezeichnung |
size_t strlen(const char *s) |
Ermittelt die Länge eines Strings (ohne 0) |
char* strcpy(char *dest, const char *source) |
Kopiert den String source in den String dest, wobei dest zurückgegeben wird |
char* strcat(char *dest, const char *source) |
Fügt den String source an den String dest an und gibt das Ergebnis zurück |
int strcmp(const char *s1, const char *s2) |
Vergleicht String s1 mit s2 und liefert 0 zurück, wenn sie identisch sind |
Es gibt hier natürlich noch andere Funktionen. Die oben genannten sind allerdings die meist gebrauchten. Um zu Veranschaulichen, wie eine solche Funktion aussieht, soll an dieser Stelle ein möglicher Quellcode der strlen Funktion beschrieben werden.
Quellcode 1:
int strlen(const char *s) |
Enum ist ein Beispiel für die Möglichkeit, gut lesbaren Code zu schreiben. Dieser Typ ist an sich int, jedoch können mit ihm Folgen von Konstanten definiert werden.
Bsp.:
enum modes = ;
enum bool = ;
Mit obigem Quellcode haben wir zwei 'neue' Datentypen erschaffen, die, verwendet im Code, die Lesbarkeit stark erhöhen. Eingesetzt können sie auf verschiedene Arten werden. Zum Einen ist es möglich, eine Variable vom Typ modes zu generieren und diese auf die Elemente davon abzufragen.
Bsp.:
modes View;
View = LASTMODE;
If(View == LASTMODE)
Dies kann bei allen enum Typen angewandt werden. Jene Elemente, denen eine Zahl zugewiesen wurde, können auch über diese abgefragt werden.
Bsp.:
bool bValue;
if(bValue == true) oder if(bValue)
Beide Möglichkeiten sind hier erlaubt.
Mit typedef können 'neue' Datentypen erzeugt werden, die allerdings auf bereits bestehenden aufbauen.
Bsp.:
typedef unsigned long DWORD;
typedef char str40[41];
Im ersten Beispiel ersetzen wir die lange und umständliche Schreibweise unsigned long durch die kürzere DWORD. Nun kann bei jeder Variable, die vom Typ unsigned long sein soll, ganz einfach nur DWORD geschrieben werden.
Eine Struktur ist eine Zusammenfassung von verschiedenen Datentypen zu einem Record. Durch die Verwendung einer Struktur ist es möglich, komplexere Objekte auf einfachem Weg zu generieren. Eine Struktur wird folgend definiert:
struct [Name] [instanzierte Variablen];
Was möglicherweise verwirrend ist, ist die Tatsache, dass Strukturen nicht zwangsweise einen Namen haben müssen. Wenn Sie jedoch keinen Namen besitzen, muss zumindest eine Variable instanziert werden, die diese Struktur repräsentiert. Eine Struktur ohne Namen, kann im weiteren Programmverlauf jedoch nicht mehr zur Deklaration von Variablen verwendet werden.
Grundsätzlich sieht eine Strukturdefinition jedoch so aus:
struct Person;
Eine namenlose Struktur würde demnach folgend aussehen:
struct MeinFreund;
Diese Struktur heißt nicht etwa 'MeinFreund', sondern eine Instanz dieser Struktur heißt 'MeinFreund'. Die Struktur kann aufgrund dieser Deklaration nicht weiter verwendet werden.
Eine union ähnelt vom Aufbau einer Struktur. Sie erlaubt es, mehrere Variablen verschiedenen Typs zu deklarieren, die sich den selben Speicherplatz teilen. Aussehen:
union [Name]{
Name;
Name;
} [instanzierte Variablen];
Es gelten bei der union die gleichen Regelungen, wie bei einer Struktur, was Namensgebung und Instanzierung der Variablen angeht. Der Unterschied liegt nun darin, dass immer nur eine der Variablen der union aktiv ist, das heißt den Speicherplatz belegt. Beispiel:
union int_long test;
test.i = 10; //Zu diesem Zeitpunkt ist die int Variable aktiv, das heißt, dass der //Speicherplatz der union i zugewiesen wird
test.j = 100; //Sobald auf das andere Objekt zugegriffen wird, übernimmt diesen den //Speicherplatz, i ist ab diesem Zeitpunkt 'verstorben'
Eine union beansprucht soviel Speicherplatz, wie ihr größtes Objekt.
Bei bisherigen Verfahren der Speicheranforderung, muss bereits zur Übersetzungszeit feststehen, wie groß z.B. ein Feld ist. Diese Variable wird dann im Stack abgelegt und bleibt während eines ganzen Blockes aktiv. Wird der Block verlassen, wird auch der Speicher, den die Variable benützt wieder freigegeben. Um allerdings eine größtmögliche Flexibilität während der Programmierung zu gewährleisten, gibt es auch die Möglichkeit, Speicher dynamisch, das heißt während der Laufzeit, anzufordern. Hierzu gibt es in C die beiden Funktionen malloc und free. Die Funktion malloc reserviert einen Bereich im Heap und free gibt ihn wieder frei. In Turbo C sind je nach Speichermodell unterschiedliche Größen vom Heap erhältlich. Im Modell large steht der gesamte RAM zur Verfügung.
Bsp.:
char *str;
str = (char *)malloc(15)
strcpy(str, 'Hello World');
free(str);
Diese Art der dynamischen Speicheranforderung existiert allerdings nur in C. In C++ stehen einem dazu wesentlich mächtigere Operatoren zur Verfügung: new und delete.
new ist das C++ Gegenstück zu malloc und delete das Gegenstück zu free. In C++ würde das gleiche Beispiel folgend aussehen:
char *str;
str = new char[15];
strcpy(str, 'Hello World');
delete [] str;
Die Art und Weise, wie delete den Speicher wieder frei gibt, ist von Compiler zu Compiler unterschiedlich. Die beiden eckigen Klammern weisen ihn an, ein Feld zu löschen. Ohne diese Klammern ist es nicht gesichert, dass er den ganzen String löscht (abhängig vom Compiler).
Folgend eine Auflistung aller in C vorhandenen Sprachelemente, die zusammen die Syntax bilden. Die Art und Weise, wie diese Elemente zusammengesetzt werden dürfen, wird bei jedem Teil einzeln beschrieben.
Eine Sprache lebt von ihren Variablen, und diese können in C verschiedenartigst verknüpft werden. Grundsätzlich wird, wie bereits erwähnt, von rechts nach links vorgegangen. Das heißt, dass zuerst das Ergebnis der rechts neben der Zuweisung stehenden Sequenz berechnet wird und dann dem 'Empfänger' auf der linken Seite zugewiesen wird. Eine sehr einfache Sequenz sieht wie folgt aus:
x = a;
Bei obiger Anweisung wird der Inhalt der Variable a in die Variable x geschrieben. Wichtig ist hierbei, dass beide Variablen vom selben Typ sind. Eine Umformung in einen anderen Typ wird später genau erklärt. Steht rechts neben dem = eine Sequenz, dann könnte die vorige Anweisung folgend aussehen:
x = a*2;
Hierbei wird zuerst das Ergebnis der Berechnung a*2 ermittelt und anschließend in die Variable x geschrieben. Dies ist ohne das Setzen von Klammern möglich, da der Operator '*' eine höhere Priorität besitzt, als das '='. Folgend mögliche Vereinfachungen von Zuweisungen:
x = x*2; entspricht: x *= 2;
x = x+10; entspricht: x += 10;
x = x+1; entspricht: x += 1; oder: x++;
Die gleichen Vereinfachungen können auch mit den Operatoren für die Subtraktion und Division durchgeführt werden.
Des weiteren können auf der rechten Seite auch Funktionen aufgerufen werden (sofern diese einen Wert zurückliefern). Möchte man mit dem Ergebnis einer Funktion arbeiten, so ist dies auch möglich:
x = sin(y) * 2;
Die Funktion sin(y) liefert einen Wert zurück, der mit 2 multipliziert und erst anschließend x zugewiesen wird. Dies funktioniert deshalb, weil der Aufruf einer Funktion die höchste Priorität besitzt. Hat der Compiler den Aufruf abgearbeitet, ersetzt er ihn durch den zurückgelieferten Wert. Dieser wird dann für die weitere Berechnung hergenommen. Ein Beispiel für die mögliche Verschachtelung in C:
x = sin(abs(y[i]) * 2) - t/2;
Wie bereits erwähnt, geht der Compiler hier von innen nach außen vor. Das bedeutet, dass er sich zuerst die innerste Anweisung sucht. Diese ist in obigem Fall die Ermittlung des Feldwertes y[i]. Danach berechnet er den Absolutwert des Ergebnisses abs(y[i]).Hat der Compiler diese Anweisung abgearbeitet, multipliziert er den ermittelten Wert mit zwei und übergibt das Ergebnis an die Funktion sin. Als Abschluss wird dem Ergebnis von sin noch ein Quotient abgezogen.
In C gibt es drei verschiedene Möglichkeiten, eine Abfrage zu realisieren. Sie unterscheiden sich in einigen wichtigen Punkten von einander, können jedoch trotzdem durch die Standardabfrage ersetzt werden.
Die Standardabfrage in C ist die if - else Abfrage. Sie besteht aus zwei Teilen, der Erfüllung einer Anweisung, bzw. ihrer Nicht-Erfüllung.
if()else
Auf eine if-Abfrage folgt kein Strichpunkt! Ein sehr häufig auftretender Fehler, vor allem bei Anfängern. Zwischen den geschwungenen Klammern können beliebig viele Anweisungen stehen, welche durch einen Strichpunkt zu terminieren sind. Gleiches gilt für den else Teil. Bsp.:
if(x == 10)else
Folgt auf einer Abfrage nur eine Sequenz, so können die geschwungenen Klammern entfallen. Bsp.:
if(x == 10)
a = 0;
else
a = 5;
Eine if - Anweisung gilt als wahr, wenn die Abfrage einen Wert ungleich Null zurückliefert. Im obigen Beispiel liefert der Operator == einen Wert gleich Null zurück, wenn x nicht 10 ist und einen Wert ungleich 0, wenn x 10 ist. Eine Abfrage, ob eine Variable ungleich 0 ist kann daher auf folgendes reduziert werden:
x=10;
if(x) //Die Anweisung in den beiden runden Klammern entscheidet darüber, ob der Wahr - //Teil, oder der Falsch - Teil einer if Anweisung ausgeführt werden soll. In diesem //Fall wird der Wahr - Teil ausgeführt, da x ungleich 0 ist. Währe x 0, dann würde der //Falsch - Teil ausgeführt werden.
Wie alles in C, können auch if Abfragen verschachtelt werden, das heißt, dass in einem else - Teil z.B. eine weitere Abfrage stecken kann. Wie tief verschachtelt werden darf hängt wieder vom Compiler ab.
Diese Abfrage wird verwendet, wenn eine Variable nicht nur auf zwei gegensätzliche Merkmale hin überprüft werden soll, sondern eine große Anzahl an möglichen Zuständen existiert. Grundlegendes Aussehen:
switch
case :
default:
Die Variable kann eine nummerisch oder auch durch mit enum ersetzte Konstanten enthalten. Diese werden in den case Abschnitten auf gewisse Merkmale hin überprüft. So kann eine normale switch - case Anweisung folgend aussehen:
switch(x)
Die Variable x wird hier auf drei verschiedene Werte hin überprüft. Je nachdem, welchen Wert sie enthält, werden unterschiedliche Anweisungen ausgeführt, die selbstverständlich ihrerseits auch wieder Abfragen enthalten können. Das Schlüsselwort break wird hier verwendet, um die Ausführung zu beschleunigen, da es zur Übergeordneten Anweisung schaltet. Das bedeutet, dass nach Aufruf von break der case Teil verlassen und in den switch Teil, der in diesem Fall den übergeordneten darstellt, gewechselt wird. Daraus ist ersichtlich, dass alle anderen case Abfragen keine Bedeutung mehr haben, da der switch Teil beendet ist. Das Schlüsselwort default bezeichnet eine Reihe von Anweisungen, die ausgeführt werden, wenn keiner der case Zweige zutrifft. Eine case Abfrage kann auch mehrere, durch einen Beichstrich getrennte Parameter erhalten:
case
Die obige Abfrage ist also wahr, wenn einer der drei Parameter als wahr gilt.
An sich nur eine Vereinfachung des zweiwertigen if Operators, dient der ternäre vor allem zur Verkürzung von Ausdrücken. Aussehen:
Gilt Anweisung 1 als wahr, wird Anweisung 2 ausgeführt, ansonsten Anweisung 3. Wie ersichtlich ist, ist der ternäre Operator lediglich eine if - else Bedingung.
Bsp.:
(x>10) ? a = 5 : a = 0;
Ist der Wert von x größer als 10, wird die Anweisung a = 5 ausgeführt. Andernfalls wird a auf 0 gesetzt.
Um die drei Grundelemente abzuschließen, sollen jetzt noch die Schleifen behandelt werden. In C gibt es hierzu drei verschiedene Möglichkeiten, die sich durch die Positionierung der Abfrage unterscheiden. Grundlegend ist zu sagen dass man bei Iteration zwischen zwei verschiedenen Arten unterscheidet: definite und indefinite.
Bei definiten Iterationen ist die Anzahl der Durchläufe bekannt und man kann daher voraussagen, wie lange sie dauern wird. Indefinite Schleifen hingegen, besitzen keine eigentliche Abbruchbedingung, und laufen unbestimmt lange. Bei jedem der folgenden Schleifentypen kann eine Definition indefinit oder definit sein.
Bei der Positionierung der Abbruchbedingung unterscheidet man zwischen pretested und posttested, was bedeutet, dass die Abbruchbedingung im Kopf (pre) oder im Fuß (post) der Schleife liegt. Schleifen, die im Fuss kontrolliert werden, laufen zumindest einmal durch. In C wird die Abfrage zur Abbruchbedingung bei jedem Schleifendurchlauf erneut ausgeführt, was zu Geschwindigkeitsverlusten führen kann, wenn die Abbruchbedingung eine komplexere Anweisung ist.
Die klassische Schleife in C. Sie wird im Kopf getestet. Folgendes Aussehen:
while
Die Schleife durchläuft alle Anweisungen, solange die Bedingung wahr ist. Trifft die Bedingung nicht mehr zu, das heißt sie ist falsch, wird die Schleife abgebrochen. Dadurch, dass sie im Kopf getestet wird, ist die minimale Anzahl an Durchläufen Null. Wie auch bei den Abfragen gilt hier die Regelung, dass die geschwungenen Klammern weggelassen werden können, wenn nur eine Anweisung folgt. Dies gilt im übrigen bei allen drei Schleifen.
Bsp.:
while(x<10)
x++;
Eine Abart der while Schleife ist die do - while. Die Abbruchbedingung befindet sich im Fuß der Schleife, was eine Minimalanzahl von einem Durchlauf zur Folge hat. Aussehen:
do
}while();
Auch in diesem Fall wird die Schleife dann durchlaufen, wenn die Bedingung wahr ist. Ist dies nicht mehr der Fall, wird die Ausführung abgebrochen.
Bsp.:
dowhile(x<10);
Die for Schleife ist die mächtigste und kompakteste Schleife in C und kann sowohl zur Variablendeklaration, Abfrage und Manipulation verwendet werden. Durch die im Kopf gelegene Abbruchbedingung wird sie mindestens null mal durchlaufen. Aussehen:
for([Ausdruck 1]; [Ausdruck 2]; [Ausdruck 3])
Ausdruck 1 enthält die Variableninitialisierung bzw. die Variablendeklaration, welche auch entfallen kann. Grundsätzlich sind alle drei Parameter optional. Im Ausdruck 2 befindet sich die Abbruchbedingung. Im dritten Ausdruck wird eine Laufvariable z.B. inkrementiert. Wie es in C üblich ist, können die Ausdrücke auch mehrere Bedingungen enthalten, die durch Beistriche getrennt werden müssen. Eine indefinite for Schleife sieht z.B. folgend aus:
for
Die Schleife wird hier unendlich oft durchlaufen, wenn keine der Anweisungen in der Schleife ein externe Abbruchbedingung enthält. Eine solche Bedingung kann z.B. mit dem Schlüsselwort break oder goto erfolgen. Folgend eine for Schleife, deren Abbruchbedingung eine Funktion ist:
for(i=0; i<strlen(text); i = i+1)
Dieser Code enthält zwar keinen Fehler, ist jedoch pragmatisch nicht optimiert. Wenn sich die Länge des Textes, welcher kontrolliert wird nicht ändert, ist es vorteilhafter, wenn folgende Lösung verwendet wird:
int len = strlen(text);
for(i=0; i<len; i = i+1)
Die Funktion strlen muss hier nur einmal ausgeführt werden und spart somit Rechenzeit.
Am optimalsten wäre folgende Version:
for(i=0; text[i]; i++)
Die folgende Schleifenkonstruktion ist in C nicht möglich und bezieht sich auf C++.
for(int i=0; i<10; i++)
Diese altertümlichen Anweisungen stammen noch aus einer Zeit, in der um jedes Byte an Speicher gerungen wurde. Es wird zwar kein Compiler einen Fehler melden, jedoch sollte ihre Verwendung im Sinne von übersichtlichem Code vermieden werden. Aussehen:
goto ;
Der Bezeichner 'Label' stellt eine beliebige Zeichenkette dar, die den selben Konventionen unterliegt wie die Variablenbezeichnung. Ein Label darf an jeder beliebigen Codezeile stehen und muss von einem Doppelpunkt abgeschlossen werden. An einer beliebigen anderen Stelle im Code (in der selben Funktion) kann dann das Schlüsselwort goto folgen und auf irgendein Label verweisen. Die Ausführung des Code wird somit abgebrochen und an der besagten Stelle mit dem Label weitergeführt. Dieses Springen eröffnet zwar eine sehr einfache Methode z.B. Schleifen zu ersetzen, kann jedoch bei zu exzessiver Nutzung zu Unübersichtlichkeit führen. Des weiteren verringern sie die Ausführungsgeschwindigkeit von Code, da moderne Prozessoren mit Befehlspipelines durcheinander kommen.
Sehr häufig treten Probleme mit unterschiedlichen Datentypen auf, wie z.B. bei einer Zuweisung. Je nach Mächtigkeit des Compilers werden bereits einige Umwandlungen ohne Zutun des Programmierers durchgeführt. Diese automatischen Konvertierungen können jedoch auch zu Problemen führen. Es stehen in C deshalb sogenannte typecasts zur Verfügung, welche diese Umwandlung explizit vornehmen. Aussehen:
//C++
//C
In das Feld für Datentyp kann jeder beliebige (auch eigene) Typ geschrieben werden. Die Umwandlungen haben jedoch auch ihre Grenzen, so kann ein String nicht in einen int verwandelt werden, was natürlich unsinnig wäre.
Im Sinne einer modularen Programmiersprache, muss Quellcode in C nicht in einer einzigen Funktion, der Hauptfunktion, stehen, sondern kann auf andere Funktionen, den sogenannten Unterprogrammen (oder Routinen) oder auch Makros aufgeteilt werden. Unterprogramme bieten die Möglichkeit, z.B. oft genutzten Code nicht ständig neu schreiben zu müssen, sondern ihn in eine eigene Routine zu verpacken und anstelle des ganzen Codes nur diese Routine zu rufen (Code Reuse). Das Herz eines C Codes stellt die Hauptfunktion oder der Programmeinstiegsspunkt main dar. In der ersten Zeile dieser Funktion beginnt der Code und wird anschließend schrittweise abgearbeitet. Aussehen (siehe 2.3).
Unterprogramme können eine Vielzahl von Gestalten annehmen, von simplem Textersatz (siehe oben) bis zu sehr komplexen Rechenvorgängen. Grundsätzlich besteht eine Funktion aus drei Teilen:
a) Rückgabetyp
b) Funktionsname
c) Parameterliste
ad a) Eine Funktion kann, muss aber nicht unbedingt etwas zurückliefern. Liefert eine Funktion nichts zurück, ist der Rückgabetyp void (engl. Leere, Nichts). Liefert sie jedoch etwas zurück, ist der Rückgabetyp jeder beliebige Datentyp (auch eigene).
ad b) Der Name einer Funktion unterliegt den selben Konventionen, wie die Namensgebung bei Variablen. Mehrere Funktionen dürfen auch den selben Namen haben, wenn sie sich in ihren Parametern unterscheiden. Solche Namensgleichheit wird auch als überladen von Funktionen oder Generik bezeichnet (nur C++).
ad c) Die Parameterliste darf beliebig lang sein. Alle Parameter müssen vom Typ identifiziert sein und dürfen nicht den selben Namen haben, wie ein anderer Parameter.
Beispiele:
void Funktion1(); //Funktion bekommt nichts und liefert nichts
void Funtkion2(int x, float y); //Funktion erhält zwei Parameter, liefert aber nichts
int Funtkion3(int x); //Funktion erhält einen Parameter, liefert einen int
char* Funtkion4(char* s, int x); //Funktion erhält zwei Parameter, liefert einen Zeiger //auf char
Um ein Unterprogramm auszuführen, muss, ähnlich wie bei Sprungbefehlen, die aktuelle Funktion verlassen werden und zum Unterprogramm gewechselt werden. Nach Beendigung des Unterprogrammes, wird zur vorigen Funktion zurückgekehrt (ganz im Unterschied zu Sprungbefehlen) und der Code dort weiter ausgeführt. Eine Unterfunktion kann durch den Aufruf des Schlüsselwortes return sofort verlassen werden. Dieses Schlüsselwort dient auch zum zurückliefern von Werten.
Zu beachten ist bei Funktionen, dass die Variablen, die in der Funktion benützt werden, nur solange gültig sind, wie die Funktion ausgeführt wird. Danach wird ihr Speicher wieder freigegeben. Auch ist darauf zu achten, dass bei globalen Variablen keine Namenskonflikte entstehen. Dies kann zu Fehlern führen, die nur sehr schwer zu finden sind.
Selbstverständlich sind in C auch Rekursionen möglich, dass heißt, dass sich ein Unterprogramm selbst wieder aufruft.
Um in C auf Funktionen verschiedenster Art zuzugreifen, gibt es den Begriff des Headerfiles oder Bibliothek. In diesen Dateien, die nach ihrem Anwendungsgebiet getrennt werden, befinden sich Funktionsköpfe, von Funktionen, die in irgendeinem Objectfile existieren. Wenn z.B. Routinen für die Bildschirmausgabe benötigt werden, ist es notwendig die Datei stdio.h zu inkludieren. Diese Vorgangsweise hilft, den Code klein zu halten, da nur das notwendige verwendet wird. Um eine Bibliothek zum Programm hinzuzufügen, gibt es den Präprozessorbefehl include, welcher eine Textersetzung vornimmt. Der Inhalt der Bibliotheksdatei wird in den Quellcode kopiert, aus dem der Bytecode erzeugt wird.
In C gibt es eine Unmenge an verschiedenen Bibliotheken, wie z.B.:
a) stdio.h > Eine der wichtigsten Bibliotheken, enthält Funktionen für die Bildschirm Ein- /Ausgabe, sowie für das Bearbeiten von Dateien
b) string.h > Funktionen zur Stringmanipulation
c) stdlib.h > Enthält Funktionen für Zufallszahlengenerierung, nützliche Makros für mathematische Operationen, usw.
Bei der objektorientierten Programmierung handelt es sich um einen vollkommen anderen Ansatz, als bei der prozeduralen Programmierung. Die Sprache C, ist eine prozedurale Sprache, ohne (oder nur mit kleinen) objektorientierten Ansätzen. Um auch Objekte in C zu verwenden, wurde die Sprache C++ entwickelt, welche vollkommen zu C kompatibel ist allerdings verbessertes Speichermanagement, Funktionsüberladungen, objektorientierte Elemente und neue Datentypen enthält.
[In diesem Text soll nur sehr kurz auf die objektorientierte Programmierung eingegangen werden, da sie viel zu komplex ist.]
Objekte stellen in der Programmiersprache einen Realitätsbezug her, dass heißt, das sie der Wirklichkeit näher kommen, als die strikte prozedurale Programmierung. In C ging es immer nur um das, was mit den Daten gemacht wird, nicht jedoch um die Daten selbst. Im Mittelpunkt einer objektorientierten Sprache stehen demnach die Daten, mit denen etwas gemacht wird. Sie bilden eine Einheit, die nach außen hin abgeschlossen ist. Objekte verfügen über einige Merkmale, die in der prozeduralen Programmierung zur Gänze fehlen. Objekte bestehen nämlich nicht nur aus Daten, sondern auch aus dazugehörigen Funktionen. Ein Objekt wird in der Programmiersprache als Klasse bezeichnet. In C++ sieht eine solche Klasse folgend aus:
Name [:Basisliste];
Das Feld Klassenschlüssel kann eine der drei Schlüsselbezeichnungen class, union oder struct sein. Der Name einer Klasse unterliegt den selben Konventionen wie Variablendeklarationen. Die Basisliste ist optional und enthält die Liste der Klassen, von denen die neue Klasse abgeleitet wird.
Die Eigenheiten der objektorientierten Programmierung werden hier nicht näher beschrieben, da sie selbst Thema einer Abhandlung wären. Soviel sei jedoch gesagt: C++ beherrscht alle genormten objektorientierten Ansätze.
C++ stellt die folgenden drei Schlüsselwörter zur Datenkapselung bereit:
a) public (engl. Öffentlich)
b) private (engl. Privat)
c) protected (engl. Geschützt)
ad a) Elemente die in C++ als public deklariert werden, können von allen Funktionen und anderen Klassen, in denen die Klasse verwendet wird, aufgerufen werden. Im Sinne der Datenkapselung sollten nur Interface-Funktionen und der Konstruktor als public deklariert werden.
ad b) All jene Elemente, die man geschützt (vor Zugriffen aus anderen Klassen) haben möchte, müssen als private deklariert werden. Nur die Klasse selbst und andere Klassen, die als friend definiert wurden, können auf diese Elemente zugreifen.
ad c) Variablen und Funktionen, die als protected deklariert wurden, sind gegen Zugriffe von außen gesichert, können jedoch von vererbten Klassen verändert werden. Auch in diesem Fall gibt es die Ausnahme, dass als friend definiert Klassen der Zugriff gestattet wird.
Im Sinne der Wiederverwendbarkeit von Code ist es möglich, einer neuen Klasse alle Eigenschaften und Funktionen der 'alten' Klasse mitzugeben. Dies ist ein Realitätsbezug, der sich folgend analogisieren lässt:
In der Natur gibt es beispielsweise eine Unzahl von verschiedenen Tischen. Allen gemeinsam ist jedoch die Tatsache, dass sie eine Tischplatte, verschiedene Schubladen und Standfüße besitzen. Diese Grundbegebenheiten können als Basisklasse dienen, von der dann eine Menge anderer Klassen abgeleitet werden können, die alle auf denselben Grundvoraussetzungen aufbauen und lediglich z.B. eine andere Farbe besitzen. Man muss also nicht für jeden Tisch eine eigene Klasse schreiben, sondern kann bereits vorhandenes weiter benutzen.
Aussehen:
Name [: Basisliste] ;
In der Basisliste stehen nun die Klassen, von denen die neue abgeleitet wird. Diese Ableitung kann als private oder public erfolgen, je nachdem, auf welche Elemente der Basisklasse zugegriffen werden soll.
Als Zusatz zur Vererbung, kann eine abgeleitete Klasse natürlich auch die selben Namen für ihre Funktionen vorsehen, wie die Basisklasse. Wird eine solche Funktion überladen, kann jedoch auf die Grundfunktion der Basisklasse nicht mehr zugegriffen werden. Durch das Schlüsselwort virtual wird es möglich, eine vererbte Funktion gleich zu nennen, wie die Basisfunktion.
Als Abschluss noch den Quellcode eines Programmes, das symbolisch für die komplexen Möglichkeiten steht, die mit C / C++ erreicht werden können:
main(t,_,a)
char *a;
w+/w#cdnr/+,r/*de}+,/*e#';dq#'l
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKKw')rw' i;#
)'c
;;{nl'-rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}'+}##(!!/'
:t<-50?_==*a?putchar(a[31]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,'%s'):*a=='/'||main(0,main(-61,*a,
'!ek;dc i@bK'(q)-[w]*%n+r3#l,:nuwloca-O;m .vpbks,fxntdCeghiry'),a+1);
Übrigens: Wenn Sie diesen Code nicht lesen können, machen Sie sich nichts daraus, es ist der komplexeste Code, den ich je gesehen habe und meiner Meinung unlesbar.
Die Ausgabe:
On the tenth day of Christmas my true love gave to me
ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.
On the eleventh day of Christmas my true love gave to me
eleven pipers piping, ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.
On the twelfth day of Christmas my true love gave to me
twelve drummers drumming, eleven pipers piping, ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.
Haupt | Fügen Sie Referat | Kontakt | Impressum | Nutzungsbedingungen