Programmieren in ANSI-C Kapitel 3: Syntax, Präprozessor, Funktionen, Parameter, Operatoren
3.1 Die C - Syntax
Bei der Syntax handelt es sich im Wesentlichen um die "grammatikalischen" Vorschriften, die man beim Programmieren zu
beachten hat. Bereits in Kapitel 2 konnte man den Beispielen entnehmen, das eine Anweisung in C immer mit einem
Strichpunkt (;) abgeschlossen werden muss.
Beispiele:
if
( x <= y )
return
x * y;
jahreszahl = jahrtausend * 1000 + jahrhundert * 100 +
jahrzehnt * 10 + jahr;
|
Wie man sieht, ist es keinesfalls notwendig, dass eine Anweisung am Ende einer Zeile beendet sein muss.
Tatsächlich können sich Anweisungen auch über mehrere Zeilen hinweg erstrecken. Wichtig ist nur, wo der Strichpunkt
steht.
Um dem Compiler anzuzeigen, wo zum Beispiel eine Funktion beginnt und endet, aber auch um Schleifen und andere Kontroll-Strukturen
zu begrenzen, verwendet man in C/C++ sogenannte Blockklammern ( { und } ). '{' markiert dabei den Anfang eines Blockes, '}' das Ende.
{
// Dies ist ein Block Nr. 1
{
// Dieser Block ist Teil des ersten Blockes
}
}
|
3.2 Der C - Präprozessor
Der C/C++ Präprozessor macht vor dem Compilieren eines Programms gewisse Vorarbeiten. So löst er zum Beispiel
selbst definierte Symbole auf und bindet externe Bibliotheken ein. Im Moment möchte ich hier nur auf die beiden wichtigsten
Präprozessor-Befehle eingehen:
3.2.1 #include
Die include-Anweisung sagt dem Präprozessor, welche vorgefertigten Bibliotheken er einbinden muss, damit alle im Programm
verwendeten Befehle auch vom Compiler gefunden werden.
#include <stdio.h>
#include "myFunctions"
|
Die erste Erkentnis ist wohl, das am Ende einer Präprozessor-Anweisung kein ';' steht. Als zweites ist ersichtlich, das es offenbar
zwei verschiedene Typen von include-Befehlen gibt.
- In spitzen Klammern stehen Bibliotheken, die die Entwicklungsumgebung mitliefert, also z.B. die Standartbibliotheken
stdio.h, math.h, windows.h usw. Diese liegen in einem gesonderten Verzeichnis der Entwicklungsumgebung.
- In Hochkommas stehen Bibliothenken, die der Programmierer (also DU) selbst für dieses Programm geschrieben hat.
Diese müssen im gleichen Verzeichnis liegen wie die zugehörige Programm- oder Projektdatei.
3.2.2 #define
Mit der define-Anweisung leget man sogenannte Makros fest. Der Präprozessor durchsucht den Kompletten Programmcode
einer Datei und ersetzt alle Makronamen durch den Makrotext. Aufschluss soll folgendes Beispiel geben:
#define LAENGE 10
#define NEWLINE printf("\n")
/* Aus den Zeilen */
int i = LAENGE;
NEWLINE;
//Macht der Präprozessor:
int i = 10;
printf("\n");
|
ACHTUNG: define nimmt keinen Einfluss auf Werte, die mit einer Ziffer beginnen oder auf Text inerhalb von Hochkommas!
3.3 Funktionen
Funktionen bilden den Kern der Programmierung in den meisten Programmiersprachen. Sie bieten die Möglichkeit, bestimmte
Programmteile aus dem Hauptprogramm auszugliedern. Entweder, weil sich sich öfter wiederholen und dann nur einmal geschrieben
werden müssen, oder aus grüden der Übersichtlichkeit. Man unterscheidet in C nicht, wie in anderen Sprachen, zwischen
Funktionen die Werte zurückgeben und Prozeduren ohne Rückgabewerte. Zu diesem Zweck gibt es in C den Typ void, der
eine Funktion ohne Rückgabewert definiert.
3.3.1 Die Funktion main()
Die Funktion main bildet den Kern der Programmierung auf der DOS-Ebene. Kein Programm läuft ohne die main-Funktion.
Säter werden wir sehen, das auch in der Windows-Programmierung eine Funktion Winmain existiert. Mit der Einführung von C++
hat sich die Vereinfachung breit gemacht, die Funktion als vom Typ 'void' zu deklarieren. Da jedoch in unseren Vorlesungen der in C
gebräuchleiche Standarttyp 'int' verwendet wird, werde ich mich auch hier daran halten. Der Typ 'int' hat hier den Vorteil, dass das
Programm einen Exit-Code an DOS zurückliefert, der z.B. in einer ERRORLEVEL-Anweisung abgefragt werden kann.
3.3.2 Andere Funktionen
Natürlich gibt es auch die Möglichkeit, andere Funktionen zu definieren. Hier ein paar Beispiele, wie so etwas in C/C++ aussieht:
double f( double x )
{return 2*x*x + 3*x + 7; }
void swap( int*, int* );
float Oeffne( int handle );
|
Jede Funktion beginnt also mit der Typdeklaration wie eine Variable auch. Darauf folgt der Name der Funktion, für den die gleichen
Regeln gelten, wie für Variablen auch. In runden Klammern folgt dann eine Liste aller Übergabe-
Parameter, durch Kommas getrennt.
Ich möchte hier nochmals darauf hinweisen, das bei der Namensgebung zwischen Groß- und Kleinschreibung unterschieden wird!
Definition: Wird einer Funktion -wie im Beispiel f( double x )- sofort ihr Programmcode zugewiesen, so spricht man von einer Funktions-Definition.
In den hier verwendeten Programmbeispielen wird dies die Regel sein.
Deklaration: Eine Funktion darf in einem Programm erst verwendet werden, nachdem Sie dem Compiler bekannt gemacht wurde.
Manchmal will man allerdings die Funktion 'main' ganz oben im Programm stehen haben oder aber die gewünschte Funktion wurde
in einer anderen Bibliothek definiert. Hierzu werden die Funktionen am Anfang des Prorammes deklariert, damit der Compiler im Folgenden
mit dem Funktionsnamen etwas anfangen kann. Dazu reicht ein einfacher Prototyp wie in der obigen swap-Funktion aus, den dem Compiler
ist es egal, wie die Übergabe-Parameter heißen. Das dritte Beispiel zeigt eine lesbarere Form für die Deklaration. Wo genau der
Unterschied ist und ob es überhaupt einen gibt, muss ich erst noch rausfinden. Natürlich muss eine Definition der Funktion trotzdem
erfolgen. Auf jeden Fall wird eine Deklaration mit einem Semikolon abgesschlossen!
3.3.3 Rückgabewerte
Aus dem ersten Beispiel oben ist bereits gut ersichtlich, was ein Rückgabewert ist. Es handelt sich um einen von der Funktion
errechneten Wert, der eben von dieser Funktion zurückgegeben wird. Die Funktion 'f(x)' würde hier also für x = 2 den Wert 5 zurückgeben.
Die return-Anweisung bestimmt also, welcher Wert genau zurückgegeben wird. Hier kann ein mathematischer Ausdruck, eine Konstante, ein Zeiger
oder eine Variable stehen. Es ist wichtig, dass der Typ dieses Ausdrucks mit dem Typ der Funktion übereinstimmt.
3.3.4 Übergabeparameter
In runden Klammern hinter dem Funktionsnamen folgt die Parameterliste, die der Funktion übergeben wird. Jeder Parameter muss einzeln mit
einem Typ versehen werden. Einzelne Parameter werden durch Kommas getrennt.
Wichtig: Die Übergebenen Variablen können von der Funktion nur funktionsintern geändert werden. Das hat keine
Änderung der Parameter im Hauptprogramm zur Folge. ->Siehe Kapitel 3.4
3.4 Gültigkeitsbereiche von Variablen
double Berechnung( double parameter ) {
double interner_wert = 100.00;
double ergebnis = 0;
if ( parameter == interner_wert ) parameter = 200.01;
else parameter = interner_wert + parameter;
ergebnis = ( 5 * interner_wert ) / ( parameter - 2 * interner_wert );
return ergebnis;
}
int main ( void ) {
double interner_wert = 50.00, ergebnis, parameter = 99.00, t1, t2;
ergebnis = Berechnung ( parameter );
t1 = interner_wert;
t2 = parameter;
return 0;
}
|
Merke:
- Einer Funktion wird beim Aufruf nicht der Parameter selbst, sondern nur eine Kopie des Parameters übergeben. Dadurch
wird auch der Wert des Parameters im aufrufenden Programm nicht verändert.
- Varaiblen sind nur dort gültig, wo sie erzeugt werden. Eine in main deklarierte Variable kann also von einer anderen Funktion
nicht direkt gelesen oder verändert werden. Wie das doch geht -> Siehe Kapitel Zeiger
Mit diesen beiden Merkregeln sollten sich folgende Verständnisfragen beantworten lassen (wir betrachten nur Variablen im main-
Teil des Programms, nicht in der Funktion Berechnen:
- Welchen Wert hat die Variable 'ergebnis' nach durchlaufen der 2. Programmzeile?
- Welchen Wert hat die Variable 't1' nach durchlaufen der 3. Programmzeile?
- Welchen Wert hat die Variable 't2' nach durchlaufen der 4. Programmzeile?
Antworten:1.)-500 2.) 50.00 3.) 99.00
Alles klar?
3.5 Operatoren
Zeit für einen kleinen Griff in die Operator-Kiste. Operatoren sind ein weiterer zentral Teil jeder Programmiersprache sind Operatoren.
Man unterscheidet zwischen mathematische, logischen und Vergleichsoperatoren. Daneben gibt es noch ein paar solcher Ausdrücke,
die ich nicht so recht einzuordnen weiß. Auf den Adress-Operator (&) und den Dereferenzier-Operator (*) werde ich im Kapitel
über Zeiger näher eingehen.
Wie am Beispiel des Dereferenzier-Operator zu sehen ist, sind zumindest einige Operatoren Kontextabhängig, will heißen,
der * kann entweder der Multiplikation oder der Dereferenzierung (was auch immer das ist) dienen. Aber sehen wir uns das mal an:
3.5.1 Mathematische Ausdrücke
Wie der Name schon sagt haben wir es hier mit dem zu tun, was die Mathematik uns an Grundrechenarten in die Hand gibt. Also
Addition, Subtraktion, Multiplikation und Division. Hinzu kommt der Modulo-Operator (%), der den ganzzahligen Rest einer Divison
berechnet.
| Operator |
Beschreibung |
Beispiel |
| + |
Addition |
x = a + b; |
| - |
Subtraktion |
x = a - b; |
| * |
Multiplikation |
x = a * b; |
| / |
Division |
x = a / b; |
%
|
Modulo - Divisonsrest |
x = a % b; |
=
|
Zuweisung
|
x = a;
|
Soweit sollte das alles kein Problem sein, oder? Um sich nun ein wenig schreibarbeit zu sparen, gibt es in C die Möglichkeit,
solche mathematische Ausdrücke abzukürzen. Sehen wir uns die Abkürzungen und deren Langform ebenfalls in einer
Tabelle an:
| Abkürzung |
Langform |
| c++; |
c = c + 1; siehe * |
| ++c; |
c = c + 1; siehe * |
| c--; |
c = c - 1; siehe * |
| --c; |
c = c - 1; siehe * |
| i *= a; |
i = i * a; |
| i += b; |
i = i + a; |
| i -= a; |
i = i - a; |
| i /= a; |
i = i / a; |
| i %= a; |
i = i % a; |
*) Der unterschied zwischen c++ und ++c (bzw. c-- und --c) besteht darin, wann die Variable um eins erhöht (bzw. erniedrigt)
wird. Dieser Unterschied tritt erst zutage, wenn der Ausdruck innerhalb eines Funktionsaufrufes steht:
x = sqrt( c++ ); heißt: ziehe die Quadratwurzel von c und erhöhe c danach um eins.
x = sqrt( ++c); heißt: erhöhe c um eins und ziehe dann die Quadratwurzel;
Alles klar?
Achtung:Ausdrücke wie i *= a + b; heißt: i = i * (a + b); => der Ausdruck hinter dem Operator wird stehts
zusammengefasst.
3.5.2 Bitweise Operatoren
Bitweise Operatoren Verknüpfen zwei Variablen Bit für Bit miteinander. Neben den boolschen Operatoren UND, ODER und Exclusiv-
Oder (XOR) gibt es noch sogenannte Bitschiebebefehle. Diese verschieben einen binären Wert (jeder Speicherinhalt ist ein binärer Wert)
um ein Bit in die entsprechende Richtung, wobei auf der anderen Seite jeweils eine null eingeschoben wird. Dabei kann
auch angegeben werden, um wieviele Bit verschoben werden soll. Aber sehen wir uns zunächst an, was passiert, wenn man
zwei Bits miteinander UND, ODER oder XOR miteinander verknüpft. Die eingehenden Bits stehen unter a und b, in den anderen
Spalten stehen dann jeweils die Ergebnis-Bits.
a
|
b
|
UND
|
ODER
|
XOR
|
0
|
0
|
0
|
0
|
0
|
0
|
1
|
0
|
1
|
1
|
1
|
0
|
0
|
1
|
1
|
1
|
1
|
1
|
1
|
0
|
Nun können wir uns auch gedanken darüber machen, wie man diese Operatoren einsetzt:
| Ausdruck |
Bedeutung |
Beispiel |
Abkürzung |
| & |
UND |
x = x & b; |
x &= b; |
| | |
ODER |
x = x | b; |
x |= b; |
| ^ |
XOR |
x = x^ b; |
x ^= b; |
| << |
Bitshift nach links |
x = x << n; |
x <<= n; |
| >> |
Bitschift nach rechts |
x = x >> n; |
x >>= n; |
Achtung: Die boolschen Operatoren können nicht dazu verwendet werden, zwei Variablen zu vergleichen.
Dazu müssen die Vergleichsoperatoren herangezogen werden!
3.5.3 Vergleichsoperatoren
Am Ende schließlich soll auf die Vergleichsoperatoren in C/C++ eingegangen werden. Wie zu erwarten lassen sich
damit zwei Variablen oder Werte miteinander vergleichen. Auf die bekannten mathematischen vergleiche möchte ich
gar nicht näher eingehen. Die Verwendung von <, >, <= und >= ist analog zur Algebra.
Achtung: Der Test auf Gleichheit zweier Werte geht mit '==' und nicht mit dem einfachen Gleichheitszeichen.
Dieser Umstand darf auf keinen Fall vergessen werden.
Hier nun eine Auflistung der Vergleichsoperatoren in gewohnter Form:
| Operator |
Beschreibung
|
Beispiel |
| == |
Gleichheit |
if ( a == b ) |
| != |
Ungleich |
if ( a != b ) |
| && |
UND |
if ( ( a != b ) && (c == d) ) |
| || |
ODER |
if ( ((a || b) == f) && (c != b)) |
| ! |
Negation |
if ( !((a || b == c ) && (c != d))) |
|