Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Rasenmaehroboter fuer schwierige und grosse Gaerten im Test

(getch)
K (Weblinks)
 
(365 dazwischenliegende Versionen von 28 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
Ein kurzer Einblick in die Programmiersprache C
+
{{Ausbauwunsch|Mehr Grundlagen und vor allem Programmbeispiele etc.}}
==Allgemeines==
+
C wurde 1971 als Gundlage für das Betriebssystem UNIX in den USA entwickelt (UNIX ist zu über 90% in C geschrieben). 1978 wurde von Brian Kernighan und Dennis Ritchie eine eindeutige Sprachedefinition entwickelt. C ist mittlerweile von ANSI und ISO standardisiert.
+
Heutzutage ist C (und ihr Nachfolger C++) die dominierende Programmiersprache. Sehr viele Anwendungen sind in C geschrieben. Leider ist C jedoch nicht einfach zu lernen, daher eignet es sich nur bedingt für Anfänger. Mit etwas Übung kann man damit jedoch sehr effiziente Programme schreiben.
+
  
C ist sehr eng an Assembler angelehnt, aber trotzdem Hardware-unabhängig. Das bedeutet, Sie können maschinennahe Programme sehr leicht (aber nicht ganz ohne Aufwand) auf ein anderes System portieren. Sie benötigen dazu lediglich einen anderen Compiler, und Inline-Assembler Anweisungen (Assembleranweisungen innerhalb eines C-Programmes) müssen der neuen Hardware (Prozessor) angepasst werden.  
+
Die Programmiersprache C wurde 1971 als Grundlage für das Betriebssystem UNIX in den USA entwickelt (UNIX ist zu über 90% in C geschrieben). 1978 wurde von Brian Kernighan und Dennis Ritchie eine eindeutige Sprachdefinition entwickelt. Mittlerweile ist C von ANSI und ISO standardisiert.
  
===Geschichte===
+
Heute sind C und ihr Nachfolger C++ die dominierenden Programmiersprachen. Sehr viele Anwendungen sind in C geschrieben, was inzwischen auch auf eingebettete Systeme zutrifft, die lange in Assembler programmiert werden mussten, da keine ausreichend leistungsfähigen [[Compiler]] zur Verfügung standen.
 +
 
 +
Leider ist C nicht einfach zu lernen – es wurde weder von noch für Hobby-Programmierer entwickelt – und eignet sich daher nur bedingt für den Einsteiger. Mit etwas Übung und einem optimierenden Compiler kann man damit jedoch sehr effiziente Programme schreiben.
 +
 
 +
Vom Design her ist C eine Hardware-unabhängig Sprache. Das bedeutet, daß C-Programme mit vertretbarem Aufwand auf ein anderes System portiert werden können. Dazu benötigt man lediglich einen anderen Compiler, und Inline-Assembler-Anweisungen (Assembleranweisungen innerhalb eines C-Programmes) müssen der neuen Hardware (Prozessor, [[Mikrocontroller]]) angepasst werden.
 +
 
 +
==Geschichte==
 
;1971: C wird entwickelt
 
;1971: C wird entwickelt
 
;1978: Kernighan und Ritchie definieren die Sprache.
 
;1978: Kernighan und Ritchie definieren die Sprache.
Zeile 12: Zeile 15:
 
;1992: Bjarne Stroustrup enwickelt die Nachfolgesprache C++.
 
;1992: Bjarne Stroustrup enwickelt die Nachfolgesprache C++.
  
==Aufbau eines C-Programmes==
+
=Aufbau eines C-Programmes=
C-Programme haben keinen fixen Aufbau wie z.B. Pascal. Es gibt zwar gewisse Regeln, aber sonst sind dem Programmierer alle Freiheiten überlassen. Der folgende "Beispiel-Aufbau" ist daher nicht zwingend und kann durchaus verändert werden.
+
<!--  
  
<pre>
+
  C-Programme haben keinen fixen Aufbau wie z.B. Pascal. Es gibt zwar gewisse Regeln, aber sonst sind dem Programmierer alle Freiheiten überlassen. Der folgende "Beispiel-Aufbau" ist daher nicht zwingend und kann durchaus verändert werden.
#include <conio.h> // Header-Include
+
#include <stdio.h> //
+
  
int Zahl1;
+
  Natürlich haben C-Programme einen fixen Aufbau! Ebenso wie Pascal-Programme auch unterliegen sie einer strikten Grammatik!
char Zeichen1;
+
Auskommentiert --[[Benutzer:SprinterSB|SprinterSB]] 10:23, 17. Feb 2006 (CET)
 +
-->
 +
Ein einfaches C-Programm könnte folgendermassen aussehen. Das Programm tut eigentlich nichts, aber das Beispiel zeigt den prinzipiellen Aufbau.
 +
#include <stdio.h>
 +
 +
int Zahl1;
 +
char Zeichen1;
 +
 +
int main (void)
 +
{
 +
    int zahl2;
 +
 +
    {{comment|Anweisungen}}
 +
    return 0;
 +
}
  
int main(void)
+
'''Beschreibung:'''
{
+
  int Zahl2;
+
  //Anweisungen
+
  return 0;
+
}
+
</pre>
+
  
Die Beschreibung (die Erklärungen folgen):  
+
;<nowiki>#include <...></nowiki>: Die Include-Direktive sagt dem Compiler, welche Header-Dateien er einbinden soll. In den Header-Dateien und den dazugehörigen Bibliotheken stehen Funktionen und Datentypen, die nicht im Compiler selbst implementiert sind, etwa komplexe Ausgabefunktionen wie "<tt>printf</tt>", die weiter unten erklärt wird. Durch den Include kann man solche Funktionen nutzen. Elementare Dinge hingegen, wie die mathematischen Operatoren <tt>+</tt>,<tt>-</tt>,<tt>*</tt>, etc. sind im Compiler selbst eingebaut.
 +
;int Zahl1;: Diese Zeile definiert eine Variable vom Typ int. Diese Variable ist im ganzen Programm gültig, sie ist ''global''. Jede Deklaration/Anweisung in C wird mit einem Strichpunkt (Semikolon  <tt>;</tt>) abgeschlossen und dadurch von der nächsten Deklaration/Anweisung getrennt.
 +
;char Zeichen1;: Hier geschieht das selbe, nur wird diesmal eine Variable des [[#Datentypen|Types char]] definiert.
 +
;int main (void): definiert ein Unterprogramm mit dem Namen <tt>main</tt>, das keine Parameter hat (<tt>void</tt>) und eine ganze Zahl ([[#Datentypen|<tt>int</tt>]]) zurückliefert. "<tt>main</tt>" ist das Hauptprogramm in C, wo mit der Ausführung nach dem Programmstart begonnen wird.
 +
;{: Die linke geschwungenen Klammer beginnt den Rumpf (auch "''body''" genannt) der main-Funktion. Danach folgen Variablendefinitionen, Kommentare und Anweisungen von <tt>main</tt>.
 +
;int zahl2;: Innerhalb von "main" wird die lokale Variable <tt>zahl2</tt> definiert.
 +
;/* Anweisungen */: Das ist ein Kommentar in C. Hier kann man Anmerkungen zum Code hinschreiben oder Codestücke "auskommentieren", um sie zu deaktivieren. Der Kommentar beginnt mit <tt>/*</tt> und wird beendet mit einem <tt>*/</tt>. Er kann mehrere Zeilen überspannen. Je nach C-Compiler werden auch einzeilige Kommentare mit <tt>//</tt> akzeptiert, die nur bis zum nächsten Zeilenende reichen. Sie gehören jedoch nicht zum standard ANSI-C. Die Leerzeile nach dem Kommentar wird nicht weiter berücksichtig, sie kann zur Untergliederung des Codes zur besseren Lesbarkeit eingefügt werden.
 +
;return 0;: Gibt den Wert&nbsp;0 zurück und beendet das Programm. Vor dem <tt>return</tt> können natürlich noch C-Anweisungen stehen, die aber erst weiter unten erklärt werden.
 +
;}: Die schliessende geschwungenen Klammer beendet den Rumpf des Hauptprogramms.
  
* Die beiden #include <...> Anweisungen sagen dem Compiler, welche Header-Dateien er einbinden soll. In den Header-Dateien und den dazugehörigen Bibliotheken stehen Anweisungen und Datentypen, die nicht im Compiler implementiert sind. Dazu ein Beispiel: Mathematische Anweisungen wie +,-,*, etc. sind, weil oft benötigt, im Compiler "eingebaut". Ein- und Ausgabefunktionen jedoch, die verhältnismäßig selten benötigt werden, stehen in der Datei "stdio.h".
+
=Das Hauptprogramm main=
* Die Anweisung "int Zahl1;" definiert eine Variable vom Typ int (integer, ganze Zahl zwischen -32000 und +32000). Diese Variable ist im ganzen Programm gültig (global).
+
Die erste Funktion, die nach dem Programmstart ausgeführt wird, ist im Allgemeinen die Funktion mit dem Namen "<tt>main</tt>". Diese ist das Hauptprogramm.  
* Das selbe geschieht bei "char Zeichen1;", nur wird diesmal eine Variable des Types char (character, ASCII-Zeichen oder Zahl zwischen -127 und +128).
+
* "void main(void)" definiert ein Unterprogramm mit dem Namen main, das keine Parameter hat (void) und eine ganze Zahl (int) zurückliefert. "main" ist aber nicht irgendein Unterprogramm, sondern es wird beim Programmstart aufgerüfen. Es entspricht daher dem "Hauptprogramm" von Pascal.
+
* Die geschwungenen Klammern beginnen bzw. beenden einen Block, in diesem Fall den von "main".
+
Innerhalb von "main" wird noch eine Variable definiert, Zahl2.
+
* Danach folgen die Anweisungen.
+
* "return", gefolgt von einem Wert, beendet das Programm.  
+
  
 +
Der main-Funktion können beim PC Parameter übergeben werden. Dies sind die sogenannten Kommandozeilenparameter und evtl. Umgebungsvariablen, die beim Aufruf eines Programmes hinter dem Dateinamen stehen. Zudem wird auch ein int-Wert als Ergebnis zurückgeliefert, der den Aufrufer &ndash; üblicher weise eine Shell &ndash; den Erfolg bzw. Fehlerstatus des Programmes mitteilt.
  
==Das Unterprogramm MAIN==
+
Beim [[Microcontroller]] ist <tt>main</tt> das Startprogramm, das nach dem RESET und der Initialisierung aufgerufen wird. Hier gibt es also keine Funktionsparameter. Ein Rückgabewert ist auch nicht sinnvoll, so daß <tt>main</tt> oft als  <tt>void</tt>-Funktion (ohne Rückgabewert) definiert wird. Um Compilerfehler/Warnungen zu vermeiden, muss der Compiler dann aber mit speziellen Einstellungen gestartet werden, denn C-Standard ist, daß main einen Wert zurückliefert!
Unterprogramme lernen Sie zwar erst später kennen, aber dieses eine muß sein. In Gegensatz zu anderen Programmiersprachen gibt es in C kein "Hauptprogramm", sondern nur Unterprogramme. Aber woher soll der Computer wissen, welches Unterprogramm er am Beginn ausführen soll? Ganz einfach, er führt immer das Unterprogramm "main" aus. Dieses ist sozusagen das "Hauptprogramm".  
+
{{comment|void-Definition von main ist nur beim Controller ueblich}}
Der Mainfunktion können bei PC Parameter übergeben werden. Dies sind die sogenannten Kommandozeilenparameter die beim Aufruf eines Programmes hinter dem Dateinamen stehen. Zudem wird auch ein Integer Wert als Ergebnis zurückgeliefert. Beim [[Microcontroller]] ist main ja gewöhnlich das Startprogramm das nach dem RESET aufgerufen wird, hier gibt es dann weder Funktionsparameter noch Rückgabewert, daher wird beides oft auch als VOID (Parameterfrei) definiert, wie oben im Beispiel zu erkennen.
+
{{comment|spezielle Compilereinstellungen sind noetig, damit bei dieser Definition von main}}
 +
{{comment|kein Fehler/Warnung erzeugt wird.}}
 +
void main ()
 +
{
 +
    ...
 +
}
 +
 
 +
=Blöcke=
 +
Im vorigen Abschnitt haben Sie bereits die geschwungenen Klammern { und } kennen gelernt. C-Programme sind in so genannte Blöcke unterteilt. Da gibt es zum einen das Hauptprogramm und die jeweiligen Unterprogramme, aber auch Schleifen und bedingte Anweisungen. Jedes dieser Konstrukte stellt ein eigenständiges Stück Code dar, das als solches gekennzeichnet werden muss.
 +
 
 +
Die Kennzeichnung für einen Blockanfang ist die öffnende geschweifte Klammer, während ein Blockende mit der schließenden Klammer notiert wird. Ein Block kann mehrere Teilblöcke enthalten, die wiederum Teilblöcke enthalten dürfen etc. Öffnende und schließende Block-Klammern tauchen immer paarweise auf.
 +
 
 +
Um Programme übersichtlich zu gestalten, wird jeder Block oder Teilblock eingerückt (siehe unten). Dann ist auch bei längerem Code sofort ersichtlich, wo ein Block beginnt und wo er endet. Geschweifte Klammern werden dann nur selten vergessen, und falls doch kann der Fehler schnell erkannt werden.
 +
 
 +
Variablen, die in einem Block deklariert werden, sind nur innerhalb dieses Blocks &ndash; und damit auch in allen seinen Teilblöcken &ndash; gültig.
 +
 
 +
int main (void)
 +
{  {{comment|der Block "main" beginnt}}
 +
    int zahl;
 +
   
 +
    {  {{comment|ein Block beginnt}}
 +
        {{comment|hier koennen Deklarationen und Anweisungen stehen}}
 +
    }  {{comment|der Block endet}}
 +
 
 +
    return 0;
 +
}  {{comment|"main" endet}}
 +
 
 +
=Datentypen=
 +
==Elementare Datentypen==
 +
Der Datentyp einer Variable gibt an, welche Werte eine Variable enthalten kann, welcher Art diese Daten sind und wie sie verarbeitet werden, etwa in arithmetischen Operationen wie einer Addition. So ist es zum Beispiel möglich, in eine Variable vom Typ <tt>int</tt> ganze Zahlen zwischen ca. -32000 und +32000 einzutragen. In einer char-Variable können ASCII-Zeichen gespeichert werden (alles, was Sie mit der Tastatur erzeugen können) oder ganze Zahlen von -128 bis 127.
 +
 
 +
;Achtung: Da C plattformabhängig ist, hängt die Größe eines Datentypes zum Teil von der genutzten Hardware (z.B. 8, 16 oder 32 Bit-Controller) und dem Compiler und dessen Einstellungen ab!
 +
 
 +
===int, char, short, long (ganze Zahlen)===
 +
In Variablen dieser Typen können Sie ganze Zahlen abspeichern, also z.B. 1, <tt>-</tt>2, 100, 12345. Jeden dieser Typen gibt es in zwei Ausprägungen: als "<tt>signed</tt>", also als vorzeichenbehafteten Typ, und als "<tt>unsigned</tt>", also ohne Vorzeichen, d.h. das Vorzeichen wird als 0 oder +1 genommen.
 +
 
 +
Vorzeichenbehaftete Ganzzahl-Typen werden intern im <tt>n-1</tt>-Komplement dargestellt, das Vorzeichen selbst findet sich also im höchstwertigen Bit. Werden zur Speicherung ''b'' Bits verwendet, dann reicht der Wertebereich von <tt>-2<sup>''b''-1</sup></tt> bis zu <tt>2<sup>''b''-1</sup>-1</tt>.
 +
 
 +
Bei Ganzzahl-Typen ohne Vorzeichen reicht der Wertebereich von <tt>0</tt> bis zu <tt>2<sup>''b''</sup>-1</tt>, wenn der Typ ''b'' Bits breit ist.
 +
 
 +
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
! Größe (Bit) || Typ || Vorzeichen || colspan="2"| Grenzen des Wertebereichs
 +
|-
 +
| 8  || <tt>char</tt>
 +
| <tt>signed</tt><br/><tt>unsigned</tt>
 +
| align="right" | -128<br />0
 +
| align="right" | 127<br />255
 +
|-
 +
| 16 || <tt>short</tt>
 +
| <tt>signed</tt><br/><tt>unsigned</tt>
 +
| align="right" | -32.768<br />0
 +
| align="right" | 32.767<br />65.535
 +
|-
 +
| 32 || <tt>long</tt>
 +
| <tt>signed</tt><br/><tt>unsigned</tt>
 +
| align="right" | -2.147.483.648<br />0
 +
| align="right" | 2.147.483.647<br />4.294.967.295
 +
|-
 +
| 64 || <tt>long long</tt>
 +
| <tt>signed</tt><br/><tt>unsigned</tt>
 +
| align="right" | -9.223.372.036.854.775.808<br />0
 +
| align="right" | 9.223.372.036.854.775.807<br />18.446.744.073.709.551.615
 +
|-
 +
|8, 16, 32, 64<br/>
 +
| <tt>int</tt>
 +
| <tt>signed</tt><br/><tt>unsigned</tt>
 +
|plattform-/compilerabhängig
 +
|plattform-/compilerabhängig
 +
|}
 +
</center>
 +
 
 +
==== Boolean (Logische Variablen)====
 +
In der Sprache C gibt es keinen Datentyp für boolsche Werte "wahr" bzw. "TRUE" oder "falsch" bzw. "FALSE". Statt dessen wird gerne der Datentyp <tt>int</tt> dafür verwendet.
 +
Hat die jeweilige Variable den Wert 0, so ist sie FALSE, sonst (ungleich 0) ist sie TRUE.
 +
;Hinweis: Bitte beachten, daß eine Variable, die TRUE ist, nicht unbedingt den Wert&nbsp;1 haben muß. Sie muß lediglich ungleich&nbsp;0 sein!
 +
 
 +
====char (Zeichen)====
 +
In einer <tt>char</tt>-Variable können Sie 8-Bit-Werte speichern. Dieser Datentyp wird oft für ASCII-Zeichen genutzt, denn für den Computer ist es egal, ob sich eine Zahl oder ein Zeichen in der Variablen befindet. Er speichert alles in Form von Binärzahlen.
 +
 
 +
Dabei darf man eines nicht vergessen: Es macht einen großen Unterschied, ob man in einer <tt>char</tt>-Variablen das Zeichen <tt>'1'</tt> (ASCII-Zeichen Nr. 49) abspeichert, oder die Zahl <tt>1</tt> (das entspricht ASCII-Zeichen Nr. 1, also irgendeinem Sonderzeichen). Man kann zwar mit beiden rechnen, aber <tt>'1' * 2</tt> ergibt nicht <tt>'2'</tt>, sondern <tt>'b'</tt> (ASCII-Zeichen Nr. 98)!
 +
 
 +
===float, double (Gleitkommazahlen)===
 +
In einer Gleitkomma-Variable können Kommazahlen gespeichert werden, z.B. 3.141592654.
 +
<tt>float</tt> reicht für die meisten Kommazahlen. Werden jedoch noch höhere Genauigkeiten benötigt, kommt der Datentyp <tt>double</tt> zum Einsatz.
 +
;Vorsicht: bei PIC (microchip) ist die innere Darstellung dieser Zahlen anders als bei den meisten anderen Compilern, beim binären Senden z.B. zum PC muß dann konvertiert werden! Bei [[avr-gcc]] finden die Rechnungen intern mit <tt>float</tt> statt, auch wenn ein Typ als <tt>double</tt> deklariert ist.
 +
 
 +
===void===
 +
Dies ist ein spezieller Typ, der soviel bedeutet wie "nicht vorhanden". Eine Funktion, die keinen Rückgabewert zurückliefert, definiert als Rückgabetyp <tt>void</tt>, und kennzeichnet damit, daß sie eben nichts zurückliefert. Objekte vom Typ <tt>void</tt> können nicht angelegt werden.
 +
 
 +
==Zeiger==
 +
Jede Variable steht an einer definierten Stelle im Speicher, an ihrer sogenannten ''Adresse''.
 +
 
 +
Ein Zeiger ist eine Variable, in der eine Adresse gespeichert werden kann. Diese stellt eine bestimmte Position im Arbeitsspeicher dar. Die Adresse eines Objektes erhält man, indem man ihm ein&nbsp;<tt>&</tt> voranstellt. Die Umkehrung davon &ndash; also der Zugriff auf die Speicherstelle, die im Zeiger enthalten ist &ndash; erledigt ein vorgestellter&nbsp;<tt>*</tt>. Der Operator &nbsp;<tt>*</tt> gibt also den ''Inhalt'' der Adresse.
  
 
<pre>
 
<pre>
void main(void) //void Definition nur beim Controller üblich
+
#include <stdio.h>
 +
 
 +
int main (void)
 
{
 
{
 +
  int * zeiger;
 +
  int zahl;
 +
 +
  zeiger = &zahl;
 +
  *zeiger = 12;
 +
 
 +
  printf ("%d = %d", zahl, *zeiger);
 +
 
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
  
Was das bedeutet, lernen Sie in einem späteren Kapitel. Wichtig ist nur - ohne main ist kein Programm komplett, es lässt sich auch nicht compilieren und ausführen!
+
Die Definition von <tt>zeiger</tt> als Zeiger ist so zu lesen: Der Inhalt von <tt>zeiger</tt> ist ein <tt>int</tt>. Damit wird <tt>zeiger</tt> zu einem "Zeiger auf <tt>int</tt>". Dabei gehört der&nbsp;<tt>*</tt> sinngemäß zum Bezeichner <tt>zeiger</tt>, nicht zum Typ. Folgende Definition definiert also nicht zwei Pointer, sondern einen Pointer (auf <tt>int</tt>) sowie einen <tt>int</tt>:
 +
int * zeiger, zahl;
  
==Blöcke==
+
Um den Zeiger mit der Adresse von <tt>zahl</tt> zu laden, schreibt man den Adress-Operator&nbsp;<tt>&</tt> von <tt>zahl</tt>:
Im vorigen Abschnitt haben Sie bereits die geschwungenen Klammern { und } kennen gelernt. Doch was bedeuten Sie? Einem Pascal-Kenner ist das schnell erklärt: { entspricht BEGIN, } entspricht END. Wenn ihnen auch das unbekannt ist, dann hilft Ihnen hoffentlich die folgende Erklärung.
+
zeiger = &zahl;
Programme sind in Abschnitte unterteilt. Da gibt es zum einen das Hauptprogramm und die jeweiligen Unterprogramme, aber auch Schleifen und bedingte Anweisungen. Jedes dieser Beispiele stellt ein eigenständiges Stück Code dar. Daher müssen Sie es auch als solches kennzeichnen. Dies geschieht mit { und }. { bedeutet so viel wie "Block Anfang" und } bedeutet "Block Ende":
+
Jetzt möchten Sie der Speicherstelle, deren Adresse der Zeiger enthält, einen Wert zuweisen. Dazu verwendet man den "Inhalts-Operator" <tt>*</tt> (<tt>z.B. *zeiger = 12</tt>).  
 +
Genauso können Sie mit dem Inhaltsoperator Werte abfragen und an <tt>printf</tt> (und jedes andere Unterprogramm) übergeben.
  
 +
==Enum==
 +
 +
Über <tt>enum</tt> können Aufzählungen definiert werden. Die Werte sind <tt>int</tt>-Werte und beginnen mit&nbsp;0. Der folgende enum hat einen um&nbsp;1 grösseren Wert. Mit einer Zuweisung können auch andere Werte zugeordnet werden. Klarer wird's im Beispiel:
 +
<tt>
 +
enum Farben
 +
{
 +
    ROT,
 +
    GRUEN,
 +
    BLAU,
 +
    BRAUN = 5,
 +
    SCHWARZ
 +
};
 +
</tt>
 +
Dies definiert die Konstanten <tt>ROT=0</tt>, <tt>GRUEN=1</tt>, <tt>BLAU=2</tt>, <tt>BRAUN=5</tt> und <tt>SCHWARZ=6</tt> und den Typ <tt>enum Farben</tt>:
 
<pre>
 
<pre>
void main(void)
+
void foo (enum Farben farbe)
{    //der Block "main" beginnt
+
{
int Zahl;
+
   switch (farbe)
+
  {
{   //ein "namenloser" Block beginnt
+
      case ROT:
  //hier stehen Anweisungen
+
        ...
}  //der "namenlose" Block endet
+
}    //der Block "main" endet
+
 
</pre>
 
</pre>
  
'''Bemerkung:'''
+
Damit kann man anstatt "magischer" Zahlen sprechende Namen im Code verwenden, etwa in Berechnungen und Zuweisungen, Vergleichen oder als Konstante hinter einem <tt>case</tt>.
Der "namenlose" Block ist eigentlich unnötig. In den folgenden Kapiteln lernen Sie jedoch bessere Beispiele kennen!
+
  
 +
==Zusammengesetzte Datentypen==
 +
===Arrays===
 +
Oft muß man sehr viele Werte gleichzeitig abspeichern und betrachten, die alle der selben Aufgabe dienen. Man schreibt z.B. ein Programm, das 10 Zahlen einlesen und anschließend wieder ausgeben soll. Man könnte das natürlich mit 10 einzelnen Variablen bewerkstelligen, aber es ist sinnvoller, dabei Arrays &ndash; teilweise auch als ''Felder'' bezeichnet &ndash; zu verwenden.
  
==Variablen und Datentypen==
+
In einem Array werden mehrere Variablen gleichen Typs zusammengefasst und hintereinander im Speicher abgelegt. So kann man viele tausend Variablen anlegen mit nur einer Zeile Code. Doch es gibt noch größere Vorteile: Sie können das Array mit einer Schleife ganz einfach nach Werten durchsuchen. Stellen Sie sich vor, Sie müssten mit 100 verschiedenen Variablen <tt>Zahl_00</tt> bis <tt>Zahl_99</tt> arbeiten!
  
===Variablen===
+
'''Syntax:'''
Eine Variable ist ein Synonym (=anderer Name) für eine Speicherstelle in einem Computer. Einfacher gesagt, eine Variable bietet Raum, um Daten (z.B. eine Zahl) zu speichern und wieder zu lesen. Dieser "Raum" wird im Hauptspeicher (RAM) des Computers angelegt.
+
{{Type}} {{Bezeichner}}&#91;{{Konstante}}&#93;;
  
===Datentypen===
+
'''Beispiel:'''
Der Datentyp einer Variable gibt an, wie viele Daten diese Variable enthalten kann, und welcher Art die Daten sind. So ist es zum Beispiel möglich, in eine Variable vom Typ int eine Zahlen zwischen ca. -32000 und +32000 einzutragen. In einer Char-Variable können Sie alle möglichen DOS-ASCII-Zeichen speichern (alles, was Sie mit der Tastatur erzeugen können).
+
unsigned int werte[100];
  
'''ACHTUNG:''' Da C platformunabhängig ist, hängt die Größe eines Datentypes zum Teil von der genutzten Hardware (z.B. 8, 16 oder 32 Bit Controller) und dem Compiler ab!
+
Der Name muß natürlich ein gültiger Bezeichner sein, als Datentyp kann jeder Typ genommen werden &ndash; sowohl elementare Datentypen als auch Zeiger, Strukturen, Unions oder selbst definierte Datentypen. In der eckigen Klammer wird die Anzahl der Elemente bekanntgegeben. Ein mit [3] definiertes Array hat Platz für drei Variablen. Da der Index immer bei 0 beginnt, greift man also mit <tt>[0]</tt>, <tt>[1]</tt> und <tt>[2]</tt> auf den jeweilige Inhalt zu. Um auf eine der im Array enthaltenen Variablen zugreifen zu können, müssen Sie den Variablennamen und in eckigen Klammern den Index (die "Nummer") der Variablen angeben. Diese Variable verhält sich dann wie eine ganz normale Variable des jeweiligen Datentypes.
 +
#include <stdio.h>
 +
 +
#define NZAHLEN 10
 +
 +
int main(void)
 +
{
 +
    int i;
 +
    int zahlen[NZAHLEN];  {{comment|zahlen[0] ... zahlen[9]}}
 +
 
 +
    for (i=0; i < NZAHLEN; i++)
 +
    {
 +
      printf ("Bitte Zahl %d eingeben: ", i);
 +
      scanf  ("%d", & zahlen[i]);
 +
      printf ("\n");
 +
    }
 +
 +
    printf ("Super!\n");
 +
   
 +
    for (i=0; i < NZAHLEN; i++)
 +
      printf ("Zahl %d ist: %d\n", i, zahlen[i]);
 +
     
 +
    return 0;
 +
}
 +
 
 +
Zuerst wird ein 10 int-Variablen großes Array angelegt.
 +
In dieses wird nun der Reihe nach 10 Zahlen eingelesen.
 +
Anschließend werden alle 10 Zahlen ausgegeben.
  
===int (ganze Zahlen)===
+
Dabei wird die Größe der Arrays und das Schleifenende über das Define "<tt>NZAHLEN</tt>" angegeben. Dadurch muss nur ''eine Stelle'' im Code geändert werden, wenn die Größe des Arrays einmal einen anderer Wert als 10 haben soll &ndash; dies vermeidet Fehler die dadurch entstehen, wenn man beim Anpassen der Array-Größe eine Codestelle vergisst, zudem wird der Code lesbarer als wenn irgendwo die Zahl "10" auftaucht.
In Variable dieses Types können Sie ganze Zahlen abspeichern, also z.B. 1, 2, 100, 12345. Unter dem DOS-Turbo C(++) Compiler kann eine Int Zahl zwischen -32000 und +32000 groß sein.
+
Gleitkommazahlen: float, double
+
In einer Gleitkomma-Variable können Kommazahlen gespeichert werden, z.B. 3.141592654. Float reicht reicht für die meisten Kommazahlen. Werden jedoch noch höhere Genauigkeiten benötigt, kommt der Datentyp double zum Einsatz.  
+
  
===char (Zeichen)===
+
{{FarbigerRahmen|
In einer char-Variable können Sie Zahlen zwischen -127 und +128 abspeichern. Dieser Datentyp wird jedoch meist für ASCII-Zeichen genutzt (für den Computer ist es ja egal, ob sich eine Zahl oder ein Zeichen in der Variable "befindet", er speichert "alles" in Form von binären Ziffernfolgen). Dabei darf man eines nicht vergessen: Es macht einen großen Unterschied, ob man in einer char Variablen das Zeichen "1" (ASCII-Zeichen Nr. 49) abspeichert, oder die Zahl 1 (entspricht Hardware-technisch ASCII-Zeichen Nr. 1). Man kann zwar mit beiden rechnen, aber "1" * 2 ergibt nicht "2", sondern "b" (ASCII-Zeichen Nr. 98)!  
+
'''Merke:'''
 +
:Wird ein ungültiger Index angeben (einer, der in der Deklaration nicht enthalten ist) können  undefinierte Dinge passieren, wenn dadurch andere Variableninhalte oder Programmcode überschrieben wird, der hinter oder vor dem Array im Speicher liegt. Schlimmstenfalls kann sogar der Computer/Controller abstürzen. Also darauf achten, daß keine ungültigen Werte als Index auftreten!
 +
}}
  
 
===Strings (Zeichenketten)===
 
===Strings (Zeichenketten)===
Bei Strings handelt es sich um Wörter und Sätze, die aus einzelnen Zeichen gebildet werden. Jede Ausgabe auf dem Bildschirm funktioniert mittels Strings. Wie dieser Datentyp genau funktioniert, erfahren Sie im Kapitel besondere Datentypen.  
+
Ein String ist nichts anderes als ein Array, das aus einzelnen Zeichen (<tt>char</tt>) gebildet wird. Die Ausgabe auf dem Bildschirm funktioniert am einfachsten mittels Strings.
  
=== Boolean (Logische Variablen)===
+
Die Definition eines Strings erfolgt also genauso wie bei Arrays:
Dieser Typ kann nur zwei Werte annehmen: "TRUE" und "FALSE". Dabei handelt es sich um logische (boolsche) Werte (deutsch: "wahr" oder "falsch"). Man kann damit logische Vergleiche machen (siehe Kaptitel 3). C hat dafür keinen eigenen Datentyp, sondern es können int und char dafür verwendet werden. Hat die jeweilige Variable den Wert 0, so ist sie FALSE, sonst (ungleich 0) ist sie TRUE.
+
char string[21];
HINWEIS: In Zukunft werden ich oft 1 als Synonym für "ungleich 0" verwenden. Bitte beachten Sie jedoch, das eine Variable, die TRUE ist, nicht unbedingt den Wert 1 haben muß. Sie muß lediglich ungleich 0 sein!
+
  
===Anlegen von Variablen===
+
Nun haben Sie eine String, in dem Sie 21 Zeichen speichern können. Ganz richtig ist das jedoch nicht. C arbeitet mit "null-terminierten Strings". Das beudeutet, dass die Länge des Strings nicht abgespeichert wird, sondern das Zeichen mit dem ASCII-Wert 0 das Stringende kennzeichnet. Daher auch die Bezeichnung  "null terminiert".  
Um eine Variable verwenden zu können, müssen Sie sie zuerst vereinbaren ("erzeugen", "bekannt machen"). Dies geht ganz einfach: Schreiben Sie zuerst den Datentyp, dann den Namen der Variablen. Zum Schluß kommt noch der Strichpunkt. Und nicht vergessen, C ist "case-sensitive", d.h. der Datentyp muß klein geschrieben werden.  
+
  
 +
Das letzte Zeichen eines Strings muß daher immer das ASCII-Zeichen Nr. 0 sein. Ist es das nicht, hat der String kein definiertes Ende, und wenn Sie versuchen, ihn durch eine Standard-Funktion auszugeben zu lassen, könnte es eine Weile dauern, bis sich im Speicher zufällig irgendwo eine 0 befindet. Es stehen ihnen daher bei dem Beispiel nur 20 Zeichen zur Verfügung.
 +
 +
===Mehrdimensionale Arrays===
 +
Manchmal benötigt man mehr als nur ein eindimensionales Array, wie Sie es bisher kennengelernt haben. Auch dies ist kein Problem. In der Deklaration geben Sie einfach mehrere eckige Klammern hintereinander an. Aber Vorsicht: der Speicherplatz ist begrenzt, ein "<tt>char feld[1024][1024]</tt>" hat die Speicherplatzgrenzen vermutlich bereits weit überschritten, und der Compiler wird einen (bei gewissen Einstellung auch keinen) Fehler liefern.
 +
Beim Zugriff auf mehrdimensionale Felder müssen auch mehrere Indizes angeben werden:
 
<pre>
 
<pre>
int Zahl1, Zahl2;
+
#include <stdio.h>
char Zeichen;
+
  
void main(void)
+
int main(void)
 
{
 
{
   float gleitZahl1;
+
   int x,y;
   // Anweisungen
+
   int feld[3][5];
 +
 +
  for (x=0; x<3; x++)
 +
  {
 +
    for (y=0; y<5; y++)
 +
    {
 +
      printf ("Feldwert x: %d,  y: %d ", x, y);
 +
      scanf  ("%d", & feld[x][y]);
 +
      printf ("\n");
 +
    }
 +
  }
 +
 
 +
  for(x=0; x<3; x++)
 +
    for (y=0; y<5; y++)
 +
        printf ("Wert: feld[%d][%d] = %d\n", x, y, feld[x][y]);
 +
 
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
  
'''Erklärung:''' In einer Zeile können auch mehrere Variablen gleichen Types vereinbart werden, wenn man einen Beistrich dazwischen setzt. Variablen können in jedem "Block" vereinbart werden. Siehe Gültigkeitsbereiche.  
+
'''Erklärung:'''  
 +
 
 +
Zuerst wird ein 3 mal 5 <tt>int</tt>-Array angelegt.
 +
Dann werden die Werte eingegeben: zuerst <tt>feld[0][0]</tt>, dann <tt>feld[0][1]</tt>, usw. bis <tt>feld[2][4]</tt>.
 +
Zum Schluß werden alle Werte noch einmal ausgegeben.
 +
 
 +
===Strukturen===
 +
In C können Sie sogenannte "Strukturen" definieren. Dabei handelt es sich um eine Zusammenfassung mehrerer Datentypen zu einem größeren. Im Unterschied zu Feldern können in Strukturen unterschiedliche Datentypen zusammengestellt und gespeichert werden:
 +
 
 +
'''Syntax:'''
 +
struct {{Bezeichner}}
 +
{
 +
    {{Deklaration}}
 +
    {{Deklaration}}
 +
    ...
 +
};
 +
 
 +
'''Beispiel:'''
 +
{{comment|Definition der Struktur 'Person'}}
 +
struct Person
 +
{
 +
    int id;
 +
    char vname[20], nname[20];
 +
    char telnr[15];
 +
    int alter;
 +
};
 +
 
 +
"<tt>struct Person {</tt>" leitet die Definition der Struktur mit dem Namen "<tt>Person</tt>" ein.
 +
Dann werden in dieser Struktur fünf Komponenten definiert: drei Strings und zwei <tt>int</tt>.  
 +
Mit <tt>}</tt> wird die Definition abgeschlossen. Sie haben damit einen Datentyp erstellt. Um eine Variable des Typs <tt>struct Person</tt> anzulegen, geben Sie einfach an
 +
struct Person {{Bezeichner}};
 +
 
 +
Zum Zugriff auf eine Komponente der Struktur gibt man den Namen der Struktur-Variablen an (im folgenden Beispiel also <tt>hubert</tt> bzw. <tt>klaus</tt>), einen Punkt und danach den Bezeichner der Komponente:
 +
{{comment|Definition zweier Struktur-Variablen}}
 +
struct Person hubert, klaus;
 +
 +
{{comment|Zugriff auf Struktur-Komponenten}}
 +
hubert.alter = 32;
 +
klaus.alter = hubert.alter + 1;
 +
 
 +
'''Hinweis:'''
 +
Der eventuell etwas lästige Gebrauch von <tt>struct</tt> kann schon bei der Definition der Struktur vermieden werden.
 +
Der Definition ist ein <tt>typedef</tt> voranzustellen. Der Definition folgt dann ein gültiger (und auch eindeutiger) C-Name.
 +
 
 +
'''Syntax:'''
 +
typedef struct {{Bezeichner}}
 +
{
 +
    {{Deklaration}}
 +
    {{Deklaration}}
 +
    ...
 +
} {{Bezeichner}} ;
 +
 
 +
 
 +
'''Beispiel:'''
 +
{{comment|Definition der Struktur '_Mensch' bzw. 'Mensch'}}
 +
typedef struct _Mensch
 +
{
 +
    int id;
 +
    char vname[20], nname[20];
 +
    char telnr[15];
 +
    int alter;
 +
} Mensch;
 +
 
 +
Jetzt sind folgende Deklarationen identisch:
 +
struct _Mensch {{Bezeichner}};
 +
Mensch {{Bezeichner}};
 +
 
 +
Ist der Struktuname nicht notwendig (im Beispiel oben <tt>_Mensch</tt>), kann auch kürzer geschrieben werden:
 +
 
 +
'''Syntax:'''
 +
typedef struct
 +
{
 +
    {{Deklaration}}
 +
    {{Deklaration}}
 +
    ...
 +
} {{Bezeichner}} ;
 +
 
 +
'''Beispiel:'''
 +
{{comment|Definition der Struktur 'Mensch'}}
 +
typedef struct
 +
{
 +
    int id;
 +
    char vname[20], nname[20];
 +
    char telnr[15];
 +
    int alter;
 +
} Mensch;
 +
 
 +
In diesem Fall ist lediglich die Deklaration <tt>Mensch {{Bezeichner}};</tt> gültig.
  
===Zuweisungen===
+
Der Hinweis gilt sinngemäß auch für die Definition <tt>union</tt> im nachfolgenden Abschnitt.
Im man kann einer vereinbarten Variable nun Werte zuweisen. Dazu schreibt man zuerst den Variablennamen, ein Gleichheitszeichen "=" und anschließend den zuzuweisenden Ausdruck.  
+
  
 +
===Unions===
 +
Eine Union wird ganz analog zu einer Struktur deklariert und verwendet. Sie unterscheidet sich von einer Struktur jedoch dadurch, daß ihre Elemente nicht nacheinander im Speicher abgelegt werden, sondern sich überlagern. Auf die in einer Union enthaltenen Daten gibt es also verschiedene Sichten: je nachdem, welche Sicht bzw. Interpretation der Daten man gerne hätte, wählt man den gewünschten Zugriff.
 
<pre>
 
<pre>
void main(void)
+
union Daten
 
{
 
{
  int Zahl1, Zahl2=12;
+
  int id;
  char Zeichen1="A".
+
  
  Zahl1=52;
+
  struct Person u_person;
  Zeichen1=Zeichen1+1;  
+
 
}
+
  struct u_double
 +
  {
 +
      int id;
 +
      double wert;
 +
  };
 +
 
 +
  struct u_pointer
 +
  {
 +
      int id;
 +
      union Daten * p1;
 +
      union Daten * p2;
 +
  };
 +
};
 +
 
 +
union Daten data;
 
</pre>
 
</pre>
 +
Dies definiert eine Union mit den vier Zugriffsmöglichkeiten <tt>id</tt>, <tt>u_person</tt>, <tt>u_double</tt> und <tt>u_pointer</tt>. Die Größe der Union richtet sich dabei nach der größten Komponente. In diesem Beispiel sind alle Komponenten so angelegt worden, daß sie an erster Stelle ein <tt>int id</tt> enthalten. In <tt>data.id</tt> könnte man sich also merken, wie die Daten in der Union zu interpretieren sind. Würde <tt>struct Person</tt> nicht dieses <tt>id</tt> enthalten, so würde sich <tt>data.id</tt> mit <tt>data.u_person.vname</tt> überlagern. Ein Ändern der ersten Buchstaben von <tt>vname</tt> hätte also ein Ändern von <tt>id</tt> zur Folge, und man könnte es nicht mehr als Merker verwenden. Mit diesem Feld überlagert das <tt>id</tt> von <tt>data</tt> die <tt>id</tt>-Felder der anderen Sichten, z.B. ist <tt>data.id</tt> der selbe Zugriff wie auf <tt>data.u_person.id</tt>.
  
Was macht das Program:
+
Ein anderes Beispiel ist eine Union, die es ermöglicht, auf die einzelnen Bytes eines <tt>long</tt> zuzugreifen:
 +
<pre>
 +
typedef union
 +
{
 +
  unsigned long  as_long;
 +
  unsigned short as_short[2];
 +
  unsigned char  as_byte[4];
 +
} data32_t;
 +
</pre>
 +
Dies überlagert einen <tt>unsigned long</tt> &ndash; also eine 32-Bit-Zahl &ndash; mit vier Bytes bzw. zwei Shorts.
 +
data32_t wert;
 +
 +
wert.as_long = 0x12345678;
 +
wert.as_byte[0] = 0xab;
 +
{{comment|nun ist wert.as_long gleich 0xab345678 oder 0x123456ab (je nach Plattform)}}
  
Zuerst werden 3 Variablen angelegt (Zahl1, Zahl2, Zeichen1).
+
==Eigene Datentypen==
Zahl2 wird gleich bei der Vereinbarung der Wert 12 zugewiesen (Ja, auch das ist möglich).
+
"Zahl1=52;" - Hier wird der Variablen Zahl1 der Wert 52 zugewiesen. Ist doch ganz einfach!
+
Zeichen1 wir um 1 erhöht. Da in der Variablen "A" gespeichert ist, gibt es ein keines Problem. Was ist "A" + 1 ? Aber wer brav aufgepasst hat, weiß das schon. "A" entspricht der Zahl 65, und 65 + 1 ist 66. Nun ist in der Variablen Ziffer1 der Wert 66 abgespeichert, was wiederum dem ASCII-Zeichen Nr. 66 entspricht, welches "B" ist. Verstanden?
+
  
===Zuweisungen bei float===
+
=Variablen=
Das funktioniert genau wie normale Zuweisungen; Nachkommastellen werden durch einen Punkt abgegrenzt.
+
Eine Variable ist ein Synonym (=anderer Name) für eine Speicherstelle in einem Computer. Einfacher gesagt, eine Variable bietet Raum, um Daten wie Zahlen oder Zeichen zu speichern und wieder zu lesen.
.....
+
floatVariable=3.14;
+
....
+
  
===Zuweisungen bei logischen Variablen===
+
==Variablennamen==
Wie bereits erwähnt, besitzt C keinen logischen Datentyp. Es müssen also int und char dafür genutzt werden. Die Zuweisung entpricht der Standard-Zuweisung. Wird der Wert 0 zugewiesen, dann ist die Variable FALSE, ansonsten ist Sie TRUE.  
+
Ein Variablenname kann zusammengesetzt werden aus den Buchstaben <tt>'''A'''</tt> bis <tt>'''Z'''</tt> und <tt>'''a'''</tt> bis <tt>'''z'''</tt>, den Ziffern <tt>'''0'''</tt> bis <tt>'''9'''</tt>, sowie dem Sonderzeichen "Unterstrich" (underscore) <tt>'''_'''</tt>. Dabei darf an erster Stelle keine Ziffer stehen. Die Bezeichner <tt>hallo</tt>, <tt>HALLO</tt>, <tt>Hallo</tt>, <tt>HALL0</tt>, <tt>_123</tt> und <tt>_HALLO</tt> sind also alle gültige und unterschiedliche Variablennamen.
.....
+
intVariable:=123;  //entspricht in Pascal:  boolVariable:=true;
+
intVariable:=1;    //entspricht in Pascal:  boolVariable:=true;
+
intVariable:=0;    //entspricht in Pascal:  boolVariable:=false;
+
.....
+
  
===Vergleiche von Variablen===
+
==Anlegen von Variablen==
Sie können Variablen miteinander vergleichen. Das geschieht mit einem der folgenden Zeichen mit den normalen mathematischen Regeln: == ("ist gleich"), != ("ist nicht gleich"), < ("ist kleiner"), <= ("ist kleiner oder gleich"), > ("ist größer"), >= ("ist größer oder gleich"). Als Ergebnis bekommen Sie einen logischen Ausdruck in Form einer int-Zahl.
+
Um eine Variable verwenden zu können, muss sie zuerst vereinbart ("erzeugt") werden. Dies wird auch als "''Definition der Variablen''" bezeichnet und geht so: Schreiben Sie zuerst den Datentyp, dann den Namen der Variablen. Zum Schluß kommt noch der Strichpunkt, wie nach jeder C-Anweisung oder Deklaration. Und nicht vergessen: C unterscheidet zwischen  Groß- und Kleinschreibung!
.....
+
int Zahl1, Zahl2;
intVariable= Zahl1<=Zahl2;
+
char Zeichen;
.....
+
intVariable ist ungleich 0 (=TRUE), wenn Zahl1 kleiner oder gleich Zahl2 ist, ist Zahl1 jedoch größer als Zahl2, dann ist intVariable 0 (FALSE).
+
int main (void)
 +
{
 +
    float gleitZahl;
 +
    {{comment|Anweisungen}}
 +
 +
    return 0;
 +
}
  
 +
'''Hinweis:'''
 +
In einer Zeile können auch mehrere Variablen gleichen Types vereinbart werden, wenn man ein Komma dazwischen setzt (siehe unten). Variablen können in jedem Block vereinbart werden. Siehe [[#Gültigkeitsbereich|Gültigkeitsbereich]].
  
===Konstanten===
+
    ...
Konstanten können als Variable angesehen werden, die nicht beschrieben, sondern nur gelesen werden können. Ein typischen Beispiel dafür ist die Zahl PI (rund 3,141592654). Niemand würde in der realen Welt versuchen, ihr einen anderen Wert zuzuweisen. Würde man PI jedoch wie eine normale Variable anlegen, wäre dies ohne weiteres möglich. Um dies zu verhindern, gibt es die Konstaten:
+
    float gleitZahl1, gleitZahl2;
 +
    ...
  
 +
==Zuweisungen==
 +
Man kann einer vereinbarten Variable Werte [[#Zuweisungen|zuweisen]]. Dazu schreibt man zuerst den Variablennamen, ein Gleichheitszeichen "<tt>=</tt>" und anschliessend den zuzuweisenden [[#Ausdrücke|Ausdruck]].
 
<pre>
 
<pre>
void main(void)
+
int main (void)
 
{
 
{
   const pi=3.141592
+
   int zahl1, zahl2 = 12;
 +
  char zeichen1 = 'A';
 +
 
 +
  zahl1 = 52;
 +
  zeichen1 = zeichen1 + 1;
 +
 
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
 +
Zuerst werden drei Variablen angelegt (<tt>zahl1</tt>, <tt>zahl2</tt>, <tt>zeichen1</tt>).
 +
;<tt>zahl2</tt>: wird gleich bei der Vereinbarung der Wert 12 zugewiesen.
 +
;<tt>zahl1 = 52</tt>: Hier wird der Variablen <tt>zahl1</tt> der Wert 52 zugewiesen.
 +
;<tt>zeichen1</tt>: wird um 1 erhöht. Da in der Variablen <tt>'A'</tt> gespeichert ist, gibt sich ihr neuer Wert aus <tt>'A' + 1</tt>. Weil <tt>'A'</tt> dem Wert 65 entspricht, ist <tt>'A' + 1</tt> gleich 66, was dem Wert für <tt>'B'</tt> entspricht.
  
Wichtig dabei ist, dass man Konstanten nur bei der Vereinbarung einen Wert zuweisen kann (Aber glauben Sie nicht ewig daran, es gibt Wege, dies zu umgehen!).  
+
==Zuweisungen bei float==
 +
Das funktioniert genau wie bei normalen Zuweisungen. Nachkommastellen werden durch einen Punkt abgegrenzt:
 +
floatVariable = 3.14;
 +
Zusätzlich kann eine Zehnerpotenz angegeben werden:
 +
floatVariable2 = -1.234E-6;
 +
Dadurch wird der erste Wert mit 10<sup><tt>-</tt>6</sup> multipliziert, der Wert der Variablen ist also
 +
:<math>-1{,}234\cdot10^{-6} = -0.000001234</math>.
  
===Gültigkeitsbereiche===
+
==Zuweisungen bei logischen Variablen==
Jede Variable ist nur in dem Block gültig, in dem Sie vereinbart wurde, und in allen darunterliegenden. Globale Variablen (solche, die außerhalb eines Blockes vereinbart wurden, also vor main) sind im gesamten Programm gültig.
+
Wie bereits erwähnt, besitzt C keinen logischen Datentyp. Es müssen also <tt>int</tt> oder <tt>char</tt> dafür genutzt werden. Die Zuweisung entpricht der Standard-Zuweisung. Wird der Wert 0 zugewiesen, dann ist die Variable "unwahr", ansonsten ist sie "wahr".  
In C können mehrere Variablen den gleichen Namen haben, solange eindeutig ist, welche in welchen Block gültig ist. Dabei gelten folgende Regeln:
+
intVariable = !0;  /* entspricht "wahr"  */
 +
intVariable = 0;    /* entspricht "unwahr" */
  
===Globale Variablen gelten überall===
+
==Konstanten==
Wird jedoch in einem Unterprogramm wie z.B. main eine Variable gleichen Namens angelegt, gilt ab hier bis zum Ende des Blockes (main) nicht mehr die globale Variable, sondern die von main.  
+
Konstanten können als Variable angesehen werden, die nicht beschrieben, sondern nur gelesen werden können. Ein typisches Beispiel dafür ist die Zahl <math>\pi</math> (rund 3,141592654). Niemand würde in der realen Welt versuchen, ihr einen anderen Wert zuzuweisen. Würde man <math>\pi</math> jedoch wie eine normale Variable anlegen, wäre dies ohne weiteres möglich. Um dies zu verhindern, gibt es das Schlüsselwort <tt>const</tt> in C:
Das Spiel kann man weiterspielen: wird in einem Unter-Block von Main wieder eine "gleiche" Variable angelegt, gilt diese, und nicht die von main (und schon garnicht die globale :-) ).
+
const {{Type}} {{Bezeichner}} = {{Konstante}};  {{comment|Zuweisung bei der Defininition der Variablen}}
  
==Kontrollanweisungen==
+
Wichtig dabei ist, dass man Konstanten nur bei der Vereinbarung einen Wert zuweisen kann.
 +
Da Konstanten gewöhnlich im gesamten Programm, zumindest einer Quelldatei genutzt werden, definiert man diese allerdings gewöhnlich außerhalb des main-Blockes entweder am Anfang eines Programmes, oder in einer sogenannten Header-Datei, die per <tt>#include</tt> eingebunden wird.
 +
const float PI = 3.141592;  {{comment|Zuweisung bei der Defininition der Variablen}}
  
===Boolsche Logik===
+
Es sei jedoch erwähnt, daß auch einer Konstanten nachträglich ein anderer Wert zugewiesen werden kann. Im obigen Beispiel könnte mit
Boolean-Variablen können miteinander verknüpft werden. Dies geschieht mit den boolschen Operatoren && ( logisches "und"), || (logisches "oder") und ! (logisches "nicht") (es gibt noch weitere, auf die hier nicht eingegangen wird).  
+
* ((float*) &PI) = 2;
 +
der Wert von <tt>PI</tt> im Nachhinein verändert werden. Es wird die Adresse von <tt>PI</tt> genommen und diese Adresse durch den Cast in eine ganz normale <tt>float</tt>-Adresse umgewandelt, über welche der Wert geändert wird. Die sei der Vollständigkeit halber erwähnt.
  
  b1= b2 && b3;
+
Je nachdem, an welcher Stelle sich das <tt>const</tt> bei einer Pointer-Deklaration befindet, markiert es den Pointer als konstant oder das Objekt, auf das dieser Pointer zeigt. Eine häufige Parameterdeklaration in Ausgabe-Funktionen, die einen String erhalten, ist
 +
  void foo (const char * str, ...);
 +
Dadurch ist <tt>str</tt> der Zeiger auf eine Zeichenkette, die innerhalb der Funktion nicht verändert wird bzw. verändert werden darf. Eine Zuweisung wie <tt>*str = 'a'</tt> ergibt also einen Fehler. <tt>str</tt> selbst kann aber sehr wohl verändert werden, etwa mit <tt>str++</tt>.
  
 +
Soll ausgedrückt werden, dass <tt>str</tt> unveränderlich ist, dann so:
 +
void foo (char * const str, ...);
 +
Jetzt wäre eine Änderung des Strings in Ordnung, etwa durch <tt>str[10] = 'a'</tt>.
  
Der Wert von b1 ist dann 1 (TRUE), wenn b2 und ("AND") b3 1 (TRUE) sind. Ist eine der beiden (oder auch beide) 0 (FALSE), dann ist b1 ebenfalls 0 (FALSE).
+
Um sich zu merken, worauf das <tt>const</tt> wirkt, trennt man die Deklaration in Gedanken beim&nbsp;<tt>*</tt> auf: Steht das <tt>const</tt> links vom&nbsp;<tt>*</tt>, dann gehört es zum <tt>char</tt>, steht es rechts davon, dann gehört es zum Pointer. Natürlich ist es auch denkbar, beides &ndash; also den Zeiger und sein Ziel &ndash; als konstant zu markieren.
  
 +
==Gültigkeitsbereich==
 +
In C können mehrere Variablen den gleichen Namen haben, solange eindeutig ist, welche in welchem Block gültig ist. Dabei gelten folgende Regeln:
  
b1= b2 || b3;
+
;Lokale Variablen: sind Variablen, die innerhalb eines Blockes definiert werden. Jede Variable ist nur in dem Block gültig, in dem sie vereinbart wurde, sowie in allen darin enthaltenen Blöcken; es sei denn, in einem Unter-Block wird eine Variable gleichen Namens definiert. Dann bezieht sich in diesem Unter-Block der Bezeichner auf die im Unter-Block angelegte Variable.
 +
;Globale Variablen: werden ausserhalb jedes Blockes definiert und gelten ab der Stelle, an der sie deklariert werden, siehe auch [[#Deklaration und Definition|Deklaration und Definition]]. Wird jedoch in einem Block eine Variable gleichen Namens angelegt, gilt ab hier bis zum Ende des Blocks nicht mehr die globale Variable, sondern die im Block deklarierte. Das Spiel kann man weiterspielen: wird in einem Unter-Block wieder eine namensgleiche Variable angelegt, gilt diese in dem Unterblock.
  
 +
==Speicherklassen==
 +
Jede Variable in C gehört zu einer bestimmten Speicherklasse
 +
;<tt>auto</tt>: Lokale Variablen sind in aller Regel sogenannte ''automatische Variablen''. Das bedeutet, sie werden automatisch angelegt, wenn ein Block bzw. eine Funktion betreten wird und danach wieder entfernt. Das Schlüsselwort "<tt>auto</tt>" wird praktisch nie hingeschrieben, denn lokale Variablen ohne die ausdrückliche Angabe einer Speicherklasse, sind automatisch automatische Variablen.
 +
;<tt>extern</tt>: Ein externes Symbol ist im ganzen Programm bekannt bzw. in dem Block, in der die Deklaration steht. In unterschiedlichen Blöcken stehende Deklarationen beziehen sich auf das gleiche Symbol! Obgleich das Datum global zugreifbar ist, ist der Gültigkeitsbereich auf den deklarierenden Block begrenzt bzw. auf das deklarierende Quell-Modul, sofern das Symbol ausserhalb jedes Blocks des Moduls deklariert wird. Siehe auch [[#Deklaration und Definition|Deklaration und Definition]].
 +
;<tt>static</tt>: Die Variable ist im Block gültig bzw. im Quell-Modul (also in der C-Datei, in der die angelegt wurde), wenn sie nicht innerhalb eines Blockes angelegt wurde. Statische Variablen werden nicht in Registern oder im Frame der Funktion angelegt, sondern im selben Speicherbereich, in dem auch die globalen Variablen liegen; Konstanten evtl. auch im Flash. Eine lokale Variable, die als <tt>static</tt> angelegt wird, "überlebt" also das Verlassen des Blocks und hat beim neuerlichen Betreten des Blockes ihren bisherigen Wert. In unterschiedlichen Blöcken angelegte lokale statische Variablen beziehen sich auf unterschiedliche Speicherstellen, genau wie bei lokalen Variablen auch.
 +
;<tt>register</tt>: Durch diese Speicherklasse wird eine Variable &ndash; falls möglich &ndash; als Registervariable angelegt, also in einem Maschinenregister des Computer/Controllers gehalten. Dadurch kann auf solche Variablen besonders schnell zugegriffen werden. Dieses Schlüsselwort ist bei modernen Compilern weitgehend überflüssig, da die entsprechenden Optimierungen selbständig vorgenommen werden, wenn ausreichend Register vorhanden sind. Auch globale Variablen können als Register angelegt werden, davon ist dem Anfänger aber dringend abzuraten, weil leicht schwerauffindbare Fehler und Abstürze auftreten, wenn man nicht genau weiss, welche Implikationen in einer solchen Definition stecken!
 +
;<tt>volatile</tt>: (FIXME: volatile ist ein Qualifier und keine Speicherklasse) Dies ist das genaue Gegenteil von <tt>register</tt> und bewirkt, dass die Variable auf keinen Fall in einem Register zwischengespeichert werden darf, sondern immer aus dem RAM gelesen und ins RAM geschrieben werden soll. <tt>volatile</tt> müssen alle ''globalen'' Variablen markiert werden, die in [[ISR | Interrupt-Handlern]] verwendet werden.
  
Der Wert von b1 ist dann TRUE, wenn b2 oder ("OR") b3 TRUE ist. Ist also einer (oder beide) TRUE, dann ist auch b1 TRUE, sind beide FALSE, ist auch b1 FALSE.
+
=Ausdrücke=
 +
Eine Variable oder eine Konstante in C stellen einfache Ausdrücke dar.
 +
Diese elementaren Ausdrücke können durch Operatoren miteinander verknüpft werden und so zu neuen, komplexeren Ausdrücken zusammen gesetzt werden.
  
 +
Einfache Beispiele für Ausdrücke sind also z.B.:
 +
1
 +
a
 +
'a'
 +
1 + a
 +
a == 1
  
b1= !b2;
+
Auch Funktionen können einen Wert zurückliefern und in Ausdrücken weiter benutzt werden.
 +
In den folgenden Abschnitten wird gezeigt, welche Operatoren in C vorhanden sind,
 +
und wie man damit neue Ausdrücke aufbauen kann.
  
 +
==Lvalues==
  
Der Wert von b1 ist dann TRUE, wenn der von b2 FALSE ist. Die NOT Verknüpfung "dreht den logischen Wert der Variablen um".
+
Ein Lvalue in C ist ein Ausdruck, dem ein anderer Ausdruck zugewiesen werden kann, dessen Wert also durch eine Zuweisung verändert werden kann.
 +
das 'L' leitet sich ab von 'left' bwz. 'links' und das 'value' bedeutet Wert: Ein Lvalue ist ein Ausdruck, der auf der linken Seite einer Zuweisung stehen darf. Ein Lvalue ist also immer auch ein gültiger Ausdruck, aber die Umkehrung gilt in aller Regel nicht.
  
===IF - Anweisung===
+
Ein einfaches Beispiel für einen Lvalue ist eine "normale" Variable, die nicht mit <tt>const</tt> als Konstante markiert ist:
Syntax: IF (Bedingung) Anweisung;
+
a = 1;
Mit Hilfe der IF Anweisung kann man Codeteile (Blöcke) ausführen lassen, wenn die dazugehörige Bedingung erfüllt (TRUE) ist.
+
Hingegen ist der Ausdruck <tt>a+1</tt> kein Lvalue, denn eine Zuweisung wie
 +
a+1 = 2;
 +
die mathematisch durchaus sinnvoll ist, erzeugt einen Compilerfehler, der etwa lauten könnte "illegal lvalue in assignment":
 +
"ungültiger Wert in Zuweisung"
  
  if (Zahl1 == 0) Zahl2=10;
+
Andere Beipiele für Lvalues sind die Komponenten von (nicht-konstanten) [[#Strukturen|Strukturen]] und [[#Unions|Unions]], [[#Arrays|Array]]-Elemente und die Dereferenzierungen von Pointern: Die Konstante&nbsp;4 wird durch den Cast in eine Adresse umgewandelt. Über die Dereferenzierung&nbsp;<tt>*</tt> wird an die Adresse&nbsp;4 im Speicher eine&nbsp;3 geschrieben. Ob das erlaubt bzw. sinnvoll ist, ist abhängig von der jeweiligen Architektur.
 +
  * ((unsigned int *) 4) = 3;
 +
Hier ist der gesamte <tt>*</tt>-Ausdruck ein Lvalue
  
Die Anweisung Zahl2=10 wird nur ausgeführt, wenn Zahl1==0 TRUE ist. Will man in die IF Abfrage mehrere Anweisungen einschließen, benötigt man eine Block mit { und }. Die Klammer um die Bedingung ist - Bedingung!
+
==Logische (boolsche) Operatoren==
 +
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>a &amp;&amp; b</tt> || wahr, wenn <tt>a</tt> wahr und <tt>b</tt> wahr
 +
|-
 +
||<tt>a <nowiki>||</nowiki> b</tt> || wahr, wenn <tt>a</tt> wahr oder <tt>b</tt> wahr
 +
|-
 +
|<tt>a == b</tt> || gleich
 +
|-
 +
|<tt>a != b</tt> || ungleich
 +
|-
 +
|<tt>a <= b</tt> || kleiner oder gleich
 +
|-
 +
|<tt>a < b</tt> || kleiner als
 +
|-
 +
|<tt>a >= b</tt> || glösser oder gleich
 +
|-
 +
|<tt>a > b</tt> || grösser als
 +
|-
 +
|<tt>!a</tt> || wahr, wenn <tt>a</tt> nicht wahr und vice versa
 +
|}
 +
</center>
  
<pre>
+
Eine interessante Eigenschaft der Operatoren <tt>&amp;&amp;</tt> und <tt>||</tt> ist, dass
if (Zahl1 >= 0) then
+
sie die Auswertung abbrechen, sobald das Ergebnis feststeht. Die Ausdrücke werden
{
+
dabei immer von links nach rechts ausgewertet. Ein oft anzutreffendes Codestück sieht so aus, dabei sei <tt>p</tt> ein Zeiger auf einen <tt>int</tt>:
      Zahl2=Zahl2+1;
+
      Zahl1=Zahl2;
+
}
+
</pre>
+
  
===Schleifen===
+
'''Beispiel:'''
Um Anweisungen mehrmals hintereinander ausführen, benötigt man Scheifen. Diese führen Anweisunge aus, bis oder solange Bedingungen erfüllt sind.  
+
  if (p && *p == 5)
 +
  {
 +
    {{comment|mach was}}
 +
  }
 +
Zuerst wird in der Bedinung geprüft, ob Zeiger&nbsp;<tt>p</tt> einen Wert ungleich Null hat, also ob er überhaupt einen gültigen Wert enthält. Es ist eine weit verbreitete Konvention in C, daß Zeiger, die keinen gültigen Wert haben, die Adresse&nbsp;0 enthalten. '''Nur dann, wenn ein Zeiger nicht ein Null-Pointer ist, darf überhaupt ein Zugriff über ihn erfolgen!'''
  
====WHILE-Schleife====
+
==Vergleich von Variablen==
Syntax: while (Bedignung) Anweisung;
+
Skalare Variablen (also ganze Zahlen, Gleitkommazahlen, Zeiger) können miteinander verglichen werden. Dazu gibt es die folgenden Operatoren in C:
Die WHILE-Schleife wird solange durchlaufen, bis die Bedingung FALSE ist (deutsch: "WÄHREND Bedingung TUE Anweisungen"). Die Schleife wird mindestens Null mal durchlaufen. Bei mehreren Anweisungen benötigen Sie einen Block!
+
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!| Operator || Bedeutung
 +
|-
 +
| <tt>==</tt> || ist gleich
 +
|-
 +
| <tt>!=</tt> || ist nicht gleich
 +
|-
 +
| <tt>&lt;</tt>  || ist kleiner
 +
|-
 +
| <tt>&lt;=</tt> || ist kleiner oder gleich
 +
|-
 +
| <tt>&gt;</tt>  || ist größer
 +
|-
 +
| <tt>&gt;=</tt> || ist größer oder gleich
 +
|}
 +
</center>
 +
Das Ergebnis der Auswertung ist eine ganze Zahl. Ist die Bedingung erfüllt, dann ist der Wert ungleich&nbsp;0. Ist die Bedingung nicht erfüllt, dann ist ihr Wert gleich&nbsp;0.
 +
Meistens wird man diese Operatoren in <tt>if</tt>-Konstrukten finden wie zum Beispiel
 +
if (x >= 10)
 +
    x = 10;
 +
oder in Abbruchbedingungen von Schleifen, wie sie weiter unten erklärt werden.
  
<pre>
+
Es ist auch möglich, das Ergebnis der Auswertung in einer <tt>int</tt>-Variablen zu speichern:
Zahl1=0;
+
int i;
while (Zahl1<3)
+
int z1, z2;
{
+
  Zahl1=Zahl1+1;
+
z1 = 5;
  Zahl2=Zahl2*2;
+
z2 = 100;
}
+
i = z1 <= z2;  {{comment|Ein Vergleich. i wird "wahr", da z1 kleinergleich z2 ist}}
</pre>
+
  
In diesem Beispiel wird die Schleife 3 mal durchlaufen. Zu Beginn des 4. Durchlaufes ist die Bedingung FALSE (Zahl1 ist dann nicht mehr kleiner, sondern gleich 3!), also wird mit dem Befehl nach der Schleife fortgesetzt.
+
Die Variable <tt>i</tt> ist ungleich&nbsp;0 ("wahr"), wenn <tt>z1</tt> kleiner oder gleich <tt>z2</tt> ist. Ist <tt>z1</tt> jedoch größer als <tt>z2</tt>, dann ist <tt>i</tt> gleich&nbsp;0 ("unwahr").
  
====FOR-SCHLEIFE====
+
==Arithmetische Operatoren==
Syntax: for (Zuweisung;Bedingung;Inkrement) Anwseisung;
+
<center>
Die FOR-Schleife ist eine Zählschleife. Die angegebene Variable wird am Anfang auf einen Wert gesetzt. Danach wird sie nach jedem Durchlauf verändert (meist um 1 erhöht). Hat sie einen bestimmten Wert erreicht, wird die Schleife beendet. Innerhalb des Anweisungsblockes können Sie auf die Variable zugreifen, d.h. Sie können sie auch verändern. Das ist jedoch nicht ratsam. Wollen Sie mehrere Anweisungen ausführen, benötigen Sie eine Block
+
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>a + b</tt> || Summe (Addition)
 +
|-
 +
|<tt>a - b</tt> || Differenz (Subtraktion)
 +
|-
 +
|<tt>a * b</tt> || Produkt (Multiplikation)
 +
|-
 +
|<tt>a / b</tt> || Quotient (Division, evtl. mit Rest)
 +
|-
 +
|<tt>a % b</tt> || Rest bei Division (Modulo)
 +
|-
 +
|<tt>-a</tt> || Vorzeichenumkehr (Zweierkomplement)
 +
|}
 +
</center>
 +
==Bit-Operatoren==
 +
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>a & b</tt> || bitweise und (and)
 +
|-
 +
|<tt>a <nowiki>|</nowiki> b</tt> || bitweise oder (or)
 +
|-
 +
|<tt>a ^ b</tt> || bitweise exclusiv-oder (xor, exor)
 +
|-
 +
|<tt>~a</tt> || jedes Bit in <tt>a</tt> invertieren (not, Einerkomplement)
 +
|}
 +
</center>
 +
==Index-Operator bei Arrays==
  
Die obenstehende Syntax wird Pascal- oder Basic-Kenner verwundern. Aber das "neue" System ist schnell erklärt: Bei "Zuweisung" wird der Zählvariable ein Wert zugewiesen (entspricht Anfangswert bei Pascal). Die gesammte Schleife wird solange durchgeführt, bis Bedingung FALSE ist (d.h. Sie brauchen garnicht die Zählvariable abfragen, Sie können hier auch jede andere Variable auswerten). Inkrement letztendlich wird nach jedem Schleifendurchlauf ausgeführt. Hier kann man z.B. den Zähler erhöhen.
+
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>a[b]</tt> || das (b+1)ste Element des Feldes a
 +
|}
 +
</center>
  
Die Theorie ist schwer, die Praxis nicht so sehr...
+
Folgendes gilt es bei der Verwendung des Indexoperators zu beachten:
  
<pre>
+
# <tt>a</tt> muss ein [[C-Tutorial#Felder | Feld]] oder [[C-Tutorial#Zeiger | Zeiger]] sein
int Zaeler;
+
# <tt>b</tt> muss ein Integer sein oder ein Datentyp, der sich in einen int umwandeln läßt (z.B. char)
for (Zaeler=1; Zaeler<=10; Zaeler++)  
+
# Es wird nicht geprüft, ob der Index <tt>b</tt> im Feld <tt>a</tt> gültig ist!
{
+
# Der erste Index eines Feldes ist immer 0. Daher ''(b+1)stes Element'' in der Beschreibung
  Zahl2=Zahl2+Zaeler;
+
}
+
</pre>
+
  
In diesem Beispiel wird Zahl2 in jedem Schleifendurchlauf um die Zählvariable Zaeler erhöht. Da Zaeler nacheinander die Werte von 1 bis 10 hat, ist in Zahl2 nach der Schleife die Summe der Zahlen 1 bis 10 gespeichert.
+
==Komponenten-Auswahl bei Structs und Unions==
  
'''Erklärung:'''
+
<center>
Zaeler=1 bedeutet, dass der Variablen Zaeler vor dem 1. Schleifendurchlauf der Wert 1 zugewiesen wird.
+
{| {{Blauetabelle}}
Zaeler<=10 ist die Abbruchbedingung; ist sie FALSE, wird die Schleife beendet.  
+
|- {{Hintergrund1}}
Zaeler++ bedeutet, dass Zaeler nach jedem Durchlauf um 1 erhöht wird.
+
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>a.b</tt> || Element b der [[C-Tutorial#Strukturen | Struktur]] oder des Unions a
 +
|}
 +
</center>
  
 +
==Adress-Operator und Dereferenzierung==
  
==Ein- und Ausgabe==
+
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Ausdruck ||Beschreibung
 +
|-
 +
|<tt>&amp;a</tt> || Speicheradresse der Variablen <tt>a</tt>
 +
|-
 +
||<tt>*a</tt> || Wert, der an der Adresse <tt>a</tt> steht
 +
|-
 +
||<tt>a-&gt;b</tt> || Wert des Elements <tt>b</tt> der Struktur, deren Adresse in <tt>a</tt> steht
 +
|}
 +
</center>
  
===Bildschirm-Ausgabe===
+
Der Adressoperator & kann auf Variablen angewendet werden und
Bisher war das Tutorial, trotz aller Beispiele, reine Theorie. Sie konnten zwar Programme schreiben, aber die Funktion nicht testen. Damit ist jetzt Schluß. Hier lernen Sie, wie Sie etwas am Bildschirm ausgeben.
+
gibt die Startadresse der Variablen im Speicher zurück.
Der dazu notwendige Befehl ist printf (das f ist KEIN Fehler!). Diese Anweisung gibt die ihr übergebenen Parameter auf dem Bildschirm aus (außer, Sie haben unter DOS die Standard-Ausgabe auf ein anderes Gerät, z.B. den Drucker umgeleitet, aber das ist eine andere Geschichte ...). Sie kann beliebig viele Parameter übernehmen. Es müssen jedoch Standard-Datentypen (z.B. int, char, float, double) sein!
+
  
<pre>
+
Handelt es sich bei einer Variable um einen [[C-Tutorial#Zeiger | Zeiger]], so enthält
#include <conio.h>
+
sie eine Speicheradresse. Um an den '''Wert''' zu gelangen, der
#include <stdio.h>
+
an dieser Adresse steht, wird der Operator * vorangestellt.
  
void main(void)
+
'''Beispiel:'''
{
+
    int Zahl1=12;
+
    char Zeichen1="A";
+
   
+
    printf("Das ist Text, und er wird als solcher ausgegeben. \n");
+
    printf("Der Wert der Variablen Zahl1 ist: %d \n",Zahl1);
+
    printf("Der Wert der Variablen Zeichen1 ist: %c \n",Zeichen1);
+
    printf("Der Wert der Variablen Zeichen1 ist: %d \n",Zeichen1);
+
}
+
</pre>
+
  
 +
{{comment|x ist eine Integervariable und hat den Wert 5}}
 +
int x = 5;
 +
   
 +
{{comment|z ist ein Zeiger auf eine Integer-Variable und enthaelt somit}}
 +
{{comment|die Speicheradresse einer Integer-Variablen}}
 +
int *z;     
 +
 
 +
{{comment|Verwendung des Adress-Operators: weist an z die Adresse von x zu}}
 +
z = &x;
 +
 +
{{comment|Verwendung der Dereferenzierung}}
 +
{{comment|erhoehe den Wert, der bei Adresse z steht, um eins}}
 +
*z = *z + 1;
 +
 +
{{comment|da z auf x zeigt, hat x jetzt den Wert 6}}
  
Hinweis:
+
Da in C häufig Zeiger auf [[C-Tutorial#Strukturen | Strukturen]] verwendet werden, ist für den Zugriff auf Struktur- und Union-Elemente eine abkürzende Schreibweise möglich:
Dass das Programm nach dem Compilieren und Ausführen sofort wieder beendet wird, ist nicht Ihr Fehler. Drücken Sie [ALT] + [F5], um zum Ausgabebildschirm zu kommen!
+
Erklärung:  
+
  
Der 1. printf Befehl gibt Text aus. Das Zeichen am Ende (\n) bedeutet "New Line", es bewegt den Cursor an den Anfang der nächsten Zeile (In Pascal verwendet man dafür bekanntlich Writeln).
+
Statt
Der 2. printf-Befehl gibt auch Text aus, am Ende befindet sich wieder das \n, um einen Zeilenvorschub zu erreichen. Aber was bedeutet %d ? Ganz einfach, der Compiler ersetzt %d durch den 1. Parameter, der (hoffentlich!!!) nach dem Text angegeben wird. In diesem Fall wird %d durch den Wert der Variablen Zahl1 ersetzt. Das d im %d bedeutet "Dezimalzahl", der Computer gibt also eine Zahl aus.
+
  (*strukturZeiger).element
In der 3. Ausgabe wird ein Zeichen ausgegeben. Diesmal bedeutet %c "char" (Zeichen). Es wird also %c durch "A" ersetzt.
+
kann geschrieben werden
Die letzte Ausgabe verhält sich merkwürdig - es wird doch tatsächlich eine Zahl ausgegeben, obwohl der Parameter ein Zeichen (Char) ist. ABER:
+
  strukturZeiger->element
%d im Text bedeutet, dass eine Zahl auszugeben ist.
+
Beide Schreibweisen sind absolut gleichbedeutend, die Klammern bei der ersteren sind notwendig.
ein Char kann ja auch als Zahl interpretiert werden.
+
Es wird also 65 (der ASCII-Wert von "A") ausgegeben. Das ist ein typisches Beispiel für das mögliche unterschiedliche Interpretieren einer char Variablen!
+
Bildschirm löschen
+
Oft sind von anderen Programmen noch "Rückstände" vorhanden. Nicht nur um die zu beseitigen, brauchen Sie einen "Bildschirm-Lösch-Befehl". Dieser ist in conio.h definiert und nennt sich "clrscr" ("clear screen", deut: "lösche Bildschirm").  
+
  
<pre>
+
'''Achtung!'''
#include <conio.h>
+
:Bei der Dereferenzierung durch <tt>*</tt> findet keine Prüfung statt, ob der Zeiger auch auf eine gültige Speicheradresse verweist. Folgendes Codestück führt zum Absturz oder zu einer Änderung '''irgendeiner''' Speicherstelle!
#include <stdio.h>
+
  
void main(void)
+
int *z; {{comment|z ist ein Zeiger auf einen int}}
{
+
  clrscr();
+
{{comment|An dieser Stelle ist z immer noch keine Speicheradresse zugewiesen.}}
  ....
+
{{comment|z enthaelt irgendeine ungueltige Adresse!!}}
}
+
</pre>
+
{{comment|"Erhoehe einen Integer _irgendwo_ im Speicher um 1" -> CRASH !!!}}
 +
*z = *z + 1;
  
 +
Viele C-Compiler erzeugen in der Standardeinstellung für das obige Codestück ''keine Warnung''!
  
===Tastatur-Eingabe===
+
==Cast-Operator==
Um ein "gscheites" Programm schreiben zu können, muß man wissen, wie der Benutzer über die Tastatur Befehle eingeben kann. Die dafür notwendigen Funktionen stelle ich in diesem Kapitel vor.
+
Der wichtigste Befehl ist "scanf". Er liest Daten von der Tastatur. Die Syntax entspricht derer von printf, nur daß man außer den %c und %d keine weiteren Angaben im "Text" machen darf:
+
  
 +
Der Cast Operator dient dazu, den Datentyp eines Wertes zu ändern. Dafür wird einfach der neue Datentyp in Klammern vor den Wert geschrieben.
 +
 +
Um zum Beispiel aus einem Float ein Integer zu machen:
 +
var  = (int) 5.60;
 +
Dabei wird der Wert aber auch gerundet, und es findet somit ein Informationsverlust statt. (Genau genommen wird nicht gerundet, sondern die Nachkommastellen werden nur abgeschnitten; wenn man runden möchte, empfiehlt es sich, zuerst 0.5 zu addieren und dann zu casten.)
 +
 +
Ein weiteres Beispiel ist das Umwandeln einer ganzen Zahl in eine Adresse:
 +
int * addr;
 +
addr = (int*) 0x1234;
 +
Damit ist <tt>addr</tt> ein Zeiger auf einen <tt>int</tt> an Adresse 0x1234.
 +
 +
{{FarbigerRahmen |
 +
'''Achtung!'''
 +
 +
Der Cast-Operator selbst führt ''keine Konvertierung'' von Darstellungen durch, etwa die Umwandlung der ganzen Zahl 123 ein den String <tt>"123"</tt>, der diese Zahl darstellt!
 
<pre>
 
<pre>
printf("Bitte geben Sie eine Zahl ein: ");
+
  #include <stdio.h>
scanf("%d",&Zahl1);
+
  int main(int argc, char ** argv)
printf("Geben Sie einen Zeichen ein: ");
+
  {
scanf("%c",&Zeichen1);
+
        char text[] = "5.6";
 +
        int zahl = (int) text;
 +
 
 +
        printf("%d\n", zahl);
 +
 
 +
        return 0;
 +
  }
 
</pre>
 
</pre>
 +
Ausgegeben wird weder 5 noch 6 sondern die Anfangsadresse des Strings <tt>"5.6"</tt>.
 +
}}
  
Das Programm gibt eine Eingabeaufforderung aus. Dann erwartet es vom Benutzer, daß er eine Zahl eingibt, die mit [ENTER] bestätigt wird. Diese Zahl wird in Zahl1 abgespeichert. Danach erfolgt wiederum eine Aufforderung zur Eingabe, diesmal eines einzelnen Zeichens. Dieses kann man nun eingeben und ebenfalls mit [ENTER] bestätigen.
+
==Komma-Operator==
 +
Mit einem <tt>,</tt> können mehrere Ausdrücke nacheinander ausgewertet werden.
 +
Die Auswertung erfolgt von links nach rechts.
  
Macht man keine dem Datentyp der erwarteten Variable entsprechende Eingabe, dann bricht das Programm mit einer Fehlermeldung ab (wenn man z.B. "1_T2" eingibt, wenn eine Zahl erwartet wird)!
+
Solche Konstrukte sieht man manchmal in Abfragen wie
 +
FILE  *file;
 +
if (file = fopen ("foo.exe", "r"), file != NULL)
 +
was erst an <tt>file</tt> einen Wert zuweist und den <tt>if</tt>-Block nur betritt,
 +
wenn <tt>file</tt> nicht der Nullpointer ist.
  
Das & vor den Parametern ist notwendig. Warum, das erfahren Sie im Kapitel "Unterprogramme". Für die Profis eine Kurz-Erklärung: Das Unterprogramm scanf bekommt zwar einen Wert übergeben, kann aber keinen zurückliefern ("call by value"). Daher wird kein Wert, sondern ein Zeiger auf eine Variable übergeben. Mit dem & Zeichen bekommen Sie die Adresse einer Variablen ("call by reference").
+
Bequem kann das auch in einer [[#for-Schleife|for-Schleife]] sein, wenn man zwei (oder mehr) Laufvariablen hat oder so:
 +
for (i=0, j=0; i < 10; i++, j += 2)
 +
    &middot;&middot;&middot;
  
====getch====
+
==Zuweisungen und Operatoren mit Nebeneffekt==
getch ist eine Funktion (Siehe Kapitel Unterprogramme), die ein char-Zeichen von der Tastatur einliest. Dazu geben Sie im Programm einfach folgede Zeile an:
+
===Zuweisung===
 +
=== ++ und -- ===
  
<pre>
+
<tt>++</tt> und <tt>--</tt> stellen einfachere Schreibweisen dar zum Addieren bzw. Subtrahieren von&nbsp;1.
charVariable=getch();
+
</pre>
+
  
Diese Zeile erwartet die Eingabe eines CHAR-Zeichens. Im Gegensatz zu scanf muß man nach der Eingabe des Zeichens jedoch nicht [ENTER] drücken, um die Eingabe zu bestätigen. In der Variable charVariable wird das erste Zeichen gespeichert, das auf der Tastatur eingegeben wird.
+
'''<tt>++</tt> (Inkrementieren)'''
Eine weiter Möglichkeit der Anwendung von getch ist es, wenn Sie einfach "getch();" als Befehl eingeben. Dann wartet der Computer, bis Sie eine Taste drücken, speichert diese aber nicht ab.
+
int foo = 1;
 +
foo++;
 +
{{comment|entspricht}}
 +
foo = foo + 1;
 +
{{comment|jetzt ist foo &#61; 3}}
  
====kbhit====
+
'''<tt>--</tt> (Dekrementieren)'''
Dies ist ebenfalls eine Funktion, die eine boolschen Ausdruck zurück liefert. Sie gibt Bescheid darüber, ob eine Taste gedrückt wurde, und mit scanf oder getch abgerufen werden kann (zum Verständnis: auf der Tastatur eingegebene Zeichen werden meist nicht direkt vom Programm verarbeitet, sondern erst vom Betriebssystem bzw. dem BIOS des Computers gespeichert. Wenn das Programm nun eine Eingabe erwartet, werden die gespeicherten Zeichen "verwendet". Dies lauft jedoch so schnell ab, daß der Benutzer davon nichts merkt). Ist diese Variable TRUE, dann ist ein Zeichen abfragebereit, sonst nicht:
+
int foo = 1;
 +
foo--;
 +
{{comment|entspricht}}
 +
foo = foo - 1;
 +
{{comment|jetzt ist foo &#61; -1}}
  
<pre>
+
Die beiden Operatoren können sowohl in der Präfix-Schreibweise (vor der Variablen) als auch als Postfix-Schreibweise (hinter der Variablen) notiert werden.
while ( !kbhit() )
+
Der Unterschied liegt darin, dass beim Präfix der Wert zuerst neu berechnet wird und die Variable dann verwendet wird. Beim Postfix wird die Variable zuerst verwendet und erst nach Auswertung des Ausdrucks, in dem sie enthalten ist, neu berechnet.
{
+
  Zahl1=Zahl1+1;
+
  printf("%d\n",Zahl1);
+
}
+
</pre>
+
  
Dieser Programmteil erhöht solange Zahl1 und gibt diese aus, bis der Benutzer eine Taste drückt.
+
'''Beispiel'''
 +
int ausgabe1, ausgabe2, var1 = 10, var2 = 10;
 +
ausgabe1 = 3 * ++var1; {{comment|ausgabe1 &#61; 33; var1 &#61; 11;}}
 +
ausgabe2 = 3 * var2++; {{comment|ausgabe2 &#61; 30; var2 &#61; 11;}}
  
 +
Für Zeiger arbeiten diese Operatoren etwas anders, siehe dazu [[#Zeiger-Arithmetik|Zeiger-Arithmetik]].
  
 +
===Bedingter Ausdruck===
 +
({{Bedingung}}) ? {{Ausdruck|1}} : {{Ausdruck|2}}
 +
Wenn <tt>Bedingung</tt> erfüllt ist, dann wertet dieser Ausdruck aus zu <tt>Ausdruck1</tt>. Ist er nicht erfüllt, dann wertet er aus zu <tt>Ausdruck2</tt>.
  
==Allgemeines zu Unterprogrammen==
+
'''Beispiel:'''
Stellen Sie sich vor, Sie haben einen eine Code-Folge, die mehrmals im Programm vorkommt, z.B. eine mathematische Formel. Anstatt dieses Codestück mehrmals zu schreiben (was Zeit beim Erstellen und Speicherplatz im ausführbaren Programm kostet), können Sie den Abschnitt in ein sogenanntes "Unterprogramm" schreiben. Dieses Unterprogramm können Sie dann von jeder Stelle ihre Hauptprogrammes aus aufrufen.
+
x = (x >= 3) ? 0 : x+1;
Anders als in anderen Programmiersprachen gibt es in C keine generelle Unterscheidung zwischen Funktionen und Prozeduren. Trotzdem möchte ich kurz darauf eingehen:
+
Startet man <tt>x</tt> mit dem Wert 0, dann nimmt es bei mehrfacher Anwendung dieser Zeile (z.B. in einer Schleife) nacheinander die folgende Werte an:
 +
:<tt>1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, ...</tt>
  
===Funktionen===
+
==Reihenfolge der Auswertung==
Syntax: Rückgabe-Typ Name(Parameterliste);
+
 
 +
Wie auch in der Mathematik gibt es auch in C genaue Regeln über die Abarbeitungsreihenfolge (precedence)
 +
der Operatoren. Dass sich alle C-Compiler genau an diesen ANSI-Vorschlag halten, ist leider nicht sicher.
 +
Sicher jedoch ist, dass nicht jeder Programmierer diese Regel jederzeit im Kopf hat.
 +
Daher ist es sinnvoll, Ausdrücke durch runde Klammern eindeutig zu kennzeichnen.
 +
Nebenbei stören sich Compiler nicht an überflüssigen Klammerpaaren.
 +
 
 +
<center>
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund1}}
 +
!|Priorität ||Operator||Assoziativität
 +
|-
 +
 
 +
|<tt>15</tt> ||<tt> ( ) [ ] -> . </tt>                      ||von links nach rechts
 +
|-
 +
|<tt>14</tt> ||<tt>! ~ ++ -- + - (TYP) * & sizeof </tt>    ||von rechts nach links
 +
|-
 +
|<tt>13</tt> ||<tt> * / % (Rechenoperationen) </tt>        ||von links nach rechts
 +
|-
 +
|<tt>12</tt> ||<tt> + - (binär) </tt>                      ||von links nach rechts
 +
|-
 +
|<tt>11</tt> ||<tt> << >> </tt>                            ||von links nach rechts
 +
|-
 +
|<tt>10</tt> ||<tt> < <= > >= </tt>                        ||von links nach rechts
 +
|-
 +
|<tt> 9</tt> ||<tt> == != </tt>                            ||von links nach rechts
 +
|-
 +
|<tt> 8</tt> ||<tt> & (bit-AND-Operator)</tt>              ||von links nach rechts
 +
|-
 +
|<tt> 7</tt> ||<tt> ^ (bit-XOR-Operator)</tt>              ||von links nach rechts
 +
|-
 +
|<tt> 6</tt> ||<tt> <nowiki>|</nowiki> (bit-OR-Operator)</tt>  ||von links nach rechts
 +
|-
 +
|<tt> 5</tt> ||<tt> && </tt>                                ||von links nach rechts
 +
|-
 +
|<tt> 4</tt> ||<tt> <nowiki>||</nowiki> </tt>              ||von links nach rechts
 +
|-
 +
|<tt> 3</tt> ||<tt>? : </tt>                              ||von rechts nach links
 +
|-
 +
|<tt> 2</tt> ||<tt> = += -= /= *= %= >>= <<= &= <nowiki>|</nowiki>= ^= </tt> ||von rechts nach links
 +
|-
 +
|<tt> 1</tt> ||<tt> , (Sequenz-Operator) </tt>              ||von links nach rechts
 +
|-
 +
 
 +
|}
 +
</center>
 +
 
 +
Die Reihenfolge der Auswertung von Funktionsargumenten ist in der ANSI-Spezifikation nicht angegeben und daher compilerabhängig. Von Konstrukten wie
 +
{
 +
    int i=0;
 +
    func (i++, i++);
 +
}
 +
ist also dringend abzuraten!
 +
 
 +
=Kontrollanweisungen=
 +
 
 +
Eine Kontrollanweisung ist eine Anweisung, die Einfluss auf den Programmfluss hat. Normalerweise werden Anweisungen so ausgeführt, wie sie in der Quelldatei stehen: Von links nach rechts (falls mehrere Anweisungen in einer Zeile stehen sollten, wovon i.A. abzuraten ist) und von oben nach unten. Mit einer Kontrollanweisung kann dieser lineare Programmfluss durchbrochen werden: Die Codeausführung kann abhängig von einer Bedingung gemacht werden (<tt>if</tt>), kann wiederholt werden (Schleife) oder an einer anderen Stelle der Funktion fortgesetzt werden (<tt>goto</tt>).
 +
 
 +
==if-Anweisung==
 +
Mit Hilfe des if-Befehls kann man Codeteile abhängig davon einer Bedingung ausführen lassen:
 +
 
 +
'''Syntax:'''
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
oder mit else-Teil
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
else
 +
    {{Anweisung}}
 +
 
 +
'''Beispiel:'''
 +
if (x > 100)
 +
{
 +
    {{comment|falls x > 100 ist: Fehlerausgabe}}
 +
    printf ("x = %d ist zu gross fuer die Berechnung!\n", x);
 +
}
 +
else
 +
{
 +
    {{comment|falls x <&#61; 100 ist: Berechne Summe der Zahlen 1...x}}
 +
    {{comment|Die lokale Variable x2 lebt nur innerhalb dieses alse-Blocks}}
 +
    int x2 = x;
 +
 +
    for (x = 0; x2 > 0; x2--)
 +
      x += x2;
 +
}
 +
 
 +
Wenn die Bedingung wahr ist (<tt>x > 100</tt>), dann wird eine Meldung ausgegeben; danach ist die if-Anweisung beendet. Der else-Block wird also nicht ausgeführt.
 +
 
 +
Ist die Bedingung nicht erfüllt (<tt>x &le; 100</tt>), dann wird gleich zum else-Teil gesprungen, und nach dessen Ausführung der if-Befehl beendet.
 +
 
 +
{{FarbigerRahmen|
 +
Ein häufiger Fehler ist es, statt <tt>if (a &#61;&#61; 23)</tt> etwas wie <tt>if (a &#61; 23)</tt> zu schreiben.
 +
Dann wird allerdings nicht geprüft, ob die Variable&nbsp;<tt>a</tt> gleich 23 ist, sondern der Variablen&nbsp;<tt>a</tt> wird der Wert 23 zugewiesen. Der Ausdruck <tt>a &#61; 23</tt> hat den Wert&nbsp;23 und ist damit immer "wahr"! Daher ist diese if-Bedingung immer erfüllt!
 +
 
 +
Die Syntax hierbei ist allerdings korrekt, der Compiler wird also keinen Fehler ausspucken sondern bestenfalls eine Warnung. Damit ist dieser Fehler sehr schwer zu finden. Abhilfe schafft die Schreibweise <tt>if (23 &#61;&#61; a)</tt>. Wenn man dort anstatt des Vergleichsoperators '<tt>&#61;&#61;</tt>' den Zuweisungsoperator '<tt>&#61;</tt>' verwendet, spuckt der Compiler sehr wohl einen Fehler aus! Ist die Zuweisung jedoch erwünscht und eine Compiler-Warnung lästig, dann wählt man eine Schreibweise wie <tt>if ((a &#61; b))</tt> oder <tt>if (a &#61; b, a)</tt>.
 +
 
 +
Ein weiterer häufiger Fehler ist zu schreiben <tt>if (Bedingung);</tt> Richtig muss es heissen "<tt>if(Bedingung)</tt>" Das Semikolon im ersten Fall ist eine leere Anweisung, die im if-Falle ausgeführt wird &ndash; sie bleibt also ohne Resultat. Auch hier liegt kein Syntaxfehler vor und der Compiler schweigt; ein auf das Semikolon folgende Anweisung die eigentlich zum <tt>if</tt> gehören soll wird immer ausgeführt, die sie nicht mehr zum <tt>if</tt> dazu gehört.
 +
}}
 +
Bei verschachtelten <tt>if</tt>-<tt>else</tt>-Konstrukten gehört ein <tt>else</tt> zu letzten "freien" <tt>if</tt>. Soll in einer <tt>if</tt>-<tt>if</tt>-<tt>else</tt>-Folge das <tt>else</tt> zum ersten <tt>if</tt> gehören, dann ist das so zu hinzuschreiben:
 +
if ({{Bedingung}})
 +
{
 +
    if ({{Bedingung}})
 +
      {{Anweisung}}
 +
}
 +
else
 +
    {{Anweisung}}
 +
Ohne die geschweiften Klammern um das zweite <tt>if</tt> gehörte das <tt>else</tt> dort hinzu.
 +
 
 +
==switch-Anweisung==
 +
'''Syntax:'''
 +
 
 +
switch ({{Ausdruck|}})
 +
{
 +
    case konstante1:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
        break;
 +
 +
    case konstante2:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
        break;
 +
         
 +
    {{comment|weitere case-Marken}}
 +
 +
    default:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
} {{comment|Ende von switch}}
 +
 
 +
Der Ausdruck muss ein skalarer Typ sein, er wird in die nächste ganze Zahl gewandelt und mit den Werten hinter den <tt>case</tt>-Marken verglichen. Bei einer Übereinstimmung werden alle Befehle ab dem zutreffenden <tt>case</tt> ausgeführt. Stimmt der Ausdruck mit keinem der Werte überein, so wird der <tt>default</tt>-Abschnitt ausgeführt falls vorhanden.
 +
 
 +
<b>Auch die Anweisungen der nachfolgenden <tt>case</tt>- und des <tt>default</tt>-Abschnitts werden ausgeführt, wenn die Anweisungen des <tt>case</tt>-Abschnitts nicht mit dem Befehl <tt>break;</tt> beendet werden!</b>
 +
 
 +
Es dürfen beliebig viele <tt>case</tt>-Abschnitte angegeben werden, pro Vergleichswert jedoch nur einer.
 +
Der <tt>default</tt>-Abschnitt ist optional. Die Reihenfolge, in der <tt>case</tt> und <tt>default</tt> angegeben werden, ist unerheblich.
 +
 
 +
==Schleifen==
 +
Um Anweisungen mehrmals hintereinander auszuführen, benötigt man Schleifen. Diese führen Anweisungen aus, bis oder solange Bedingungen erfüllt sind.<br>
 +
Wichtig ist also, ob die Bedingung '''vor''' oder '''nach''' den Schleifen-Anweisungen geprüft wird.
 +
 
 +
===while-Schleife===
 +
'''Syntax:'''
 +
while ({{Bedingung}})
 +
    {{Anweisung}}
 +
 
 +
Die while-Schleife wird solange durchlaufen, wie die Bedingung erfüllt ist. Die Schleife wird also unter Umständen garnicht durchlaufen. Die Anweisung kann natürlich auch ein Block sein, der aus mehreren Deklarationen und Anweisungen besteht.
 +
 
 +
int zahl1 = 0;
 +
int zahl2 = 1;
 +
 +
while (zahl1 < 3)
 +
{
 +
    zahl1 = zahl1 + 1;
 +
    zahl2 = zahl2 * 2;
 +
}
 +
 
 +
In diesem Beispiel wird die Schleife drei mal durchlaufen. Zu Beginn des vierten Durchlaufs ist die Bedingung nicht mehr erfüllt (<tt>zahl1</tt> ist dann nicht mehr kleiner, sondern gleich 3!), also wird mit dem Befehl nach der Schleife fortgesetzt.
 +
 
 +
===do-while-Schleife===
 +
'''Syntax:'''
 +
do
 +
    {{Anweisung}}
 +
while ({{Bedingung}});
 +
 
 +
Die do-while-Schleife wird auf jeden Fall einmal durchlaufen und dann solange wiederholt, wie die Bedingung erfüllt ist.
 +
int i = 2;
 +
 +
do
 +
{
 +
    i = i*i;  {{comment|i quadrieren}}
 +
    printf ("i = %d\n", i);
 +
}
 +
while (i < 20);
 +
 
 +
Die Schleife wird durchlaufen und wiederholt, solange <tt>i</tt> kleiner als 20 ist. Es werden also nacheinander die Werte 2, 4 und 16 ausgegeben. Nach der Schleife hat&nbsp;<tt>i</tt> den Wert 256.
 +
 
 +
===for-Schleife===
 +
'''Syntax:'''
 +
for ({{Ausdruck|1}}; {{Bedingung}}; {{Ausdruck|2}})
 +
    {{Anweisung}}
 +
 
 +
Bei den Ausdrücken wird es sich um einen Ausdrücke mit Nebeneffekt handeln wie etwa <tt>i=0</tt> oder <tt>i=i+2</tt>. Es werden folgende Aktionen ausgeführt:
 +
# <tt>Ausdruck1</tt> wird ausgewertet
 +
# <tt>Bedingung</tt> wird ausgewertet
 +
# falls die Bedingung wahr ist, dann führe <tt>Anweisung</tt> aus.
 +
# falls die Bedingung unwahr ist, dann sprinte zu 7 (Ende).
 +
# <tt>Ausdruck2</tt> wird ausgewertet
 +
# gehe zu 2
 +
# nächste Anweisung nach der for-Schleife
 +
 
 +
'''Beispiel:'''
 +
int lauf, summe;
 +
 +
for (lauf=1, summe=0; lauf <= 10; lauf += 2)
 +
{
 +
    summe += lauf;
 +
}
 +
 
 +
In diesem Beispiel ist <tt>Ausdruck1</tt> ein [[#Komma-Operator|Komma-Ausdruck]], der zwei Anweisungen kombiniert und daher sogar zwei Nebeneffente hat: er setzt <tt>lauf</tt> auf&nbsp;1 und <tt>summe</tt> auf&nbsp;0.
 +
 
 +
Das Äquivalent als while-Schleife:
 +
 
 +
int lauf  = 1;                {{comment|Anfangswerte}}
 +
int summe = 0;
 +
 +
while (lauf <= 10)            {{comment|Bedingung}}
 +
{
 +
    summe += lauf;
 +
    lauf  += 2;                {{comment|Inkrement}}
 +
}
 +
 
 +
In diesem Beispiel wird <tt>summe</tt> in jedem Schleifendurchlauf um die Laufvariable <tt>lauf</tt> erhöht. Da <tt>lauf</tt> nacheinander die ungeraden Werte von&nbsp;1 bis&nbsp;10 annimmt, ist in <tt>summe</tt> nach der Schleife die Summe der ungeraden Zahlen von&nbsp;1 bis kleinergleich 10 gespeichert, also der Wert&nbsp;25. <tt>lauf</tt> hat nach der Schleife den Wert&nbsp;11.
 +
 
 +
'''Erklärung:'''
 +
<tt>lauf = 1</tt> bedeutet, dass der Variablen <tt>lauf</tt> vor dem ersten Schleifendurchlauf der Wert&nbsp;1 zugewiesen wird.
 +
<tt>lauf <= 10</tt> ist die Schleifenbedingung; ist sie nicht erfüllt, wird die Schleife beendet.
 +
<tt>lauf += 2</tt> bedeutet, dass <tt>lauf</tt> nach jedem Durchlauf um&nbsp;2 erhöht wird.
 +
 
 +
===continue-Anweisung===
 +
Innerhalb einer Schleife darf die <tt>continue</tt>-Instruktion stehen. Sie bewirkt, daß die nachfolgenden Anweisungen übersprungen werden und mit dem nächsten Schleifendurchlauf fortgesetzt wird &ndash; vorausgesetzt die Schleifenbedingung ist noch erfüllt. Ein <tt>continue</tt> darf natürlich auch innerhalb eines <tt>if</tt> oder <tt>switch</tt> etc. stehen, wenn dieses innerhalb einer Schleife steht.
 +
 
 +
==break-Anweisung==
 +
Innerhalb einer Schleife oder eines <tt>switch</tt> darf die <tt>break</tt>-Instruktion stehen. Sie bewirkt, daß die Schleifen-/Switch-Anweisung sofort verlassen wird und das Programm dahinter weiter macht. Bei mehrfach geschachtelten Schleifen wird nur die innere verlassen. Ein <tt>break</tt> darf natürlich auch innerhalb eines <tt>if</tt> stehen, wenn dieses innerhalb einer Schleife/Switch-Anweisung steht.
 +
 
 +
==goto-Anweisung==
 +
 
 +
Innerhalb ein und derselben Funktion kann mit <tt>goto</tt> an eine andere Stelle gesprungen werden. Dazu gibt man hinter dem <tt>goto</tt> einen Bezeichner an, der dadurch als Label fungiert:
 +
 
 +
'''Syntax:'''
 +
goto {{Bezeichner}};
 +
Die Bezeichner selbst steht irgendwo in der Funktion und wird dadurch zur Sprungmarke (Label), daß er von einem Doppelpunkt (und mindestens einer C-Anweisung, die auch leer sein darf) gefolgt wird.
 +
 
 +
Das Beispiel durchsucht das 2-dimensionale <tt>int</tt>-Array <tt>feld</tt> mit den <tt>SIZE_X &times; SIZE_Y</tt> Werten nach dem Wert&nbsp;<tt>0</tt>. Wird er gefunden, dann wird die 2-fach geschachtelte Suchschleife verlassen.
 +
 
 +
'''Beispiel:'''
 +
int x, y;
 +
 +
for (x=0; x < SIZE_X; x++)
 +
    for (y=0; y < SIZE_Y; y++)
 +
      if (feld[x][y] == 0)
 +
          goto done;
 +
{{Label|done:}};
 +
 
 +
Der folgende Code hat die gleiche Funktion, arbeitet jedoch ohne <tt>goto</tt>:
 +
int x, y;
 +
int found = 0; {{comment|FALSE}}
 +
 +
for (x=0; x < SIZE_X && !found; x++)
 +
    for (y=0; y < SIZE_Y && !found; y++)
 +
      found = (0 == feld[x][y]);
 +
Der Nachteil der <tt>goto</tt>-losen Variante ist, daß man eine Variable, die merkt, ob das Suchziel gefunden wurde, mitschleppen und in ''jedem'' Schleifendurchlauf abtesten muss. Dies bedeutet einen höheren Programmier- und Laufzeitaufwand und ist nicht so klar formuliert wie das <tt>goto</tt>-Beispiel.
 +
 
 +
Gleichwohl sei angemerkt, daß die Verwendung von <tt>goto</tt> einem gewissen Dogmatismus unterliegt, der sich wie folgt subsummieren liesse:
 +
:''goto ist böse und sollte keinesfalls verwendet werden! Wer es dennoch tut, offenbart dadurch seinen schlechten Geschmach sowie mangelhafte C-Kenntnis.'' <div align="right">http://www.roboternetz.de/phpBB2/images/smiles/icon_wink.gif</div>
 +
 
 +
=Funktionen=
 +
Stell Dir vor, Du hast eine Code-Folge, die mehrmals im Programm vorkommt, z.B. eine mathematische Formel. Anstatt dieses Codestück mehrmals zu schreiben &ndash; was Dich Zeit beim Erstellen des Programms und Speicherplatz im ausführbaren Programm kostet &ndash; kannst Du den Code-Abschnitt in eine Funktion schreiben und diese von jeder Stelle des Programms aus verwenden. Die Hauptgründe, um Funktionen zu verwenden, sind:
 +
;Wiederverwendung von Code: Mehrfach verwendete Codestücke müssen nicht mehrfach implementiert werden. Oft unterscheiden sich die Codesequenzen nur in Kleinigkeiten, die man der Funktion über Parameter mitteilen kann.
 +
;Übersichtlichkeit: Ein gut gegliedertes C-Programm implementiert klar umrissene Aufgaben in einer Funktion, auch wenn diese Funktion nur einmal im Code aufgerufen wird! Dadurch bleibt der Code um die Aufrufstelle besser verständlich, und man kann auf verschiedenen "Ebenen" denken. Eine Funktion wie "Datei öffnen" kann recht komplex sein. Auf höherer Ebene interessieren die Innereien nicht mehr, man möchte sich um andere Dinge kümmern und will den Code an der Stelle garnicht sehen...
 +
;Rekursive Funktionen: Eine Funktion kann sich auch selbst aufrufen. In dem Falle nennt man die Funktion ''rekursiv''. Zwar lässt sich das, was eine rekursive Funktion tut, auch mit anderen Mitteln formulieren, die keine rekursiven Funktionen brauchen, aber oft ist der rekursive Weg knackiger und klarer formulierbar als eine nicht-rekursiven Ansatz, auch wenn es etwas mehr Resourcen verbraucht.
 +
;Modulare Programmierung: Funktionen können anhand ihres Aufgabenbereichs auf verschiedene C-Quellen &ndash; sogenannte ''Module'' &ndash; verteilt werden. Funktionen, die etwas mit dem USB-Bus anstellen, werden in einem anderen Modul sein als mathematische Funktionen. Dies erhöht die Übersichtlichkeit und vereinfacht die Entwicklung im Team.
 +
;Bibliotheken: Standard-Funktionen wie das hier oft auftauchende <tt>printf</tt> sind in Bibliotheken gespeichert. Wenn das eigene Programm übersetzt wird, dann müssen nicht mehr alle Standard-Funktionen übersetzt werden, sondern werden nur noch aus der Bibliothek gelesen und ihr Code zum Programm dazugelinkt. Die Bibliotheks-Funktionen wurden schon zu einem früheren Zeitpunkt compiliert und liegen in dieser compilerten Form in der Bibliothek. Das spart mächtig Entwicklungszeit. Man kann auch selbst solche Bibliotheken erstellen und in diversen Projekten wiederverwenden.
 +
;Generische Programmierung: In C ist es möglich, einer Funktion eine andere Funktion zu übergeben. (Damit ist nicht gemeint, ihr deren ''Rückgabewert'' zu übergeben (was auch ginge), sondern ''die Funktion selbst'' wird als Parameter übergeben und kann aufgerufen werden.) Ein typisches Beispiel dafür sind Sortieralgorithmen. Einem Sortieralgorithmus kann es egal sein, ''was'' er sortiert. Er muss lediglich wissen, ''wie'' er das Zeug zu sortieren hat: aufsteigend, absteigend, als Zahl, in lexikographischer Ordnung, nach der Quersumme, Körper nach Oberfläche, Durchmesser, Gewicht oder Volumen... Diese Vergleichsfunktion, die für zwei Objekte entscheidet, welches davon "kleiner" ist, kann man dem Sortierer übergeben. Will er zwei Werte vergleichen, dann muss er nur die Vergleichsfunktion aufrufen, ohne zu wissen, was diese tut. Damit kann der Sortieralgorithmus unanhängig von den Objekten gehalten werden, mit denen er hantieren soll.
 +
 
 +
 
 +
 
 +
==Definition==
 +
 
 +
In der ''Definition'' der Funktion wird gesagt, welche Werte sie liefern kann, wie sie heisst (Bezeichner) und wieviele und welche Parameter sie hat. Danach folgt ihre Implementierung:
 +
 
 +
'''Syntax:'''
 +
{{Type}} {{Bezeichner}} ({{Parameterliste}})
 +
{
 +
    {{Deklaration}}
 +
    {{Deklaration}}
 +
    ...
 +
 +
    {{Anweisung}}
 +
    {{Anweisung}}
 +
    ...
 +
}
 +
Für Funktionen, die keinen Wert zurückliefern, gibt es den speziellen Typ <tt>void</tt>, der besagt, daß die Funktion nichts zurückgibt. Die einfachste denkbare Funktion ist eine solch void-Funktion. Sie bekommt keine Parameter, gibt nicht zurück und ihr Body ist leer:
 +
void dummy()
 +
{
 +
}
 +
 
 +
==return-Anweisung==
 +
An jeder Stelle des Programmflusses einer Funktion kann diese mit <tt>return</tt> '''beendet''' werden.
 +
 
 +
'''bei void-Funktionen:'''
 +
return;
 +
'''Funktionen mit Rückgabe-Wert:'''
 +
return {{Ausdruck|}};
 +
Die zweite Variante gibt an, welcher Wert zurückgegeben wird.
 +
int main (int argc, char * argv[])
 +
{
 +
    if (argc < 2)
 +
      return -1;
 +
 +
    return 0;
 +
}
 +
Falls die letzte Anweisung einer void-Funktion ein <tt>return</tt> ist, kann es auch weggelassen werden wie oben bei der Funktion <tt>dummy</tt>.
 +
 
 +
==Aufruf==
 +
Um die Funktion aufzurufen gibt man ihren Namen an, gefolgt von den durch Komma getrennten Argumenten in runden Klammern wie im Beispiel unten das
 +
quadrat (5)
 +
Da <tt>quadrat</tt> einen Wert liefert, kann man damit weiter rechnen wie mit einem normalen Ausdruck:
 +
if (quadrat (a) + quadrat (b) == quadrat (c))
 +
    c = quadrat (quadrat (a)); {{comment|c &#61; a hoch 4}}
 +
 
 +
;Ein Hinweis am Rande: Der Name einer Funktion ohne die beiden runden Klammern ist der Pointer/Zeiger auf ihren Anfang. Damit kann ein Funktionsname überall dort verwendet werden, wo Pointer/Zeiger zulässig sind. Insbesondere kann er als Argument einer weiteren Funktion dienen. Siehe auch [[#Zeiger auf Funktionen|Zeiger auf Funktionen]].
 +
 
 +
==Rekursive Funktionen==
 +
Eine Funktion die sich selbst &ndash; möglicheweise auch über andere Zwischenfunktionen &ndash; wieder selbst aufruft, wird als ''rekursive Funktion'' bezeichnet. In der Definition ist nichts besonderes zu beachten. Ist die Verschachtelungstiefe im laufenden Programm zu tief, dann gibt das natürlich Probleme, aber das gilt bei tief verschachtelten 'normalen' Funktionen ebenso...
 +
 
 +
Das Beispiel berechnet den Größten Gemeinsamen Teiler zweier Zahlen <tt>a</tt> und <tt>b</tt>:
 +
int ggT (int a, int b)
 +
{
 +
    if (0 == a)
 +
        return b;
 +
 +
    return ggT (b % a, a);
 +
}
  
Funktionen sind Unterprogramme, die am Ende der Ausführung einen Wert zurückliefern, wie z.B. getch. Paradebeispiele für die Anwendung einer Funktion sind jedoch mathematische Formeln:
+
==Beispiel==
  
 +
Ein komplettes kleines Programm:
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
  
int hoch2(int param1)
+
int quadrat (int param1)
 
{
 
{
 
   int zahl;
 
   int zahl;
   zahl=Param1*Param1;
+
   zahl = param1 * param1;
 
   return zahl;
 
   return zahl;
 
}
 
}
  
void main(void)
+
int main ()
 
{
 
{
   int zahl1,ergebnis;
+
   int zahl, ergebnis;
 
    
 
    
   printf("Bitte Zahl eingeben: ");
+
   printf ("Bitte Zahl eingeben: ");
   scanf("%d",&zahl1);
+
   scanf ("%d", &zahl);
   ergebnis:=hoch2(zahl1);
+
 
   printf("%d hoch 2 = %d\n",zahl1,ergebnis);
+
   ergebnis = quadrat (zahl);
   printf("5 hoch 2 = %d\n",hoch2(5));
+
 
   getch();
+
   printf ("%d hoch 2 = %d\n", zahl, ergebnis);
 +
   printf ("%d hoch 2 = %d\n", 5, quadrat (5));
 +
    
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
  
'''Bemerkungen zum Programm:'''
+
Ein Unterprogramm kann an jeder beliebigen Stelle innerhalb eines Programmes stehen, aber nur ausserhalb von Blöcken. Geschachtelte Unterprogramme sind in Standard-C nicht möglich.  
Ein Unterprogramm kann an jeder beliebigen Stelle innerhalb eines Programmes stehen, aber nur außerhalb von Blöcken. Siehe auch Prototypen.
+
Geschachtelte-Unterprogramme wie z.B. in Pascal sind nicht möglich.
+
In der Deklaration der Funktion kommt zuerst der Rückgabe-Datentyp. Jede Funktion liefert einen Wert zurück, dieser Datentyp gibt den Typ der Varaiblen an. Bei z.B. getch ist das char. Danach folgt der Name der Funktion (case-sensitiv!). Danach kommt die Parameter-Liste. Dabei handelt es sich um eine Liste von Parametern, die jeweils durch Typ und Name angegeben werden, und eventuell mittels Beistrich voneinander getrennt werden. Am Ende ist kein Beistrich!
+
Im Block der Funktion können alle Anweisungen ausgeführt werden und Variablen deklariert werden.
+
Am Ende der Funktion muß diese ordnungsgemäß beendet werden. Dies geschieht mittels der Anweisung "return" und dem Rückgabewert.
+
Um eine Funktion aufzurufen (aus einem anderen Unterprogramm oder main), geben Sie folgendes an "Variable=Funktion(Parameter);". Bei Variable muß es sich jedoch nicht unbedingt um eine von Ihnen deklarierte Variable handeln. Es kann z.B. auch der Parameter für ein weiteres Unterprogramm sein (wie im Beispiel, letzte Zeile).
+
Die Einrückungen der Anweisungen innerhalb der Funktion nach rechts ist nicht notwendig, sie dient jedoch der Übersicht und ist sehr empfehlenswert.  
+
  
===Prozedur===
 
Syntax: void Name(Parameterliste);
 
  
Eine Prozedur ist nichts anderes als eine Funktion ohne Rückgabewert. Sie ist daher prinzipiell gleich aufgebaut wie eine Funktion, bis auf die folgenden Unterschiede:  
+
{{FarbigerRahmen |
 +
'''Merke:''' Auch wenn eine Funktion keine Parameter hat, müssen beim Aufruf die Klammern angeben werden:
 +
dummy();
 +
}}
  
Als Rückgabe-Datentyp wird void angegeben.
+
==Prototypen==
Eine Prozedur kann daher beim Aufruf nicht einer Variablen zugewiesen werden (wie FUNCTION's) sonder wird wie eine normale Anweisung behandelt (wie z.B. printf).  
+
Wie oben erwähnt, kann ein Unterprogramm an jeder beliebigen Stelle im Programm stehen. Damit ist jedoch eine Bedingung verknüpft: Das Unterprogramm muß in der Datei oberhalb des ersten Aufrufes definiert worden sein. Wenn Sie ein Unterprogramm in Zeile 10 zum ersten mal aufrufen, müssen Sie die Deklaration davor erledigt haben. Verstanden?
 +
Um dies zu erreichen, gibt es zwei Möglichkeiten:
 +
 
 +
Entweder Sie schreiben alle Unterprogramme vor <tt>main</tt> in die Datei. Dies muß jedoch wiederum so geschehen, dass Funktionen zum Zeitpunkt ihres Aufrufes bereits bekannt sind!
 +
Wo dies nicht möglich ist (z.B. sich gegenseitig aufrufende Unterprogramme), oder wenn Sie das stört, müssen Sie Prototypen verwenden.
 +
Wie definiert man nun Prototypen? Sie kopieren einfach die erste Zeile des Unterprogrammes (z.B. "<tt>void ausgeben (int zahl)</tt>"), fügen einen Strichpunkt&nbsp;<tt>;</tt>an und fügen es an einer geeigneten Stelle ein (so, dass alle Aufrufe später in der Datei kommen).
 +
Solche Definitionen stehen gewöhnlich am Anfang der Quelldatei oder in einer Header-Datei, die eingebunden wird.
  
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
  
void ausgeben(int Param1);
+
void ausgeben (int zahl); /* Der Prototyp */
 +
 
 +
int main (void)
 
{
 
{
  printf("Der Wert der Variablen beträgt: %d\n",Param1);
+
  ausgeben (12);
 +
 
 +
  return 0;
 
}
 
}
  
void main(void)
+
void ausgeben (int zahl)   /* Die eigentliche Prozedur */
 
{
 
{
  int Zahl1;
+
  printf ("Ausgabe: %d\n", zahl);
  ausgeben(23);
+
  Zahl1=4;
+
  ausgeben(Zahl1);
+
  getch();
+
 
}
 
}
 
</pre>
 
</pre>
  
'''Bemerkungen zum Programm:'''
+
==Parameterübergabe==
Durch den Befehl "ausgeben(23)" wird die Prozedur "ausgeben" mit dem Parameter 23 aufgerufen. Param1 hat also den Wert 23. Die einzige Anweisung innerhalb der Prozedur ist printf, was nun ausgeführt wird.
+
Danach wird mit dem nächsten Befehl innerhalb des Hauptprogrammes fortgefahren ("Zahl1=4").
+
Der 2. Aufruf von ausgeben erfolgt mit dem Parameter Zahl1, also wird Param1 der Wert 4 zugewiesen. Dieser wird nun wieder mittels printf ausgeben.
+
Unterprogramme ohne Parameter
+
Das ist ihnen sicherlich schon aufgefallen: Viele Unterprogramme, z.B. main, haben statt der Parameter immer nur void stehen. Was bedeutet das? Nun ja, C erwartet, das Sie die Parameter angeben. void bedeutet "es gibt keine Parameter". "void main(void)" bedeutet also: "Erstelle ein Unterprogramm Namens main, das keinen Rückgabewert und keine Parameter hat".
+
ACHTUNG! Auch wenn eine Funktion keine Parameter hat, z.B. getch, müssen Sie beim Aufruf trotzdem die Klammern angeben. Der Befehl "getch;" führt nicht getch ohne Parameter aus, sondern liefert die Adresse der Funktion getch. Alles klar? Na ja, jedenfalls: Beim Aufruf jedes Unterpgrogrammes immmer Klammern angeben.
+
  
===Prototypen===
+
Alle Werte, die an Prozeduren und Funktionen übergeben werden, werden grundsätzlich '''kopiert'''.
Wie oben erwähnt, kann ein Unterprogramm an jeder beliebigen Stelle im Programm stehen. Damit ist jedoch eine Bedingung verknüpft: Das Unterprogramm muß in der Datei oberhalb des 1. Aufrufes definiert worden sein. Wenn Sie ein Unterprogramm in Zeile 10 zum ersten mal aufrufen, müssen Sie die Definition davor erledigt haben. Verstanden?
+
Das hat folgende Auswirkungen:
Um dies zu erreichen, gibt es zwei Möglichkeiten:  
+
  
Entweder Sie schreiben alle Unterprogramme vor main in die Datei. Dies muß jedoch wiederum so geschehen, dass Funktionen zum Zeitpunkt ihres Aufrufes bereits bekannt sind!  
+
# Änderungen an einem Parameter in einer Funktion erscheinen ''nicht'' beim Aufrufer!
Wo dies nicht möglich ist (z.B. sich gegenseitig aufrufende, rekursive Unterprogramme), oder wenn Sie das stört, müssen Sie Prototypen verwenden.
+
# Möchte man, dass eine Funktion einen Wert trotzdem dauerhaft ändern soll, so muss die Adresse des Wertes via [[#Zeiger|Zeiger]] übergeben werden.
Wie definiert man nun Prototypen? Sie kopieren einfach die erste Zeile des Unterprogrammes (z.B. "void up1(char Zeichen)") und fügen es an einer geeigneten Stelle ein (so, dass alle Aufrufe später in der Datei kommen). Beachten Sie jedoch, daß Prototypen einen Strichpunkt am Ende haben!
+
# Werden [[#Strukturen|Strukturen]] übergeben, so wird von ihnen eine Kopie erstellt, was bei großen Strukturen viel Zeit und Arbeitsspeicher kostet. Deshalb wird häufig nur die Adresse von Strukturen übergeben, da die Adresse viel schneller und platzsparender als die Struktur selbst kopiert werden kann.
  
<pre>
+
'''Beispiele:'''
#include <conio.h>
+
void erhoehe (int x)
#include <stdio.h>
+
{
 +
    x = x + 1;
 +
}
 +
 +
int main (void)
 +
{
 +
    int a = 0;
 +
    erhoehe(a);
 +
    {{comment|a ist immer noch 0}}
 +
   
 +
    return 0;
 +
}
  
void ausgeben(int Zahl)//Der Prototyp
+
Beim Aufruf von <tt>erhoehe</tt> wird eine Kopie des Wertes von <tt>a</tt> (im Beispiel also 0) erstellt und der Prozedur als Parameter <tt>x</tt> übergeben. Weil dann die Prozedur <tt>erhoehe</tt> die Kopie verändert, hat dies keine Auswirkung auf das Original <tt>a</tt> im Hauptprogramm.
  
void main(void)
+
void erhoehe (int *x)
{
+
{
  clrscr();
+
    {{comment|erhoehe den Wert an der Adresse x um eins}}
  ausgeben(12);
+
    *x = *x + 1;
  getch();
+
}
}
+
 +
int main(void)
 +
{
 +
    int a = 0;
 +
    erhoehe (&a);
 +
    {{comment|a ist jetzt 1}}
 +
   
 +
    return 0;
 +
}
  
void ausgeben(int Zahl)  //Die eigentliche Prozedur
+
Jetzt wird im Hauptprogramm mittels [[#Adress-Operator und Dereferenzierung|Adress-Operator]] <tt>&amp;</tt> die Speicheradresse von <tt>a</tt> bestimmt. Dann wird eine ''Kopie der Adresse'' an das Unterprogramm <tt>erhoehe</tt> übergeben. Jetzt kennt das Unterprogramm die
{
+
Adresse des Originals <tt>a</tt> und kann direkt mit dem Inhalts-Operator&nbsp;<tt>*</tt> auf den Wert an dieser Adresse zugreifen.
  printf("Ausgabe: %d\n",Zahl);
+
}
+
</pre>
+
  
 +
'''Besonderheit bei Feldern'''
  
==Besondere Datentypen==
+
Bei der Übergabe von [[#Felder|Feldern]] gibt es eine Besonderheit. Schreibt man nämlich den Namen eines Feldes, so ist das nichts anderes als die '''Speicheradresse des ersten Elements'''.
 +
Bei der Übergabe eines Feldes wird also eine Kopie der Startadresse übergeben. Somit kann das Unterprogramm auf den Originaldaten arbeiten und diese verändern.
  
===Felder===
+
'''Beispiel:'''
Oft muß man sehr viele Werte gleichzeitig abspeichern und betrachten, die alle der selben Aufgabe dienen. Man schreibt z.B. ein Programm, das 10 Zahlen einlesen und anschließend wieder ausgeben soll. Man könnte das natürlich mit 10 einzelnen Variablen bewerkstelligen, aber es ist sinnvoller, dabei Felder, sogenannte "ARRAY"s, zu verwenden.
+
void erhoehe (int x[])
 +
{
 +
    x[0] = x[0] + 1;
 +
    x[1] = x[1] + 3;
 +
    x[2] = x[2] + 5;
 +
}
 +
 +
int main(int argc, char **argv)
 +
{
 +
  int a[] = {10, 20, 30};
 +
 
 +
  erhoehe (a);
 +
  {{comment|a hat jetzt folgenden Inhalte: 11, 23, 35}}
 +
 
 +
  return 0;
 +
}
  
In einem Array werden mehrere Variablen gleichen Typs zusammengefasst und hintereinander im Speicher abgelegt. So kann man viele tausend Variablen anlegen mit nur einer Zeile Code. Doch es gibt noch größere Vorteile: Sie können das Array mit z.B. einer for-Schleife ganz einfach nach Werten durchsuchen. Stellen Sie sich vor, Sie müssten mit 100 verschiedenen Variablen Zahl_001 bis Zahl_100 arbeiten! Ein ihnen bereits bekanntes Beispiel für ein Array ist ein String. Ein String ist nichts anderes als ein Array des Datentypes char.
+
Dass die Übergabe einer Adresse erfolgt, sieht man an folgendem Beispiel, das von der Funktionsweise '''absolut identisch''' mit dem vorhergehenden ist:
  
Syntax: Dantentyp Variablenname[Anzahl]
+
{{comment|Bei Parametern gibt es keinen Unterschied zwischen Zeiger und Feld}}
 +
void erhoehe (int *x)
 +
{
 +
    x[0] = x[0] + 1;
 +
    x[1] = x[1] + 3;
 +
    x[2] = x[2] + 5;
 +
}
 +
 +
int main(int argc, char **argv)
 +
{
 +
    int a[] = {10, 20, 30};
 +
 
 +
    erhoehe (a);
 +
 +
    {{comment|a hat jetzt folgenden Inhalt: 11, 23, 35}}
 +
}
  
Der Name muß natürlich ein gültiger Bezeichner sein, als Datentyp kann jeder Typ genommen werden. In der eckigen Klammer wird die Anzahl der Elemente bekanntgegeben. Ein mit [3] definiertes Array hat 3 Variablen, nämlich [0], [1] und [2] (kein [3], da immer mit [0] begonnen wird). Um auf eine der im Array enthaltenen Variablen zugreifen zu können, müssen Sie den Variablennamen und in eckigen Klammern den Index (die "Nummer") der Variablen angeben. Diese Variable verhält sich dann wie eine ganz normale Variable des jeweiligen Datentypes.  
+
{{FarbigerRahmen |
 +
Die Länge des Feldes wird nicht automatisch übergeben. Dafür ist ggf. ein zusätzlicher Parameter notwendig.
 +
}}
  
<pre>
+
==Inlining==
#include <conio.h>
+
#include <stdio.h>
+
  
void main(void)
+
In C gibt es die Möglichkeit, eine Funktion als ''inline''-Funktion zu definieren.
{
+
Für eine inline-Funktion wird üblicher Weise kein Code erzeugt, der beim Aufruf der Funktion angesprungen wird, sondern an der Stelle des Aufrufs wird eine Kopie der inline-Funktion eingefügt.
  int Zaeler;
+
  int Zahlen[10];  //Zahlen[0] bis Zahlen[9] !!!
+
  clrscr();
+
  for (Zaeler=0; Zaeler<10; Zaeler++)
+
  {
+
    printf("Bitte Zahl %d eingeben: ",Zaeler);
+
    scanf("%d",Zahlen[Zaeler]);
+
    printf("\n");
+
  }
+
  printf("Danke!\n");
+
  for (Zaeler=0; Zaeler<10; Zaeler++) printf("Zahl %d war: %d",Zaeler,Zahlen[Zaeler]);
+
END.
+
</pre>
+
  
'''Bemerkungen:'''
+
Vom Effekt her ist eine inline-Funktion also ähnlich wie ein Makro. Allerdings wird das Einfügen des Codes nicht vom Präprozessor übernommen, sondern vom eigentlichen C-Compiler. Damit der Compiler in der Lage ist, eine Funktion zu inlinen, muss ihm der Code zur Verfügung stehen, da er ansonsten natürlich keinen Code einfügen kann.
Zuerst wird ein 10 int-Variablen großes Array angelegt.  
+
In dieses wird nun der Reihe nach 10 Zahlen eingelesen.  
+
Anschließend werden alle 10 Zahlen ausgegeben.  
+
  
'''ACHTUNG!:'''
+
Das Schlüsselwort ist ''inline'':
Wenn Sie einen ungültigen Index angeben (einen, der in der Deklaration nicht enthalten war) können je nach Compiler und Einstellung mehrere Dinge geschehen: entweder liefert der Compiler einen "Bereichsüberschreitungs-" Fehler, oder das Programm funktioniert nicht oder falsch (im schlimmsten Fall könnte sogar der Computer abstürzen). Also achten Sie darauf, daß Sie keine ungültigen Werte als Index angeben!
+
{{ccomment|Deklariere ''increment'' als inline-Funktion}}
 +
static inline int increment (int);
 +
 +
{{ccomment|Implementierung von ''increment''}}
 +
int increment (int i)
 +
{
 +
    return i+1;
 +
}
 +
 +
{{ccomment|Aufruf von ''increment'' wie eine normale Funktion}}
 +
int foo (int n)
 +
{
 +
    if (n < MAX_INT)
 +
      n = increment (n);
 +
 +
    return n;
 +
}
 +
 
 +
Inline-Funktionen werden verwendet, wenn der Funktionscode recht klein ist und ein Funktionsaufruf schon so aufwändig ist wie das, was die Funktion zu erledigen hat. Im Beispiel wird der gleiche Code erzeugt, wie wenn <tt>n = n + 1</tt> im Aufrufer stünde, was deutlich schneller ist als ein Funktionsaufruf mit Register-Sicherung, Parameterübergabe, Wertrückgabe, etc.
 +
 
 +
==Variable Argumentanzahl==
 +
 
 +
In C ist es möglich, einer Funktion eine variable Anzahl an Argumenten zu übergeben. Solche Funktionen haben eine Anzahl benamter Argumente wie "normale" Funktionen auch, jedoch folgt nach dem letzten benamten Argument eine beliebige Anzahl weiterer Argumente.
 +
 
 +
Ein Beispiel für eine solche Funktion ist das Bekannte <tt>printf</tt>, für das zB folgende Aufrufe möglich sind:
 +
printf ("Hallo");
 +
printf ("%c", c);
 +
printf ("%s %d", einString, 15);
 +
printf ("%d %d %d", zahl1, zahl2, zahl3);
 +
 
 +
Einer solchen Funktion muss die Anzahl der Argumente mitgeleilt werden, die ihr übergeben werden. Sies könnte dadurch geschehen, daß man ihr die Argumentanzahl explizit übergibt. Bei <tt>printf</tt> wird die Anzahl der Übergabeparameter im Formatstring transportiert, ebenso wie die Typen der Übergebenen Variablen, denn die Funktion muss wissen, wie die übergebenen Werte zu interpretieren sind. Bei <tt>printf</tt> geschieht dies wiederum mittls des Format-Strings.
 +
 
 +
Der Prototyp einer varargs-Funktion sieht aus wie folgt, wobei die drei Pünktchen wörtlich zu nehmen sind und nicht etwa als abkürzende Schreibweise für die Parameterliste!
 +
#include <stdio.h>
 +
#include <stdarg.h>
 +
 +
extern void fprintf (FILE * file, const char * format, ...);
 +
extern void printf  (const char * format, ...);
 +
extern void fprintf_va (FILE * file, const char * format, va_list args);
 +
 
 +
Die Funktion <tt>fprintf</tt> soll eine Ausgabe zum File (Stream) <tt>file</tt> erledigen und ansonsten genauso funktionieren wie das altbekannte <tt>printf</tt>. Es ist also anzustreben, in <tt>printf</tt> nur die File-Version aufzurufen um eine Doppel-Implementierung der Funktionalität zu vermeiden. Hierzu dient die Funktion <tt>fprintf_va</tt>, die von <tt>printf</tt> bzw. <tt>fprintf</tt> einen File-Pointer, den Format-String sowie die Argumente erhält:
 +
 
 +
void fprintf (FILE * file, const char * format, ...)
 +
{
 +
{{ccomment|Die Argumentliste 'args'}}
 +
va_list args;
 +
{{ccomment|Die variablen Argument beginnen nach 'format' }}
 +
va_start (args, format);
 +
{{ccomment|Die eigentliche Arbeit erledigt 'fprintf_va' }}
 +
fprintf_va (file, format, args);
 +
{{ccomment|fertig }}
 +
va_end (args);
 +
}
 +
 
 +
Fast genauso sieht unser <tt>printf</tt> aus:
 +
 
 +
void printf (const char * format, ...)
 +
{
 +
{{ccomment|Die Argumentliste 'args'}}
 +
va_list args;
 +
{{ccomment|Die variablen Argument beginnen nach 'format' }}
 +
va_start (args, format);
 +
{{ccomment|Die eigentliche Arbeit erledigt 'fprintf_va' }}
 +
fprintf_va (stdout, format, args);
 +
{{ccomment|fertig }}
 +
va_end (args);
 +
}
 +
 
 +
Was zu tun bleibt, ist die Implementioerung der eigentlichen Funktionalität in <tt>fprintf_va</tt>. Diese durchfostet den Format-String und bei jedem %-Ausdruck wird ein weiteres Argument eingelesen und an eine passende Ausgabefunktion delegiert. Hier wird der Einfachheit halber nur <tt>%c</tt> und <tt>%s</tt> implementiert:
 +
 
 +
static void fprintf_va (FILE * file, const char * fmt, va_list args)
 +
{
 +
    char c;
 +
 +
    {{ccomment|Nächstes Zeichen des Formatstrings lesen und String-Ende abtesten }}
 +
    while (c = *fmt++, c != '\0')
 +
    {
 +
        {{ccomment|Das Format-Zeichen }}
 +
        if ('%' == c)
 +
        {
 +
            {{ccomment|Weiterlesen: das Zeichen nach dem % }}
 +
            c = fmt++;
 +
 +
            {{ccomment|%% --> ein % ausgeben }}
 +
            if ('%' == c)    fputc ('%', file);
 +
            {{ccomment|%c --> Character ausgeben }}
 +
            else if ('c' == c)    fputc (va_arg (args, int), file);
 +
            {{ccomment|%s --> String ausgeben }}
 +
            else if ('s' == c)    fputs (va_arg (args, char*), file);
 +
            {{ccomment|Unbekannts %-Format }}
 +
            else fputs ("???", file);
 +
 +
            {{ccomment|Falls ein % am String-Ende steht }}
 +
            if ('\0' == c)  return;
 +
 +
            continue;
 +
        }
 +
 +
        {{ccomment|Für Win32: Zeilenumbruch ist carriage return + line feed }}
 +
        if ('\n' == c)
 +
            putc ('\r');
 +
 +
        {{ccomment|Zeichen ausgenen }}
 +
        putc (c);
 +
    } {{ccomment|while }}
 +
}
 +
 
 +
== Funktionen indirekt aufrufen ==
 +
Siehe [[#Zeiger auf Funktionen|Zeiger auf Funktionen]]
 +
 
 +
=Zeiger II=
 +
 
 +
Zeiger haben wir bereits weiter oben kennen gelernt. Zeiger sind ein zentrales Konzept in C und sollen hier etwas eingehender behandelt werden.
 +
 
 +
==Zeiger-Arithmetik==
 +
In C kann man den Wert eines Zeigers verändern. Betrachten wir dazu die Funktion <tt>suche_0</tt>, die einen Zeiger auf einen <tt>long</tt> erhält. Die Funktion soll ab der gegebenen Adresse nach dem ersten long-Wert suchen, der 0 ist, und dessen Adresse zurückgeben:
 +
long * suche_0 (long * addr)
 +
{
 +
    while (*addr != 0)
 +
      addr = addr + 1;
 +
 +
    return addr;
 +
}
 +
In der Bedingung der while-Schleife wird der Inhalt an der Speicherstelle <tt>addr</tt> auf 0 getestet. Ist der Wert 0, dann wird die Schleife beendet und die Adresse zurückgeliefert. Ist der Wert ungleich 0, dann wird <tt>addr</tt> auf den nächste long gesetzt, <tt>addr</tt> also um 4 Bytes weitergezählt. <tt>addr</tt> ist ja ein Zeiger auf <tt>long</tt>, und ein <tt>long</tt> ist 4 Bytes lang.
 +
 
 +
Die Bedeutung von
 +
address + n
 +
ist also, die Adresse um das <tt>n</tt>-fache der Größe des Typs, auf den <tt>address</tt> zeigt, zu erhöhen. Dabei ist <tt>n</tt> eine ganze Zahl und darf auch negativ sein.
 +
 
 +
Hier noch ein Beispiel einer Funktion, die nach einer Person mit einer bestimmten ID sucht (für die Definition von <tt>struct Person</tt> siehe [[#Strukturen|Strukturen]]). Der Parameter <tt>person</tt> ist dabei ein Array von Strukturen. Eine Person mit der gesuchten ID muss existieren, ansonsten hat die Suchfunktion kein definiertes Verhalten.
 +
{{comment|Sucht nach einer Person mit der ID person_id}}
 +
struct Person *
 +
suche_person_id (struct Person * person, int person_id)
 +
{
 +
    while (person->id != person_id)
 +
      person++;
 +
 +
    return person;
 +
}
 +
 
 +
<div style="margin:1em; padding:1em; border:solid 2px #FF0040;">
 +
<!-- Vorlage:FarbigerRahmen funzt hier net -->
 +
Beachte, daß es nicht sinnvoll ist, zwei Zeiger zu addieren oder zu multiplizieren. Ausserdem ist das <tt>+</tt> der Zeiger-Arithmetik nicht kommutativ. Eine Zeiger auf <tt>long</tt>, der an Adresse 1 im Speicher zeigt, wird man schreiben als
 +
(long *) 1
 +
Addiert man darauf eine ganze Zahl, dann haben die entstehenden Ausdrücke unterschiedliche Werte:
 +
(long *) 1 + 2    {{comment|zeigt zu Adresse 9}}
 +
(long *) 2 + 1    {{comment|zeigt zu Adresse 6}}
 +
(long *) (1 + 2)  {{comment|zeigt zu Adresse 3}}
 +
</div>
 +
 
 +
==void-Pointer==
 +
Eine besondere Art von Zeiger ist der void-Pointer
 +
void * addr;
 +
Ein void-Pointer ist ein "Zeiger auf irgendwas", dementsprechend kann er nicht dereferenziert werden, Anwenden von <tt>*</tt> auf einen solchen Zeiger gibt also einen Fehler. Ausserdem ist es nicht möglich, mit einem void-Pointer Zeigerarithmetik zu machen, weil er nicht auf eine definierte Art von Objekt zeigt. Der Vorteil eines void-Pointers ist, daß er jede Art von Zeiger aufnehmen kann.
 +
 
 +
Dazu betrachten wir die Funktion <tt>send_buf</tt>, die eine Adresse erhält und ab dieser Adresse <tt>num</tt> Bytes versenden soll. Wir könnten die Funktion so schreiben:
 +
void send_buf (unsigned char * buf, unsigned int num)
 +
{
 +
  ...
 +
Das ist jedoch hässlich, wenn wir damit etwas anderes verschicken wollen als <tt> unsigned char</tt>, etwa eine Struktur wie <tt>hubert</tt> (vom Typ <tt>struct Person</tt>):
 +
send_buf ((unsigned char*) & hubert, sizeof (struct Person));
 +
Ohne den Cast der Adresse von <tt>hubert</tt> zu einem Zeiger auf <tt>unsigned char</tt> bekommt man eine Warnung oder gar einen Compilerfehler.
 +
Dieses Zeiger gecaste ist mühsam und hässlich, es muss bei jedem Aufruf der Funktion explizit hingeschrieben werden.
 +
 
 +
Besser ist es, den ersten Parameter der Funktion als void-Pointer zu definieren und den Cast in der Funktion zu machen:
 +
void send_buf (void * vbuf, unsigned int num)
 +
{
 +
  unsigned char *buf = (unsigned char*) vbuf;
 +
  ...
 +
Durch den Cast in der Funktion kann auf den Inhalt des Zeigers zugegriffen werden. Man muss nur festlegen, ''wie'' man zugreifen will, nämlich als <tt>unsigned char</tt>.
 +
Der Aufruf kann jetzt ohne Pointer-Cast erfolgen:
 +
send_buf (& hubert, sizeof (struct Person));
 +
 
 +
==Null-Pointer==
 +
==Zeiger als Parameter==
 +
Wenn Sie ein Unterprogramm aufrufen, können Sie diesem Parameter übergeben, aber keine Werte zurückgekommen (außer den Funktionswert bei Funktionen). Dies hat einen guten Grund: beim Aufruf werden nicht die aufgerufenen Parameter benutzt, sondern es werden deren Werte in neue Variablen kopiert. Diese Variablen werden am Ende des Unterprogrammes "zerstört", ohne ihre Werte an die aufrufenden Parameter zu übergeben. Jede Veränderung eines Parameters hat daher keine Auswirkung auf den Parameter.
  
===Mehrdimensionale Felder===
+
Doch was ist, wenn Sie Parameter in Unterprogrammen verändern möchten? Ganz einfach, Sie verwenden Zeiger. Der C-Compiler legt dann immer noch Kopien an. In dieser Kopie steht aber kein Wert, sondern die Adresse einer Varaiblen. Und auf diese können Sie dann zugreifen. Denken Sie nur an <tt>scanf</tt> &ndash; da übergeben Sie ja auch die Adresse einer Variablen.
Manchmal benötigen mehr als nur ein ein-dimensionales Array, wie Sie es bisher kennengelernt haben. Auch dies ist kein Problem. In der Deklaration geben Sie einfach mehrere eckige Klammernhintereinander an. Aber Vorsicht: der Speicherplatz ist begrenzt, ein "char feld[1024][1024]" hat die Speicherplatzgrenzen bereits weit überschritten, und der Compiler wird einen (bei gewissen Einstellung auch keinen) Fehler liefern.
+
Beim Zuweisen von Werten etc. müssen Sie bei mehrdimensionalen Feldern auch mehr als einen Index angeben, die Erklärung folgt als Beispiel:
+
  
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
  
void main(void)
+
void erhoehe (int *zeiger)
 
{
 
{
   int x,y;
+
   *zeiger = 1 + *zeiger;
  int feld[3][5];
+
}
+
 
   for(x=0; x<3; x++) for (y=0; y<5; y++)
+
int main ()
   {
+
{
    printf("Feldwert X: %d Y: %d",x,y);
+
   int zahl;
    scanf("%d",feld[x][y]);
+
    
    printf("\n");
+
  printf ("Zahl eingeben: ");
   }
+
  scanf ("%d", &zahl);
  clrscr();
+
   erhoehe (&zahl);
   for(x=0; x<3; x++) for (y=0; y<5; y++) printf("Wert: feld[%d][%d]= %d\n",x,y,feld[x][y]);
+
   printf ("\nDie erhoehte Zahl lautet: %d\n", zahl);
 +
 
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
  
'''Erklärung:'''
+
==Zeiger auf Funktionen==
  
Zuerst wird ein 3 mal 5 int Array angelegt.  
+
Stell dir vor, du willst einen Sortieralgorithmus wie Bubble-Sort oder Quick-Sort oder wie sie alle heissen implementieren. Für den Sortieralgorithmus ist eigentlich egal, ''was'' er zu sortieren hat. Ihm ist es egal, ob er Zahlen aufwärts sortieren soll oder Strings in lexikographischer Reihenfolge, ob Objekte nach Größe oder Gewicht, Personen nach Alter oder Adressen nach Postleitzahl. Das einzige, was der Algorithmus wissen muss, ist ''wie'' er zwei Objekte zu vergleichen hat und wann eines davon "kleiner" (im Sinne der Ordnung, nach der sortiert werden soll) ist.  
Dann werden die Werte eingegeben: zuerst feld[1][1], dann feld[1][2], usw. bis feld[3][5].
+
Zum Schluß werden alle Werte noch einmal ausgegeben.  
+
  
'''Strings'''
+
Eine einfache Sortierfunktion, die nur zwei Zahlen sortiert, könnte man also so schreiben:
 +
{{comment|Sortiert ein Array von 2 int-Zeigern nach den Inhalten
 +
  * an den Zeiger-Adressen}}
 +
void sort2_a (int * p[])
 +
{
 +
    {{comment|Inhalte vergleichen...}}
 +
    if (*p[0] > *p[1])
 +
    {
 +
      {{comment|... und ggf. Dreieckstausch der 2 Zeiger}}
 +
      int * p0 = p[0];
 +
      p[0] = p[1];
 +
      p[1] = p0;
 +
    }
 +
}
 +
Die Funktion bekommt ein Array der Länge&nbsp;2. In diesem Array stehen Zeiger auf die zu sortierenden Zahlen. Ein Array mit Zeigern zu verwenden und nicht ein Array von <tt>int</tt> scheint recht umständlich, und das ist es hier auch. Aber stell dir vor, du willst Strukturen wie <tt>struct Person</tt> sortieren. Das Tauschen zweier Strukturen würde bedeuten, ihre kompletten Inhalte umzukopieren! Das wäre sehr aufwändig. Viel einfacher ist das Kopieren, wenn nur die Adressen zu kopieren sind.
  
Im Kapitel Variablen und Datentypen haben Sie bereits etwas von Strings gehört, mangels Wissen über Felder und Unterprogramme aber nicht sehr ausführlich. Hier folgt nun die ausführlichere Erklärung.
+
Der Aufruf von <tt>sort2_a</tt> könnte dann so aussehen:
Bei Strings handelt es sich um char Felder. Wenn Sie einen String definieren, müssen Sie das mittels char machen:  
+
#include <stdio.h>
 +
 +
void sortiere (int a, int b)
 +
{
 +
    {{comment|p[] enthält 2 int-Zeiger: die Adressen von a und b}}
 +
    int * p[2];
 +
    p[0] = &a;
 +
    p[1] = &b;
 +
 +
    {{comment|Sortiere die Zeiger}}
 +
    sort2_a (p);
 +
 +
    printf ("Sortiert: %d, %d\n", *p[0], *p[1]);
 +
}
  
<pre>
 
char string[21];
 
</pre>
 
  
Nun haben Sie eine string, in dem Sie 21 Zeichen speichern können. Ganz richtig ist das jedoch nicht. C arbeitet mit "null-terminierten Strings". Das beudeutet, dass die Länge des Strings nicht abgespeichert wird (wie in Pascal), sondern ein Zeichen innerhalb des Strings das Ende desselben angibt (ASCII Nr. 0, daher der Name "null terminiert").  
+
Für den nächsten Schritt überlegen wir uns, daß das Array in <tt>sort2_a</tt> ebensogut void-Pointer enthalten kann. Die einzige Stelle, an der wir auf die endgültigen int-Objekte zugreifen, ist der Vergleich. Diesen Vergleich lagern wir in die Funktion <tt>compare_int</tt> aus:
Das letzte Zeichen eines Strings muß daher immer das ASCII-Zeichen Nr. 0 sein. Ist es das nicht, hat der String kein definiertes Ende, und wenn Sie versuchen, ihn auszugeben, könnte es eine Weile dauern, bis sich im Speicher zufällig irgendwo eine 0 befindet. Es stehen ihnen daher (im Beispiel) nur 20 Zeichen zur Verfügung.
+
{{comment|Bekommt zwei void-Pointer und vergleicht die Inhalte.
 +
  * Liefert 0 bei Gleichheit,
 +
  * -1 wenn der erste Wert kleiner ist als der zweite und
 +
  * 1  wenn der erste Wert größer ist als der zweite}}
 +
int compare_int (void * p0, void * p1)
 +
{
 +
    {{comment|Um über die Zeiger zugreifen zu können müssen wir diese
 +
    * erst zu int-Zeigern casten}}
 +
    int a0 = * (int*) p0;
 +
    int a1 = * (int*) p1;
 +
 +
    if (a0 > a1)  return  1;
 +
    if (a0 < a1)  return -1;
 +
 +
    return 0;
 +
}
 +
 +
void sort2_b (void * p[])
 +
{
 +
    if (compare_int (p[0], p[1]) > 0)
 +
    {
 +
      void * p0 = p[0];
 +
      p[0] = p[1];
 +
      p[1] = p0;
 +
    }
 +
}
 +
Ein Aufruf von <tt>sort2_b</tt> sieht dann genauso aus wie ein Aufruf von <tt>sort2_a</tt>.
 +
 
 +
 
 +
Im nächsten Schritt definieren wir uns den neuen Datentyp <tt>comparator_t</tt>. Dieser ist ein Zeiger auf eine Funktion, die zwei void-Pointer erhält und einen <tt>int</tt> zurückliefert, also analog arbeitet zu <tt>compare_int</tt> von oben.
 +
 
 +
Unsere Sortierfunktion bekommt nun neben dem zu sortierenden Zeiger-Array auch eine Vergleichsfunktion <tt>compare</tt> mitgeliefert, die sie aufruft, wenn sie zwei Objekte vergleichen will
 +
{{comment|comparator_t sind Zeiger auf Funktionen, die 2 void-Pointer
 +
  * erhalten und einen int zurückliefern}}
 +
typedef int (*comparator_t) (void*, void*);
 +
 +
{{comment|Der Sortierer bekommt einen Funktionszeiger auf den Vergleicher.
 +
  * Der Aufruf vom compare geht so als wäre es eine "normale" Funktion
 +
  * (ist es im Endeffekt ja auch)}}
 +
void sort2_c (comparator_t compare, void * p[])
 +
{
 +
    if (compare (p[0], p[1]) > 0)
 +
    {
 +
      void * p0 = p[0];
 +
      p[0] = p[1];
 +
      p[1] = p0;
 +
    }
 +
}
 +
Bei einem Aufruf von <tt>sort2_c</tt> muss man dann einen Komparator mit angeben. In einem Beispiel analog zu <tt>sort2_a</tt> von oben ist das:
 +
sort2_c (compare_int, p);
 +
Um zwei Strings lexikographisch zu sortieren nehmen wie die Standard-Funktion <tt>strcmp</tt>:
 +
#include <string.h>
 +
#include <stdio.h>
 +
 +
void foo()
 +
{
 +
    char * worte[] = { "Wort1", "Wort2" };
 +
 +
    sort2_c ((comparator_t) strcmp, (void**) worte);
 +
}
 +
Die Casts sind hier erforderlich. Alternativ könnte man <tt>sort2_c</tt> mit reinen void-Pointern versorgen und diese dann dort umcasten.
 +
 
 +
===Syntax===
 +
 
 +
Die Syntax zur Definition/Deklaration von Funktionszeigern ist etwas verzwackt. Zur Verdeutlichung ein paar Beispiele. Dabei legt das linke <tt><Type></tt> jeweils den Return-Typ fest.
 +
{{comment|definiert einen neuen Funktionszeiger-Typ}}
 +
typedef {{type}} (*{{bezeichner}}) ({{type}}, {{type}}, ...);
 +
 +
{{comment|deklariert eine Variable als Funktionszeiger}}
 +
{{type}} (*{{bezeichner}}) ({{type}}, {{type}}, ...);
 +
 +
{{comment|deklariert ein Array von Funktionszeigern (mit Initializer)}}
 +
{{type}} (*{{bezeichner}}[]) ({{type}}, {{type}}, ...) = { wert1, wert2, ... };
 +
 +
{{comment|Castet Bezeichner zu einem Funktionspointer}}
 +
({{type}}(*)({{type}}, {{type}}, ...)) {{bezeichner}}
 +
 +
{{comment|Castet Bezeichner zu einem Funktionspointer und ruft die Funktion auf}}
 +
(({{type}}(*)({{type}}, {{type}}, ...)) {{bezeichner}}) (arg1, arg2, ...);
 +
 
 +
=Standard-Funktionen=
 +
 
 +
==String-Funktionen==
  
 
===strcpy===
 
===strcpy===
Sie können einem String nicht direkt einen Wert (Text) zuweisen. Dazu müssen Sie die Prozedur strcpy benutzen. Diese erwartet als ersten Parameter eine String-Variable (ohne eckige Klammern) und als zweiten Parameter einen String. Dieser String kann sein: eine andere String-Variable, oder ein in doppelten Hochkommas (") eingeschlossener Text. Die Funktion fügt am Ende automatisch ein 0-Zeichen ein. Um diese Funktion nutzen zu können, müssen Sie die Datei string.h includieren!  
+
Bei vielen Compilern können sie einem String nicht direkt einen Wert (Text) zuweisen. Dazu müssen Sie dann die Prozedur strcpy() benutzen. Diese erwartet als ersten Parameter den Namen einer String-Variablen (ohne eckige Klammern) und als zweiten Parameter den eines (anderen) Strings. Letzterer kann auch ein in doppelten Hochkommas (") eingeschlossener Text sein. Die Funktion fügt am Ende automatisch ein 0-Zeichen ein. Um diese Funktion nutzen zu können, müssen Sie die Datei string.h includieren!  
  
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <string.h>
  
void main(void)
+
int main (void)
 
{
 
{
   char stri1[21],eingabe[21];
+
   char stri1[21], eingabe[21];
  
   strcpy(stri1,"hallo");
+
   strcpy (stri1, "hallo");
  clrscr();
+
 
   printf("Der 1. String: %s\n",stri1);
+
   printf ("Der 1. String: %s\n", stri1);
   printf("Bitte geben Sie maximal 20 Zeichen ein: ");
+
   printf ("Bitte geben Sie maximal 20 Zeichen ein: ");
   scanf("%s",eingabe);
+
   scanf ("%s", eingabe);
   strcpy(stri1,eingabe);
+
   strcpy (stri1, eingabe);
   printf("\n %s = %s",stri1,eingabe);
+
   printf ("\n%s = %s", stri1, eingabe);
   getch();
+
    
 +
  return 0;
 
}
 
}
 
</pre>
 
</pre>
  
 
'''Hinweis:'''  
 
'''Hinweis:'''  
Da ein String, wie jedes Feld, eigentlich ein Zeiger ist, brauchen Sie kein & bei scanf angeben!
+
Da ein String, wie jedes Feld, eigentlich ein Zeiger ist, dürfen Sie kein <tt>&amp;</tt> bei <tt>scanf</tt> angeben!
  
 
'''Erklärung:'''  
 
'''Erklärung:'''  
Es werden 2 gleich große Strings definiert: stri1 und eingabe, mit je 20 "nutzbaren" Zeichen.  
+
Es werden zwei gleich große Strings definiert: <tt>stri1</tt> und <tt>eingabe</tt>, mit je 20 "nutzbaren" Zeichen.  
stri1 wird der Wert "hallo" zugewiesen. Das 0-Zeichen wird automatisch angefügt!
+
In <tt>stri1</tt> wird die Zeichenkette <tt>"hallo"</tt> hineinkopiert. Das 0-Zeichen am Ende wird automatisch angefügt.
Der String wird ausgegeben. Als neues "Sonderzeichen" kommt %s ins Spiel. Es hat die gleiche Aufgabe wie %d oder %c, nur halt für Strings.  
+
Der String wird ausgegeben. Als neues "Sonderzeichen" kommt <tt>%s</tt> ins Spiel. Es hat die gleiche Aufgabe wie <tt>%d</tt> oder <tt>%c</tt>, nur für Strings.  
Sie werden gebeten, eine String einzugeben.  
+
Sie werden gebeten, einen String einzugeben.  
Dieser String wird danach in die Variable stri1 kopiert.  
+
Dieser String wird danach in die Variable <tt>stri1</tt> kopiert.  
Beide Strings (die ja auch den selben Wert haben) werden ausgegeben.  
+
Beide Strings, die ja nun die gleiche Zeichenkette enthalten, werden ausgegeben.
  
 
===strlen===
 
===strlen===
Die Funktion strlen, die als Parameter eine String-Variable erwartet, liefert die Länge diese Strings zurück. Sie werden jetzt vermutlich sagen: "DAs ist doch klar, wie lang der String ist. Ich habe es ja bei der Deklaratin angegeben". Das stimmt schon, aber denken Sie noch einmal an die null-terminierten Strings. Das 0-Zeichen steht am Ende des Strings (am Ende der gültigen Zeichenfolge), aber nicht unbedingt am Ende des reservierten Speicherplatzes. Haben Sie eine Variable "char Variable[21];", und ihr den Wert "hallo" zugewiesen, dann steht das null-Zeichen in Variable[5]. Der "gültige" String ist also 5 Zeichen (0-4) lang. Und genau das (5) würde strlen zurück liefern.  
+
Die Funktion <tt>strlen</tt>, die als Parameter eine String-Variable erwartet, liefert die Länge dieses Strings zurück. Sie werden jetzt vermutlich sagen: "Das ist doch klar, wie lang der String ist. Ich habe es ja bei der Deklaratin angegeben". Das stimmt schon, aber denken Sie noch einmal an die null-terminierten Strings. Das 0-Zeichen steht am Ende des Strings (am Ende der gültigen Zeichenfolge), aber nicht unbedingt am Ende des reservierten Speicherplatzes. Haben Sie eine Variable "char Variable[21];", und ihr den Wert "hallo" zugewiesen, dann steht das null-Zeichen in Variable[5]. Der "gültige" String ist also 5 Zeichen (0-4) lang. Und genau das (5) würde strlen zurück liefern.  
  
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <string.h>
  
void main(void)
+
int main (void)
 
{
 
{
 
   char stri[21];
 
   char stri[21];
   strcpy(stri,"hallo");
+
 
   printf("Der String ist %d Zeichen lang", strlen(stri) );
+
   strcpy (stri, "hallo");
  getch();
+
   printf ("Der String ist %d Zeichen lang", strlen (stri));
 
}
 
}
 
</pre>
 
</pre>
  
Diese Funktion wird vor allem gebraucht, wenn Sie direkt auf den String zugreifen, mittels stri[0], stri[1], etc.  
+
Diese Funktion wird vor allem gebraucht, wenn Sie direkt auf den String zugreifen, mittels <tt>stri[0]</tt>, <tt>stri[1]</tt>, etc.
  
 +
==Ein- und Ausgabe-Funktionen==
  
===Strukturen===
+
===Bildschirm-Ausgabe===
In C können Sie sogenannte "Strukturen" definieren. Dabei handelt es sich um eine Zusammenfassung von mehreren Datentypen zu einem größeren. Im Unterschied zu Feldern können in Strukturen jedoch unterschiedliche Datentypen gespeichert werden:
+
Bisher war das Tutorial trotz aller Beispiele reine Theorie. Sie konnten zwar Programme schreiben, aber die Funktion nicht testen. Hier lernen Sie nun, wie Sie etwas am Bildschirm ausgeben.
  
 +
Die dazu notwendige Funktione heisst <tt>printf</tt> (das '<tt>f</tt>' ist kein Fehler!). Diese Anweisung gibt die ihr übergebenen Parameter auf das Standard-Ausgabegerät aus, in der Regel also auf den Bildschirm. Sie kann beliebig viele Parameter übernehmen. Es müssen jedoch Standard-Datentypen (z.B. <tt>int</tt>, <tt>char</tt>, <tt>double</tt>...) sein!
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
  
struct {
+
int main (void)
  char vname[20],nname[20];
+
{
  char telnr[15];
+
    int zahl1 = 12;
  int alter;
+
    char zeichen1 = 'A';
} PERSON;
+
   
 
+
    printf ("Das ist Text, und er wird als solcher ausgegeben. \n");
PERSON hubert;
+
    printf ("Der Wert der Variablen 'zahl1' ist: %d \n", zahl1);
PERSON stefan;
+
    printf ("Der Wert der Variablen 'zeichen1' ist: %c \n", zeichen1);
 +
    printf ("Der Wert der Variablen 'zeichen1' ist: %d \n", zeichen1);
 +
   
 +
    return 0;
 +
}
 
</pre>
 
</pre>
  
'''Erklärung:'''
+
Der erste <tt>printf</tt>-Befehl gibt Text aus. Das Zeichen am Ende (<tt>\n</tt>) bedeutet "New Line", es bewegt den Cursor an den Anfang der nächsten Zeile.  
"struct {" leitet die Definition ein.  
+
Dann werden 4 Variablen angelege: 3 Strings und ein int.
+
mit } wird der Block wieder geschlossen, anschließend muß der Struktur ein Name zugewiesen werden (z.B. PERSON). Sie haben damit einen Datentyp erstellt. Um eine Variable dieses Typs (PERSON) anzulegen, geben Sie einfach "PERSON Variablenname;" an.
+
Hinweis: Das der Datentyp groß geschrieben wird, ist nicht notwendig. Es dient, wie schon so oft, nur der Übersichtlichkeit.
+
Wie spricht man die einzelnen Variablen einer Struktur nun an? Das funktioniert so:
+
  
Zuerst gibt man die Struktur-Variable an, im Beispiel "hubert" oder "stefan".
+
Der zweite <tt>printf</tt>-Befehl gibt auch Text aus, am Ende befindet sich wieder das <tt>\n</tt>, um einen Zeilenvorschub zu erreichen. Das <tt>%d</tt> wird vom Compiler durch den ersten Parameter ersetzt, der nach dem Text angegeben wird. In diesem Fall wird <tt>%d</tt> also durch den Wert der Variablen <tt>zahl1</tt> ersetzt. Das <tt>d</tt> im <tt>%d</tt> bedeutet "Dezimalzahl", der Computer gibt also eine ganze Zahl aus.  
Danach schreibt man einen Punkt.  
+
Zum Schluß folgt die gewünschte Variable, also z.B. vname.  
+
Bemerkung: Die Anwendung von Strukturen verdeutlicht folgendes Beispiel (Typen und Variablendeklaration des letzten Beispieles dazudenken!):
+
  
<pre>
+
In der dritten Ausgabe wird ein Zeichen ausgegeben. Diesmal bedeutet <tt>%c</tt> "char" (Zeichen). Es wird also <tt>%c</tt> durch ein <tt>A</tt> ersetzt, denn die Variable <tt>zeichen1</tt> wird als Character interpretiert.
hubert.Vorname:="Max";
+
Person1.Nachname:="Mustermann";
+
Readln(Person1.strasse);
+
</pre>
+
  
 +
Die letzte Ausgabe interpretiert den Inhalt von <tt>zeichen1</tt> als Zahl, und gibt dager den ASCII-Wert von <tt>A</tt>, also 65 aus. Das ist ein typisches Beispiel für das mögliche unterschiedliche Interpretieren einer  Variablen!
  
==Zeiger==
+
===Tastatur-Eingabe===
Jede Variable steht an einer genau definierten stelle im Speicher. Diese Stelle kann man in erfahrung bringen. Wie man das macht und was das überhaupt für Vorteile bringt, erfahren Sie in diesem Abschnitt.
+
 
Ein Zeiger ist im Prinzip eine (int-) Variable. In ihr ist jedoch keine Zahl gespeichert, sondern eine "Adresse". Diese gibt eine bestimmte Position im Arbeitsspeicher dar. Hier sehen Sie, wie man einen Zeiger definiert und mit ihm arbeitet:  
+
Um ein "gscheites" Programm schreiben zu können, muß man wissen, wie der Benutzer über die Tastatur Befehle eingeben kann. Die dafür notwendigen Funktionen stelle ich in diesem Kapitel vor.
 +
Die wichtigste Funktion ist <tt>scanf</tt>. Er liest Daten von der Tastatur. Die Syntax entspricht derer von <tt>printf</tt>:  
  
 
<pre>
 
<pre>
#include <conio.h>
+
int  zahl1;
#include <stdio.h>
+
char zeichen1;
  
void main(void)
+
printf ("Bitte geben Sie eine Zahl ein: ");
{
+
scanf ("%d", &zahl1);
  int Zahl;
+
printf ("Geben Sie einen Zeichen ein: ");
  int *Zeiger;
+
scanf  ("%c", &zeichen1);
   
+
  Zeiger=&Zahl;
+
  *Zeiger=12;
+
 
+
  printf("%d = %d", Zahl, *Zeiger);
+
 
</pre>
 
</pre>
  
'''Erklärung:'''
+
Das Programm gibt eine Eingabeaufforderung aus. Dann erwartet es vom Benutzer, daß er eine Zahl eingibt, die mit [ENTER] bestätigt wird. Dieser Wert wird in <tt>zahl1</tt> abgespeichert. Danach erfolgt wiederum eine Aufforderung zur Eingabe, diesmal eines einzelnen Zeichens. Dieses kann man nun eingeben und ebenfalls mit [ENTER] bestätigen.
Um einen Zeiger anzulegen, gibt man folgendes an: Datentyp, dann einen Stern (*), anschließend den Namen (und einen Strichpunkt).
+
Um einen Zeiger auf eine bestimmte Zahl "zeigen" zu lassen, muß man dem Zeiger die Adresse einer Variablen zuordnen. Das funktioniert mittels dem Adress-Operator & (Zeiger=&Zahl).
+
Jetzt möchten Sie dem Zeiger einen Wert zuweisen. Aber natürlich nicht dem Zeiger, sondern der Variablen, auf die der Zeiger "zeigt". Na, "Zahl=Wert;", werden Sie sagen. Aber wozu dann Zeiger? Na, ja, jednefalls geht das mittels des "Inhalts-Operators" * (*Zeiger=12).
+
Genauso können Sie mit dem Inhaltsoperator Werte abfragen und an printf (und jedes andere Unterprogramm) übergeben.  
+
  
===Zeiger als Parameter===
+
Macht man keine dem Datentyp der erwarteten Variable entsprechende Eingabe, dann bricht das Programm mit einer Fehlermeldung ab (wenn man z.B. "1_T2" eingibt, wenn eine Zahl erwartet wird)!
Wenn Sie ein Unterprogramm aufrufen, können Sie diesem Parameter übergeben, aber keine Werte zurückgekommen (außer den Funktionswert bei Funktionen). Dies hat einen guten Grund: beim Aufruf werden nicht die aufgerufenen Parameter benutzt, sondern es werden deren Werte in neue Variablen kopiert. Diese Variablen werden am Ende des Unterprogrammes "zerstört", ohne ihre Werte an die aufrufenden Parameter zu übergeben. Jede Veränderung eines Parameters hat daher keine Auswirkung auf den Parameter.
+
Doch was ist, wenn Sie Parameter in Unterprogrammen verändern möchten? Ganz einfach, Sie verwenden Zeiger. Der Computer legt dann immer noch Kopien an. In dieser Kopie steht aber kein Wert, sondern die Adresse einer Varaiblen. Und auf diese können Sie dann zugreifen. Denken Sie nur an scanf - da übergeben Sie ja auch die Adresse einer Variablen ("&Variable").
+
  
 +
Das <tt>&</tt> vor den Parametern ist notwendig. Warum, das erfahren Sie im Kapitel "Unterprogramme". Für die Profis eine Kurz-Erklärung: Das Unterprogramm <tt>scanf</tt> bekommt zwar einen Wert übergeben, kann aber keinen zurückliefern ("call by value"). Daher wird kein Wert, sondern ein Zeiger auf eine Variable übergeben. Mit dem & Zeichen bekommen Sie die Adresse einer Variablen ("call by reference").
 +
 +
=Parameter von <tt>main</tt>=
 +
Das Unterprogramm "<tt>main</tt>" kann, wie jede andere Funktion, Parameter besitzen. Doch keine selbst gewählten, sondern nur bestimmte. Doch warum braucht main Parameter? Denken Sie einmal an alle Betriebssystembefehle:
 +
<tt>dir *.exe </tt> oder <tt>copy *.* a:</tt> oder <tt>ls -la </tt>. All diese Befehle sind aus zwei Teilen aufgebaut: Befehl und Parameter. Und genau diese Parameter können Sie mit den <tt>main</tt>-Parametern abfragen.
 +
<pre>
 +
int main (int argc, char *argv[], char* environ[])
 +
</pre>
 +
 +
Bei "<tt>argc</tt>" handelt es sich um eine normale int-Variable (engl. "''argument count''", "Parameter-Zähler"). In ihr steht die Anzahl der übergebenen Parameter. Die Parameter selbst folgen im zweiten Argument, das als Array von Strings übergeben wird. Das dritte Argument ist ein Array mit den Umgebungsvariablen. Seine Länge wird nicht explizit übergeben; nach dem letzten Element steht ein Null-String, also ein String der Länge&nbsp;0. In dieser Array befindet sich auch der Inhalt der Umgebungsvariablen <tt>PATH</tt>, die den Suchpfad für ausführbare Programme enthält.
 
<pre>
 
<pre>
#include <conio.h>
 
 
#include <stdio.h>
 
#include <stdio.h>
 +
#include <stdlib.h>
  
void erhoehe(int *zeiger)
+
int main (int argc, char *argv[], char * environ[])
 
{
 
{
   *zeiger=*zeiger+1;
+
   int i;
}
+
  
void main(void)
+
   printf ("Es wurden %d Parameter angegeben", argc);
{
+
 
  int Zahl;
+
   for (i=0; i < argc; i++)
   printf("Zahl eingeben: ");
+
    printf ("Parameter %d: %s\n", i, argv[i]);
   scanf("%d",&Zahl);
+
 
   erhoehe(&Zahl);
+
   for (i = 0; environ[i] != NULL; ++i)
  printf("\nDie erhoehte Zahl lautet: %d\n",Zahl);
+
    printf ("environ[%d] = %s\n", i, environ[i]);
  getch();
+
 
}
 
}
 
</pre>
 
</pre>
  
===Parameter von Main===
+
;Erklärung: Bei der ersten Ausgabe wird ausgegeben, wie viele Parameter insgesammt angegeben wurden. Dabei gibt immer mindestens einen Parameter, nämlich <tt>argc[0]</tt>. Dort steht der Name der aufgerufenen Datei selbst. Außerdem ist das letzte gültige Feldelement &ndash; wie in C üblich &ndash; das Element <tt>argv[argc-1]</tt>. In der for-Schleife werden alle Parameter, inklusive ihrer Nummer, ausgegeben. Experimentieren Sie mit den Parametern, um das System zu vertehen!
Das Unterprogramm "main" kann, wie jede andere Funktion, Parameter besitzen. Doch keine selbst gewählten, sondern nur bestimmte. Doch warum braucht main Parameter? Denken Sie einmal an alle Betriebssystembefehle: "dir *.exe", "copy *.* a:" oder "ls -la". All diese Befehle sind aus 2 Teilen aufgebaut: Befehl und Parameter. Und genau diese Parameter können Sie mit den main-Parametern abfragen.
+
  
<pre>
+
=Kurzreferenz=
void main(int argc, char *argv[])
+
==Syntax-Bausteine==
</pre>
+
  
Bei "argc" handelt es sich um eine normale int Variable (engl. "Argument count", Parameter-Zähler"). In ihm steht die Anzahl der angegeben Parameter.  
+
Die Erklärung des Aufbaus von C-Befehlen erfolgt neben einfachen Beispielen auch durch ihren prinzipellen Aufbau. In diesen Syntax-Beschreibungen finden sich immer wieder die gleichen Bausteine, die hier näher erklärt werden sollen. Falls Dir solch ein Syntax-Baustein begegnet, kannst Du ihn anklicken und kommst dann zu seiner Erläuterung.  
  
<pre>
+
In den Beispielen selbst gehören auch die spitzen Klammern zu dem Baustein (was daran zu erkennen ist, daß auch die Klammern eingefärbt sind). Die Klammern dürfen in einem konkreten C-Programm daher nicht eingetippt werden.
#include <conio.h>
+
#include <stdio.h>
+
  
void main(int argc,char *argv[])
+
=====<tt><Bezeichner></tt>=====
{
+
Bezeichner in C dienen dazu, Variablen zu identifizieren und ihnen sprechende Namen zu geben, um die Quelle lesbarer zu machen. Man braucht Bezeichner auch, um selbstdefinierte Datentypen zu benennen und zum Benennen von Struct- und Union-Komponenten sowie als Namen für Funktionen und Sprungmarken (Labels).
  int i;
+
  
  clrscr();
+
Bezeichner dürfen aus den Kleinbuchstaben <tt>a</tt>...<tt>z</tt>, den Großbuchstaben <tt>A</tt>...<tt>Z</tt>, dem Unterstrich&nbsp;<tt>_</tt> und den Ziffern <tt>0</tt>...<tt>9</tt> aufgebaut werden, wobei an erster Stelle jedoch keine Ziffer stehen darf.
  printf("Es wurden %d Parameter angegeben",argc);
+
  for (i=0; i<argc;i++) printf("Parameter %d: %s\n",i,argv[i]);
+
  getch();
+
}
+
</pre>
+
  
'''Erklärung:'''
+
Es wird zwischen Groß- und Kleinschreibung unterschieden.
Bei der 1. Ausgabe wird ausgegeben, wie viele Parameter insgesammt angegeben wurden. Aber Achtung: Es gibt immer mindestens einen Parameter! In argc[0] steht nämlich die aufgerufene EXE-Datei. Außerdem: da die Liste bei argv[0] anfängt, ist der maximal gültige Parameter argv[argc-1]!
+
In der For-Schleife werden alle Parameter, inklusive ihrer Nummer, ausgegeben. Experimentieren Sie hier mit Parametern, um das System zu vertehen!
+
  
 +
=====<tt><Ausdruck></tt>=====
 +
Ein Ausdruck in C ist ein Konstrukt, das einen Wert hat. Ob dieser Wert eine ganze Zahl ist, eine Kommazahl oder ein Zeiger, etc. ist dabei egal. Die einfachsten Ausdrücke sind Konstanten wie
 +
2
 +
oder Variablen wie
 +
ein_zahl
 +
Mehrere Ausdrücke können durch [[#Liste der Operatoren|Operatoren]] zu komplexeren Ausdrücken kombiniert werden, etwa
 +
eine_zahl + andere_zahl == 2
 +
oder
 +
eine_zahl = 2
 +
Letzterer hat den Wert&nbsp;<tt>2</tt> und den Nebeneffekt, daß er diesen Wert an <tt>eine_zahl</tt> zuweist.
  
'''Autoren'''
+
Auch der Aufruf einer Funktion, die einen Rückgabewert liefert, ist ein Ausdruck:
 +
sin (1.2)
 +
und kann zum Aufbau komplexerer Ausdrüche verwendet werden.
 +
 
 +
=====<tt><Bedingung></tt>=====
 +
Eine Bedingung ist ein Ausdruck, bei der nur interessiert, ob dieser zu&nbsp;0 (unwahr) auswertet oder zu ungleich&nbsp;0 (wahr). Solche Ausdrücke findet man in if-Anweisungen, in Schleifenbedingungen und bedingten Zuweisungen
 +
(ein_wert < 2) || (ein_wert > 40)
 +
 
 +
=====<tt><Lvalue></tt>=====
 +
Ein Lvalue ist ein Ausdruck, dem etwas zugewiesen werden kann. Der Name ''Lvalue'' kommt aus dem Englischen. Das ''L'' steht abkürzend für left. Ein Lvalue ist damit ein Ausdruck, der auf der linken Seite eine Zuweisung in C stehen darf. Das <tt>x</tt> in den folgenden Beispiel-Ausdrücken muss ein Lvalue sein:
 +
x = y-1
 +
 
 +
x++
 +
 
 +
=====<tt><Konstante></tt>=====
 +
Eine Konstante ist ein Ausdruck, dessen Wert dem Compiler bekannt ist. Beispiele für Konstanten sind etwa
 +
7
 +
'B'
 +
-13.98e12
 +
1+(2*3)
 +
und die Werte von [[#Enum|Enum]]s.
 +
 
 +
Das <tt>Pi</tt> aus dem folgenden Codestück definiert jedoch keine Konstante in diesem Sinne
 +
const double Pi = 3.14159256;
 +
denn in einem anderen Quellmodul könnte durch die Deklaration
 +
extern const double Pi;
 +
das Symbol <tt>Pi</tt> bekannt sein, ohne daß sein Wert bekannt ist!
 +
 
 +
=====<tt><Adresse></tt>=====
 +
Eine Adresse ist ein Ausdruck, der einen Speicherort (physikalisch oder virtuell) halten kann. Adressen erhält man dadurch, daß man einem Bezeichner den Adress-Operator&nbsp;<tt>&</tt>voranstellt, Adressen durch Arithmetik berechnet oder Zahlen zu Adressen castet. Folgende Ausdrücke sind Adressen (eine sinnvolle Deklaration der auftretenden Variablen vorausgesetzt)
 +
& eine_zahl
 +
& ein_array[10]
 +
& ein_struct
 +
& ein_struct.komponente
 +
(int *) 0x1234
 +
(int *) eine_zahl
 +
 
 +
=====<tt><Deklaration></tt>=====
 +
 
 +
=====<tt><Anweisung></tt>=====
 +
Anweisungen sind gewissermassen die Atome (oder Moleküle?), aus denen ein C-Programm besteht. Jedes C-Programm ist eine Abfolge von Deklarationen und Anweisungen. Einfache Anweisungen erhält man, in dem man einen Ausdruck nimmt und einen Strichpunkt dahinter schreibt:
 +
{{Ausdruck|}};
 +
wie in
 +
x = x+1;
 +
 
 +
Andere Anweisungen sind die unten aufgeführten Schleifen und die if- sowie die switch-Anweisung.
 +
 
 +
Mehrere Deklarationen und Anweisungen können zu einem Block zusammengefasst werden. Dieser Block stellt dann wieder eine einzelne Anweisung dar und kann genau so gehandhabt werden!
 +
{
 +
    {{Deklaration}}
 +
    {{Deklaration}}
 +
    ...
 +
    {{Anweisung}}
 +
    {{Anweisung}}
 +
    ...
 +
}
 +
In diesem Sinne ist auch z.B. die Syntax der if-Anweisung zu verstehen
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
besagt, daß der abhängig ausgeführte Code eine einzelne Anweisung sein darf oder eben ein kompletter Block oder die Verschachtelung mehrerer Blöcke etc.
 +
 
 +
Eine Anweisung kann auch "leer" sein, also nichts tun. Diese Anweisungen sind der leere Block
 +
{
 +
}
 +
und der Strichpunkt
 +
;
 +
die man gelegentlich in Schleifen findet:
 +
while (!timeout())
 +
    {}
 +
oder hinter Sprungmarken, die sonst direkt vor einer schliessenden Blockklammer stünden:
 +
{
 +
    ...
 +
    goto ein_label;
 +
    ...
 +
    {{Label|ein_label}}:;
 +
}
 +
 
 +
Nicht jede Anweisung ist an jeder Stelle eines C-Programms erlaubt, so darf ein <tt>continue</tt> nut innerhalb einer Schleife stehen. Gleiches gilt für <tt>break</tt>, das aber auch innerhalb eines <tt>switch</tt> vorkommen darf.
 +
 
 +
=====<tt><Type></tt>=====
 +
Dies steht für einen Datentyp. Es kann ein elementarer Typ sein wie <tt>int</tt> oder <tt>double</tt>, ein Zeiger darauf wie <tt>char*</tt> oder <tt>void*</tt>, und auch Qualifier enthalten wie das <tt>unsigned</tt> im Typ <tt>unsigned long long</tt>.
 +
 
 +
Zu den Typen gehören auch zusammengesetzte Datentypen wie Strukturen und Unions, mit <tt>typedef</tt> selbst definierte Typen und natürlich Zeiger darauf, wie aus dem Abschnitt [[#Datentypen|Datentypen]]:
 +
* <tt>struct Person</tt>
 +
* <tt>struct Person *</tt>
 +
* <tt>data32_t</tt>
 +
* <tt>enum Farben</tt>
 +
und Zeiger auf Funktionen.
 +
 
 +
=====<tt><Parameterliste></tt>=====
 +
 
 +
Die Parameterliste bei einer Funktionsdefinition gibt an, wieviel Übergabeparameter sie bekommt, wie diese heissen und welchen Typs diese sind. Der prinzipielle Aufbau ist
 +
{{Type}} {{Bezeichner}}, {{Type}} {{Bezeichner}}, ...
 +
Falls die Funktion keine Parameter hat, dann ist die Parameterliste leer.
 +
 
 +
Hier als Beispiel die zweiparameterige Funktion <tt>produkt</tt>. Der erste Parameter heisst&nbsp;<tt>a</tt> und ist ein <tt>double</tt>. Der zweite namens&nbsp;<tt>b</tt> ist vom Typ "Zeiger auf double", der Inhalt <tt>*b</tt> ist also auch ein double.
 +
 
 +
'''Definition der Funktion:'''
 +
double produkt (double a, double *b)
 +
{
 +
    return a * (*b);
 +
}
 +
In älteren C-Quellen findet man noch eine andere Syntax für die Deklaration der Parameter, die aber heute praktisch nicht mehr verwendet wird:
 +
'''alte Definition der Funktion:'''
 +
double produkt (a, b)
 +
double a, *b;
 +
{
 +
    return a * (*b);
 +
}
 +
 
 +
Um die Funktion bekannt zu machen, verwendet man eine Deklaration bzw. den Prototypen, der dem Compiler nur mitteilt, welche Parameter die Funktion bekommt und was sie zurückliefert. Für den Aufruf der Funktion muss der Compiler nur diesen Prototyp kennen, ''was'' die Funktion im Endeffekt macht und wie sie implementiert wurde ist egal, sie wird als BlackBox angesehen.
 +
 
 +
'''Prototyp der Funktion:'''
 +
double produkt (double a, double *b);
 +
Hier dürfen die Bezeichner auch fehlen:
 +
double produkt (double, double*);
 +
 
 +
==if==
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
 
 +
==if-else==
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
else
 +
    {{Anweisung}}
 +
 
 +
==for==
 +
for ({{Ausdruck|1}}; {{Bedingung}}; {{Ausdruck|2}})
 +
    {{Anweisung}}
 +
Eine for-Schleife entspricht folgendem Konstrukt. Dabei sind die drei Ausdrücke optional. Fehlt die Bedingung, dann wird diese als "wahr" angenommen. Die beiden anderen Ausdrücke wird man als Ausdrücke mit Nebeneffekt wählen wie z.B. <tt>x=0</tt> oder <tt>x=x-2</tt>.
 +
{
 +
    {{Ausdruck|1}};
 +
 +
    {{Label|_loop}}:
 +
    if ({{Bedingung}})
 +
      {{Anweisung}}
 +
    else
 +
      goto _break;
 +
 +
    {{Label|_continue}}:
 +
    {{Ausdruck|2}};
 +
    goto _loop;
 +
 +
    {{Label|_break}}:;
 +
}
 +
Die Labels <tt>_break</tt> und <tt>_continue</tt> entsprechen den Sprungzielen einer <tt>break</tt> bzw. <tt>continue</tt>-Anweisung innerhalb von <tt><Anweisung></tt>.
 +
 
 +
==do-while==
 +
do
 +
    {{Anweisung}}
 +
while  ({{Bedingung}});
 +
 
 +
==while==
 +
while  ({{Bedingung}})
 +
    {{Anweisung}}
 +
 
 +
==switch==
 +
switch  ({{Bedingung}})
 +
{
 +
    case {{Konstante}}:
 +
      {{Anweisung}}
 +
      {{Anweisung}}
 +
      ...
 +
 +
    case {{Konstante}}:
 +
      {{Anweisung}}
 +
      {{Anweisung}}
 +
      ...
 +
 +
    ...
 +
 
 +
    default:
 +
      {{Anweisung}}
 +
      {{Anweisung}}
 +
      ...
 +
}
 +
 
 +
=Liste der Schlüsselworte=
 +
[[#Speicherklassen|<tt>auto</tt>]],
 +
[[#break-Anweisung|<tt>break</tt>]],
 +
[[#Elementare Datentypen|<tt>double</tt>]],
 +
[[#Elementare Datentypen|<tt>char</tt>]],
 +
[[#switch-Anweisung|<tt>case</tt>]],
 +
[[#Konstanten|<tt>const</tt>]],
 +
[[#continue-Anweisung|<tt>continue</tt>]],
 +
[[#switch-Anweisung|<tt>default</tt>]],
 +
[[#do-while-Schleife|<tt>do</tt>]],
 +
[[#if-Anweisung|<tt>else</tt>]],
 +
[[#Enum|<tt>enum</tt>]],
 +
[[#Speicherklassen|<tt>extern</tt>]],
 +
[[#Elementare Datentypen|<tt>float</tt>]],
 +
[[#for-Schleife|<tt>for</tt>]],
 +
[[#goto-Anweisung|<tt>goto</tt>]],
 +
[[#Inlining|<tt>inline</tt>]],
 +
[[#if-Anweisung|<tt>if</tt>]],
 +
[[#Elementare Datentypen|<tt>int</tt>]],
 +
[[#Elementare Datentypen|<tt>long</tt>]],
 +
[[#return-Anweisung|<tt>return</tt>]],
 +
[[#Speicherklassen|<tt>register</tt>]],
 +
[[#Elementare Datentypen|<tt>short</tt>]],
 +
[[#Elementare Datentypen|<tt>signed</tt>]],
 +
[[#Liste der Operatoren|sizeof]],
 +
[[#Speicherklassen|<tt>static</tt>]],
 +
[[#Strukturen|<tt>struct</tt>]],
 +
[[#switch-Anweisung|<tt>switch</tt>]],
 +
[[#Eigene Datentypen|<tt>typedef</tt>]],
 +
[[#Unions|<tt>union</tt>]],
 +
[[#Elementare Datentypen|<tt>unsigned</tt>]],
 +
[[#Elementare Datentypen|<tt>void</tt>]],
 +
[[#Speicherklassen|<tt>volatile</tt>]],
 +
[[#while-Schleife|<tt>while</tt>]]
 +
 
 +
=Liste der Operatoren=
 +
 
 +
{| {{Blauetabelle}}
 +
|- {{Hintergrund2}}
 +
!| Operator || Bedeutung
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Arithmetische Operatoren
 +
|-
 +
|colspan="2"| Dies sind die "normalen" arithmetischen Operationen, wie man sie aus der Schule kennt. Man kann damit und allen anderen Operatoren auch komplexere Ausdrücke aufbauen. Die Prioritäten sind so, wie man sie kennt, also "Punktrechnung vor Strichrechnung". Will man dies ändern, dann mit den runden Klammern:<br/>
 +
<tt>1+2*3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; 7<br/>
 +
<tt>(1+2)*3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; 9<br/>
 +
|-
 +
| <tt>{{Ausdruck|}}&nbsp;+&nbsp;{{Ausdruck|}}</tt> || Addition
 +
|-
 +
| <tt>{{Ausdruck|}} - {{Ausdruck|}}</tt> || Subtraktion
 +
|-
 +
| <tt>{{Ausdruck|}} * {{Ausdruck|}}</tt> || Multiplikation
 +
|-
 +
| <tt>{{Ausdruck|}} / {{Ausdruck|}}</tt> || Division
 +
|-
 +
| <tt>{{Ausdruck|}} % {{Ausdruck|}}</tt> || Rest der Division (modulo)
 +
|-
 +
| <tt>- {{Ausdruck|}}</tt> || Vorzeichenumkehr, Zweier-Komplement
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Logische Operatoren und Vergleiche
 +
|-
 +
|colspan="2"|Die logischen und die vergleichenden Operatoren liefern als Ergebnis den Wert&nbsp;<tt>0</tt> (wahr) oder einen Wert ungleich&nbsp;<tt>0</tt> (falsch, um genau zu sein den Wert <tt>!0</tt>).
 +
 
 +
Man kann das Ergebnis zwar einer Variablen zuweisen, in aller Regel wird man solche Ausdrücke jedoch in Bedingungen zu <tt>if</tt> oder in Abbruch-Bedingungen von Schleifen finden.
 +
|-
 +
| <tt>{{Ausdruck|}} && {{Ausdruck|}}</tt> || logisches AND: beides wahr (ungleich 0)
 +
|-
 +
| <tt>{{Ausdruck|}} &#124;&#124; {{Ausdruck|}}</tt> || logisches OR: mind. eines ist wahr (ungleich 0)
 +
|-
 +
| <tt>! {{Ausdruck|}}</tt> || logisches NOT (0 &harr; ungleich 0)
 +
|-
 +
| <tt>{{Ausdruck|}} == {{Ausdruck|}}</tt> || ist gleich
 +
|-
 +
| <tt>{{Ausdruck|}} != {{Ausdruck|}}</tt> || ist nicht gleich
 +
|-
 +
| <tt>{{Ausdruck|}} &lt; {{Ausdruck|}}</tt>  || ist kleiner
 +
|-
 +
| <tt>{{Ausdruck|}} &lt;= {{Ausdruck|}}</tt> || ist kleiner oder gleich
 +
|-
 +
| <tt>{{Ausdruck|}} &gt; {{Ausdruck|}}</tt>  || ist größer
 +
|-
 +
| <tt>{{Ausdruck|}} &gt;= {{Ausdruck|}}</tt> || ist größer oder gleich
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Bitweise Operatoren
 +
|-
 +
| <tt>~ {{Ausdruck|}}</tt> || bitweise NOT (Einser-Komplement)
 +
|-
 +
| <tt>{{Ausdruck|}} & {{Ausdruck|}}</tt> || bitweise AND
 +
|-
 +
| <tt>{{Ausdruck|}} &#124; {{Ausdruck|}}</tt> || bitweise ODER
 +
|-
 +
| <tt>{{Ausdruck|}} ^ {{Ausdruck|}}</tt> ||bitweise XOR
 +
|-
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Shift-Operatoren
 +
|-
 +
| <tt>{{Ausdruck|}} << {{Ausdruck|}}</tt> || Bits nach links schieben
 +
|-
 +
| <tt>{{Ausdruck|}} >> {{Ausdruck|}}</tt> || Bits nach rechts schieben
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Typen
 +
|-
 +
|colspan="2"|Ein Cast in C kann dazu verwendet werden, den Typ eines Ausdruckes zu ändern oder den Ausdruck mit einer bestimmten Genauigkeit zu berechnen. Wird z.B. eine Berechnung standardmässig in 16 Bit ausgeführt, dann kann man mit einem Cast <br/>
 +
<tt>(long) &middot;&middot;&middot;</tt><br/>
 +
ausdrücken, daß die Berechnung in 32 Bit erfolgen soll. Des weiteren kann man Zeiger und ganze Zahlen und Gleitkommazahlen ineinander umwandeln.
 +
 
 +
Casts können ''nicht'' dazu verwendet werden, um z.B. eine Zahl in einen String zu konvertieren, der diese Zahl darstellt! Dafür gibt es spezielle Funktionen wie <tt>itoa</tt>!
 +
|-
 +
| <tt>({{Type}}) {{Ausdruck|}}</tt> || Cast, Typwandlung
 +
|-
 +
|valign="top"| <tt>sizeof ({{Type}})</tt> || Eine Konstante, deren Wert die Größe (in Bytes) des Typs ist. <tt>sizeof</tt> ist auch auf Objekte anwendbar wie <tt>int</tt>, Arrays bekannter Größe, Strukturen und Unions,  Array-, Struktur- und Union-Komponenten, Pointer, etc. Beispiel:<br/>
 +
int i, sum=0, array[] = { 1, -13, 4, 0, sizeof (int*) };
 +
 +
for (i=0; i< sizeof (array) / sizeof (array[0]); i++)
 +
    sum += array[i];
 +
Alle Elemente des Arrays werden aufaddiert, ohne daß deren Anzahl explizit in der Schleife genannt ist.
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Zeiger und Adressen
 +
|-
 +
| <tt>* {{Adresse}}</tt> || der Inhalt an Adresse
 +
|-
 +
| <tt>& {{Lvalue}}</tt> || Adresse von
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Strukturen, Unions, Arrays
 +
|-
 +
| <tt>{{blau|<Struct>}}.{{Bezeichner}}</tt> || Komponente einer Struktur/Union
 +
|-
 +
| <tt>{{blau|<Zeiger-auf-Struct>}} -> {{Bezeichner}}</tt> || Komponente einer Struktur/Union, deren Adresse man hat
 +
|-
 +
| <tt>{{Adresse}}&#91;{{Ausdruck|}}&#93;</tt> || Array-Element
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Bedingte Auswertung
 +
|-
 +
| <tt>({{Bedingung}}) ? {{Ausdruck|}} : {{Ausdruck|}}</tt> || Auswahl des Wertes abhängig von der Bedingung
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Zuweisung und Operatoren mit Nebeneffekt
 +
|-
 +
|colspan="2"|Die Unterschiede der post- und pre-Varianten der Increment/Decrement kommen in Konstrukten wie <tt>x = *p++</tt> zum tragen:
 +
<tt>x = *p++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; <tt> x = *p; p = p+1;</tt><br/>
 +
<tt>x = *++p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; <tt> p = p+1; x = *p;</tt><br/>
 +
<tt>x = (*p)++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; <tt> x = *p; *p = (*p)+1;</tt><br/>
 +
<tt>x = ++(*p);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; <tt> *p = (*p)+1; x = *p;</tt>
 +
|-
 +
| <tt>{{Lvalue}} = {{Ausdruck|}}</tt> || Zuweisung
 +
|-
 +
| <tt>++ {{Lvalue}}</tt> || Pre-Increment
 +
|-
 +
| <tt>-- {{Lvalue}}</tt> || Pre-Decrement
 +
|-
 +
| <tt>{{Lvalue}} ++</tt> || Post-Increment
 +
|-
 +
| <tt>{{Lvalue}} --</tt> || Post-Decrement
 +
 
 +
|- {{Hintergrund1}}
 +
!colspan="2"|Kurzschreibweisen
 +
|-
 +
|colspan="2"| Für ganz Faule gibt es anstatt<br/>
 +
<tt>a = a @ b</tt><br/>
 +
für viele Operatoren (hier dargestellt durch ein&nbsp;<tt>@</tt>) die abkürzende Schreibweise<br/>
 +
<tt>a @= b</tt>
 +
|-
 +
| <tt>{{Lvalue}} += {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} -= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} *= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} /= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} %= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} ^= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} &= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} &#124; {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} <<= {{Ausdruck|}}</tt> ||
 +
|-
 +
| <tt>{{Lvalue}} >>= {{Ausdruck|}}</tt> ||
 +
|}
 +
 
 +
= Autoren=
 
* Plasma
 
* Plasma
 
* Bernd
 
* Bernd
 +
* [[Benutzer:SprinterSB|SprinterSB]]
  
'''Quellen:'''
+
=Quellen=
* Kernighan und Ritchie - Buch
+
* Kernighan und Ritchie
* Christian Wirth , C Tutorial
+
* Christian Wirth, C-Tutorial
* Prof. Dr. J. Dankert Ausführungen  
+
* Prof. Dr. J. Dankert Ausführungen
 
+
* W. Alex, Einführung in C/C++
 +
* Peter Baeumle-Courth, ANSI-C im Überblick
  
==Siehe auch==
+
=Siehe auch=
* [[Avr-gcc]]
+
* [[C-Tutorial/Interrupt-Programmierung]]
 +
* [[avr-gcc]]
 +
* [[Compiler]]
 +
* [[WinAVR]]
 +
* [[Fallstricke bei der C-Programmierung]]
 +
* [[Code::Blocks]]
 +
* [[Installationsanleitung_von_avrlib]]
  
==Weblinks==
+
=Weblinks=
 
* [http://www.uni-bayreuth.de/departments/math/~rbaier/lectures/c_ss2002/html/html.html C-Tutorial Uni Bayreuth]
 
* [http://www.uni-bayreuth.de/departments/math/~rbaier/lectures/c_ss2002/html/html.html C-Tutorial Uni Bayreuth]
* [http://www.gdv.uni-hannover.de/doc/cpp/cundcpp/ctutor1.pdf PDF-Tutorial ANSI-C für UNIX und DOS]
+
* [http://wwwuser.gwdg.de/~kboehm/ebook/inhalt.html C-Programmieren unter Linux]
* [http://www.gdv.uni-hannover.de/doc/cpp/cundcpp/ctutor2.pdf PDF-Tutorial C-Programmierung für MS-Windows]
+
* [http://www.gdv.uni-hannover.de/documentation.php Skripte zum Selbststudium: C, C++, Java, etc]
* [http://www.gdv.uni-hannover.de/doc/cpp/cundcpp/ctutor3.pdf PDF-Tutorial C++ für C-Programmierer]
+
* [http://info.baeumle.com/ansic.html Einführung in ANSI-C]
 +
* [http://roboternetz.de/download/c_tutorial.zip Download umfangreiches C-Tutorial mit Beispielen für RN-Control und andere AVR-Boards im ZIP-Archiv]
  
[[Kategorie:Microcontroller]]
+
[[Kategorie:Quellcode C|!]]
 
[[Kategorie:Software]]
 
[[Kategorie:Software]]
[[Kategorie:Robotikeinstieg]]
 
 
[[Kategorie:Grundlagen]]
 
[[Kategorie:Grundlagen]]

Aktuelle Version vom 8. April 2014, 14:36 Uhr

Dieser Artikel ist noch lange nicht vollständig. Der Auto/Initiator hofft das sich weitere User am Ausbau des Artikels beteiligen.

Das Ergänzen ist also ausdrücklich gewünscht! Besonders folgende Dinge würden noch fehlen:

Mehr Grundlagen und vor allem Programmbeispiele etc.


Die Programmiersprache C wurde 1971 als Grundlage für das Betriebssystem UNIX in den USA entwickelt (UNIX ist zu über 90% in C geschrieben). 1978 wurde von Brian Kernighan und Dennis Ritchie eine eindeutige Sprachdefinition entwickelt. Mittlerweile ist C von ANSI und ISO standardisiert.

Heute sind C und ihr Nachfolger C++ die dominierenden Programmiersprachen. Sehr viele Anwendungen sind in C geschrieben, was inzwischen auch auf eingebettete Systeme zutrifft, die lange in Assembler programmiert werden mussten, da keine ausreichend leistungsfähigen Compiler zur Verfügung standen.

Leider ist C nicht einfach zu lernen – es wurde weder von noch für Hobby-Programmierer entwickelt – und eignet sich daher nur bedingt für den Einsteiger. Mit etwas Übung und einem optimierenden Compiler kann man damit jedoch sehr effiziente Programme schreiben.

Vom Design her ist C eine Hardware-unabhängig Sprache. Das bedeutet, daß C-Programme mit vertretbarem Aufwand auf ein anderes System portiert werden können. Dazu benötigt man lediglich einen anderen Compiler, und Inline-Assembler-Anweisungen (Assembleranweisungen innerhalb eines C-Programmes) müssen der neuen Hardware (Prozessor, Mikrocontroller) angepasst werden.

Inhaltsverzeichnis

Geschichte

1971
C wird entwickelt
1978
Kernighan und Ritchie definieren die Sprache.
1983
ANSI und ISO standardisieren C.
1992
Bjarne Stroustrup enwickelt die Nachfolgesprache C++.

Aufbau eines C-Programmes

Ein einfaches C-Programm könnte folgendermassen aussehen. Das Programm tut eigentlich nichts, aber das Beispiel zeigt den prinzipiellen Aufbau.

#include <stdio.h>

int Zahl1;
char Zeichen1;

int main (void)
{
   int zahl2;

   /* Anweisungen */
   return 0;
}

Beschreibung:

#include <...>
Die Include-Direktive sagt dem Compiler, welche Header-Dateien er einbinden soll. In den Header-Dateien und den dazugehörigen Bibliotheken stehen Funktionen und Datentypen, die nicht im Compiler selbst implementiert sind, etwa komplexe Ausgabefunktionen wie "printf", die weiter unten erklärt wird. Durch den Include kann man solche Funktionen nutzen. Elementare Dinge hingegen, wie die mathematischen Operatoren +,-,*, etc. sind im Compiler selbst eingebaut.
int Zahl1;
Diese Zeile definiert eine Variable vom Typ int. Diese Variable ist im ganzen Programm gültig, sie ist global. Jede Deklaration/Anweisung in C wird mit einem Strichpunkt (Semikolon ;) abgeschlossen und dadurch von der nächsten Deklaration/Anweisung getrennt.
char Zeichen1;
Hier geschieht das selbe, nur wird diesmal eine Variable des Types char definiert.
int main (void)
definiert ein Unterprogramm mit dem Namen main, das keine Parameter hat (void) und eine ganze Zahl (int) zurückliefert. "main" ist das Hauptprogramm in C, wo mit der Ausführung nach dem Programmstart begonnen wird.
{
Die linke geschwungenen Klammer beginnt den Rumpf (auch "body" genannt) der main-Funktion. Danach folgen Variablendefinitionen, Kommentare und Anweisungen von main.
int zahl2;
Innerhalb von "main" wird die lokale Variable zahl2 definiert.
/* Anweisungen */
Das ist ein Kommentar in C. Hier kann man Anmerkungen zum Code hinschreiben oder Codestücke "auskommentieren", um sie zu deaktivieren. Der Kommentar beginnt mit /* und wird beendet mit einem */. Er kann mehrere Zeilen überspannen. Je nach C-Compiler werden auch einzeilige Kommentare mit // akzeptiert, die nur bis zum nächsten Zeilenende reichen. Sie gehören jedoch nicht zum standard ANSI-C. Die Leerzeile nach dem Kommentar wird nicht weiter berücksichtig, sie kann zur Untergliederung des Codes zur besseren Lesbarkeit eingefügt werden.
return 0;
Gibt den Wert 0 zurück und beendet das Programm. Vor dem return können natürlich noch C-Anweisungen stehen, die aber erst weiter unten erklärt werden.
}
Die schliessende geschwungenen Klammer beendet den Rumpf des Hauptprogramms.

Das Hauptprogramm main

Die erste Funktion, die nach dem Programmstart ausgeführt wird, ist im Allgemeinen die Funktion mit dem Namen "main". Diese ist das Hauptprogramm.

Der main-Funktion können beim PC Parameter übergeben werden. Dies sind die sogenannten Kommandozeilenparameter und evtl. Umgebungsvariablen, die beim Aufruf eines Programmes hinter dem Dateinamen stehen. Zudem wird auch ein int-Wert als Ergebnis zurückgeliefert, der den Aufrufer – üblicher weise eine Shell – den Erfolg bzw. Fehlerstatus des Programmes mitteilt.

Beim Microcontroller ist main das Startprogramm, das nach dem RESET und der Initialisierung aufgerufen wird. Hier gibt es also keine Funktionsparameter. Ein Rückgabewert ist auch nicht sinnvoll, so daß main oft als void-Funktion (ohne Rückgabewert) definiert wird. Um Compilerfehler/Warnungen zu vermeiden, muss der Compiler dann aber mit speziellen Einstellungen gestartet werden, denn C-Standard ist, daß main einen Wert zurückliefert!

/* void-Definition von main ist nur beim Controller ueblich */
/* spezielle Compilereinstellungen sind noetig, damit bei dieser Definition von main */
/* kein Fehler/Warnung erzeugt wird. */
void main ()
{
    ...
}

Blöcke

Im vorigen Abschnitt haben Sie bereits die geschwungenen Klammern { und } kennen gelernt. C-Programme sind in so genannte Blöcke unterteilt. Da gibt es zum einen das Hauptprogramm und die jeweiligen Unterprogramme, aber auch Schleifen und bedingte Anweisungen. Jedes dieser Konstrukte stellt ein eigenständiges Stück Code dar, das als solches gekennzeichnet werden muss.

Die Kennzeichnung für einen Blockanfang ist die öffnende geschweifte Klammer, während ein Blockende mit der schließenden Klammer notiert wird. Ein Block kann mehrere Teilblöcke enthalten, die wiederum Teilblöcke enthalten dürfen etc. Öffnende und schließende Block-Klammern tauchen immer paarweise auf.

Um Programme übersichtlich zu gestalten, wird jeder Block oder Teilblock eingerückt (siehe unten). Dann ist auch bei längerem Code sofort ersichtlich, wo ein Block beginnt und wo er endet. Geschweifte Klammern werden dann nur selten vergessen, und falls doch kann der Fehler schnell erkannt werden.

Variablen, die in einem Block deklariert werden, sind nur innerhalb dieses Blocks – und damit auch in allen seinen Teilblöcken – gültig.

int main (void)
{  /* der Block "main" beginnt */
   int zahl;
   
   {   /* ein Block beginnt */
       /* hier koennen Deklarationen und Anweisungen stehen */
   }   /* der Block endet */
  
   return 0;
}  /* "main" endet */

Datentypen

Elementare Datentypen

Der Datentyp einer Variable gibt an, welche Werte eine Variable enthalten kann, welcher Art diese Daten sind und wie sie verarbeitet werden, etwa in arithmetischen Operationen wie einer Addition. So ist es zum Beispiel möglich, in eine Variable vom Typ int ganze Zahlen zwischen ca. -32000 und +32000 einzutragen. In einer char-Variable können ASCII-Zeichen gespeichert werden (alles, was Sie mit der Tastatur erzeugen können) oder ganze Zahlen von -128 bis 127.

Achtung
Da C plattformabhängig ist, hängt die Größe eines Datentypes zum Teil von der genutzten Hardware (z.B. 8, 16 oder 32 Bit-Controller) und dem Compiler und dessen Einstellungen ab!

int, char, short, long (ganze Zahlen)

In Variablen dieser Typen können Sie ganze Zahlen abspeichern, also z.B. 1, -2, 100, 12345. Jeden dieser Typen gibt es in zwei Ausprägungen: als "signed", also als vorzeichenbehafteten Typ, und als "unsigned", also ohne Vorzeichen, d.h. das Vorzeichen wird als 0 oder +1 genommen.

Vorzeichenbehaftete Ganzzahl-Typen werden intern im n-1-Komplement dargestellt, das Vorzeichen selbst findet sich also im höchstwertigen Bit. Werden zur Speicherung b Bits verwendet, dann reicht der Wertebereich von -2b-1 bis zu 2b-1-1.

Bei Ganzzahl-Typen ohne Vorzeichen reicht der Wertebereich von 0 bis zu 2b-1, wenn der Typ b Bits breit ist.

Größe (Bit) Typ Vorzeichen Grenzen des Wertebereichs
8 char signed
unsigned
-128
0
127
255
16 short signed
unsigned
-32.768
0
32.767
65.535
32 long signed
unsigned
-2.147.483.648
0
2.147.483.647
4.294.967.295
64 long long signed
unsigned
-9.223.372.036.854.775.808
0
9.223.372.036.854.775.807
18.446.744.073.709.551.615
8, 16, 32, 64
int signed
unsigned
plattform-/compilerabhängig plattform-/compilerabhängig

Boolean (Logische Variablen)

In der Sprache C gibt es keinen Datentyp für boolsche Werte "wahr" bzw. "TRUE" oder "falsch" bzw. "FALSE". Statt dessen wird gerne der Datentyp int dafür verwendet. Hat die jeweilige Variable den Wert 0, so ist sie FALSE, sonst (ungleich 0) ist sie TRUE.

Hinweis
Bitte beachten, daß eine Variable, die TRUE ist, nicht unbedingt den Wert 1 haben muß. Sie muß lediglich ungleich 0 sein!

char (Zeichen)

In einer char-Variable können Sie 8-Bit-Werte speichern. Dieser Datentyp wird oft für ASCII-Zeichen genutzt, denn für den Computer ist es egal, ob sich eine Zahl oder ein Zeichen in der Variablen befindet. Er speichert alles in Form von Binärzahlen.

Dabei darf man eines nicht vergessen: Es macht einen großen Unterschied, ob man in einer char-Variablen das Zeichen '1' (ASCII-Zeichen Nr. 49) abspeichert, oder die Zahl 1 (das entspricht ASCII-Zeichen Nr. 1, also irgendeinem Sonderzeichen). Man kann zwar mit beiden rechnen, aber '1' * 2 ergibt nicht '2', sondern 'b' (ASCII-Zeichen Nr. 98)!

float, double (Gleitkommazahlen)

In einer Gleitkomma-Variable können Kommazahlen gespeichert werden, z.B. 3.141592654. float reicht für die meisten Kommazahlen. Werden jedoch noch höhere Genauigkeiten benötigt, kommt der Datentyp double zum Einsatz.

Vorsicht
bei PIC (microchip) ist die innere Darstellung dieser Zahlen anders als bei den meisten anderen Compilern, beim binären Senden z.B. zum PC muß dann konvertiert werden! Bei avr-gcc finden die Rechnungen intern mit float statt, auch wenn ein Typ als double deklariert ist.

void

Dies ist ein spezieller Typ, der soviel bedeutet wie "nicht vorhanden". Eine Funktion, die keinen Rückgabewert zurückliefert, definiert als Rückgabetyp void, und kennzeichnet damit, daß sie eben nichts zurückliefert. Objekte vom Typ void können nicht angelegt werden.

Zeiger

Jede Variable steht an einer definierten Stelle im Speicher, an ihrer sogenannten Adresse.

Ein Zeiger ist eine Variable, in der eine Adresse gespeichert werden kann. Diese stellt eine bestimmte Position im Arbeitsspeicher dar. Die Adresse eines Objektes erhält man, indem man ihm ein & voranstellt. Die Umkehrung davon – also der Zugriff auf die Speicherstelle, die im Zeiger enthalten ist – erledigt ein vorgestellter *. Der Operator  * gibt also den Inhalt der Adresse.

#include <stdio.h>

int main (void)
{
  int * zeiger;
  int zahl;
 
  zeiger = &zahl;
  *zeiger = 12;
  
  printf ("%d = %d", zahl, *zeiger);
  
  return 0;
}

Die Definition von zeiger als Zeiger ist so zu lesen: Der Inhalt von zeiger ist ein int. Damit wird zeiger zu einem "Zeiger auf int". Dabei gehört der * sinngemäß zum Bezeichner zeiger, nicht zum Typ. Folgende Definition definiert also nicht zwei Pointer, sondern einen Pointer (auf int) sowie einen int:

int * zeiger, zahl;

Um den Zeiger mit der Adresse von zahl zu laden, schreibt man den Adress-Operator & von zahl:

zeiger = &zahl; 

Jetzt möchten Sie der Speicherstelle, deren Adresse der Zeiger enthält, einen Wert zuweisen. Dazu verwendet man den "Inhalts-Operator" * (z.B. *zeiger = 12). Genauso können Sie mit dem Inhaltsoperator Werte abfragen und an printf (und jedes andere Unterprogramm) übergeben.

Enum

Über enum können Aufzählungen definiert werden. Die Werte sind int-Werte und beginnen mit 0. Der folgende enum hat einen um 1 grösseren Wert. Mit einer Zuweisung können auch andere Werte zugeordnet werden. Klarer wird's im Beispiel:

enum Farben
{
   ROT,
   GRUEN,
   BLAU,
   BRAUN = 5,
   SCHWARZ
};

Dies definiert die Konstanten ROT=0, GRUEN=1, BLAU=2, BRAUN=5 und SCHWARZ=6 und den Typ enum Farben:

void foo (enum Farben farbe)
{
   switch (farbe)
   {
      case ROT:
         ...

Damit kann man anstatt "magischer" Zahlen sprechende Namen im Code verwenden, etwa in Berechnungen und Zuweisungen, Vergleichen oder als Konstante hinter einem case.

Zusammengesetzte Datentypen

Arrays

Oft muß man sehr viele Werte gleichzeitig abspeichern und betrachten, die alle der selben Aufgabe dienen. Man schreibt z.B. ein Programm, das 10 Zahlen einlesen und anschließend wieder ausgeben soll. Man könnte das natürlich mit 10 einzelnen Variablen bewerkstelligen, aber es ist sinnvoller, dabei Arrays – teilweise auch als Felder bezeichnet – zu verwenden.

In einem Array werden mehrere Variablen gleichen Typs zusammengefasst und hintereinander im Speicher abgelegt. So kann man viele tausend Variablen anlegen mit nur einer Zeile Code. Doch es gibt noch größere Vorteile: Sie können das Array mit einer Schleife ganz einfach nach Werten durchsuchen. Stellen Sie sich vor, Sie müssten mit 100 verschiedenen Variablen Zahl_00 bis Zahl_99 arbeiten!

Syntax:

<Type> <Bezeichner>[<Konstante>];

Beispiel:

unsigned int werte[100];

Der Name muß natürlich ein gültiger Bezeichner sein, als Datentyp kann jeder Typ genommen werden – sowohl elementare Datentypen als auch Zeiger, Strukturen, Unions oder selbst definierte Datentypen. In der eckigen Klammer wird die Anzahl der Elemente bekanntgegeben. Ein mit [3] definiertes Array hat Platz für drei Variablen. Da der Index immer bei 0 beginnt, greift man also mit [0], [1] und [2] auf den jeweilige Inhalt zu. Um auf eine der im Array enthaltenen Variablen zugreifen zu können, müssen Sie den Variablennamen und in eckigen Klammern den Index (die "Nummer") der Variablen angeben. Diese Variable verhält sich dann wie eine ganz normale Variable des jeweiligen Datentypes.

#include <stdio.h>

#define NZAHLEN 10

int main(void)
{
   int i;
   int zahlen[NZAHLEN];  /* zahlen[0] ... zahlen[9] */
 
   for (i=0; i < NZAHLEN; i++)
   {
      printf ("Bitte Zahl %d eingeben: ", i);
      scanf  ("%d", & zahlen[i]);
      printf ("\n");
   }

   printf ("Super!\n");
   
   for (i=0; i < NZAHLEN; i++) 
      printf ("Zahl %d ist: %d\n", i, zahlen[i]);
     
   return 0;
}

Zuerst wird ein 10 int-Variablen großes Array angelegt. In dieses wird nun der Reihe nach 10 Zahlen eingelesen. Anschließend werden alle 10 Zahlen ausgegeben.

Dabei wird die Größe der Arrays und das Schleifenende über das Define "NZAHLEN" angegeben. Dadurch muss nur eine Stelle im Code geändert werden, wenn die Größe des Arrays einmal einen anderer Wert als 10 haben soll – dies vermeidet Fehler die dadurch entstehen, wenn man beim Anpassen der Array-Größe eine Codestelle vergisst, zudem wird der Code lesbarer als wenn irgendwo die Zahl "10" auftaucht.

Merke:

Wird ein ungültiger Index angeben (einer, der in der Deklaration nicht enthalten ist) können undefinierte Dinge passieren, wenn dadurch andere Variableninhalte oder Programmcode überschrieben wird, der hinter oder vor dem Array im Speicher liegt. Schlimmstenfalls kann sogar der Computer/Controller abstürzen. Also darauf achten, daß keine ungültigen Werte als Index auftreten!

Strings (Zeichenketten)

Ein String ist nichts anderes als ein Array, das aus einzelnen Zeichen (char) gebildet wird. Die Ausgabe auf dem Bildschirm funktioniert am einfachsten mittels Strings.

Die Definition eines Strings erfolgt also genauso wie bei Arrays:

char string[21];

Nun haben Sie eine String, in dem Sie 21 Zeichen speichern können. Ganz richtig ist das jedoch nicht. C arbeitet mit "null-terminierten Strings". Das beudeutet, dass die Länge des Strings nicht abgespeichert wird, sondern das Zeichen mit dem ASCII-Wert 0 das Stringende kennzeichnet. Daher auch die Bezeichnung "null terminiert".

Das letzte Zeichen eines Strings muß daher immer das ASCII-Zeichen Nr. 0 sein. Ist es das nicht, hat der String kein definiertes Ende, und wenn Sie versuchen, ihn durch eine Standard-Funktion auszugeben zu lassen, könnte es eine Weile dauern, bis sich im Speicher zufällig irgendwo eine 0 befindet. Es stehen ihnen daher bei dem Beispiel nur 20 Zeichen zur Verfügung.

Mehrdimensionale Arrays

Manchmal benötigt man mehr als nur ein eindimensionales Array, wie Sie es bisher kennengelernt haben. Auch dies ist kein Problem. In der Deklaration geben Sie einfach mehrere eckige Klammern hintereinander an. Aber Vorsicht: der Speicherplatz ist begrenzt, ein "char feld[1024][1024]" hat die Speicherplatzgrenzen vermutlich bereits weit überschritten, und der Compiler wird einen (bei gewissen Einstellung auch keinen) Fehler liefern. Beim Zugriff auf mehrdimensionale Felder müssen auch mehrere Indizes angeben werden:

#include <stdio.h>

int main(void)
{
  int x,y;
  int feld[3][5];
 
  for (x=0; x<3; x++) 
  {
     for (y=0; y<5; y++)
     {
       printf ("Feldwert x: %d,  y: %d ", x, y);
       scanf  ("%d", & feld[x][y]);
       printf ("\n");
     }
  }

  for(x=0; x<3; x++) 
     for (y=0; y<5; y++) 
        printf ("Wert: feld[%d][%d] = %d\n", x, y, feld[x][y]);

  return 0;
}

Erklärung:

Zuerst wird ein 3 mal 5 int-Array angelegt. Dann werden die Werte eingegeben: zuerst feld[0][0], dann feld[0][1], usw. bis feld[2][4]. Zum Schluß werden alle Werte noch einmal ausgegeben.

Strukturen

In C können Sie sogenannte "Strukturen" definieren. Dabei handelt es sich um eine Zusammenfassung mehrerer Datentypen zu einem größeren. Im Unterschied zu Feldern können in Strukturen unterschiedliche Datentypen zusammengestellt und gespeichert werden:

Syntax:

struct <Bezeichner>
{
   <Deklaration>
   <Deklaration>
   ...
};

Beispiel:

/* Definition der Struktur 'Person' */
struct Person 
{
   int id;
   char vname[20], nname[20];
   char telnr[15];
   int alter;
};

"struct Person {" leitet die Definition der Struktur mit dem Namen "Person" ein. Dann werden in dieser Struktur fünf Komponenten definiert: drei Strings und zwei int. Mit } wird die Definition abgeschlossen. Sie haben damit einen Datentyp erstellt. Um eine Variable des Typs struct Person anzulegen, geben Sie einfach an

struct Person <Bezeichner>;

Zum Zugriff auf eine Komponente der Struktur gibt man den Namen der Struktur-Variablen an (im folgenden Beispiel also hubert bzw. klaus), einen Punkt und danach den Bezeichner der Komponente:

/* Definition zweier Struktur-Variablen */
struct Person hubert, klaus;

/* Zugriff auf Struktur-Komponenten */
hubert.alter = 32;
klaus.alter = hubert.alter + 1;

Hinweis: Der eventuell etwas lästige Gebrauch von struct kann schon bei der Definition der Struktur vermieden werden. Der Definition ist ein typedef voranzustellen. Der Definition folgt dann ein gültiger (und auch eindeutiger) C-Name.

Syntax:

typedef struct <Bezeichner>
{
   <Deklaration>
   <Deklaration>
   ...
} <Bezeichner> ;


Beispiel:

/* Definition der Struktur '_Mensch' bzw. 'Mensch' */
typedef struct _Mensch
{
   int id;
   char vname[20], nname[20];
   char telnr[15];
   int alter;
} Mensch;

Jetzt sind folgende Deklarationen identisch:

struct _Mensch <Bezeichner>;
Mensch <Bezeichner>;

Ist der Struktuname nicht notwendig (im Beispiel oben _Mensch), kann auch kürzer geschrieben werden:

Syntax:

typedef struct 
{
   <Deklaration>
   <Deklaration>
   ...
} <Bezeichner> ;

Beispiel:

/* Definition der Struktur 'Mensch' */
typedef struct 
{
   int id;
   char vname[20], nname[20];
   char telnr[15];
   int alter;
} Mensch;

In diesem Fall ist lediglich die Deklaration Mensch <Bezeichner>; gültig.

Der Hinweis gilt sinngemäß auch für die Definition union im nachfolgenden Abschnitt.

Unions

Eine Union wird ganz analog zu einer Struktur deklariert und verwendet. Sie unterscheidet sich von einer Struktur jedoch dadurch, daß ihre Elemente nicht nacheinander im Speicher abgelegt werden, sondern sich überlagern. Auf die in einer Union enthaltenen Daten gibt es also verschiedene Sichten: je nachdem, welche Sicht bzw. Interpretation der Daten man gerne hätte, wählt man den gewünschten Zugriff.

union Daten 
{
   int id;

   struct Person u_person;

   struct u_double
   {
      int id;
      double wert;
   };

   struct u_pointer
   {
      int id;
      union Daten * p1;
      union Daten * p2;
   };
};

union Daten data;

Dies definiert eine Union mit den vier Zugriffsmöglichkeiten id, u_person, u_double und u_pointer. Die Größe der Union richtet sich dabei nach der größten Komponente. In diesem Beispiel sind alle Komponenten so angelegt worden, daß sie an erster Stelle ein int id enthalten. In data.id könnte man sich also merken, wie die Daten in der Union zu interpretieren sind. Würde struct Person nicht dieses id enthalten, so würde sich data.id mit data.u_person.vname überlagern. Ein Ändern der ersten Buchstaben von vname hätte also ein Ändern von id zur Folge, und man könnte es nicht mehr als Merker verwenden. Mit diesem Feld überlagert das id von data die id-Felder der anderen Sichten, z.B. ist data.id der selbe Zugriff wie auf data.u_person.id.

Ein anderes Beispiel ist eine Union, die es ermöglicht, auf die einzelnen Bytes eines long zuzugreifen:

typedef union
{
   unsigned long  as_long;
   unsigned short as_short[2];
   unsigned char  as_byte[4];
} data32_t;

Dies überlagert einen unsigned long – also eine 32-Bit-Zahl – mit vier Bytes bzw. zwei Shorts.

data32_t wert;

wert.as_long = 0x12345678;
wert.as_byte[0] = 0xab;
/* nun ist wert.as_long gleich 0xab345678 oder 0x123456ab (je nach Plattform) */

Eigene Datentypen

Variablen

Eine Variable ist ein Synonym (=anderer Name) für eine Speicherstelle in einem Computer. Einfacher gesagt, eine Variable bietet Raum, um Daten wie Zahlen oder Zeichen zu speichern und wieder zu lesen.

Variablennamen

Ein Variablenname kann zusammengesetzt werden aus den Buchstaben A bis Z und a bis z, den Ziffern 0 bis 9, sowie dem Sonderzeichen "Unterstrich" (underscore) _. Dabei darf an erster Stelle keine Ziffer stehen. Die Bezeichner hallo, HALLO, Hallo, HALL0, _123 und _HALLO sind also alle gültige und unterschiedliche Variablennamen.

Anlegen von Variablen

Um eine Variable verwenden zu können, muss sie zuerst vereinbart ("erzeugt") werden. Dies wird auch als "Definition der Variablen" bezeichnet und geht so: Schreiben Sie zuerst den Datentyp, dann den Namen der Variablen. Zum Schluß kommt noch der Strichpunkt, wie nach jeder C-Anweisung oder Deklaration. Und nicht vergessen: C unterscheidet zwischen Groß- und Kleinschreibung!

int Zahl1, Zahl2;
char Zeichen;

int main (void)
{
   float gleitZahl;
   /* Anweisungen */

   return 0;
}

Hinweis: In einer Zeile können auch mehrere Variablen gleichen Types vereinbart werden, wenn man ein Komma dazwischen setzt (siehe unten). Variablen können in jedem Block vereinbart werden. Siehe Gültigkeitsbereich.

   ...
   float gleitZahl1, gleitZahl2;
   ...

Zuweisungen

Man kann einer vereinbarten Variable Werte zuweisen. Dazu schreibt man zuerst den Variablennamen, ein Gleichheitszeichen "=" und anschliessend den zuzuweisenden Ausdruck.

int main (void)
{
  int zahl1, zahl2 = 12;
  char zeichen1 = 'A';

  zahl1 = 52;
  zeichen1 = zeichen1 + 1; 

  return 0;
}

Zuerst werden drei Variablen angelegt (zahl1, zahl2, zeichen1).

zahl2
wird gleich bei der Vereinbarung der Wert 12 zugewiesen.
zahl1 = 52
Hier wird der Variablen zahl1 der Wert 52 zugewiesen.
zeichen1
wird um 1 erhöht. Da in der Variablen 'A' gespeichert ist, gibt sich ihr neuer Wert aus 'A' + 1. Weil 'A' dem Wert 65 entspricht, ist 'A' + 1 gleich 66, was dem Wert für 'B' entspricht.

Zuweisungen bei float

Das funktioniert genau wie bei normalen Zuweisungen. Nachkommastellen werden durch einen Punkt abgegrenzt:

floatVariable = 3.14;

Zusätzlich kann eine Zehnerpotenz angegeben werden:

floatVariable2 = -1.234E-6;

Dadurch wird der erste Wert mit 10-6 multipliziert, der Wert der Variablen ist also

[math]-1{,}234\cdot10^{-6} = -0.000001234[/math].

Zuweisungen bei logischen Variablen

Wie bereits erwähnt, besitzt C keinen logischen Datentyp. Es müssen also int oder char dafür genutzt werden. Die Zuweisung entpricht der Standard-Zuweisung. Wird der Wert 0 zugewiesen, dann ist die Variable "unwahr", ansonsten ist sie "wahr".

intVariable = !0;   /* entspricht "wahr"   */
intVariable = 0;    /* entspricht "unwahr" */

Konstanten

Konstanten können als Variable angesehen werden, die nicht beschrieben, sondern nur gelesen werden können. Ein typisches Beispiel dafür ist die Zahl [math]\pi[/math] (rund 3,141592654). Niemand würde in der realen Welt versuchen, ihr einen anderen Wert zuzuweisen. Würde man [math]\pi[/math] jedoch wie eine normale Variable anlegen, wäre dies ohne weiteres möglich. Um dies zu verhindern, gibt es das Schlüsselwort const in C:

const <Type> <Bezeichner> = <Konstante>;  /* Zuweisung bei der Defininition der Variablen */

Wichtig dabei ist, dass man Konstanten nur bei der Vereinbarung einen Wert zuweisen kann. Da Konstanten gewöhnlich im gesamten Programm, zumindest einer Quelldatei genutzt werden, definiert man diese allerdings gewöhnlich außerhalb des main-Blockes entweder am Anfang eines Programmes, oder in einer sogenannten Header-Datei, die per #include eingebunden wird.

const float PI = 3.141592;  /* Zuweisung bei der Defininition der Variablen */

Es sei jedoch erwähnt, daß auch einer Konstanten nachträglich ein anderer Wert zugewiesen werden kann. Im obigen Beispiel könnte mit

* ((float*) &PI) = 2;

der Wert von PI im Nachhinein verändert werden. Es wird die Adresse von PI genommen und diese Adresse durch den Cast in eine ganz normale float-Adresse umgewandelt, über welche der Wert geändert wird. Die sei der Vollständigkeit halber erwähnt.

Je nachdem, an welcher Stelle sich das const bei einer Pointer-Deklaration befindet, markiert es den Pointer als konstant oder das Objekt, auf das dieser Pointer zeigt. Eine häufige Parameterdeklaration in Ausgabe-Funktionen, die einen String erhalten, ist

void foo (const char * str, ...);

Dadurch ist str der Zeiger auf eine Zeichenkette, die innerhalb der Funktion nicht verändert wird bzw. verändert werden darf. Eine Zuweisung wie *str = 'a' ergibt also einen Fehler. str selbst kann aber sehr wohl verändert werden, etwa mit str++.

Soll ausgedrückt werden, dass str unveränderlich ist, dann so:

void foo (char * const str, ...);

Jetzt wäre eine Änderung des Strings in Ordnung, etwa durch str[10] = 'a'.

Um sich zu merken, worauf das const wirkt, trennt man die Deklaration in Gedanken beim * auf: Steht das const links vom *, dann gehört es zum char, steht es rechts davon, dann gehört es zum Pointer. Natürlich ist es auch denkbar, beides – also den Zeiger und sein Ziel – als konstant zu markieren.

Gültigkeitsbereich

In C können mehrere Variablen den gleichen Namen haben, solange eindeutig ist, welche in welchem Block gültig ist. Dabei gelten folgende Regeln:

Lokale Variablen
sind Variablen, die innerhalb eines Blockes definiert werden. Jede Variable ist nur in dem Block gültig, in dem sie vereinbart wurde, sowie in allen darin enthaltenen Blöcken; es sei denn, in einem Unter-Block wird eine Variable gleichen Namens definiert. Dann bezieht sich in diesem Unter-Block der Bezeichner auf die im Unter-Block angelegte Variable.
Globale Variablen
werden ausserhalb jedes Blockes definiert und gelten ab der Stelle, an der sie deklariert werden, siehe auch Deklaration und Definition. Wird jedoch in einem Block eine Variable gleichen Namens angelegt, gilt ab hier bis zum Ende des Blocks nicht mehr die globale Variable, sondern die im Block deklarierte. Das Spiel kann man weiterspielen: wird in einem Unter-Block wieder eine namensgleiche Variable angelegt, gilt diese in dem Unterblock.

Speicherklassen

Jede Variable in C gehört zu einer bestimmten Speicherklasse

auto
Lokale Variablen sind in aller Regel sogenannte automatische Variablen. Das bedeutet, sie werden automatisch angelegt, wenn ein Block bzw. eine Funktion betreten wird und danach wieder entfernt. Das Schlüsselwort "auto" wird praktisch nie hingeschrieben, denn lokale Variablen ohne die ausdrückliche Angabe einer Speicherklasse, sind automatisch automatische Variablen.
extern
Ein externes Symbol ist im ganzen Programm bekannt bzw. in dem Block, in der die Deklaration steht. In unterschiedlichen Blöcken stehende Deklarationen beziehen sich auf das gleiche Symbol! Obgleich das Datum global zugreifbar ist, ist der Gültigkeitsbereich auf den deklarierenden Block begrenzt bzw. auf das deklarierende Quell-Modul, sofern das Symbol ausserhalb jedes Blocks des Moduls deklariert wird. Siehe auch Deklaration und Definition.
static
Die Variable ist im Block gültig bzw. im Quell-Modul (also in der C-Datei, in der die angelegt wurde), wenn sie nicht innerhalb eines Blockes angelegt wurde. Statische Variablen werden nicht in Registern oder im Frame der Funktion angelegt, sondern im selben Speicherbereich, in dem auch die globalen Variablen liegen; Konstanten evtl. auch im Flash. Eine lokale Variable, die als static angelegt wird, "überlebt" also das Verlassen des Blocks und hat beim neuerlichen Betreten des Blockes ihren bisherigen Wert. In unterschiedlichen Blöcken angelegte lokale statische Variablen beziehen sich auf unterschiedliche Speicherstellen, genau wie bei lokalen Variablen auch.
register
Durch diese Speicherklasse wird eine Variable – falls möglich – als Registervariable angelegt, also in einem Maschinenregister des Computer/Controllers gehalten. Dadurch kann auf solche Variablen besonders schnell zugegriffen werden. Dieses Schlüsselwort ist bei modernen Compilern weitgehend überflüssig, da die entsprechenden Optimierungen selbständig vorgenommen werden, wenn ausreichend Register vorhanden sind. Auch globale Variablen können als Register angelegt werden, davon ist dem Anfänger aber dringend abzuraten, weil leicht schwerauffindbare Fehler und Abstürze auftreten, wenn man nicht genau weiss, welche Implikationen in einer solchen Definition stecken!
volatile
(FIXME: volatile ist ein Qualifier und keine Speicherklasse) Dies ist das genaue Gegenteil von register und bewirkt, dass die Variable auf keinen Fall in einem Register zwischengespeichert werden darf, sondern immer aus dem RAM gelesen und ins RAM geschrieben werden soll. volatile müssen alle globalen Variablen markiert werden, die in Interrupt-Handlern verwendet werden.

Ausdrücke

Eine Variable oder eine Konstante in C stellen einfache Ausdrücke dar. Diese elementaren Ausdrücke können durch Operatoren miteinander verknüpft werden und so zu neuen, komplexeren Ausdrücken zusammen gesetzt werden.

Einfache Beispiele für Ausdrücke sind also z.B.:

1
a
'a'
1 + a
a == 1

Auch Funktionen können einen Wert zurückliefern und in Ausdrücken weiter benutzt werden. In den folgenden Abschnitten wird gezeigt, welche Operatoren in C vorhanden sind, und wie man damit neue Ausdrücke aufbauen kann.

Lvalues

Ein Lvalue in C ist ein Ausdruck, dem ein anderer Ausdruck zugewiesen werden kann, dessen Wert also durch eine Zuweisung verändert werden kann. das 'L' leitet sich ab von 'left' bwz. 'links' und das 'value' bedeutet Wert: Ein Lvalue ist ein Ausdruck, der auf der linken Seite einer Zuweisung stehen darf. Ein Lvalue ist also immer auch ein gültiger Ausdruck, aber die Umkehrung gilt in aller Regel nicht.

Ein einfaches Beispiel für einen Lvalue ist eine "normale" Variable, die nicht mit const als Konstante markiert ist:

a = 1;

Hingegen ist der Ausdruck a+1 kein Lvalue, denn eine Zuweisung wie

a+1 = 2;

die mathematisch durchaus sinnvoll ist, erzeugt einen Compilerfehler, der etwa lauten könnte "illegal lvalue in assignment": "ungültiger Wert in Zuweisung"

Andere Beipiele für Lvalues sind die Komponenten von (nicht-konstanten) Strukturen und Unions, Array-Elemente und die Dereferenzierungen von Pointern: Die Konstante 4 wird durch den Cast in eine Adresse umgewandelt. Über die Dereferenzierung * wird an die Adresse 4 im Speicher eine 3 geschrieben. Ob das erlaubt bzw. sinnvoll ist, ist abhängig von der jeweiligen Architektur.

* ((unsigned int *) 4) = 3;

Hier ist der gesamte *-Ausdruck ein Lvalue

Logische (boolsche) Operatoren

Ausdruck Beschreibung
a && b wahr, wenn a wahr und b wahr
a || b wahr, wenn a wahr oder b wahr
a == b gleich
a != b ungleich
a <= b kleiner oder gleich
a < b kleiner als
a >= b glösser oder gleich
a > b grösser als
!a wahr, wenn a nicht wahr und vice versa

Eine interessante Eigenschaft der Operatoren && und || ist, dass sie die Auswertung abbrechen, sobald das Ergebnis feststeht. Die Ausdrücke werden dabei immer von links nach rechts ausgewertet. Ein oft anzutreffendes Codestück sieht so aus, dabei sei p ein Zeiger auf einen int:

Beispiel:

 if (p && *p == 5)
 {
    /* mach was */
 }

Zuerst wird in der Bedinung geprüft, ob Zeiger p einen Wert ungleich Null hat, also ob er überhaupt einen gültigen Wert enthält. Es ist eine weit verbreitete Konvention in C, daß Zeiger, die keinen gültigen Wert haben, die Adresse 0 enthalten. Nur dann, wenn ein Zeiger nicht ein Null-Pointer ist, darf überhaupt ein Zugriff über ihn erfolgen!

Vergleich von Variablen

Skalare Variablen (also ganze Zahlen, Gleitkommazahlen, Zeiger) können miteinander verglichen werden. Dazu gibt es die folgenden Operatoren in C:

Operator Bedeutung
== ist gleich
!= ist nicht gleich
< ist kleiner
<= ist kleiner oder gleich
> ist größer
>= ist größer oder gleich

Das Ergebnis der Auswertung ist eine ganze Zahl. Ist die Bedingung erfüllt, dann ist der Wert ungleich 0. Ist die Bedingung nicht erfüllt, dann ist ihr Wert gleich 0. Meistens wird man diese Operatoren in if-Konstrukten finden wie zum Beispiel

if (x >= 10)
   x = 10;

oder in Abbruchbedingungen von Schleifen, wie sie weiter unten erklärt werden.

Es ist auch möglich, das Ergebnis der Auswertung in einer int-Variablen zu speichern:

int i;
int z1, z2;

z1 = 5;
z2 = 100;
i = z1 <= z2;  /* Ein Vergleich. i wird "wahr", da z1 kleinergleich z2 ist */

Die Variable i ist ungleich 0 ("wahr"), wenn z1 kleiner oder gleich z2 ist. Ist z1 jedoch größer als z2, dann ist i gleich 0 ("unwahr").

Arithmetische Operatoren

Ausdruck Beschreibung
a + b Summe (Addition)
a - b Differenz (Subtraktion)
a * b Produkt (Multiplikation)
a / b Quotient (Division, evtl. mit Rest)
a % b Rest bei Division (Modulo)
-a Vorzeichenumkehr (Zweierkomplement)

Bit-Operatoren

Ausdruck Beschreibung
a & b bitweise und (and)
a | b bitweise oder (or)
a ^ b bitweise exclusiv-oder (xor, exor)
~a jedes Bit in a invertieren (not, Einerkomplement)

Index-Operator bei Arrays

Ausdruck Beschreibung
a[b] das (b+1)ste Element des Feldes a

Folgendes gilt es bei der Verwendung des Indexoperators zu beachten:

  1. a muss ein Feld oder Zeiger sein
  2. b muss ein Integer sein oder ein Datentyp, der sich in einen int umwandeln läßt (z.B. char)
  3. Es wird nicht geprüft, ob der Index b im Feld a gültig ist!
  4. Der erste Index eines Feldes ist immer 0. Daher (b+1)stes Element in der Beschreibung

Komponenten-Auswahl bei Structs und Unions

Ausdruck Beschreibung
a.b Element b der Struktur oder des Unions a

Adress-Operator und Dereferenzierung

Ausdruck Beschreibung
&a Speicheradresse der Variablen a
*a Wert, der an der Adresse a steht
a->b Wert des Elements b der Struktur, deren Adresse in a steht

Der Adressoperator & kann auf Variablen angewendet werden und gibt die Startadresse der Variablen im Speicher zurück.

Handelt es sich bei einer Variable um einen Zeiger, so enthält sie eine Speicheradresse. Um an den Wert zu gelangen, der an dieser Adresse steht, wird der Operator * vorangestellt.

Beispiel:

/* x ist eine Integervariable und hat den Wert 5 */
int x = 5;
    
/* z ist ein Zeiger auf eine Integer-Variable und enthaelt somit */
/* die Speicheradresse einer Integer-Variablen */
int *z;       
 
/* Verwendung des Adress-Operators: weist an z die Adresse von x zu */
z = &x;

/* Verwendung der Dereferenzierung */
/* erhoehe den Wert, der bei Adresse z steht, um eins */
*z = *z + 1;

/* da z auf x zeigt, hat x jetzt den Wert 6 */

Da in C häufig Zeiger auf Strukturen verwendet werden, ist für den Zugriff auf Struktur- und Union-Elemente eine abkürzende Schreibweise möglich:

Statt

 (*strukturZeiger).element

kann geschrieben werden

 strukturZeiger->element

Beide Schreibweisen sind absolut gleichbedeutend, die Klammern bei der ersteren sind notwendig.

Achtung!

Bei der Dereferenzierung durch * findet keine Prüfung statt, ob der Zeiger auch auf eine gültige Speicheradresse verweist. Folgendes Codestück führt zum Absturz oder zu einer Änderung irgendeiner Speicherstelle!
int *z; /* z ist ein Zeiger auf einen int */

/* An dieser Stelle ist z immer noch keine Speicheradresse zugewiesen. */
/* z enthaelt irgendeine ungueltige Adresse!! */

/* "Erhoehe einen Integer _irgendwo_ im Speicher um 1" -> CRASH !!! */
*z = *z + 1;

Viele C-Compiler erzeugen in der Standardeinstellung für das obige Codestück keine Warnung!

Cast-Operator

Der Cast Operator dient dazu, den Datentyp eines Wertes zu ändern. Dafür wird einfach der neue Datentyp in Klammern vor den Wert geschrieben.

Um zum Beispiel aus einem Float ein Integer zu machen:

var  = (int) 5.60;

Dabei wird der Wert aber auch gerundet, und es findet somit ein Informationsverlust statt. (Genau genommen wird nicht gerundet, sondern die Nachkommastellen werden nur abgeschnitten; wenn man runden möchte, empfiehlt es sich, zuerst 0.5 zu addieren und dann zu casten.)

Ein weiteres Beispiel ist das Umwandeln einer ganzen Zahl in eine Adresse:

int * addr;
addr = (int*) 0x1234;

Damit ist addr ein Zeiger auf einen int an Adresse 0x1234.

Achtung!

Der Cast-Operator selbst führt keine Konvertierung von Darstellungen durch, etwa die Umwandlung der ganzen Zahl 123 ein den String "123", der diese Zahl darstellt!

  #include <stdio.h>
  int main(int argc, char ** argv)
  {
        char text[] = "5.6";
        int zahl = (int) text;

        printf("%d\n", zahl);

        return 0;
  }

Ausgegeben wird weder 5 noch 6 sondern die Anfangsadresse des Strings "5.6".

Komma-Operator

Mit einem , können mehrere Ausdrücke nacheinander ausgewertet werden. Die Auswertung erfolgt von links nach rechts.

Solche Konstrukte sieht man manchmal in Abfragen wie

FILE  *file;
if (file = fopen ("foo.exe", "r"), file != NULL)

was erst an file einen Wert zuweist und den if-Block nur betritt, wenn file nicht der Nullpointer ist.

Bequem kann das auch in einer for-Schleife sein, wenn man zwei (oder mehr) Laufvariablen hat oder so:

for (i=0, j=0; i < 10; i++, j += 2)
   ···

Zuweisungen und Operatoren mit Nebeneffekt

Zuweisung

++ und --

++ und -- stellen einfachere Schreibweisen dar zum Addieren bzw. Subtrahieren von 1.

++ (Inkrementieren)

int foo = 1; 
foo++; 
/* entspricht */
foo = foo + 1;
/* jetzt ist foo = 3 */

-- (Dekrementieren)

int foo = 1; 
foo--;
/* entspricht */
foo = foo - 1;
/* jetzt ist foo = -1 */

Die beiden Operatoren können sowohl in der Präfix-Schreibweise (vor der Variablen) als auch als Postfix-Schreibweise (hinter der Variablen) notiert werden. Der Unterschied liegt darin, dass beim Präfix der Wert zuerst neu berechnet wird und die Variable dann verwendet wird. Beim Postfix wird die Variable zuerst verwendet und erst nach Auswertung des Ausdrucks, in dem sie enthalten ist, neu berechnet.

Beispiel

int ausgabe1, ausgabe2, var1 = 10, var2 = 10;
ausgabe1 = 3 * ++var1; /* ausgabe1 = 33; var1 = 11; */
ausgabe2 = 3 * var2++; /* ausgabe2 = 30; var2 = 11; */

Für Zeiger arbeiten diese Operatoren etwas anders, siehe dazu Zeiger-Arithmetik.

Bedingter Ausdruck

(<Bedingung>) ? <Ausdruck1> : <Ausdruck2>

Wenn Bedingung erfüllt ist, dann wertet dieser Ausdruck aus zu Ausdruck1. Ist er nicht erfüllt, dann wertet er aus zu Ausdruck2.

Beispiel:

x = (x >= 3) ? 0 : x+1;

Startet man x mit dem Wert 0, dann nimmt es bei mehrfacher Anwendung dieser Zeile (z.B. in einer Schleife) nacheinander die folgende Werte an:

1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, ...

Reihenfolge der Auswertung

Wie auch in der Mathematik gibt es auch in C genaue Regeln über die Abarbeitungsreihenfolge (precedence) der Operatoren. Dass sich alle C-Compiler genau an diesen ANSI-Vorschlag halten, ist leider nicht sicher. Sicher jedoch ist, dass nicht jeder Programmierer diese Regel jederzeit im Kopf hat. Daher ist es sinnvoll, Ausdrücke durch runde Klammern eindeutig zu kennzeichnen. Nebenbei stören sich Compiler nicht an überflüssigen Klammerpaaren.

Priorität Operator Assoziativität
15 ( ) [ ] -> . von links nach rechts
14 ! ~ ++ -- + - (TYP) * & sizeof von rechts nach links
13 * / % (Rechenoperationen) von links nach rechts
12 + - (binär) von links nach rechts
11 << >> von links nach rechts
10 < <= > >= von links nach rechts
9 == != von links nach rechts
8 & (bit-AND-Operator) von links nach rechts
7 ^ (bit-XOR-Operator) von links nach rechts
6 | (bit-OR-Operator) von links nach rechts
5 && von links nach rechts
4 || von links nach rechts
3 ? : von rechts nach links
2 = += -= /= *= %= >>= <<= &= |= ^= von rechts nach links
1 , (Sequenz-Operator) von links nach rechts

Die Reihenfolge der Auswertung von Funktionsargumenten ist in der ANSI-Spezifikation nicht angegeben und daher compilerabhängig. Von Konstrukten wie

{
   int i=0;
   func (i++, i++);
}

ist also dringend abzuraten!

Kontrollanweisungen

Eine Kontrollanweisung ist eine Anweisung, die Einfluss auf den Programmfluss hat. Normalerweise werden Anweisungen so ausgeführt, wie sie in der Quelldatei stehen: Von links nach rechts (falls mehrere Anweisungen in einer Zeile stehen sollten, wovon i.A. abzuraten ist) und von oben nach unten. Mit einer Kontrollanweisung kann dieser lineare Programmfluss durchbrochen werden: Die Codeausführung kann abhängig von einer Bedingung gemacht werden (if), kann wiederholt werden (Schleife) oder an einer anderen Stelle der Funktion fortgesetzt werden (goto).

if-Anweisung

Mit Hilfe des if-Befehls kann man Codeteile abhängig davon einer Bedingung ausführen lassen:

Syntax:

if (<Bedingung>)
   <Anweisung>

oder mit else-Teil

if (<Bedingung>)
   <Anweisung>
else
   <Anweisung>

Beispiel:

if (x > 100)
{
   /* falls x > 100 ist: Fehlerausgabe */
   printf ("x = %d ist zu gross fuer die Berechnung!\n", x);
}
else
{
   /* falls x <= 100 ist: Berechne Summe der Zahlen 1...x */
   /* Die lokale Variable x2 lebt nur innerhalb dieses alse-Blocks */
   int x2 = x;

   for (x = 0; x2 > 0; x2--)
      x += x2;
}

Wenn die Bedingung wahr ist (x > 100), dann wird eine Meldung ausgegeben; danach ist die if-Anweisung beendet. Der else-Block wird also nicht ausgeführt.

Ist die Bedingung nicht erfüllt (x ≤ 100), dann wird gleich zum else-Teil gesprungen, und nach dessen Ausführung der if-Befehl beendet.

Ein häufiger Fehler ist es, statt if (a == 23) etwas wie if (a = 23) zu schreiben. Dann wird allerdings nicht geprüft, ob die Variable a gleich 23 ist, sondern der Variablen a wird der Wert 23 zugewiesen. Der Ausdruck a = 23 hat den Wert 23 und ist damit immer "wahr"! Daher ist diese if-Bedingung immer erfüllt!

Die Syntax hierbei ist allerdings korrekt, der Compiler wird also keinen Fehler ausspucken sondern bestenfalls eine Warnung. Damit ist dieser Fehler sehr schwer zu finden. Abhilfe schafft die Schreibweise if (23 == a). Wenn man dort anstatt des Vergleichsoperators '==' den Zuweisungsoperator '=' verwendet, spuckt der Compiler sehr wohl einen Fehler aus! Ist die Zuweisung jedoch erwünscht und eine Compiler-Warnung lästig, dann wählt man eine Schreibweise wie if ((a = b)) oder if (a = b, a).

Ein weiterer häufiger Fehler ist zu schreiben if (Bedingung); Richtig muss es heissen "if(Bedingung)" Das Semikolon im ersten Fall ist eine leere Anweisung, die im if-Falle ausgeführt wird – sie bleibt also ohne Resultat. Auch hier liegt kein Syntaxfehler vor und der Compiler schweigt; ein auf das Semikolon folgende Anweisung die eigentlich zum if gehören soll wird immer ausgeführt, die sie nicht mehr zum if dazu gehört.

Bei verschachtelten if-else-Konstrukten gehört ein else zu letzten "freien" if. Soll in einer if-if-else-Folge das else zum ersten if gehören, dann ist das so zu hinzuschreiben:

if (<Bedingung>)
{
   if (<Bedingung>)
      <Anweisung>
}
else
   <Anweisung>

Ohne die geschweiften Klammern um das zweite if gehörte das else dort hinzu.

switch-Anweisung

Syntax:

switch (<Ausdruck>) 
{
    case konstante1:
        <Anweisung>
        <Anweisung>
        ...
        break;

    case konstante2:
        <Anweisung>
        <Anweisung>
        ...
        break;
         
    /* weitere case-Marken */

    default:
        <Anweisung>
        <Anweisung>
        ...
} /* Ende von switch */ 

Der Ausdruck muss ein skalarer Typ sein, er wird in die nächste ganze Zahl gewandelt und mit den Werten hinter den case-Marken verglichen. Bei einer Übereinstimmung werden alle Befehle ab dem zutreffenden case ausgeführt. Stimmt der Ausdruck mit keinem der Werte überein, so wird der default-Abschnitt ausgeführt falls vorhanden.

Auch die Anweisungen der nachfolgenden case- und des default-Abschnitts werden ausgeführt, wenn die Anweisungen des case-Abschnitts nicht mit dem Befehl break; beendet werden!

Es dürfen beliebig viele case-Abschnitte angegeben werden, pro Vergleichswert jedoch nur einer. Der default-Abschnitt ist optional. Die Reihenfolge, in der case und default angegeben werden, ist unerheblich.

Schleifen

Um Anweisungen mehrmals hintereinander auszuführen, benötigt man Schleifen. Diese führen Anweisungen aus, bis oder solange Bedingungen erfüllt sind.
Wichtig ist also, ob die Bedingung vor oder nach den Schleifen-Anweisungen geprüft wird.

while-Schleife

Syntax:

while (<Bedingung>)
   <Anweisung>

Die while-Schleife wird solange durchlaufen, wie die Bedingung erfüllt ist. Die Schleife wird also unter Umständen garnicht durchlaufen. Die Anweisung kann natürlich auch ein Block sein, der aus mehreren Deklarationen und Anweisungen besteht.

int zahl1 = 0;
int zahl2 = 1;

while (zahl1 < 3)
{
   zahl1 = zahl1 + 1;
   zahl2 = zahl2 * 2;
}

In diesem Beispiel wird die Schleife drei mal durchlaufen. Zu Beginn des vierten Durchlaufs ist die Bedingung nicht mehr erfüllt (zahl1 ist dann nicht mehr kleiner, sondern gleich 3!), also wird mit dem Befehl nach der Schleife fortgesetzt.

do-while-Schleife

Syntax:

do
   <Anweisung>
while (<Bedingung>);

Die do-while-Schleife wird auf jeden Fall einmal durchlaufen und dann solange wiederholt, wie die Bedingung erfüllt ist.

int i = 2;

do 
{
    i = i*i;   /* i quadrieren */
    printf ("i = %d\n", i);
}
while (i < 20);

Die Schleife wird durchlaufen und wiederholt, solange i kleiner als 20 ist. Es werden also nacheinander die Werte 2, 4 und 16 ausgegeben. Nach der Schleife hat i den Wert 256.

for-Schleife

Syntax:

for (<Ausdruck1>; <Bedingung>; <Ausdruck2>)
   <Anweisung>

Bei den Ausdrücken wird es sich um einen Ausdrücke mit Nebeneffekt handeln wie etwa i=0 oder i=i+2. Es werden folgende Aktionen ausgeführt:

  1. Ausdruck1 wird ausgewertet
  2. Bedingung wird ausgewertet
  3. falls die Bedingung wahr ist, dann führe Anweisung aus.
  4. falls die Bedingung unwahr ist, dann sprinte zu 7 (Ende).
  5. Ausdruck2 wird ausgewertet
  6. gehe zu 2
  7. nächste Anweisung nach der for-Schleife

Beispiel:

int lauf, summe;

for (lauf=1, summe=0; lauf <= 10; lauf += 2) 
{
   summe += lauf;
}

In diesem Beispiel ist Ausdruck1 ein Komma-Ausdruck, der zwei Anweisungen kombiniert und daher sogar zwei Nebeneffente hat: er setzt lauf auf 1 und summe auf 0.

Das Äquivalent als while-Schleife:

int lauf  = 1;                 /* Anfangswerte */
int summe = 0;

while (lauf <= 10)             /* Bedingung */
{
   summe += lauf;
   lauf  += 2;                 /* Inkrement */
}

In diesem Beispiel wird summe in jedem Schleifendurchlauf um die Laufvariable lauf erhöht. Da lauf nacheinander die ungeraden Werte von 1 bis 10 annimmt, ist in summe nach der Schleife die Summe der ungeraden Zahlen von 1 bis kleinergleich 10 gespeichert, also der Wert 25. lauf hat nach der Schleife den Wert 11.

Erklärung: lauf = 1 bedeutet, dass der Variablen lauf vor dem ersten Schleifendurchlauf der Wert 1 zugewiesen wird. lauf <= 10 ist die Schleifenbedingung; ist sie nicht erfüllt, wird die Schleife beendet. lauf += 2 bedeutet, dass lauf nach jedem Durchlauf um 2 erhöht wird.

continue-Anweisung

Innerhalb einer Schleife darf die continue-Instruktion stehen. Sie bewirkt, daß die nachfolgenden Anweisungen übersprungen werden und mit dem nächsten Schleifendurchlauf fortgesetzt wird – vorausgesetzt die Schleifenbedingung ist noch erfüllt. Ein continue darf natürlich auch innerhalb eines if oder switch etc. stehen, wenn dieses innerhalb einer Schleife steht.

break-Anweisung

Innerhalb einer Schleife oder eines switch darf die break-Instruktion stehen. Sie bewirkt, daß die Schleifen-/Switch-Anweisung sofort verlassen wird und das Programm dahinter weiter macht. Bei mehrfach geschachtelten Schleifen wird nur die innere verlassen. Ein break darf natürlich auch innerhalb eines if stehen, wenn dieses innerhalb einer Schleife/Switch-Anweisung steht.

goto-Anweisung

Innerhalb ein und derselben Funktion kann mit goto an eine andere Stelle gesprungen werden. Dazu gibt man hinter dem goto einen Bezeichner an, der dadurch als Label fungiert:

Syntax:

goto <Bezeichner>;

Die Bezeichner selbst steht irgendwo in der Funktion und wird dadurch zur Sprungmarke (Label), daß er von einem Doppelpunkt (und mindestens einer C-Anweisung, die auch leer sein darf) gefolgt wird.

Das Beispiel durchsucht das 2-dimensionale int-Array feld mit den SIZE_X × SIZE_Y Werten nach dem Wert 0. Wird er gefunden, dann wird die 2-fach geschachtelte Suchschleife verlassen.

Beispiel:

int x, y;

for (x=0; x < SIZE_X; x++)
   for (y=0; y < SIZE_Y; y++)
      if (feld[x][y] == 0)
         goto done;
done:;

Der folgende Code hat die gleiche Funktion, arbeitet jedoch ohne goto:

int x, y;
int found = 0; /* FALSE */

for (x=0; x < SIZE_X && !found; x++)
   for (y=0; y < SIZE_Y && !found; y++)
      found = (0 == feld[x][y]);

Der Nachteil der goto-losen Variante ist, daß man eine Variable, die merkt, ob das Suchziel gefunden wurde, mitschleppen und in jedem Schleifendurchlauf abtesten muss. Dies bedeutet einen höheren Programmier- und Laufzeitaufwand und ist nicht so klar formuliert wie das goto-Beispiel.

Gleichwohl sei angemerkt, daß die Verwendung von goto einem gewissen Dogmatismus unterliegt, der sich wie folgt subsummieren liesse:

goto ist böse und sollte keinesfalls verwendet werden! Wer es dennoch tut, offenbart dadurch seinen schlechten Geschmach sowie mangelhafte C-Kenntnis.

Funktionen

Stell Dir vor, Du hast eine Code-Folge, die mehrmals im Programm vorkommt, z.B. eine mathematische Formel. Anstatt dieses Codestück mehrmals zu schreiben – was Dich Zeit beim Erstellen des Programms und Speicherplatz im ausführbaren Programm kostet – kannst Du den Code-Abschnitt in eine Funktion schreiben und diese von jeder Stelle des Programms aus verwenden. Die Hauptgründe, um Funktionen zu verwenden, sind:

Wiederverwendung von Code
Mehrfach verwendete Codestücke müssen nicht mehrfach implementiert werden. Oft unterscheiden sich die Codesequenzen nur in Kleinigkeiten, die man der Funktion über Parameter mitteilen kann.
Übersichtlichkeit
Ein gut gegliedertes C-Programm implementiert klar umrissene Aufgaben in einer Funktion, auch wenn diese Funktion nur einmal im Code aufgerufen wird! Dadurch bleibt der Code um die Aufrufstelle besser verständlich, und man kann auf verschiedenen "Ebenen" denken. Eine Funktion wie "Datei öffnen" kann recht komplex sein. Auf höherer Ebene interessieren die Innereien nicht mehr, man möchte sich um andere Dinge kümmern und will den Code an der Stelle garnicht sehen...
Rekursive Funktionen
Eine Funktion kann sich auch selbst aufrufen. In dem Falle nennt man die Funktion rekursiv. Zwar lässt sich das, was eine rekursive Funktion tut, auch mit anderen Mitteln formulieren, die keine rekursiven Funktionen brauchen, aber oft ist der rekursive Weg knackiger und klarer formulierbar als eine nicht-rekursiven Ansatz, auch wenn es etwas mehr Resourcen verbraucht.
Modulare Programmierung
Funktionen können anhand ihres Aufgabenbereichs auf verschiedene C-Quellen – sogenannte Module – verteilt werden. Funktionen, die etwas mit dem USB-Bus anstellen, werden in einem anderen Modul sein als mathematische Funktionen. Dies erhöht die Übersichtlichkeit und vereinfacht die Entwicklung im Team.
Bibliotheken
Standard-Funktionen wie das hier oft auftauchende printf sind in Bibliotheken gespeichert. Wenn das eigene Programm übersetzt wird, dann müssen nicht mehr alle Standard-Funktionen übersetzt werden, sondern werden nur noch aus der Bibliothek gelesen und ihr Code zum Programm dazugelinkt. Die Bibliotheks-Funktionen wurden schon zu einem früheren Zeitpunkt compiliert und liegen in dieser compilerten Form in der Bibliothek. Das spart mächtig Entwicklungszeit. Man kann auch selbst solche Bibliotheken erstellen und in diversen Projekten wiederverwenden.
Generische Programmierung
In C ist es möglich, einer Funktion eine andere Funktion zu übergeben. (Damit ist nicht gemeint, ihr deren Rückgabewert zu übergeben (was auch ginge), sondern die Funktion selbst wird als Parameter übergeben und kann aufgerufen werden.) Ein typisches Beispiel dafür sind Sortieralgorithmen. Einem Sortieralgorithmus kann es egal sein, was er sortiert. Er muss lediglich wissen, wie er das Zeug zu sortieren hat: aufsteigend, absteigend, als Zahl, in lexikographischer Ordnung, nach der Quersumme, Körper nach Oberfläche, Durchmesser, Gewicht oder Volumen... Diese Vergleichsfunktion, die für zwei Objekte entscheidet, welches davon "kleiner" ist, kann man dem Sortierer übergeben. Will er zwei Werte vergleichen, dann muss er nur die Vergleichsfunktion aufrufen, ohne zu wissen, was diese tut. Damit kann der Sortieralgorithmus unanhängig von den Objekten gehalten werden, mit denen er hantieren soll.


Definition

In der Definition der Funktion wird gesagt, welche Werte sie liefern kann, wie sie heisst (Bezeichner) und wieviele und welche Parameter sie hat. Danach folgt ihre Implementierung:

Syntax:

<Type> <Bezeichner> (<Parameterliste>)
{
   <Deklaration>
   <Deklaration>
   ...

   <Anweisung>
   <Anweisung>
   ...
}

Für Funktionen, die keinen Wert zurückliefern, gibt es den speziellen Typ void, der besagt, daß die Funktion nichts zurückgibt. Die einfachste denkbare Funktion ist eine solch void-Funktion. Sie bekommt keine Parameter, gibt nicht zurück und ihr Body ist leer:

void dummy()
{
}

return-Anweisung

An jeder Stelle des Programmflusses einer Funktion kann diese mit return beendet werden.

bei void-Funktionen:

return;

Funktionen mit Rückgabe-Wert:

return <Ausdruck>;

Die zweite Variante gibt an, welcher Wert zurückgegeben wird.

int main (int argc, char * argv[])
{
   if (argc < 2)
      return -1;

   return 0;
}

Falls die letzte Anweisung einer void-Funktion ein return ist, kann es auch weggelassen werden wie oben bei der Funktion dummy.

Aufruf

Um die Funktion aufzurufen gibt man ihren Namen an, gefolgt von den durch Komma getrennten Argumenten in runden Klammern wie im Beispiel unten das

quadrat (5) 

Da quadrat einen Wert liefert, kann man damit weiter rechnen wie mit einem normalen Ausdruck:

if (quadrat (a) + quadrat (b) == quadrat (c))
   c = quadrat (quadrat (a)); /* c = a hoch 4 */
Ein Hinweis am Rande
Der Name einer Funktion ohne die beiden runden Klammern ist der Pointer/Zeiger auf ihren Anfang. Damit kann ein Funktionsname überall dort verwendet werden, wo Pointer/Zeiger zulässig sind. Insbesondere kann er als Argument einer weiteren Funktion dienen. Siehe auch Zeiger auf Funktionen.

Rekursive Funktionen

Eine Funktion die sich selbst – möglicheweise auch über andere Zwischenfunktionen – wieder selbst aufruft, wird als rekursive Funktion bezeichnet. In der Definition ist nichts besonderes zu beachten. Ist die Verschachtelungstiefe im laufenden Programm zu tief, dann gibt das natürlich Probleme, aber das gilt bei tief verschachtelten 'normalen' Funktionen ebenso...

Das Beispiel berechnet den Größten Gemeinsamen Teiler zweier Zahlen a und b:

int ggT (int a, int b)
{
    if (0 == a)
       return b;

   return ggT (b % a, a);
}

Beispiel

Ein komplettes kleines Programm:

#include <stdio.h>

int quadrat (int param1)
{
  int zahl;
  zahl = param1 * param1;
  return zahl;
}

int main ()
{
  int zahl, ergebnis;
  
  printf ("Bitte Zahl eingeben: ");
  scanf  ("%d", &zahl);
  
  ergebnis = quadrat (zahl);
  
  printf ("%d hoch 2 = %d\n", zahl, ergebnis);
  printf ("%d hoch 2 = %d\n", 5, quadrat (5));
  
  return 0;
}

Ein Unterprogramm kann an jeder beliebigen Stelle innerhalb eines Programmes stehen, aber nur ausserhalb von Blöcken. Geschachtelte Unterprogramme sind in Standard-C nicht möglich.


Merke: Auch wenn eine Funktion keine Parameter hat, müssen beim Aufruf die Klammern angeben werden:

dummy();

Prototypen

Wie oben erwähnt, kann ein Unterprogramm an jeder beliebigen Stelle im Programm stehen. Damit ist jedoch eine Bedingung verknüpft: Das Unterprogramm muß in der Datei oberhalb des ersten Aufrufes definiert worden sein. Wenn Sie ein Unterprogramm in Zeile 10 zum ersten mal aufrufen, müssen Sie die Deklaration davor erledigt haben. Verstanden? Um dies zu erreichen, gibt es zwei Möglichkeiten:

Entweder Sie schreiben alle Unterprogramme vor main in die Datei. Dies muß jedoch wiederum so geschehen, dass Funktionen zum Zeitpunkt ihres Aufrufes bereits bekannt sind! Wo dies nicht möglich ist (z.B. sich gegenseitig aufrufende Unterprogramme), oder wenn Sie das stört, müssen Sie Prototypen verwenden. Wie definiert man nun Prototypen? Sie kopieren einfach die erste Zeile des Unterprogrammes (z.B. "void ausgeben (int zahl)"), fügen einen Strichpunkt ;an und fügen es an einer geeigneten Stelle ein (so, dass alle Aufrufe später in der Datei kommen). Solche Definitionen stehen gewöhnlich am Anfang der Quelldatei oder in einer Header-Datei, die eingebunden wird.

#include <stdio.h>

void ausgeben (int zahl);  /* Der Prototyp */

int main (void)
{
   ausgeben (12);
   
   return 0;
}

void ausgeben (int zahl)   /* Die eigentliche Prozedur */
{
  printf ("Ausgabe: %d\n", zahl);
}

Parameterübergabe

Alle Werte, die an Prozeduren und Funktionen übergeben werden, werden grundsätzlich kopiert. Das hat folgende Auswirkungen:

  1. Änderungen an einem Parameter in einer Funktion erscheinen nicht beim Aufrufer!
  2. Möchte man, dass eine Funktion einen Wert trotzdem dauerhaft ändern soll, so muss die Adresse des Wertes via Zeiger übergeben werden.
  3. Werden Strukturen übergeben, so wird von ihnen eine Kopie erstellt, was bei großen Strukturen viel Zeit und Arbeitsspeicher kostet. Deshalb wird häufig nur die Adresse von Strukturen übergeben, da die Adresse viel schneller und platzsparender als die Struktur selbst kopiert werden kann.

Beispiele:

void erhoehe (int x)
{
   x = x + 1;
}

int main (void)
{
   int a = 0;
   erhoehe(a);
   /* a ist immer noch 0 */
   
   return 0;
}

Beim Aufruf von erhoehe wird eine Kopie des Wertes von a (im Beispiel also 0) erstellt und der Prozedur als Parameter x übergeben. Weil dann die Prozedur erhoehe die Kopie verändert, hat dies keine Auswirkung auf das Original a im Hauptprogramm.

void erhoehe (int *x)
{
   /* erhoehe den Wert an der Adresse x um eins */
   *x = *x + 1;
}

int main(void)
{
   int a = 0;
   erhoehe (&a);
   /* a ist jetzt 1 */
   
   return 0;
}

Jetzt wird im Hauptprogramm mittels Adress-Operator & die Speicheradresse von a bestimmt. Dann wird eine Kopie der Adresse an das Unterprogramm erhoehe übergeben. Jetzt kennt das Unterprogramm die Adresse des Originals a und kann direkt mit dem Inhalts-Operator * auf den Wert an dieser Adresse zugreifen.

Besonderheit bei Feldern

Bei der Übergabe von Feldern gibt es eine Besonderheit. Schreibt man nämlich den Namen eines Feldes, so ist das nichts anderes als die Speicheradresse des ersten Elements. Bei der Übergabe eines Feldes wird also eine Kopie der Startadresse übergeben. Somit kann das Unterprogramm auf den Originaldaten arbeiten und diese verändern.

Beispiel:

void erhoehe (int x[])
{
   x[0] = x[0] + 1;
   x[1] = x[1] + 3;
   x[2] = x[2] + 5;
}

int main(int argc, char **argv)
{
  int a[] = {10, 20, 30};
  
  erhoehe (a);
  /* a hat jetzt folgenden Inhalte: 11, 23, 35 */
  
  return 0;
}

Dass die Übergabe einer Adresse erfolgt, sieht man an folgendem Beispiel, das von der Funktionsweise absolut identisch mit dem vorhergehenden ist:

/* Bei Parametern gibt es keinen Unterschied zwischen Zeiger und Feld */
void erhoehe (int *x)
{
   x[0] = x[0] + 1;
   x[1] = x[1] + 3;
   x[2] = x[2] + 5;
}

int main(int argc, char **argv)
{
   int a[] = {10, 20, 30};
  
   erhoehe (a);

   /* a hat jetzt folgenden Inhalt: 11, 23, 35 */
}

Die Länge des Feldes wird nicht automatisch übergeben. Dafür ist ggf. ein zusätzlicher Parameter notwendig.

Inlining

In C gibt es die Möglichkeit, eine Funktion als inline-Funktion zu definieren. Für eine inline-Funktion wird üblicher Weise kein Code erzeugt, der beim Aufruf der Funktion angesprungen wird, sondern an der Stelle des Aufrufs wird eine Kopie der inline-Funktion eingefügt.

Vom Effekt her ist eine inline-Funktion also ähnlich wie ein Makro. Allerdings wird das Einfügen des Codes nicht vom Präprozessor übernommen, sondern vom eigentlichen C-Compiler. Damit der Compiler in der Lage ist, eine Funktion zu inlinen, muss ihm der Code zur Verfügung stehen, da er ansonsten natürlich keinen Code einfügen kann.

Das Schlüsselwort ist inline:

// Deklariere increment als inline-Funktion
static inline int increment (int);

// Implementierung von increment
int increment (int i)
{
   return i+1;
}

// Aufruf von increment wie eine normale Funktion
int foo (int n)
{
   if (n < MAX_INT)
      n = increment (n);

   return n;
}

Inline-Funktionen werden verwendet, wenn der Funktionscode recht klein ist und ein Funktionsaufruf schon so aufwändig ist wie das, was die Funktion zu erledigen hat. Im Beispiel wird der gleiche Code erzeugt, wie wenn n = n + 1 im Aufrufer stünde, was deutlich schneller ist als ein Funktionsaufruf mit Register-Sicherung, Parameterübergabe, Wertrückgabe, etc.

Variable Argumentanzahl

In C ist es möglich, einer Funktion eine variable Anzahl an Argumenten zu übergeben. Solche Funktionen haben eine Anzahl benamter Argumente wie "normale" Funktionen auch, jedoch folgt nach dem letzten benamten Argument eine beliebige Anzahl weiterer Argumente.

Ein Beispiel für eine solche Funktion ist das Bekannte printf, für das zB folgende Aufrufe möglich sind:

printf ("Hallo");
printf ("%c", c);
printf ("%s %d", einString, 15);
printf ("%d %d %d", zahl1, zahl2, zahl3);

Einer solchen Funktion muss die Anzahl der Argumente mitgeleilt werden, die ihr übergeben werden. Sies könnte dadurch geschehen, daß man ihr die Argumentanzahl explizit übergibt. Bei printf wird die Anzahl der Übergabeparameter im Formatstring transportiert, ebenso wie die Typen der Übergebenen Variablen, denn die Funktion muss wissen, wie die übergebenen Werte zu interpretieren sind. Bei printf geschieht dies wiederum mittls des Format-Strings.

Der Prototyp einer varargs-Funktion sieht aus wie folgt, wobei die drei Pünktchen wörtlich zu nehmen sind und nicht etwa als abkürzende Schreibweise für die Parameterliste!

#include <stdio.h>
#include <stdarg.h>

extern void fprintf (FILE * file, const char * format, ...);
extern void printf  (const char * format, ...);
extern void fprintf_va (FILE * file, const char * format, va_list args);

Die Funktion fprintf soll eine Ausgabe zum File (Stream) file erledigen und ansonsten genauso funktionieren wie das altbekannte printf. Es ist also anzustreben, in printf nur die File-Version aufzurufen um eine Doppel-Implementierung der Funktionalität zu vermeiden. Hierzu dient die Funktion fprintf_va, die von printf bzw. fprintf einen File-Pointer, den Format-String sowie die Argumente erhält:

void fprintf (FILE * file, const char * format, ...)
{
	// Die Argumentliste 'args'
	va_list args;
	// Die variablen Argument beginnen nach 'format' 
	va_start (args, format);
	// Die eigentliche Arbeit erledigt 'fprintf_va' 
	fprintf_va (file, format, args);
	// fertig 
	va_end (args);
}

Fast genauso sieht unser printf aus:

void printf (const char * format, ...)
{
	// Die Argumentliste 'args'
	va_list args;
	// Die variablen Argument beginnen nach 'format' 
	va_start (args, format);
	// Die eigentliche Arbeit erledigt 'fprintf_va' 
	fprintf_va (stdout, format, args);
	// fertig 
	va_end (args);
}

Was zu tun bleibt, ist die Implementioerung der eigentlichen Funktionalität in fprintf_va. Diese durchfostet den Format-String und bei jedem %-Ausdruck wird ein weiteres Argument eingelesen und an eine passende Ausgabefunktion delegiert. Hier wird der Einfachheit halber nur %c und %s implementiert:

static void fprintf_va (FILE * file, const char * fmt, va_list args)
{
    char c;

    // Nächstes Zeichen des Formatstrings lesen und String-Ende abtesten 
    while (c = *fmt++, c != '\0')
    {
        // Das Format-Zeichen 
        if ('%' == c)
        {
            // Weiterlesen: das Zeichen nach dem % 
            c = fmt++;

            // %% --> ein % ausgeben 
            if ('%' == c)    fputc ('%', file);
            // %c --> Character ausgeben 
            else if ('c' == c)    fputc (va_arg (args, int), file);
            // %s --> String ausgeben 
            else if ('s' == c)    fputs (va_arg (args, char*), file);
            // Unbekannts %-Format 
            else fputs ("???", file);

            // Falls ein % am String-Ende steht 
            if ('\0' == c)  return;

            continue;
        }

        // Für Win32: Zeilenumbruch ist carriage return + line feed 
        if ('\n' == c)
            putc ('\r');

        // Zeichen ausgenen 
        putc (c);
    } // while 
}

Funktionen indirekt aufrufen

Siehe Zeiger auf Funktionen

Zeiger II

Zeiger haben wir bereits weiter oben kennen gelernt. Zeiger sind ein zentrales Konzept in C und sollen hier etwas eingehender behandelt werden.

Zeiger-Arithmetik

In C kann man den Wert eines Zeigers verändern. Betrachten wir dazu die Funktion suche_0, die einen Zeiger auf einen long erhält. Die Funktion soll ab der gegebenen Adresse nach dem ersten long-Wert suchen, der 0 ist, und dessen Adresse zurückgeben:

long * suche_0 (long * addr)
{
   while (*addr != 0)
      addr = addr + 1;

   return addr;
}

In der Bedingung der while-Schleife wird der Inhalt an der Speicherstelle addr auf 0 getestet. Ist der Wert 0, dann wird die Schleife beendet und die Adresse zurückgeliefert. Ist der Wert ungleich 0, dann wird addr auf den nächste long gesetzt, addr also um 4 Bytes weitergezählt. addr ist ja ein Zeiger auf long, und ein long ist 4 Bytes lang.

Die Bedeutung von

address + n

ist also, die Adresse um das n-fache der Größe des Typs, auf den address zeigt, zu erhöhen. Dabei ist n eine ganze Zahl und darf auch negativ sein.

Hier noch ein Beispiel einer Funktion, die nach einer Person mit einer bestimmten ID sucht (für die Definition von struct Person siehe Strukturen). Der Parameter person ist dabei ein Array von Strukturen. Eine Person mit der gesuchten ID muss existieren, ansonsten hat die Suchfunktion kein definiertes Verhalten.

/* Sucht nach einer Person mit der ID person_id */
struct Person * 
suche_person_id (struct Person * person, int person_id)
{
   while (person->id != person_id)
      person++;

   return person;
}

Beachte, daß es nicht sinnvoll ist, zwei Zeiger zu addieren oder zu multiplizieren. Ausserdem ist das + der Zeiger-Arithmetik nicht kommutativ. Eine Zeiger auf long, der an Adresse 1 im Speicher zeigt, wird man schreiben als

(long *) 1

Addiert man darauf eine ganze Zahl, dann haben die entstehenden Ausdrücke unterschiedliche Werte:

(long *) 1 + 2    /* zeigt zu Adresse 9 */
(long *) 2 + 1    /* zeigt zu Adresse 6 */
(long *) (1 + 2)  /* zeigt zu Adresse 3 */

void-Pointer

Eine besondere Art von Zeiger ist der void-Pointer

void * addr;

Ein void-Pointer ist ein "Zeiger auf irgendwas", dementsprechend kann er nicht dereferenziert werden, Anwenden von * auf einen solchen Zeiger gibt also einen Fehler. Ausserdem ist es nicht möglich, mit einem void-Pointer Zeigerarithmetik zu machen, weil er nicht auf eine definierte Art von Objekt zeigt. Der Vorteil eines void-Pointers ist, daß er jede Art von Zeiger aufnehmen kann.

Dazu betrachten wir die Funktion send_buf, die eine Adresse erhält und ab dieser Adresse num Bytes versenden soll. Wir könnten die Funktion so schreiben:

void send_buf (unsigned char * buf, unsigned int num)
{
  ...

Das ist jedoch hässlich, wenn wir damit etwas anderes verschicken wollen als unsigned char, etwa eine Struktur wie hubert (vom Typ struct Person):

send_buf ((unsigned char*) & hubert, sizeof (struct Person));

Ohne den Cast der Adresse von hubert zu einem Zeiger auf unsigned char bekommt man eine Warnung oder gar einen Compilerfehler. Dieses Zeiger gecaste ist mühsam und hässlich, es muss bei jedem Aufruf der Funktion explizit hingeschrieben werden.

Besser ist es, den ersten Parameter der Funktion als void-Pointer zu definieren und den Cast in der Funktion zu machen:

void send_buf (void * vbuf, unsigned int num)
{
  unsigned char *buf = (unsigned char*) vbuf;
  ...

Durch den Cast in der Funktion kann auf den Inhalt des Zeigers zugegriffen werden. Man muss nur festlegen, wie man zugreifen will, nämlich als unsigned char. Der Aufruf kann jetzt ohne Pointer-Cast erfolgen:

send_buf (& hubert, sizeof (struct Person));

Null-Pointer

Zeiger als Parameter

Wenn Sie ein Unterprogramm aufrufen, können Sie diesem Parameter übergeben, aber keine Werte zurückgekommen (außer den Funktionswert bei Funktionen). Dies hat einen guten Grund: beim Aufruf werden nicht die aufgerufenen Parameter benutzt, sondern es werden deren Werte in neue Variablen kopiert. Diese Variablen werden am Ende des Unterprogrammes "zerstört", ohne ihre Werte an die aufrufenden Parameter zu übergeben. Jede Veränderung eines Parameters hat daher keine Auswirkung auf den Parameter.

Doch was ist, wenn Sie Parameter in Unterprogrammen verändern möchten? Ganz einfach, Sie verwenden Zeiger. Der C-Compiler legt dann immer noch Kopien an. In dieser Kopie steht aber kein Wert, sondern die Adresse einer Varaiblen. Und auf diese können Sie dann zugreifen. Denken Sie nur an scanf – da übergeben Sie ja auch die Adresse einer Variablen.

#include <stdio.h>

void erhoehe (int *zeiger)
{
  *zeiger = 1 + *zeiger;
}

int main ()
{
  int zahl;
  
  printf ("Zahl eingeben: ");
  scanf  ("%d", &zahl);
  erhoehe (&zahl);
  printf ("\nDie erhoehte Zahl lautet: %d\n", zahl);
  
  return 0;
}

Zeiger auf Funktionen

Stell dir vor, du willst einen Sortieralgorithmus wie Bubble-Sort oder Quick-Sort oder wie sie alle heissen implementieren. Für den Sortieralgorithmus ist eigentlich egal, was er zu sortieren hat. Ihm ist es egal, ob er Zahlen aufwärts sortieren soll oder Strings in lexikographischer Reihenfolge, ob Objekte nach Größe oder Gewicht, Personen nach Alter oder Adressen nach Postleitzahl. Das einzige, was der Algorithmus wissen muss, ist wie er zwei Objekte zu vergleichen hat und wann eines davon "kleiner" (im Sinne der Ordnung, nach der sortiert werden soll) ist.

Eine einfache Sortierfunktion, die nur zwei Zahlen sortiert, könnte man also so schreiben:

/* Sortiert ein Array von 2 int-Zeigern nach den Inhalten 
 * an den Zeiger-Adressen */
void sort2_a (int * p[])
{
   /* Inhalte vergleichen... */
   if (*p[0] > *p[1])
   {
      /* ... und ggf. Dreieckstausch der 2 Zeiger */
      int * p0 = p[0];
      p[0] = p[1];
      p[1] = p0;
   }
}

Die Funktion bekommt ein Array der Länge 2. In diesem Array stehen Zeiger auf die zu sortierenden Zahlen. Ein Array mit Zeigern zu verwenden und nicht ein Array von int scheint recht umständlich, und das ist es hier auch. Aber stell dir vor, du willst Strukturen wie struct Person sortieren. Das Tauschen zweier Strukturen würde bedeuten, ihre kompletten Inhalte umzukopieren! Das wäre sehr aufwändig. Viel einfacher ist das Kopieren, wenn nur die Adressen zu kopieren sind.

Der Aufruf von sort2_a könnte dann so aussehen:

#include <stdio.h>

void sortiere (int a, int b)
{
   /* p[] enthält 2 int-Zeiger: die Adressen von a und b */
   int * p[2];
   p[0] = &a; 
   p[1] = &b; 

   /* Sortiere die Zeiger */ 
   sort2_a (p);

   printf ("Sortiert: %d, %d\n", *p[0], *p[1]);
}


Für den nächsten Schritt überlegen wir uns, daß das Array in sort2_a ebensogut void-Pointer enthalten kann. Die einzige Stelle, an der wir auf die endgültigen int-Objekte zugreifen, ist der Vergleich. Diesen Vergleich lagern wir in die Funktion compare_int aus:

/* Bekommt zwei void-Pointer und vergleicht die Inhalte.
 * Liefert 0 bei Gleichheit,
 * -1 wenn der erste Wert kleiner ist als der zweite und
 * 1  wenn der erste Wert größer ist als der zweite */
int compare_int (void * p0, void * p1)
{
   /* Um über die Zeiger zugreifen zu können müssen wir diese
    * erst zu int-Zeigern casten */
   int a0 = * (int*) p0;
   int a1 = * (int*) p1;

   if (a0 > a1)  return  1;
   if (a0 < a1)  return -1;

   return 0;
}

void sort2_b (void * p[])
{
   if (compare_int (p[0], p[1]) > 0)
   {
      void * p0 = p[0];
      p[0] = p[1];
      p[1] = p0;
   }
}

Ein Aufruf von sort2_b sieht dann genauso aus wie ein Aufruf von sort2_a.


Im nächsten Schritt definieren wir uns den neuen Datentyp comparator_t. Dieser ist ein Zeiger auf eine Funktion, die zwei void-Pointer erhält und einen int zurückliefert, also analog arbeitet zu compare_int von oben.

Unsere Sortierfunktion bekommt nun neben dem zu sortierenden Zeiger-Array auch eine Vergleichsfunktion compare mitgeliefert, die sie aufruft, wenn sie zwei Objekte vergleichen will

/* comparator_t sind Zeiger auf Funktionen, die 2 void-Pointer
 * erhalten und einen int zurückliefern */
typedef int (*comparator_t) (void*, void*);

/* Der Sortierer bekommt einen Funktionszeiger auf den Vergleicher.
 * Der Aufruf vom compare geht so als wäre es eine "normale" Funktion
 * (ist es im Endeffekt ja auch) */
void sort2_c (comparator_t compare, void * p[])
{
   if (compare (p[0], p[1]) > 0)
   {
      void * p0 = p[0];
      p[0] = p[1];
      p[1] = p0;
   }
}

Bei einem Aufruf von sort2_c muss man dann einen Komparator mit angeben. In einem Beispiel analog zu sort2_a von oben ist das:

sort2_c (compare_int, p);

Um zwei Strings lexikographisch zu sortieren nehmen wie die Standard-Funktion strcmp:

#include <string.h>
#include <stdio.h>

void foo()
{
   char * worte[] = { "Wort1", "Wort2" };

   sort2_c ((comparator_t) strcmp, (void**) worte);
}

Die Casts sind hier erforderlich. Alternativ könnte man sort2_c mit reinen void-Pointern versorgen und diese dann dort umcasten.

Syntax

Die Syntax zur Definition/Deklaration von Funktionszeigern ist etwas verzwackt. Zur Verdeutlichung ein paar Beispiele. Dabei legt das linke <Type> jeweils den Return-Typ fest.

/* definiert einen neuen Funktionszeiger-Typ */
typedef <Type> (*<Bezeichner>) (<Type>, <Type>, ...);

/* deklariert eine Variable als Funktionszeiger */
<Type> (*<Bezeichner>) (<Type>, <Type>, ...);

/* deklariert ein Array von Funktionszeigern (mit Initializer) */
<Type> (*<Bezeichner>[]) (<Type>, <Type>, ...) = { wert1, wert2, ... };

/* Castet Bezeichner zu einem Funktionspointer */
(<Type>(*)(<Type>, <Type>, ...)) <Bezeichner>

/* Castet Bezeichner zu einem Funktionspointer und ruft die Funktion auf */
((<Type>(*)(<Type>, <Type>, ...)) <Bezeichner>) (arg1, arg2, ...);

Standard-Funktionen

String-Funktionen

strcpy

Bei vielen Compilern können sie einem String nicht direkt einen Wert (Text) zuweisen. Dazu müssen Sie dann die Prozedur strcpy() benutzen. Diese erwartet als ersten Parameter den Namen einer String-Variablen (ohne eckige Klammern) und als zweiten Parameter den eines (anderen) Strings. Letzterer kann auch ein in doppelten Hochkommas (") eingeschlossener Text sein. Die Funktion fügt am Ende automatisch ein 0-Zeichen ein. Um diese Funktion nutzen zu können, müssen Sie die Datei string.h includieren!

#include <stdio.h>
#include <string.h>

int main (void)
{
  char stri1[21], eingabe[21];

  strcpy (stri1, "hallo");

  printf ("Der 1. String: %s\n", stri1);
  printf ("Bitte geben Sie maximal 20 Zeichen ein: ");
  scanf  ("%s", eingabe);
  strcpy (stri1, eingabe);
  printf ("\n%s = %s", stri1, eingabe);
  
  return 0;
}

Hinweis: Da ein String, wie jedes Feld, eigentlich ein Zeiger ist, dürfen Sie kein & bei scanf angeben!

Erklärung: Es werden zwei gleich große Strings definiert: stri1 und eingabe, mit je 20 "nutzbaren" Zeichen. In stri1 wird die Zeichenkette "hallo" hineinkopiert. Das 0-Zeichen am Ende wird automatisch angefügt. Der String wird ausgegeben. Als neues "Sonderzeichen" kommt %s ins Spiel. Es hat die gleiche Aufgabe wie %d oder %c, nur für Strings. Sie werden gebeten, einen String einzugeben. Dieser String wird danach in die Variable stri1 kopiert. Beide Strings, die ja nun die gleiche Zeichenkette enthalten, werden ausgegeben.

strlen

Die Funktion strlen, die als Parameter eine String-Variable erwartet, liefert die Länge dieses Strings zurück. Sie werden jetzt vermutlich sagen: "Das ist doch klar, wie lang der String ist. Ich habe es ja bei der Deklaratin angegeben". Das stimmt schon, aber denken Sie noch einmal an die null-terminierten Strings. Das 0-Zeichen steht am Ende des Strings (am Ende der gültigen Zeichenfolge), aber nicht unbedingt am Ende des reservierten Speicherplatzes. Haben Sie eine Variable "char Variable[21];", und ihr den Wert "hallo" zugewiesen, dann steht das null-Zeichen in Variable[5]. Der "gültige" String ist also 5 Zeichen (0-4) lang. Und genau das (5) würde strlen zurück liefern.

#include <stdio.h>
#include <string.h>

int main (void)
{
  char stri[21];
  
  strcpy (stri, "hallo");
  printf ("Der String ist %d Zeichen lang", strlen (stri));
}

Diese Funktion wird vor allem gebraucht, wenn Sie direkt auf den String zugreifen, mittels stri[0], stri[1], etc.

Ein- und Ausgabe-Funktionen

Bildschirm-Ausgabe

Bisher war das Tutorial trotz aller Beispiele reine Theorie. Sie konnten zwar Programme schreiben, aber die Funktion nicht testen. Hier lernen Sie nun, wie Sie etwas am Bildschirm ausgeben.

Die dazu notwendige Funktione heisst printf (das 'f' ist kein Fehler!). Diese Anweisung gibt die ihr übergebenen Parameter auf das Standard-Ausgabegerät aus, in der Regel also auf den Bildschirm. Sie kann beliebig viele Parameter übernehmen. Es müssen jedoch Standard-Datentypen (z.B. int, char, double...) sein!

#include <stdio.h>

int main (void)
{
    int zahl1 = 12;
    char zeichen1 = 'A';
    
    printf ("Das ist Text, und er wird als solcher ausgegeben. \n");
    printf ("Der Wert der Variablen 'zahl1' ist: %d \n", zahl1);
    printf ("Der Wert der Variablen 'zeichen1' ist: %c \n", zeichen1);
    printf ("Der Wert der Variablen 'zeichen1' ist: %d \n", zeichen1);
    
    return 0;
}

Der erste printf-Befehl gibt Text aus. Das Zeichen am Ende (\n) bedeutet "New Line", es bewegt den Cursor an den Anfang der nächsten Zeile.

Der zweite printf-Befehl gibt auch Text aus, am Ende befindet sich wieder das \n, um einen Zeilenvorschub zu erreichen. Das %d wird vom Compiler durch den ersten Parameter ersetzt, der nach dem Text angegeben wird. In diesem Fall wird %d also durch den Wert der Variablen zahl1 ersetzt. Das d im %d bedeutet "Dezimalzahl", der Computer gibt also eine ganze Zahl aus.

In der dritten Ausgabe wird ein Zeichen ausgegeben. Diesmal bedeutet %c "char" (Zeichen). Es wird also %c durch ein A ersetzt, denn die Variable zeichen1 wird als Character interpretiert.

Die letzte Ausgabe interpretiert den Inhalt von zeichen1 als Zahl, und gibt dager den ASCII-Wert von A, also 65 aus. Das ist ein typisches Beispiel für das mögliche unterschiedliche Interpretieren einer Variablen!

Tastatur-Eingabe

Um ein "gscheites" Programm schreiben zu können, muß man wissen, wie der Benutzer über die Tastatur Befehle eingeben kann. Die dafür notwendigen Funktionen stelle ich in diesem Kapitel vor. Die wichtigste Funktion ist scanf. Er liest Daten von der Tastatur. Die Syntax entspricht derer von printf:

int  zahl1;
char zeichen1;

printf ("Bitte geben Sie eine Zahl ein: ");
scanf  ("%d", &zahl1);
printf ("Geben Sie einen Zeichen ein: ");
scanf  ("%c", &zeichen1);

Das Programm gibt eine Eingabeaufforderung aus. Dann erwartet es vom Benutzer, daß er eine Zahl eingibt, die mit [ENTER] bestätigt wird. Dieser Wert wird in zahl1 abgespeichert. Danach erfolgt wiederum eine Aufforderung zur Eingabe, diesmal eines einzelnen Zeichens. Dieses kann man nun eingeben und ebenfalls mit [ENTER] bestätigen.

Macht man keine dem Datentyp der erwarteten Variable entsprechende Eingabe, dann bricht das Programm mit einer Fehlermeldung ab (wenn man z.B. "1_T2" eingibt, wenn eine Zahl erwartet wird)!

Das & vor den Parametern ist notwendig. Warum, das erfahren Sie im Kapitel "Unterprogramme". Für die Profis eine Kurz-Erklärung: Das Unterprogramm scanf bekommt zwar einen Wert übergeben, kann aber keinen zurückliefern ("call by value"). Daher wird kein Wert, sondern ein Zeiger auf eine Variable übergeben. Mit dem & Zeichen bekommen Sie die Adresse einer Variablen ("call by reference").

Parameter von main

Das Unterprogramm "main" kann, wie jede andere Funktion, Parameter besitzen. Doch keine selbst gewählten, sondern nur bestimmte. Doch warum braucht main Parameter? Denken Sie einmal an alle Betriebssystembefehle: dir *.exe oder copy *.* a: oder ls -la . All diese Befehle sind aus zwei Teilen aufgebaut: Befehl und Parameter. Und genau diese Parameter können Sie mit den main-Parametern abfragen.

int main (int argc, char *argv[], char* environ[])

Bei "argc" handelt es sich um eine normale int-Variable (engl. "argument count", "Parameter-Zähler"). In ihr steht die Anzahl der übergebenen Parameter. Die Parameter selbst folgen im zweiten Argument, das als Array von Strings übergeben wird. Das dritte Argument ist ein Array mit den Umgebungsvariablen. Seine Länge wird nicht explizit übergeben; nach dem letzten Element steht ein Null-String, also ein String der Länge 0. In dieser Array befindet sich auch der Inhalt der Umgebungsvariablen PATH, die den Suchpfad für ausführbare Programme enthält.

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[], char * environ[])
{
  int i;

  printf ("Es wurden %d Parameter angegeben", argc);

  for (i=0; i < argc; i++) 
     printf ("Parameter %d: %s\n", i, argv[i]);

  for (i = 0; environ[i] != NULL; ++i) 
     printf ("environ[%d] = %s\n", i, environ[i]);
}
Erklärung
Bei der ersten Ausgabe wird ausgegeben, wie viele Parameter insgesammt angegeben wurden. Dabei gibt immer mindestens einen Parameter, nämlich argc[0]. Dort steht der Name der aufgerufenen Datei selbst. Außerdem ist das letzte gültige Feldelement – wie in C üblich – das Element argv[argc-1]. In der for-Schleife werden alle Parameter, inklusive ihrer Nummer, ausgegeben. Experimentieren Sie mit den Parametern, um das System zu vertehen!

Kurzreferenz

Syntax-Bausteine

Die Erklärung des Aufbaus von C-Befehlen erfolgt neben einfachen Beispielen auch durch ihren prinzipellen Aufbau. In diesen Syntax-Beschreibungen finden sich immer wieder die gleichen Bausteine, die hier näher erklärt werden sollen. Falls Dir solch ein Syntax-Baustein begegnet, kannst Du ihn anklicken und kommst dann zu seiner Erläuterung.

In den Beispielen selbst gehören auch die spitzen Klammern zu dem Baustein (was daran zu erkennen ist, daß auch die Klammern eingefärbt sind). Die Klammern dürfen in einem konkreten C-Programm daher nicht eingetippt werden.

<Bezeichner>

Bezeichner in C dienen dazu, Variablen zu identifizieren und ihnen sprechende Namen zu geben, um die Quelle lesbarer zu machen. Man braucht Bezeichner auch, um selbstdefinierte Datentypen zu benennen und zum Benennen von Struct- und Union-Komponenten sowie als Namen für Funktionen und Sprungmarken (Labels).

Bezeichner dürfen aus den Kleinbuchstaben a...z, den Großbuchstaben A...Z, dem Unterstrich _ und den Ziffern 0...9 aufgebaut werden, wobei an erster Stelle jedoch keine Ziffer stehen darf.

Es wird zwischen Groß- und Kleinschreibung unterschieden.

<Ausdruck>

Ein Ausdruck in C ist ein Konstrukt, das einen Wert hat. Ob dieser Wert eine ganze Zahl ist, eine Kommazahl oder ein Zeiger, etc. ist dabei egal. Die einfachsten Ausdrücke sind Konstanten wie

2

oder Variablen wie

ein_zahl

Mehrere Ausdrücke können durch Operatoren zu komplexeren Ausdrücken kombiniert werden, etwa

eine_zahl + andere_zahl == 2

oder

eine_zahl = 2

Letzterer hat den Wert 2 und den Nebeneffekt, daß er diesen Wert an eine_zahl zuweist.

Auch der Aufruf einer Funktion, die einen Rückgabewert liefert, ist ein Ausdruck:

sin (1.2)

und kann zum Aufbau komplexerer Ausdrüche verwendet werden.

<Bedingung>

Eine Bedingung ist ein Ausdruck, bei der nur interessiert, ob dieser zu 0 (unwahr) auswertet oder zu ungleich 0 (wahr). Solche Ausdrücke findet man in if-Anweisungen, in Schleifenbedingungen und bedingten Zuweisungen

(ein_wert < 2) || (ein_wert > 40)
<Lvalue>

Ein Lvalue ist ein Ausdruck, dem etwas zugewiesen werden kann. Der Name Lvalue kommt aus dem Englischen. Das L steht abkürzend für left. Ein Lvalue ist damit ein Ausdruck, der auf der linken Seite eine Zuweisung in C stehen darf. Das x in den folgenden Beispiel-Ausdrücken muss ein Lvalue sein:

x = y-1
x++
<Konstante>

Eine Konstante ist ein Ausdruck, dessen Wert dem Compiler bekannt ist. Beispiele für Konstanten sind etwa

7
'B'
-13.98e12
1+(2*3)

und die Werte von Enums.

Das Pi aus dem folgenden Codestück definiert jedoch keine Konstante in diesem Sinne

const double Pi = 3.14159256;

denn in einem anderen Quellmodul könnte durch die Deklaration

extern const double Pi;

das Symbol Pi bekannt sein, ohne daß sein Wert bekannt ist!

<Adresse>

Eine Adresse ist ein Ausdruck, der einen Speicherort (physikalisch oder virtuell) halten kann. Adressen erhält man dadurch, daß man einem Bezeichner den Adress-Operator &voranstellt, Adressen durch Arithmetik berechnet oder Zahlen zu Adressen castet. Folgende Ausdrücke sind Adressen (eine sinnvolle Deklaration der auftretenden Variablen vorausgesetzt)

& eine_zahl
& ein_array[10]
& ein_struct
& ein_struct.komponente
(int *) 0x1234
(int *) eine_zahl
<Deklaration>
<Anweisung>

Anweisungen sind gewissermassen die Atome (oder Moleküle?), aus denen ein C-Programm besteht. Jedes C-Programm ist eine Abfolge von Deklarationen und Anweisungen. Einfache Anweisungen erhält man, in dem man einen Ausdruck nimmt und einen Strichpunkt dahinter schreibt:

<Ausdruck>;

wie in

x = x+1;

Andere Anweisungen sind die unten aufgeführten Schleifen und die if- sowie die switch-Anweisung.

Mehrere Deklarationen und Anweisungen können zu einem Block zusammengefasst werden. Dieser Block stellt dann wieder eine einzelne Anweisung dar und kann genau so gehandhabt werden!

{
   <Deklaration>
   <Deklaration>
   ...
   <Anweisung>
   <Anweisung>
   ...
}

In diesem Sinne ist auch z.B. die Syntax der if-Anweisung zu verstehen

if (<Bedingung>)
   <Anweisung>

besagt, daß der abhängig ausgeführte Code eine einzelne Anweisung sein darf oder eben ein kompletter Block oder die Verschachtelung mehrerer Blöcke etc.

Eine Anweisung kann auch "leer" sein, also nichts tun. Diese Anweisungen sind der leere Block

{
}

und der Strichpunkt

;

die man gelegentlich in Schleifen findet:

while (!timeout())
   {}

oder hinter Sprungmarken, die sonst direkt vor einer schliessenden Blockklammer stünden:

{
   ...
   goto ein_label;
   ...
   ein_label:;
}

Nicht jede Anweisung ist an jeder Stelle eines C-Programms erlaubt, so darf ein continue nut innerhalb einer Schleife stehen. Gleiches gilt für break, das aber auch innerhalb eines switch vorkommen darf.

<Type>

Dies steht für einen Datentyp. Es kann ein elementarer Typ sein wie int oder double, ein Zeiger darauf wie char* oder void*, und auch Qualifier enthalten wie das unsigned im Typ unsigned long long.

Zu den Typen gehören auch zusammengesetzte Datentypen wie Strukturen und Unions, mit typedef selbst definierte Typen und natürlich Zeiger darauf, wie aus dem Abschnitt Datentypen:

  • struct Person
  • struct Person *
  • data32_t
  • enum Farben

und Zeiger auf Funktionen.

<Parameterliste>

Die Parameterliste bei einer Funktionsdefinition gibt an, wieviel Übergabeparameter sie bekommt, wie diese heissen und welchen Typs diese sind. Der prinzipielle Aufbau ist

<Type> <Bezeichner>, <Type> <Bezeichner>, ...

Falls die Funktion keine Parameter hat, dann ist die Parameterliste leer.

Hier als Beispiel die zweiparameterige Funktion produkt. Der erste Parameter heisst a und ist ein double. Der zweite namens b ist vom Typ "Zeiger auf double", der Inhalt *b ist also auch ein double.

Definition der Funktion:

double produkt (double a, double *b)
{
   return a * (*b);
}

In älteren C-Quellen findet man noch eine andere Syntax für die Deklaration der Parameter, die aber heute praktisch nicht mehr verwendet wird: alte Definition der Funktion:

double produkt (a, b)
double a, *b;
{
   return a * (*b);
}

Um die Funktion bekannt zu machen, verwendet man eine Deklaration bzw. den Prototypen, der dem Compiler nur mitteilt, welche Parameter die Funktion bekommt und was sie zurückliefert. Für den Aufruf der Funktion muss der Compiler nur diesen Prototyp kennen, was die Funktion im Endeffekt macht und wie sie implementiert wurde ist egal, sie wird als BlackBox angesehen.

Prototyp der Funktion:

double produkt (double a, double *b);

Hier dürfen die Bezeichner auch fehlen:

double produkt (double, double*);

if

if (<Bedingung>)
   <Anweisung>

if-else

if (<Bedingung>)
   <Anweisung>
else
   <Anweisung>

for

for (<Ausdruck1>; <Bedingung>; <Ausdruck2>)
   <Anweisung>

Eine for-Schleife entspricht folgendem Konstrukt. Dabei sind die drei Ausdrücke optional. Fehlt die Bedingung, dann wird diese als "wahr" angenommen. Die beiden anderen Ausdrücke wird man als Ausdrücke mit Nebeneffekt wählen wie z.B. x=0 oder x=x-2.

{
   <Ausdruck1>;

   _loop:
   if (<Bedingung>)
      <Anweisung>
   else
      goto _break;

   _continue:
   <Ausdruck2>;
   goto _loop;

   _break:;
}

Die Labels _break und _continue entsprechen den Sprungzielen einer break bzw. continue-Anweisung innerhalb von <Anweisung>.

do-while

do
   <Anweisung>
while  (<Bedingung>);

while

while  (<Bedingung>)
   <Anweisung>

switch

switch  (<Bedingung>)
{
   case <Konstante>:
      <Anweisung>
      <Anweisung>
      ...

   case <Konstante>:
      <Anweisung>
      <Anweisung>
      ...

   ...
 
   default:
      <Anweisung>
      <Anweisung>
      ...
}

Liste der Schlüsselworte

auto, break, double, char, case, const, continue, default, do, else, enum, extern, float, for, goto, inline, if, int, long, return, register, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while

Liste der Operatoren

Operator Bedeutung
Arithmetische Operatoren
Dies sind die "normalen" arithmetischen Operationen, wie man sie aus der Schule kennt. Man kann damit und allen anderen Operatoren auch komplexere Ausdrücke aufbauen. Die Prioritäten sind so, wie man sie kennt, also "Punktrechnung vor Strichrechnung". Will man dies ändern, dann mit den runden Klammern:

1+2*3          → 7
(1+2)*3        → 9

<Ausdruck> + <Ausdruck> Addition
<Ausdruck> - <Ausdruck> Subtraktion
<Ausdruck> * <Ausdruck> Multiplikation
<Ausdruck> / <Ausdruck> Division
<Ausdruck> % <Ausdruck> Rest der Division (modulo)
- <Ausdruck> Vorzeichenumkehr, Zweier-Komplement
Logische Operatoren und Vergleiche
Die logischen und die vergleichenden Operatoren liefern als Ergebnis den Wert 0 (wahr) oder einen Wert ungleich 0 (falsch, um genau zu sein den Wert !0).

Man kann das Ergebnis zwar einer Variablen zuweisen, in aller Regel wird man solche Ausdrücke jedoch in Bedingungen zu if oder in Abbruch-Bedingungen von Schleifen finden.

<Ausdruck> && <Ausdruck> logisches AND: beides wahr (ungleich 0)
<Ausdruck> || <Ausdruck> logisches OR: mind. eines ist wahr (ungleich 0)
! <Ausdruck> logisches NOT (0 ↔ ungleich 0)
<Ausdruck> == <Ausdruck> ist gleich
<Ausdruck> != <Ausdruck> ist nicht gleich
<Ausdruck> < <Ausdruck> ist kleiner
<Ausdruck> <= <Ausdruck> ist kleiner oder gleich
<Ausdruck> > <Ausdruck> ist größer
<Ausdruck> >= <Ausdruck> ist größer oder gleich
Bitweise Operatoren
~ <Ausdruck> bitweise NOT (Einser-Komplement)
<Ausdruck> & <Ausdruck> bitweise AND
<Ausdruck> | <Ausdruck> bitweise ODER
<Ausdruck> ^ <Ausdruck> bitweise XOR
Shift-Operatoren
<Ausdruck> << <Ausdruck> Bits nach links schieben
<Ausdruck> >> <Ausdruck> Bits nach rechts schieben
Typen
Ein Cast in C kann dazu verwendet werden, den Typ eines Ausdruckes zu ändern oder den Ausdruck mit einer bestimmten Genauigkeit zu berechnen. Wird z.B. eine Berechnung standardmässig in 16 Bit ausgeführt, dann kann man mit einem Cast

(long) ···
ausdrücken, daß die Berechnung in 32 Bit erfolgen soll. Des weiteren kann man Zeiger und ganze Zahlen und Gleitkommazahlen ineinander umwandeln.

Casts können nicht dazu verwendet werden, um z.B. eine Zahl in einen String zu konvertieren, der diese Zahl darstellt! Dafür gibt es spezielle Funktionen wie itoa!

(<Type>) <Ausdruck> Cast, Typwandlung
sizeof (<Type>) Eine Konstante, deren Wert die Größe (in Bytes) des Typs ist. sizeof ist auch auf Objekte anwendbar wie int, Arrays bekannter Größe, Strukturen und Unions, Array-, Struktur- und Union-Komponenten, Pointer, etc. Beispiel:
int i, sum=0, array[] = { 1, -13, 4, 0, sizeof (int*) };

for (i=0; i< sizeof (array) / sizeof (array[0]); i++)
   sum += array[i];

Alle Elemente des Arrays werden aufaddiert, ohne daß deren Anzahl explizit in der Schleife genannt ist.

Zeiger und Adressen
* <Adresse> der Inhalt an Adresse
& <Lvalue> Adresse von
Strukturen, Unions, Arrays
<Struct>.<Bezeichner> Komponente einer Struktur/Union
<Zeiger-auf-Struct> -> <Bezeichner> Komponente einer Struktur/Union, deren Adresse man hat
<Adresse>[<Ausdruck>] Array-Element
Bedingte Auswertung
(<Bedingung>) ? <Ausdruck> : <Ausdruck> Auswahl des Wertes abhängig von der Bedingung
Zuweisung und Operatoren mit Nebeneffekt
Die Unterschiede der post- und pre-Varianten der Increment/Decrement kommen in Konstrukten wie x = *p++ zum tragen:

x = *p++;        x = *p; p = p+1;
x = *++p;        p = p+1; x = *p;
x = (*p)++;      x = *p; *p = (*p)+1;
x = ++(*p);      *p = (*p)+1; x = *p;

<Lvalue> = <Ausdruck> Zuweisung
++ <Lvalue> Pre-Increment
-- <Lvalue> Pre-Decrement
<Lvalue> ++ Post-Increment
<Lvalue> -- Post-Decrement
Kurzschreibweisen
Für ganz Faule gibt es anstatt

a = a @ b
für viele Operatoren (hier dargestellt durch ein @) die abkürzende Schreibweise
a @= b

<Lvalue> += <Ausdruck>
<Lvalue> -= <Ausdruck>
<Lvalue> *= <Ausdruck>
<Lvalue> /= <Ausdruck>
<Lvalue> %= <Ausdruck>
<Lvalue> ^= <Ausdruck>
<Lvalue> &= <Ausdruck>
<Lvalue> | <Ausdruck>
<Lvalue> <<= <Ausdruck>
<Lvalue> >>= <Ausdruck>

Autoren

Quellen

  • Kernighan und Ritchie
  • Christian Wirth, C-Tutorial
  • Prof. Dr. J. Dankert Ausführungen
  • W. Alex, Einführung in C/C++
  • Peter Baeumle-Courth, ANSI-C im Überblick

Siehe auch

Weblinks


LiFePO4 Speicher Test