Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
LiFePO4 Speicher Test

(Syntax-Bausteine)
K (Weblinks)
 
(186 dazwischenliegende Versionen von 20 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.
 +
 
 +
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==
 
==Geschichte==
Zeile 21: Zeile 24:
 
-->
 
-->
 
Ein einfaches C-Programm könnte folgendermassen aussehen. Das Programm tut eigentlich nichts, aber das Beispiel zeigt den prinzipiellen Aufbau.  
 
Ein einfaches C-Programm könnte folgendermassen aussehen. Das Programm tut eigentlich nichts, aber das Beispiel zeigt den prinzipiellen Aufbau.  
<pre>
+
#include <stdio.h>
#include <stdio.h>
+
 +
int Zahl1;
 +
char Zeichen1;
 +
 +
int main (void)
 +
{
 +
    int zahl2;
 +
 +
    {{comment|Anweisungen}}
 +
    return 0;
 +
}
  
int Zahl1;
+
'''Beschreibung:'''
char Zeichen1;
+
 
+
int main (void)
+
{
+
  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.
 
;<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.
Zeile 49: Zeile 50:
  
 
=Das Hauptprogramm main=
 
=Das Hauptprogramm main=
Die erste Funktion, die nach dem Programmstart ausgeführt wird, ist immer die Funktion mit dem Namen "<tt>main</tt>". Diese ist das Hauptprogramm.  
+
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.  
  
Der main-Funktion können beim PC Parameter übergeben werden. Dies sind die sogenannten Kommandozeilenparameter, 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 &ndahs; den Erfolg bzw. Fehlerstatus des Programmes mitteilt.  
+
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.  
  
Beim [[Microcontroller]] ist <tt>main</tt> das Startprogramm, das nach dem RESET 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!
+
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!
<pre>
+
  {{comment|void-Definition von main ist nur beim Controller ueblich}}
/*
+
  {{comment|spezielle Compilereinstellungen sind noetig, damit bei dieser Definition von main}}
  * void Definition von main ist nur beim Controller üblich
+
  {{comment|kein Fehler/Warnung erzeugt wird.}}
  * spezielle Compilereinstellungen sind nötig, damit bei dieser Definition von main
+
  void main ()
  * kein Fehler/Warnung erzeugt wird.  
+
{
  */
+
    ...
void main (void)
+
}
{
+
  ...
+
}
+
</pre>
+
  
 
=Blöcke=
 
=Blöcke=
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.
+
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.  
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":
+
  
<pre>
+
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.
int main (void)
+
 
{   /* der Block "main" beginnt */
+
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.
  int zahl;
+
 
 +
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}}
 
    
 
    
  {  /* ein Block beginnt */
+
    return 0;
      /* hier können Deklarationen und Anweisungen stehen */
+
} {{comment|"main" endet}}
  }  /* der Block endet */
+
 
+
  return 0;
+
}   /* "main" endet */
+
</pre>
+
 
+
'''Bemerkung:'''
+
Der "namenlose" Block ist eigentlich unnötig. In den folgenden Kapiteln lernen Sie jedoch bessere Beispiele kennen!
+
  
 
=Datentypen=
 
=Datentypen=
Zeile 93: Zeile 90:
  
 
===int, char, short, long (ganze Zahlen)===
 
===int, char, short, long (ganze Zahlen)===
In Variable 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.  
+
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>.
 
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>.
Zeile 177: Zeile 174:
 
Um den Zeiger mit der Adresse von <tt>zahl</tt> zu laden, schreibt man den Adress-Operator&nbsp;<tt>&</tt> von <tt>zahl</tt>:
 
Um den Zeiger mit der Adresse von <tt>zahl</tt> zu laden, schreibt man den Adress-Operator&nbsp;<tt>&</tt> von <tt>zahl</tt>:
 
  zeiger = &zahl;  
 
  zeiger = &zahl;  
Jetzt möchten Sie der Speicherstelle, deren Adresse der Zeiger enthält, einen Wert zuweisen. Dazu verwendet man den "Inhalts-Operators" <tt>*</tt> (<tt>*zeiger = 12</tt>).  
+
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.
 
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>
 +
void foo (enum Farben farbe)
 +
{
 +
  switch (farbe)
 +
  {
 +
      case ROT:
 +
        ...
 +
</pre>
 +
 +
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>.
  
 
==Zusammengesetzte Datentypen==
 
==Zusammengesetzte Datentypen==
Zeile 186: Zeile 208:
 
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!
 
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!
  
'''Syntax:''' Datentyp Variablenname[Anzahl]
+
'''Syntax:'''
 +
{{Type}} {{Bezeichner}}&#91;{{Konstante}}&#93;;
  
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 3 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.
+
'''Beispiel:'''
<pre>
+
unsigned int werte[100];
#include <stdio.h>
+
  
int main(void)
+
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>
  int i;
+
  int zahlen[10];  /* zahlen[0] bis zahlen[9] !!! */
+
#define NZAHLEN 10
 
+
  for (i=0; i<10; i++)
+
int main(void)
  {
+
{
    printf ("Bitte Zahl %d eingeben: ", i);
+
    int i;
    scanf  ("%d", & zahlen[i]);
+
    int zahlen[NZAHLEN];  {{comment|zahlen[0] ... zahlen[9]}}
    printf ("\n");
+
  }
+
  printf ("Super!\n");
+
 
    
 
    
  for (i=0; i<10; i++)  
+
    for (i=0; i < NZAHLEN; i++)
    printf ("Zahl %d ist: %d\n", i, zahlen[i]);
+
    {
   
+
      printf ("Bitte Zahl %d eingeben: ", i);
  return 0;
+
      scanf  ("%d", & zahlen[i]);
}
+
      printf ("\n");
</pre>
+
    }
 +
 +
    printf ("Super!\n");
 +
   
 +
    for (i=0; i < NZAHLEN; i++)
 +
      printf ("Zahl %d ist: %d\n", i, zahlen[i]);
 +
     
 +
    return 0;
 +
}
  
'''Bemerkungen:'''
 
 
Zuerst wird ein 10 int-Variablen großes Array angelegt.  
 
Zuerst wird ein 10 int-Variablen großes Array angelegt.  
 
In dieses wird nun der Reihe nach 10 Zahlen eingelesen.  
 
In dieses wird nun der Reihe nach 10 Zahlen eingelesen.  
 
Anschließend werden alle 10 Zahlen ausgegeben.  
 
Anschließend werden alle 10 Zahlen ausgegeben.  
 +
 +
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.
  
 
{{FarbigerRahmen|
 
{{FarbigerRahmen|
 
'''Merke:'''
 
'''Merke:'''
Wenn Sie einen ungültigen Index angeben (einen, der in der Deklaration nicht enthalten war) können je nach Compiler und Einstellung undefinierbare Dinge passieren, da dadurch andere Variableninhalte oder Programmcode überschrieben wird. Im schlimmsten Fall könnte sogar der Computer/Controller abstürzen. Also achten Sie darauf, daß Sie keine ungültigen Werte als Index angeben!
+
: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!
 
}}
 
}}
  
Zeile 270: Zeile 298:
 
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:  
 
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:  
  
<pre>
+
'''Syntax:'''
struct Person  
+
struct {{Bezeichner}}
{
+
{
  int id;
+
    {{Deklaration}}
  char vname[20], nname[20];
+
    {{Deklaration}}
  char telnr[15];
+
    ...
  int alter;
+
};
};
+
 
</pre>
+
'''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.  
 
"<tt>struct Person {</tt>" leitet die Definition der Struktur mit dem Namen "<tt>Person</tt>" ein.  
Dann werden in dieser Struktur fünf Komponenten angelegt: drei Strings und zwei <tt>int</tt>.  
+
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 dieses Typs <tt>struct Person</tt> anzulegen, geben Sie einfach  
+
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 Variablenname;
+
  struct Person {{Bezeichner}};
an.
+
  
Zum Zugriff auf eine Komponente der Struktur gibt man den Namen der Struktur-Variablen an (im folgenden Beispiel also <tt>hubert</tt>), einen Punkt und danach den Bezeichner der Komponente:
+
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:
<pre>
+
{{comment|Definition zweier Struktur-Variablen}}
  struct Person hubert, klaus;
+
struct Person hubert, klaus;
 +
 +
{{comment|Zugriff auf Struktur-Komponenten}}
 +
hubert.alter = 32;
 +
klaus.alter = hubert.alter + 1;
  
  hubert.alter = 32;
+
'''Hinweis:'''
  klaus.alter = hubert.alter + 1;
+
Der eventuell etwas lästige Gebrauch von <tt>struct</tt> kann schon bei der Definition der Struktur vermieden werden.
</pre>
+
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.
 +
 
 +
Der Hinweis gilt sinngemäß auch für die Definition <tt>union</tt> im nachfolgenden Abschnitt.
  
 
===Unions===
 
===Unions===
Zeile 300: Zeile 386:
 
{
 
{
 
   int id;
 
   int id;
 +
 +
  struct Person u_person;
  
 
   struct u_double
 
   struct u_double
Zeile 306: Zeile 394:
 
       double wert;
 
       double wert;
 
   };
 
   };
 
  struct Person u_person;
 
  
 
   struct u_pointer
 
   struct u_pointer
Zeile 319: Zeile 405:
 
union Daten data;
 
union Daten data;
 
</pre>
 
</pre>
Dies definiert eine Union mit den vier Zugriffsmöglichkeiten <tt>id</tt>, <tt>u_double</tt>, <tt>u_person</tt> und <tt>u_pointer</tt>. Die Große der Union richtet sich dabei nach der grössten 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.
+
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>.
  
 
Ein anderes Beispiel ist eine Union, die es ermöglicht, auf die einzelnen Bytes eines <tt>long</tt> zuzugreifen:
 
Ein anderes Beispiel ist eine Union, die es ermöglicht, auf die einzelnen Bytes eines <tt>long</tt> zuzugreifen:
Zeile 325: Zeile 411:
 
typedef union
 
typedef union
 
{
 
{
   unsigned long as_long;
+
   unsigned long as_long;
   unsignen chat as_byte[4];
+
   unsigned short as_short[2];
 +
  unsigned char  as_byte[4];
 
} data32_t;
 
} data32_t;
 
</pre>
 
</pre>
Dies überlagert einen <tt>unsigned long</tt> &ndash; also eine 32-Bit-Zahl &ndash; mit vier Bytes.
+
Dies überlagert einen <tt>unsigned long</tt> &ndash; also eine 32-Bit-Zahl &ndash; mit vier Bytes bzw. zwei Shorts.
<pre>
+
data32_t wert;
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)}}
  
wert.as_long = 0x12345678;
+
==Eigene Datentypen==
wert.as_byte[0] = 0xab;
+
/* nun ist wert.as_long gleich 0xab345678 oder 0x123456ab (je nach Plattform) */
+
</pre>
+
  
 
=Variablen=
 
=Variablen=
Zeile 346: Zeile 433:
 
==Anlegen von Variablen==
 
==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!  
 
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!  
<pre>
+
int Zahl1, Zahl2;
int Zahl1, Zahl2;
+
char Zeichen;
char Zeichen;
+
 +
int main (void)
 +
{
 +
    float gleitZahl;
 +
    {{comment|Anweisungen}}
 +
 +
    return 0;
 +
}
  
int main (void)
+
'''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]].
  float gleitZahl;
+
  /* Anweisungen */
+
  
  return 0;
+
    ...
}
+
    float gleitZahl1, gleitZahl2;
</pre>
+
    ...
;Erklärung: In einer Zeile können auch mehrere Variablen gleichen Types vereinbart werden, wenn man ein Komma dazwischen setzt. Variablen können in jedem Block vereinbart werden. Siehe [[#Gültigkeitsbereich|Gültigkeitsbereich]].
+
  
 
==Zuweisungen==
 
==Zuweisungen==
Zeile 380: Zeile 471:
  
 
==Zuweisungen bei float==
 
==Zuweisungen bei float==
Das funktioniert genau wie bei normale Zuweisungen. Nachkommastellen werden durch einen Punkt abgegrenzt:  
+
Das funktioniert genau wie bei normalen Zuweisungen. Nachkommastellen werden durch einen Punkt abgegrenzt:  
 
  floatVariable = 3.14;
 
  floatVariable = 3.14;
 
Zusätzlich kann eine Zehnerpotenz angegeben werden:
 
Zusätzlich kann eine Zehnerpotenz angegeben werden:
 
  floatVariable2 = -1.234E-6;
 
  floatVariable2 = -1.234E-6;
Dadurch wird der erst Wert mit 10<sup><tt>-</tt>6</sup> multipliziert, der Wert der Variablen ist also  
+
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>.
 
:<math>-1{,}234\cdot10^{-6} = -0.000001234</math>.
  
 
==Zuweisungen bei logischen Variablen==
 
==Zuweisungen bei logischen Variablen==
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 "wahr", ansonsten ist sie "unwahr".  
+
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".  
 
  intVariable = !0;  /* entspricht "wahr"  */
 
  intVariable = !0;  /* entspricht "wahr"  */
 
  intVariable = 0;    /* entspricht "unwahr" */
 
  intVariable = 0;    /* entspricht "unwahr" */
 
==Vergleich von Variablen==
 
Sie können Variablen miteinander vergleichen. Das geschieht mit einem der folgenden Zeichen mit den normalen mathematischen Regeln:
 
<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>
 
Als Ergebnis bekommen Sie einen logischen Ausdruck in Form einer int-Zahl.
 
intVariable = zahl1 <= zahl2;
 
das komplette Beispiel zeigt es deutlicher:
 
<pre>
 
  int i;
 
  int z1, z2;
 
 
  z1 = 5;
 
  z2 = 100;
 
  i = z1 <= z1  /* Ein Vergleich. Die int-Variable i wird wahr, da z1 kleiner als z2 */
 
 
  printf ("Ergebnis: %d\n", i);
 
</pre>
 
Die Variable <tt>i</tt> ist ungleich 0 ("wahr"), wenn z1 kleiner oder gleich z2 ist. Ist z1 jedoch größer als z2, dann ist <tt>i</tt> gleich 0 ("unwahr").
 
  
 
==Konstanten==
 
==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 dafür ein Schlüsselwort in C:
+
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:
  const ''datentyp'' name = value/* Zuweisung bei der Defininition der Variablen */
+
  const {{Type}} {{Bezeichner}} = {{Konstante}}{{comment|Zuweisung bei der Defininition der Variablen}}
  
 
Wichtig dabei ist, dass man Konstanten nur bei der Vereinbarung einen Wert zuweisen kann.  
 
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.
 
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;  /* Zuweisung bei der Defininition der Variablen */
+
  const float PI = 3.141592;  {{comment|Zuweisung bei der Defininition der Variablen}}
  
Es sei jedoch erwähnt, daß auch einer Konstanten nachträglich ein anderer Wert zugewieden werden kann. Im obigen Beispiel könnte mit
+
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;
 
  * ((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.
 
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.
Zeile 441: Zeile 497:
 
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
 
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, ...);
 
  void foo (const char * str, ...);
Dadurch ist <tt>str</tt> der Zeiger auf eine Zeichenkette, die innerhalt 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>.
+
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, daß <tt>str</tt> unveränderlich ist, dann so:
+
Soll ausgedrückt werden, dass <tt>str</tt> unveränderlich ist, dann so:
 
  void foo (char * const str, ...);
 
  void foo (char * const str, ...);
Jetzt wäre eine Änderung des Strings in Ordnung, etwa durch <tt>str[10] = 'a'</tt>. 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.
+
Jetzt wäre eine Änderung des Strings in Ordnung, etwa durch <tt>str[10] = 'a'</tt>.  
 +
 
 +
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==
 
==Gültigkeitsbereich==
In C können mehrere Variablen den gleichen Namen haben, solange eindeutig ist, welche in welchen Block gültig ist. Dabei gelten folgende Regeln:  
+
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.
 
;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.
Zeile 455: Zeile 513:
 
==Speicherklassen==
 
==Speicherklassen==
 
Jede Variable in C gehört zu einer bestimmten Speicherklasse
 
Jede Variable in C gehört zu einer bestimmten Speicherklasse
;<tt>auto</tt>: Lokale Variablen sind in aller Regal 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>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>: Eine 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>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>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>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!
Zeile 474: Zeile 532:
 
Auch Funktionen können einen Wert zurückliefern und in Ausdrücken weiter benutzt werden.
 
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,
 
In den folgenden Abschnitten wird gezeigt, welche Operatoren in C vorhanden sind,
und wei man damit neue Ausdrücke aufbauen kann.
+
und wie man damit neue Ausdrücke aufbauen kann.
  
 
==Lvalues==
 
==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.
 
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 alles Regel nicht.
+
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 <tt>const</tt> als Konstante markiert ist:
 
Ein einfaches Beispiel für einen Lvalue ist eine "normale" Variable, die nicht mit <tt>const</tt> als Konstante markiert ist:
Zeile 518: Zeile 576:
 
</center>
 
</center>
  
Eine interessante Eigenheit der Operatoren <tt>&amp;&amp;</tt> und <tt>||</tt> ist, dass
+
Eine interessante Eigenschaft der Operatoren <tt>&amp;&amp;</tt> und <tt>||</tt> ist, dass
sie die Auswertung abbrechen, wenn das Ergebnis bereits feststeht. Die Ausdrücke werden
+
sie die Auswertung abbrechen, sobald das Ergebnis feststeht. Die Ausdrücke werden
dabei immer von links nach rechts ausgewertet.
+
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>:
  
Beispiel:
+
'''Beispiel:'''
   (x > 5) || datei_auf_festplatte_gefunden("hallo.txt") /* gut */
+
   if (p && *p == 5)
   datei_auf_festplatte_gefunden("hallo.txt") || (x > 5) /* nicht so gut */
+
  {
 +
    {{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!'''
  
<tt>datei_auf_festplatte_gefunden</tt> ist fiktiv und steht für eine Funktion,
+
==Vergleich von Variablen==
die eine aufwändige Operation durchführt.
+
Skalare Variablen (also ganze Zahlen, Gleitkommazahlen, Zeiger) können miteinander verglichen werden. Dazu gibt es die folgenden Operatoren in C:
Beide Zeilen ergeben <tt>wahr</tt> wenn die Variable x einen Wert größer 5 hat oder
+
<center>
es eine Datei namens <tt>hallo.txt</tt> auf der Festplatte gibt.
+
{| {{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.
 +
 
 +
Es ist auch möglich, das Ergebnis der Auswertung in einer <tt>int</tt>-Variablen zu speichern:
 +
int i;
 +
int z1, z2;
 +
 +
z1 = 5;
 +
z2 = 100;
 +
i = z1 <= z2;  {{comment|Ein Vergleich. i wird "wahr", da z1 kleinergleich z2 ist}}
  
Wo ist also der Unterschied?<br>
+
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").
In der ersten Zeile wird geprüft, ob x größer als 5 ist und wenn das der Fall ist,
+
dann wird ''nicht mehr geprüft'', ob <tt>hallo.txt</tt> auf der Festplatte existiert.
+
In der zweiten Zeile wird ''immer geprüft'', ob <tt>hallo.txt</tt> auf der Festplatte
+
existiert und erst, wenn sie nicht gefunden wird, wird die Variable x ausgewertet.
+
  
 
==Arithmetische Operatoren==
 
==Arithmetische Operatoren==
Zeile 622: Zeile 708:
 
an dieser Adresse steht, wird der Operator * vorangestellt.
 
an dieser Adresse steht, wird der Operator * vorangestellt.
  
Beispiel:
+
'''Beispiel:'''
<pre>
+
  int x = 5;    /* x ist eine Integervariable und hat den Wert 5 */
+
  
  /* z ist ein Zeiger auf eine Integervariable und enthält somit
+
{{comment|x ist eine Integervariable und hat den Wert 5}}
    die Speicheradresse einer Integervariablen
+
int x = 5;
  */
+
   
  int *z;       
+
{{comment|z ist ein Zeiger auf eine Integer-Variable und enthaelt somit}}
 
+
{{comment|die Speicheradresse einer Integer-Variablen}}
   /* Verwendung des Adressoperators... */
+
int *z;       
  z = &x;       /* weise z die Speicheradresse von x zu */
+
    
 
+
{{comment|Verwendung des Adress-Operators: weist an z die Adresse von x zu}}
  /* Verwendung der Dereferenzierung */
+
z = &x;
  /* erhöhe den Wert, der bei Adresse z steht um eins */
+
  *z = *z + 1;
+
{{comment|Verwendung der Dereferenzierung}}
 
+
{{comment|erhoehe den Wert, der bei Adresse z steht, um eins}}
  /* da z auf x zeigt, hat auch x jetzt den Wert 6 */
+
*z = *z + 1;
</pre>
+
 +
{{comment|da z auf x zeigt, hat x jetzt den Wert 6}}
  
Da in C häufig Zeiger auf [[C-Tutorial#Strukturen | Strukturen]] verwendet werden, ist für den Zugriff
+
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:
auf ein Element eine abkürzende Schreibweise möglich:
+
  
 
Statt  
 
Statt  
Zeile 650: Zeile 734:
 
Beide Schreibweisen sind absolut gleichbedeutend, die Klammern bei der ersteren sind notwendig.
 
Beide Schreibweisen sind absolut gleichbedeutend, die Klammern bei der ersteren sind notwendig.
  
{{FarbigerRahmen |
 
 
'''Achtung!'''
 
'''Achtung!'''
 +
: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!
  
Bei der Dereferenzierung durch <tt>*</tt> findet keine Prüfung statt, ob der Zeiger auch
+
int *z; {{comment|z ist ein Zeiger auf einen int}}
auf eine gültige Speicheradresse verweist. Folgendes Codestück führt zum Absturz oder zu
+
einer Änderung '''irgendeiner''' Speicherstelle:
+
{{comment|An dieser Stelle ist z immer noch keine Speicheradresse zugewiesen.}}
 +
{{comment|z enthaelt irgendeine ungueltige Adresse!!}}
 +
 +
{{comment|"Erhoehe einen Integer _irgendwo_ im Speicher um 1" -> CRASH !!!}}
 +
*z = *z + 1;
  
<pre>
+
Viele C-Compiler erzeugen in der Standardeinstellung für das obige Codestück ''keine Warnung''!
  int *z; /* z ist ein Zeiger auf einen Integer */
+
 
+
  /* An dieser Stelle ist z immer noch keine Speicheradresse zugewiesen.
+
      z enthält irgendeine(!) Adresse
+
  */
+
 
+
  /* "Erhöhe einen Integer irgendwo im Speicher um 1" -> CRASH */
+
  *z = *z + 1;
+
</pre>
+
 
+
Viele C-Compiler (z.B. gcc) erzeugen in der Standardeinstellung für das obige Codestück ''keine Warnung''!
+
}}
+
  
 
==Cast-Operator==
 
==Cast-Operator==
Zeile 677: Zeile 753:
 
Um zum Beispiel aus einem Float ein Integer zu machen:
 
Um zum Beispiel aus einem Float ein Integer zu machen:
 
  var  = (int) 5.60;
 
  var  = (int) 5.60;
Dabei wird der Wert aber auch gerundet, und es findet somit ein Informationsverlust statt.
+
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:
 
Ein weiteres Beispiel ist das Umwandeln einer ganzen Zahl in eine Adresse:
Zeile 687: Zeile 763:
 
'''Achtung!'''
 
'''Achtung!'''
  
Der Cast-Operator selbst führt ''keine Konvertierung'' durch, sondern unterdrückt nur die Compilerwarnungen oder
+
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!
-fehler, die bei einer "verdächtigen" Umwandlung passieren. Er kann also nicht benutzt werden, um einen Text in
+
eine Zahl oder umgekehrt zu wandeln:
+
 
+
 
<pre>
 
<pre>
 
   #include <stdio.h>
 
   #include <stdio.h>
Zeile 696: Zeile 769:
 
   {
 
   {
 
         char text[] = "5.6";
 
         char text[] = "5.6";
         int zahl = (int)text;
+
         int zahl = (int) text;
  
 
         printf("%d\n", zahl);
 
         printf("%d\n", zahl);
Zeile 703: Zeile 776:
 
   }
 
   }
 
</pre>
 
</pre>
 
+
Ausgegeben wird weder 5 noch 6 sondern die Anfangsadresse des Strings <tt>"5.6"</tt>.
Ausgegeben wird weder 5 noch 6 sondern die Speicheradresse, an der der Text "5.6" beginnt. Der Cast-Operator
+
unterdrückt nur die Warnung, die auf den Fehler hinweisen will.
+
 
}}
 
}}
  
==Komma Operator==
+
==Komma-Operator==
 
Mit einem <tt>,</tt> können mehrere Ausdrücke nacheinander ausgewertet werden.
 
Mit einem <tt>,</tt> können mehrere Ausdrücke nacheinander ausgewertet werden.
 
Die Auswertung erfolgt von links nach rechts.
 
Die Auswertung erfolgt von links nach rechts.
Zeile 717: Zeile 788:
 
was erst an <tt>file</tt> einen Wert zuweist und den <tt>if</tt>-Block nur betritt,
 
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.
 
wenn <tt>file</tt> nicht der Nullpointer ist.
 +
 +
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;
  
 
==Zuweisungen und Operatoren mit Nebeneffekt==
 
==Zuweisungen und Operatoren mit Nebeneffekt==
===Bedingte Zuweisung===
+
===Zuweisung===
lvalue = (bediungung) ? if-ausdruck : else-ausdruck;
+
=== ++ und -- ===
  
<!--
+
<tt>++</tt> und <tt>--</tt> stellen einfachere Schreibweisen dar zum Addieren bzw. Subtrahieren von&nbsp;1.
Kein ANSI-C
+
--[[Benutzer:SprinterSB|SprinterSB]] 17:15, 29. Jun 2006 (CEST)
+
==Blöcke==
+
Selbst Blöcke haben in C einen Wert, der in einer Berechnung verwendet werden kann.
+
Der Wert eines Blockes ist der Wert der letzten Anweisung des Blocks.  
+
Der Block muss un runden Klammern stehen:
+
int sum;
+
sum = ({
+
    int i, s = 0;
+
    for (i=1; i<= 100; i++)
+
      s += i;
+
    s;
+
    });
+
  
Ein weiteres Beispiel ist das Makro <tt>PSTR</tt> von avr-gcc,
+
'''<tt>++</tt> (Inkrementieren)'''
das einen String ins Flash legt und die Adresse zurückliefert, was nur in einer
+
int foo = 1;  
Instruktion nicht machbar wäre:
+
  foo++;  
<pre>
+
{{comment|entspricht}}
#define PSTR(s)                         \
+
foo = foo + 1;
  ({                                    \
+
{{comment|jetzt ist foo &#61; 3}}
      static char __c[] PROGMEM = (s)\
+
      __c;                             \
+
  })
+
</pre>
+
-->
+
  
==Reihenfolge der Auswertung==
+
'''<tt>--</tt> (Dekrementieren)'''
 +
int foo = 1;
 +
foo--;
 +
{{comment|entspricht}}
 +
foo = foo - 1;
 +
{{comment|jetzt ist foo &#61; -1}}
  
=Kontrollanweisungen=
+
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.
  
==Boolsche Logik==
+
'''Beispiel'''
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).
+
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;}}
  
b1= b2 && b3;
+
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>.
  
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).
+
'''Beispiel:'''
 +
x = (x >= 3) ? 0 : x+1;
 +
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>
  
 +
==Reihenfolge der Auswertung==
  
b1= b2 || b3;
+
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
 +
|-
  
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.
+
|}
 +
</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!
  
b1= !b2;
+
=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>).
Der Wert von b1 ist dann TRUE, wenn der von b2 FALSE ist. Die NOT Verknüpfung "dreht den logischen Wert der Variablen um".
+
  
 
==if-Anweisung==
 
==if-Anweisung==
Syntax:
+
Mit Hilfe des if-Befehls kann man Codeteile abhängig davon einer Bedingung ausführen lassen:
 +
 
 +
'''Syntax:'''
 
  if ({{Bedingung}})
 
  if ({{Bedingung}})
 
     {{Anweisung}}
 
     {{Anweisung}}
Mit Hilfe des if-Befehls kann man Codeteile abhängig davon einer Bedingung ausführen lassen. Im Flussdiagramm sieht eine if-Anweisung folgendermassen aus:
+
oder mit else-Teil
 +
if ({{Bedingung}})
 +
    {{Anweisung}}
 +
else
 +
    {{Anweisung}}
  
[[bild:ifthenelse.png]]
+
'''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;
 +
}
  
Und in C schreibt man:
+
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.  
<pre>
+
if (Bedingung)        /* WENN */
+
{                    /* DANN */
+
      Anweisung1;
+
      Anweisung2;
+
      ...
+
}
+
else
+
{                    /* SONST */
+
      Anweisung3;
+
      Anweisung4;
+
      ...
+
}
+
</pre>
+
Wenn die Bedingung wahr ist, wird Anweisung1 ausgeführt. Anschliessend wird aus der if-Bedingung herausgesprungen. Anweisung2 wird also nicht ausgeführt.  
+
  
Ist die Bedingung unwahr, dann wird gleich zu Anweisung2 gesprungen, diese ausgeführt, und anschliessend wird der if-Befehl beendet.
+
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|
 
{{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.  
 
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 die if-Bedingung immer erfüllt!
+
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>.
 
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 Resultet. 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.
+
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.
  
==<tt>switch</tt>-Anweisung==
+
==switch-Anweisung==
Syntax:  
+
'''Syntax:'''
<pre>
+
switch (ausdruck)
+
{
+
    case wert1:
+
        Anweisungen1...
+
    case wert2:
+
        Anweisungen2...
+
    case wert3:
+
        Anweisungen3...
+
    ...
+
    default:
+
        Anweisungen4...
+
}
+
</pre>
+
  
Der <tt>ausdruck</tt> muss eine Ganzzahl (int) ergeben und wird dann der Reihe nach mit den Werten, die hinter den <tt>case</tt>-Anweisungen angegeben sind, verglichen. Bei einer Übereinstimmung werden alle Befehle ab der zutreffenden case-Anweisung ausgeführt. Stimmt der <tt>ausdruck</tt> mit keinem der Werte hinter <tt>case</tt>
+
switch ({{Ausdruck|}})  
überein, so wird der <tt>default</tt>-Abschnitt ausgeführt falls vorhanden.
+
{
 +
    case konstante1:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
        break;
 +
 +
    case konstante2:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
        break;
 +
         
 +
    {{comment|weitere case-Marken}}
 +
 +
    default:
 +
        {{Anweisung}}
 +
        {{Anweisung}}
 +
        ...
 +
} {{comment|Ende von switch}}
  
<b>Auch die Anweisungen der folgenden <tt>case</tt>- und des <tt>default</tt>-Abschnitts werden ausgeführt, wenn die Anweisungen eines
+
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.
<tt>case</tt>-Abschnitts nicht mit dem Befehl <tt>break;</tt> beendet werden!</b>
+
 
 +
<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.
 
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 egal.
+
Der <tt>default</tt>-Abschnitt ist optional. Die Reihenfolge, in der <tt>case</tt> und <tt>default</tt> angegeben werden, ist unerheblich.
  
 
==Schleifen==
 
==Schleifen==
Zeile 839: Zeile 977:
 
Wichtig ist also, ob die Bedingung '''vor''' oder '''nach''' den Schleifen-Anweisungen geprüft wird.  
 
Wichtig ist also, ob die Bedingung '''vor''' oder '''nach''' den Schleifen-Anweisungen geprüft wird.  
  
===<tt>while</tt>-Schleife===
+
===while-Schleife===
'''Syntax:''' while (Bedingung) { Anweisung; }
+
'''Syntax:'''
 +
while ({{Bedingung}})
 +
    {{Anweisung}}
  
Die WHILE-Schleife wird solange durchlaufen, wie die Bedingung erfüllt (TRUE) ist. Die Schleife wird also unter Umständen garnicht durchlaufen. Bei mehreren Anweisungen benötigen man einen Block!
+
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.
  
<pre>
+
int zahl1 = 0;
Zahl1=0;
+
int zahl2 = 1;
while (Zahl1<3)
+
{
+
while (zahl1 < 3)
  Zahl1=Zahl1+1;
+
{
  Zahl2=Zahl2*2;
+
    zahl1 = zahl1 + 1;
}
+
    zahl2 = zahl2 * 2;
</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.
+
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.
===<tt>do</tt>-<tt>while</tt>-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. Das ist dann sinnvoll, wenn der Vergleichswert für die Bedingung erst im Anweisungsblock gesetzt wird
+
===do-while-Schleife===
 +
'''Syntax:'''
 +
do
 +
    {{Anweisung}}
 +
while ({{Bedingung}});
  
<pre>
+
Die do-while-Schleife wird auf jeden Fall einmal durchlaufen und dann solange wiederholt, wie die Bedingung erfüllt ist.
int i=2;
+
int i = 2;
do  
+
{
+
do  
    i = i*i;  /* i quadrieren */
+
{
    printf ("i = %d\n", i);
+
    i = i*i;  {{comment|i quadrieren}}
}
+
    printf ("i = %d\n", i);
while (i < 20);
+
}
</pre>
+
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.
+
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.
  
===<tt>for</tt>-Schleife===
+
===for-Schleife===
'''Syntax:''' for (Zuweisung;Bedingung;Inkrement) { Anweisung; }
+
'''Syntax:'''  
 +
for ({{Ausdruck|1}}; {{Bedingung}}; {{Ausdruck|2}})
 +
    {{Anweisung}}
  
Die FOR-Schleife ist eine spezielle WHILE-Variante für die Formulierung von Zählschleifen. Die angegebene Variable wird am Anfang auf einen Wert gesetzt. Danach wird sie nach jedem Durchlauf verändert (meist um 1 erhöht). Ist die Bedingung nicht mehr erfüllt, 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.
+
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
  
Die gesammte Schleife wird solange durchgeführt, wie die Bedingung wahr 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.
+
'''Beispiel:'''
 +
int lauf, summe;
 +
 +
for (lauf=1, summe=0; lauf <= 10; lauf += 2)  
 +
{
 +
    summe += lauf;
 +
}
  
Die Theorie ist schwer, die Praxis nicht so sehr. In der Schleife wird die Summe ungerader Zahlen kleinergleich 10 berechnet.  
+
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.
  
<pre>
+
Das Äquivalent als while-Schleife:
int lauf;
+
int summe = 0;
+
  
for (lauf = 1; lauf <= 10; lauf = lauf+2 )  
+
int lauf = 1;                 {{comment|Anfangswerte}}
{
+
int summe = 0;
  summe = summe + lauf;
+
}
+
while (lauf <= 10)             {{comment|Bedingung}}
</pre>
+
{
Das Äquivalent als <tt>while</tt>-Schleife:
+
    summe += lauf;
<pre>
+
    lauf  += 2;                {{comment|Inkrement}}
int lauf  = 1;                /* Anfangswerte */
+
}
int summe = 0;
+
  
while (lauf <= 10)            /* Bedingung */
+
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.
{
+
  summe = summe + lauf;
+
  lauf += 2;                  /* Inkrement */
+
}
+
</pre>
+
 
+
 
+
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 1 bis 10 hat, ist in <tt>summe</tt> nach der Schleife die Summe der ungeraden Zahlen von 1 bis kleinergleich 10 gespeichert.
+
  
 
'''Erklärung:'''  
 
'''Erklärung:'''  
<tt>lauf = 1</tt> bedeutet, dass der Variablen <tt>lauf</tt> vor dem ersten Schleifendurchlauf der Wert 1 zugewiesen wird.  
+
<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 <= 10</tt> ist die Schleifenbedingung; ist sie nicht erfüllt, wird die Schleife beendet.  
<tt>lauf = lauf+2</tt> bedeutet, dass <tt>lauf</tt> nach jedem Durchlauf um&nbsp;2 erhöht wird.
+
<tt>lauf += 2</tt> bedeutet, dass <tt>lauf</tt> nach jedem Durchlauf um&nbsp;2 erhöht wird.
  
=Ein- und Ausgabe=
+
===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.
  
==Bildschirm-Ausgabe==
+
==break-Anweisung==
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.
+
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.
  
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!
+
==goto-Anweisung==
<pre>
+
#include <stdio.h>
+
  
int main (void)
+
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:
{
+
    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;
+
}
+
</pre>
+
  
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.  
+
'''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.
  
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.  
+
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.
  
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.
+
'''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:}};
  
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!
+
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.
  
==Tastatur-Eingabe==
+
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>
  
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.
+
=Funktionen=
Die wichtigste Funktion ist <tt>scanf</tt>. Er liest Daten von der Tastatur. Die Syntax entspricht derer von <tt>printf</tt>:  
+
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.
  
<pre>
 
int  zahl1;
 
char zeichen1;
 
  
printf ("Bitte geben Sie eine Zahl ein: ");
 
scanf  ("%d", &zahl1);
 
printf ("Geben Sie einen Zeichen ein: ");
 
scanf  ("%c", &zeichen1);
 
</pre>
 
  
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.
+
==Definition==
  
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)!
+
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:
  
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").
+
'''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.
  
=Allgemeines zu Unterprogrammen=
+
'''bei void-Funktionen:'''
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.
+
return;
Anders als in anderen Programmiersprachen gibt es in C keine generelle Unterscheidung zwischen Funktionen und Prozeduren. Trotzdem möchte ich kurz darauf eingehen:
+
'''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>.
  
==Funktionen==
+
==Aufruf==
'''Syntax:''' Rückgabe-Typ Name(Parameterliste);
+
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}}
  
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:
+
;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);
 +
}
 +
 +
==Beispiel==
 +
 +
Ein komplettes kleines Programm:
 
<pre>
 
<pre>
 
#include <stdio.h>
 
#include <stdio.h>
  
int hoch2 (int param1)
+
int quadrat (int param1)
 
{
 
{
 
   int zahl;
 
   int zahl;
Zeile 981: Zeile 1.174:
 
}
 
}
  
int 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);
+
   ergebnis = quadrat (zahl);
 
    
 
    
   printf ("%d hoch 2 = %d\n",zahl1, ergebnis);
+
   printf ("%d hoch 2 = %d\n", zahl, ergebnis);
   printf ("5 hoch 2 = %d\n", hoch2(5));
+
   printf ("%d hoch 2 = %d\n", 5, quadrat (5));
 
    
 
    
 
   return 0;
 
   return 0;
Zeile 997: Zeile 1.190:
 
</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 sind in Standard-C 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 Variablen an. Danach folgt der Name der Funktion (case-sensitiv!). Dann kommt in Klammern die Parameter-Liste. Dabei handelt es sich um eine Liste von Parametern, die jeweils durch Typ und Name angegeben werden und mittels Komma voneinander getrennt werden.
+
Im Block der Funktion können lokale Variablen definiert werden und alle Anweisungen ausgeführt werden.
+
Am Ende der Funktion muß diese ordnungsgemäß beendet werden. Dies geschieht mittels der Anweisung <tt>return</tt> 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:''' <tt>void Name (Parameterliste)</tt>
+
 
+
Eine Prozedur ist nichts anderes als eine Funktion mit dem speziellen  Rückgabetyp <tt>void</tt>, der kennzeichnet, daß die Funktion keinen Wert zurückliefert. Sie ist daher prinzipiell gleich aufgebaut wie eine Funktion, bis auf die folgenden Unterschiede:
+
 
+
Als Rückgabe-Datentyp wird <tt>void</tt> angegeben.
+
Einer Variablen kann daher nicht der Rückgabewert einer <tt>void</tt>-Funktion zugewiesen werden, da diese garkeinen Rückgabewert liefert.
+
 
+
<pre>
+
#include <stdio.h>
+
 
+
void ausgeben (int param1)
+
{
+
  printf ("Der Wert der Variablen ist: %d\n", param1);
+
}
+
 
+
int main(void)
+
{
+
  int zahl1 = 4;
+
 
+
  ausgeben (23);
+
  ausgeben (zahl1);
+
 
+
  return 0;
+
}
+
</pre>
+
 
+
'''Bemerkungen zum Programm:'''
+
Durch den Befehl "<tt>ausgeben(23)</tt>" wird die Prozedur "<tt>ausgeben</tt>" mit dem Parameter <tt>23</tt> aufgerufen. <tt>param1</tt> hat also den Wert <tt>23</tt>. Die einzige Anweisung innerhalb der Prozedur ist <tt>printf</tt>, was nun ausgeführt wird.
+
Der zweite Aufruf von <tt>ausgeben</tt> erfolgt mit dem Parameter <tt>zahl1</tt>, also wird <tt>param1</tt> der Wert 4 zugewiesen. Dieser wird nun wieder mittels <tt>printf</tt> ausgeben.  
+
  
Unterprogramme ohne Parameter
 
Das ist ihnen sicherlich schon aufgefallen: Viele Unterprogramme, haben statt einer Parameterliste nur <tt>void</tt> stehen. Das <tt>void</tt> bedeutet "die Funktion erhält keine Parameter". "<tt>void main (void)</tt>" bedeutet also: "Erstelle ein Unterprogramm Namens <tt>main</tt>, das keinen Rückgabewert und keine Parameter hat".
 
  
 
{{FarbigerRahmen |
 
{{FarbigerRahmen |
'''Merke:''' Auch wenn eine Funktion keine Parameter hat, müssen Sie beim Aufruf trotzdem die Klammern angeben. Beispiel für eine parameterlose Funktion <tt>foo</tt>:
+
'''Merke:''' Auch wenn eine Funktion keine Parameter hat, müssen beim Aufruf die Klammern angeben werden:
  foo();
+
  dummy();
 
}}
 
}}
  
Zeile 1.080: Zeile 1.231:
  
 
# Änderungen an einem Parameter in einer Funktion erscheinen ''nicht'' beim Aufrufer!
 
# Änderungen an einem Parameter in einer Funktion erscheinen ''nicht'' beim Aufrufer!
# Möchte man, dass eine Funktion einen Wert trotzdem dauerhaft ändern soll, so muss die Adresse des Wertes via [[#Zeiger | Zeiger]] übergeben werden.
+
# Möchte man, dass eine Funktion einen Wert trotzdem dauerhaft ändern soll, so muss die Adresse des Wertes via [[#Zeiger|Zeiger]] übergeben werden.
# 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.
+
# 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.
  
 +
'''Beispiele:'''
 +
void erhoehe (int x)
 +
{
 +
    x = x + 1;
 +
}
 +
 +
int main (void)
 +
{
 +
    int a = 0;
 +
    erhoehe(a);
 +
    {{comment|a ist immer noch 0}}
 +
   
 +
    return 0;
 +
}
  
Beispiele:
+
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.
<pre>
+
void erhoehe (int x)
+
{
+
  x = x + 1;
+
}
+
  
int main (void)
+
void erhoehe (int *x)
{
+
{
  int a = 0;
+
    {{comment|erhoehe den Wert an der Adresse x um eins}}
  erhoehe(a);
+
    *x = *x + 1;
  /* a ist immer noch 0 */
+
}
 
+
  return 0;
+
int main(void)
}
+
{
</pre>
+
    int a = 0;
 +
    erhoehe (&a);
 +
    {{comment|a ist jetzt 1}}
 +
   
 +
    return 0;
 +
}
  
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 x übergeben. Dass dann die Prozedur <tt>erhoehe</tt> die Kopie verändert, hat keine Auswirkung auf das Original <tt>a</tt> im Hauptprogramm.
+
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.
  
<pre>
+
'''Besonderheit bei Feldern'''
void erhoehe (int *x)
+
{
+
  /* erhöhe den Wert an der Adresse x um eins */
+
  *x = *x + 1;
+
}
+
  
int main(void)
+
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.
   int a = 0;
+
 
   erhoehe (&a);
+
'''Beispiel:'''
   /* a ist jetzt 1 */
+
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;
 
   return 0;
}
+
}
</pre>
+
  
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
+
Dass die Übergabe einer Adresse erfolgt, sieht man an folgendem Beispiel, das von der Funktionsweise '''absolut identisch''' mit dem vorhergehenden ist:
Adresse des Originals <tt>a</tt> und kann direkt mit dem Operator <tt>*</tt> auf den Wert an dieser Adresse zugreifen.
+
  
 +
{{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}}
 +
}
  
'''Besonderheit bei Feldern'''
+
{{FarbigerRahmen |
 +
Die Länge des Feldes wird nicht automatisch übergeben. Dafür ist ggf. ein zusätzlicher Parameter notwendig.
 +
}}
  
Bei der Übergabe von [[#Felder|Feldern]] gibt es eine Besonderheit. Schreibt man nämlich den Namen eines
+
==Inlining==
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
+
In C gibt es die Möglichkeit, eine Funktion als ''inline''-Funktion zu definieren.
den Originaldaten arbeiten und diese verändern.
+
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'':
 +
{{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.
 +
 
 +
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.
  
Beispiel:
 
 
<pre>
 
<pre>
void erhoehe (int x[])
+
#include <stdio.h>
 +
 
 +
void erhoehe (int *zeiger)
 
{
 
{
  x[0] = x[0] + 1;
+
  *zeiger = 1 + *zeiger;
  x[1] = x[1] + 3;
+
  x[2] = x[2] + 5;
+
 
}
 
}
  
int main(int argc, char **argv)
+
int main ()
 
{
 
{
   int a[] = {10, 20, 30};
+
   int zahl;
 
    
 
    
   erhoehe (a);
+
  printf ("Zahl eingeben: ");
   /* a hat jetzt folgenden Inhalt: 11, 23, 35 */
+
  scanf  ("%d", &zahl);
 +
   erhoehe (&zahl);
 +
   printf ("\nDie erhoehte Zahl lautet: %d\n", zahl);
 
    
 
    
 
   return 0;
 
   return 0;
Zeile 1.151: Zeile 1.523:
 
</pre>
 
</pre>
  
 +
==Zeiger auf Funktionen==
  
Dass die Übergabe einer Adresse erfolgt, sieht man an folgendem Beispiel, das von der Funktionsweise '''absolut identisch''' mit dem vorhergehenden ist:
+
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.
  
<pre>
+
Eine einfache Sortierfunktion, die nur zwei Zahlen sortiert, könnte man also so schreiben:
/* Bei Parametern gibt es keinen Unterschied zwischen Zeiger und Feld */
+
{{comment|Sortiert ein Array von 2 int-Zeigern nach den Inhalten
  void erhoehe (int *x)
+
  * an den Zeiger-Adressen}}
{
+
void sort2_a (int * p[])
  x[0] = x[0] + 1;
+
{
  x[1] = x[1] + 3;
+
    {{comment|Inhalte vergleichen...}}
  x[2] = x[2] + 5;
+
    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.
  
int main(int argc, char **argv)
+
Der Aufruf von <tt>sort2_a</tt> könnte dann so aussehen:
{
+
#include <stdio.h>
  int a[] = {10, 20, 30};
+
 
+
void sortiere (int a, int b)
  erhoehe (a);
+
{
 +
    {{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]);
 +
}
  
  /* a hat jetzt folgenden Inhalt: 11, 23, 35 */
 
}
 
</pre>
 
  
{{FarbigerRahmen |
+
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:
Die Länge des Feldes wird nicht automatisch übergeben. Dafür ist ggf. ein zusätzlicher Parameter notwendig.
+
{{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=
 
=Standard-Funktionen=
  
==strcpy==
+
==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!  
 
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!  
  
Zeile 1.209: Zeile 1.675:
 
In <tt>stri1</tt> wird die Zeichenkette <tt>"hallo"</tt> hineinkopiert. Das 0-Zeichen am Ende 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 <tt>%s</tt> ins Spiel. Es hat die gleiche Aufgabe wie <tt>%d</tt> oder <tt>%c</tt>, nur 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 <tt>stri1</tt> kopiert.  
 
Dieser String wird danach in die Variable <tt>stri1</tt> kopiert.  
 
Beide Strings, die ja nun die gleiche Zeichenkette enthalten, werden ausgegeben.
 
Beide Strings, die ja nun die gleiche Zeichenkette enthalten, werden ausgegeben.
  
==strlen==
+
===strlen===
Die Funktion <tt>strlen</tt>, 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>
Zeile 1.229: Zeile 1.695:
 
</pre>
 
</pre>
  
Diese Funktion wird vor allem gebraucht, wenn Sie direkt auf den String zugreifen, mittels <tt>stri[0]</tt>, <tt>stri[1]</tt>, 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==
  
 +
===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.
  
===Zeiger als Parameter===
+
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!
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").
+
 
+
 
<pre>
 
<pre>
 
#include <stdio.h>
 
#include <stdio.h>
 
void erhoehe (int *zeiger)
 
{
 
  *zeiger = *zeiger+1;
 
}
 
  
 
int main (void)
 
int main (void)
 
{
 
{
  int zahl;
+
    int zahl1 = 12;
 
+
    char zeichen1 = 'A';
  printf ("Zahl eingeben: ");
+
   
  scanf  ("%d", &zahl);
+
    printf ("Das ist Text, und er wird als solcher ausgegeben. \n");
  erhoehe (&zahl);
+
    printf ("Der Wert der Variablen 'zahl1' ist: %d \n", zahl1);
  printf ("\nDie erhoehte Zahl lautet: %d\n", zahl);
+
    printf ("Der Wert der Variablen 'zeichen1' ist: %c \n", zeichen1);
 
+
    printf ("Der Wert der Variablen 'zeichen1' ist: %d \n", zeichen1);
  return 0;
+
   
 +
    return 0;
 
}
 
}
 
</pre>
 
</pre>
  
==Parameter von <tt>main</tt>==
+
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.
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: "dir *.exe", "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 <tt>main</tt>-Parametern abfragen.  
+
 
 +
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.
 +
 
 +
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.
 +
 
 +
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!
 +
 
 +
===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 <tt>scanf</tt>. Er liest Daten von der Tastatur. Die Syntax entspricht derer von <tt>printf</tt>:
 +
 
 +
<pre>
 +
int  zahl1;
 +
char zeichen1;
 +
 
 +
printf ("Bitte geben Sie eine Zahl ein: ");
 +
scanf  ("%d", &zahl1);
 +
printf ("Geben Sie einen Zeichen ein: ");
 +
scanf  ("%c", &zeichen1);
 +
</pre>
 +
 
 +
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.
 +
 
 +
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 <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>
 
<pre>
 
int main (int argc, char *argv[], char* environ[])
 
int main (int argc, char *argv[], char* environ[])
Zeile 1.283: Zeile 1.775:
 
</pre>
 
</pre>
  
;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><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!  
+
;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!
  
 
=Kurzreferenz=
 
=Kurzreferenz=
 
==Syntax-Bausteine==
 
==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 werden. Falls die solch ein Syntax-Baustein begegnet, kannst du ihn anclicken und kommst dann zu seiner Erläuterung.
+
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.
  
 
=====<tt><Bezeichner></tt>=====
 
=====<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, wenn man eigene Typen definiern will um diese zu benennen und zum Benamen von Struct- und Union-Komponenten und als Namen für Funktionen und Sprungmarken.
+
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 <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.
  
Bezeichner dürfen auf den Kleinbuchstaben <tt>a</tt> bis <tt>z</tt>, den Großbuchstaben <tt>A</tt> bis <tt>Z</tt>, dem Unterstrich&nbsp;<tt>_</tt> und den Ziffern <tt>0</tt> bis <tt>9</tt> aufgebaut werden, wobei an erster Stelle jedoch keine Ziffer stehen darf.
+
Es wird zwischen Groß- und Kleinschreibung unterschieden.
  
 
=====<tt><Ausdruck></tt>=====
 
=====<tt><Ausdruck></tt>=====
Zeile 1.326: Zeile 1.822:
 
  -13.98e12
 
  -13.98e12
 
  1+(2*3)
 
  1+(2*3)
und die Werte von [[enum]]s.  
+
und die Werte von [[#Enum|Enum]]s.  
  
 
Das <tt>Pi</tt> aus dem folgenden Codestück definiert jedoch keine Konstante in diesem Sinne
 
Das <tt>Pi</tt> aus dem folgenden Codestück definiert jedoch keine Konstante in diesem Sinne
 
  const double Pi = 3.14159256;
 
  const double Pi = 3.14159256;
denn in einem anderen Quellmodul könnte <tt>Pi</tt> durch die Deklaration
+
denn in einem anderen Quellmodul könnte durch die Deklaration
 
  extern const double Pi;
 
  extern const double Pi;
bekannt sein, ohne daß sein Wert bekannt ist!  
+
das Symbol <tt>Pi</tt> bekannt sein, ohne daß sein Wert bekannt ist!
  
 
=====<tt><Adresse></tt>=====
 
=====<tt><Adresse></tt>=====
Zeile 1.365: Zeile 1.861:
 
  if ({{Bedingung}})
 
  if ({{Bedingung}})
 
     {{Anweisung}}
 
     {{Anweisung}}
besagt, daß der abhängig ausgeführte Code eine einzelne Anweisung sein datf oder eben ein kompletter Block oder die Verschachtelung mehrerer Blöcke etc.
+
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
 
Eine Anweisung kann auch "leer" sein, also nichts tun. Diese Anweisungen sind der leere Block
Zeile 1.382: Zeile 1.878:
 
     {{Label|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==
Zeile 1.412: Zeile 1.948:
 
     {{Label|_break}}:;
 
     {{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>Statement</tt>.
+
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-while==
 
  do
 
  do
Zeile 1.430: Zeile 1.967:
 
       ...
 
       ...
 
   
 
   
 +
    case {{Konstante}}:
 +
      {{Anweisung}}
 +
      {{Anweisung}}
 +
      ...
 +
 +
    ...
 +
 
 
     default:
 
     default:
 
       {{Anweisung}}
 
       {{Anweisung}}
Zeile 1.437: Zeile 1.981:
  
 
=Liste der Schlüsselworte=
 
=Liste der Schlüsselworte=
 
+
[[#Speicherklassen|<tt>auto</tt>]],
* [[#Speicherklassen|auto]]
+
[[#break-Anweisung|<tt>break</tt>]],
* break
+
[[#Elementare Datentypen|<tt>double</tt>]],
* [[#Elementare Datentypen|double]]
+
[[#Elementare Datentypen|<tt>char</tt>]],
* [[#Elementare Datentypen|char]]
+
[[#switch-Anweisung|<tt>case</tt>]],
* case
+
[[#Konstanten|<tt>const</tt>]],
* [[#Konstanten|const]]
+
[[#continue-Anweisung|<tt>continue</tt>]],
* continue
+
[[#switch-Anweisung|<tt>default</tt>]],
* default
+
[[#do-while-Schleife|<tt>do</tt>]],
* do
+
[[#if-Anweisung|<tt>else</tt>]],
* else
+
[[#Enum|<tt>enum</tt>]],
* enum
+
[[#Speicherklassen|<tt>extern</tt>]],
* [[#Speicherklassen|extern]]
+
[[#Elementare Datentypen|<tt>float</tt>]],
* [[#Elementare Datentypen|float]]
+
[[#for-Schleife|<tt>for</tt>]],
* for
+
[[#goto-Anweisung|<tt>goto</tt>]],
* goto
+
[[#Inlining|<tt>inline</tt>]],
* if
+
[[#if-Anweisung|<tt>if</tt>]],
* [[##Elementare Datentypen|int]]
+
[[#Elementare Datentypen|<tt>int</tt>]],
* [[#Elementare Datentypen|long]]
+
[[#Elementare Datentypen|<tt>long</tt>]],
* return
+
[[#return-Anweisung|<tt>return</tt>]],
* [[#Speicherklassen|register]]
+
[[#Speicherklassen|<tt>register</tt>]],
* [[#Elementare Datentypen|short]]
+
[[#Elementare Datentypen|<tt>short</tt>]],
* signed
+
[[#Elementare Datentypen|<tt>signed</tt>]],
* sizeof
+
[[#Liste der Operatoren|sizeof]],
* [[#Speicherklassen|static]]
+
[[#Speicherklassen|<tt>static</tt>]],
* [[#Strukturen|struct]]
+
[[#Strukturen|<tt>struct</tt>]],
* switch
+
[[#switch-Anweisung|<tt>switch</tt>]],
* typedef
+
[[#Eigene Datentypen|<tt>typedef</tt>]],
* [[#Unions|union]]
+
[[#Unions|<tt>union</tt>]],
* unsigned
+
[[#Elementare Datentypen|<tt>unsigned</tt>]],
* [[#Elementare Datentypen|void]]
+
[[#Elementare Datentypen|<tt>void</tt>]],
* volatile
+
[[#Speicherklassen|<tt>volatile</tt>]],
* while
+
[[#while-Schleife|<tt>while</tt>]]
 
+
Baustelle
+
  
 
=Liste der Operatoren=
 
=Liste der Operatoren=
Zeile 1.485: Zeile 2.027:
 
<tt>(1+2)*3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; 9<br/>
 
<tt>(1+2)*3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt>&rarr; 9<br/>
 
|-
 
|-
| <tt>{{Ausdruck|}} + {{Ausdruck|}}</tt> || Addition
+
| <tt>{{Ausdruck|}}&nbsp;+&nbsp;{{Ausdruck|}}</tt> || Addition
 
|-
 
|-
 
| <tt>{{Ausdruck|}} - {{Ausdruck|}}</tt> || Subtraktion
 
| <tt>{{Ausdruck|}} - {{Ausdruck|}}</tt> || Subtraktion
Zeile 1.539: Zeile 2.081:
 
| <tt>{{Ausdruck|}} << {{Ausdruck|}}</tt> || Bits nach links schieben
 
| <tt>{{Ausdruck|}} << {{Ausdruck|}}</tt> || Bits nach links schieben
 
|-
 
|-
| <tt>{{Ausdruck|}} >> {{Ausdruck|}}</tt> || Schieben nach rechts schieben
+
| <tt>{{Ausdruck|}} >> {{Ausdruck|}}</tt> || Bits nach rechts schieben
  
 
|- {{Hintergrund1}}
 
|- {{Hintergrund1}}
!colspan="2"|Typumwandlung
+
!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/>
 
|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/>
Zeile 1.551: Zeile 2.093:
 
|-
 
|-
 
| <tt>({{Type}}) {{Ausdruck|}}</tt> || Cast, Typwandlung
 
| <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}}
 
|- {{Hintergrund1}}
Zeile 1.621: Zeile 2.170:
 
|}
 
|}
  
 
+
= Autoren=
'''Autoren'''
+
 
* Plasma
 
* Plasma
 
* Bernd
 
* Bernd
 
* [[Benutzer:SprinterSB|SprinterSB]]
 
* [[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=
 +
* [[C-Tutorial/Interrupt-Programmierung]]
 
* [[avr-gcc]]
 
* [[avr-gcc]]
 
* [[Compiler]]
 
* [[Compiler]]
 
* [[WinAVR]]
 
* [[WinAVR]]
 
* [[Fallstricke bei der C-Programmierung]]
 
* [[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://wwwuser.gwdg.de/~kboehm/ebook/inhalt.html C-Programmieren unter Linux]
 
* [http://www.gdv.uni-hannover.de/documentation.php Skripte zum Selbststudium: C, C++, Java, etc]
 
* [http://www.gdv.uni-hannover.de/documentation.php Skripte zum Selbststudium: C, C++, Java, etc]
 
* [http://info.baeumle.com/ansic.html Einführung in ANSI-C]
 
* [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:Quellcode C|!]]
 
[[Kategorie:Quellcode C|!]]
 
[[Kategorie:Software]]
 
[[Kategorie:Software]]
 
[[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