http://www.sirius-net.de
Eindimensionale Arrays *
Technische Bedeutung des Arrayindex *
Der Zufallsgenerator von C *
Häufigster Sonderfall der Arrayverarbeitung: Strings *
Pointer (Zeiger) *
Pointer und Arrays *
Arithmetik mit Pointern *
Auf Pointer erlaubte Operationen *
Operatoren zur Bitmanipulation *
Der Preprozessor und die wichtigsten Preprozessoranweisungen *
Die wichtigsten Preprozessoranweisungen *
Mögliche Inhalte der include-Dateien *
(Tabellen, Vektoren, Felder)
Array: Zusammenfassung mehrerer gleichartiger Variablen zur bessern
Handhabung.
Das heißt, Array ist ein zusammengesetzter Datentyp.
C/C++ garantiert per Sprachdefinition:
char g [81] ; Array bestehend aus 81 Elementen von Typ char. Arrayname g « Anfangsadresse
double h[20] ; Array bestehend aus 20 Elementen vom Datentyp double. Arrayname h « Anfangsadresse
in, char, double Typ der einzelnen Arrayelemente
f, g, h Name des Arrays « Arrayanfangsadresse
Platzbedarf für ein Array
Beispiel:
int f[5] ; | 5 * sizeoff (int) | => Bytes |
Ermittelt die Größe eines Datentyps oder eines Speicherobjekts ( Variable, Array, etc. )in Bytes.
Beispiel:
int i ; | ||
printf("Größe von int %d Bytes\n", sizeoff ( int)) ; | bezogen auf Datetyp | |
printf("Größe von int %d Bytes\n", sizeoff ( i )) ; | bezogen auf Speicherobjekt (Variable) |
int f[5], i ;
[0] | [1] | [2] | [3] | [4] |
Das einzelne Arrayelement kann in C/C++ angesprochen werden mit einem Index (ganze Zahl) und dem Arrayzugriffsoperator [ ], Priorität 1.
Beispiel:
f [ 2 ] = 49 ;
Der Arrayindex beginnt in C/C++ immer mit 0 zu laufen, das heißt das "erste" Arrayelement hat den Index 0.
i = f[2] ;
f [2] ++ ;
« f[2] +1 ;
« f [2] += 1 ;
printf("%d", f [2] ) ;
scanf("%d", &f [2] ) ;
Technische Bedeutung des Arrayindex
Beschreibt den Abstand des betreffenden Arrayelements vom Arrayanfang in Anzahl Arrayelemente.
Beispiel:
f [i] = 0 ;
i++
also:
for (i = 0 ; i < 5 ; i++ )
f [ i ] = 0 ;
Achtung: In C/C++ gibt es keine Bereichsprüfungen, d.h. lesen und schreiben hinter dem Arrayende wird von der Programmiersprache nicht abgefangen. Selber aufpassen !
In C können Arrays prinzipiell nicht als Ganzes verarbeitet werden, sondern nur elementweise, d.h. Arrays können auch nicht mit den üblichen Operatoren zugewiesen oder verglichen werden, sondern nur einzelne Arrayelemente.
int f[5] , i ;
/* Array mit scanf( ) vollesen: */
for ( i = 0 ; i < 5 ; i++)
{
printf ("%d-ter Wert -> ", i + 1) ;
scanf ("%d" , &f [ i ] ) ;
fflush (stdin) ;
}
/* Arrayelemente wieder mit printf( ) ausgeben */
for ( i = 0 ; i < 5 ; i++)
printf("%8d", f [ i ] ) ;
Praktikum:
Funktionen srand ( ) und rand ( ) #include <stdlib.h>
rand ( ) | liefert bei mehrfachem Aufruf eine unregelmäßig
Zahlenreihe.
Der zurückgegebene Typ ist int. |
Beispiel:
int x ;
|
|
srand ( ) | Initialisiert den Zufallsgenerator rand ( ),
sollte einmal am Anfang das Programmes aufgerufen werden.
Syntax: srand (time (NULL)) ; |
time ( NULL ) | liefert die Systemzeit im UNIX-Format #include
<time.h>
Dies ist eine ganze Zahl vom Typ long ( 4 Byte integer)
|
( datentyp ) | Typerzwingungsoperator «
cast operator; Prioritätsstufe 2.
Überführt den Wert seines Operanden temporär in den Zieldatentyp, der in den Klammern angegeben ist. Dem ursprünglichen Datentyp der variablen wird nicht geändert, sondern nur für die Dauer der angegebene Operation. |
int f [ 5 ] , i ;
for ( i = 0 ; i < 5 ; i++) /* stellt 5 zufällige Zahlen in
den Array f
f [i ] = rand ( ) ;
x = rand ( ) % 6 + 1 ; /* liefert Zahlen von 1 6 also Würfel */
x = rand ( ) % 49 + 1 ; /* liefert Zahlen zwischen 1 und 49 "Lotto" */
x = rand ( ) % n ; /* liefert Zahlen zwischen 0 und n-1 */
x = rand ( ) % n + a ; /* liefert Zahlen zwischen a und n-1 + a */
double d ;
d = 1.0 * rand ( ) /RAND_MAX * y ; /* d soll Wert zwischen 0.0 und
1.0 ergeben */
/* oder: d = (double) rand ( ) /RAND_MAX * y ; */
Konzept: Lotteprogramm
6 eindeutige (!) Zufallszahlen im Bereich von 1 bis 49 ermitteln und in einem Array in f [6] stellen.
Schleife mindestens 6 mal laufen bis Array gefüllt.
x = rand ( ) % 49 + 1 ;
Neue Zahl in x prüfen, ob im Array schon vorhanden.
Falls ja: nächste Zufallszahl,
falls nein: ins Array stellen.
Arrayelemente aufsteigend sortieren
int f[6], i, j , h;
for ( j = 0 ; j < 6 1 ; j++ ) /* Steuerung sorgt dafür, daß
oft genug ver- */
{ /* glichen und vertauscht wird */
for (i = 0 ; i < 6 1 ; i++ )
{
if ( f[i] > f[i+1] ) ; /* Vergleich zweier Arrayelemente */
{
h = f [i] ; f [i] = f[i+1] ; f[i+1] = h ; /* Vertauschen zweier Arrayelemente
*/
}
}
}
Dieser Sortierer vergleicht und vertauscht immer nur benachbarte Arrayelemente. (Bubble-Sort)
Werte aus dem Array mit printf ( ) ausgeben.
Konzept: Test des Zufallszahlengenerators rand ( )
Ansatz: eine große Anzahl Werte (z. B. 500.000) aus dem Bereich
von 0 bis 9 ermitteln
x = rand ( ) % 10 ;
Dabei werden die Anzahl der Vorkommen für jeden Wert gezählt.
Die Zähler werden in einem Array organisiert. => long int z [10] ;
Array zunächst mit 0 vorzubelegen.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
500000 mal die Schleife x = rand ( ) % 10 ;
z [x] ++ ;
Erwartung (ideales Ergebnis): Jeder Wert kommt gleich oft vor. Hier also 50000 mal.
Ausgabe der tatsächlichen Anzahl jedes Werts und die Abweichung
vom Idealwert in Prozent und absolut.
Häufigster Sonderfall der Arrayverarbeitung: Strings
'H' | 'a' | 'l' | 'l' | 'o' | 0 | ? | ? |
[0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] |
Beispiel:
/* for ( i = 0 ; f1[i] ; i++ ) ; entspricht der vorherigen Anweisung */
Beispiel:
String kopieren von f1[ ] nach f2[ ]
'H' | 'a' | 'l' | 'l' | 'o' | 0 | ? | ? |
[0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] |
printf("String eingeben -> ") ;
gets (f1) ;
for ( i = 0 ; f1[i] != 0 ; i++ ) f2[i] = f1[i] ;
f2[i] = 0 ;
puts(f2) ;
Beispiel:
String anhängen in f2[ ] an string in f1[ ]. Wichtig: In Array f1[ ] muß genug Platz für den verketteten string sein.
'k' | 'r' | 'i' | 'x' | 'l' | 'i' | 0 | ? | |||||||
[i] |
[] | [] | [] | [] | [] | [i] | [] | [] | [] | [] |
Ende sobald Ende-Null
An der letzten Stelle des verketteten strings noch eine Abschluß-Null
einfügen.
Ausgabe des verketteten strings als Test
puts ( f1 ) ;
'b' | 'u' | 'x' | 'l' | 'i' | 0 | ? | ? | |||||||
[j] [0] |
[] | [] | [] | [j] | [] | [] | [] | [] | [] | [] |
Zur Laufzeit konstante Pointer | Pointer als Variablen | ||
Arraynamen sind solche Pointer.
Implizite Pointer sind Konstanten, d.h. sie können durch Zuweisung nicht verändert werden. |
Beispiel:
Wird ein Array deklariert wie int f[5] ; so steht der Arrayname f für die Anfangsadresse des Arrays. Deklariert wurde ein Array. Da Arranamen aber per Sprachdefinition für die Anfangsadresse des
Array stehen heißen Arranamen implizite Pointer.
f = 0xFACE ; =falsch ! |
Solche Variablen werden mit einem * vor ihrem
Namen deklariert.
Mit * in der Deklaration erhält man explizite Pointer. Das sind Variablen, die dazu gedacht sind eine Hauptspeicheradresse aufzunehmen. Zunächst enthalten auch Pointer-Variablen einen unbekannten Wert, d.h. sind nicht initialsiert. |
Beispiel:
int *p ; |
Beispiel Deklarationen:
char * p1 ; /* Pointer auf char */
int * p2 ; /* Pointer auf int */
double * p3 ; /* Pointer auf double */
p3 ist eine Adressvariable (expliziter Pointer), die dafür gedacht ist die Adresse einer Speicherstelle aufzunehmen, an bzw. ab der Daten vom Typ double stehen.
Neuer datentyp: double *; char * ; int *
int * p2 ; | ||
p2 ist eine "Variable vom Typ int *" | p2 ist eine Variable, die deklariert ist als "Pointer (Zeiger) auf int" |
printf("%d\n", sizeoff(char *)) ;
printf("%d\n", sizeoff(int *)) ; Bildschirmausgabe jeweils => 4
printf("%d\n", sizeoff(double *)) ;
Pointer-Variablen sind so groß, daß auf der betreffenden Maschine/Entwicklungsumgebung eine Adresse reinpasst. Heute meist 4 Bytes, manchmal noch 2 Bytes.
int i , x , * p ;
p = &i ; /* weist pointer p die Adresse von der Variablen i zu,
die von dem Adressoperator & ermittelt wurde. Jetzt ist in der Adressvariable
p die Adresse von p gespeichert. Sprechweise: " p zeigt auf i" */
printf("Wert ->") ;
scanf("%d\n", p) ; /* Der Inhalt der Variable p wird an scanf( ) übergeben.
Das ist hier die Adresse von i. */
fflush (stdin) ;
Das Gegenstück zum Adressoperator ist der unäre Operator *, Priorität 2. Auch Sternoperator, Verweisoperator oder Derefrenzierungsoperator genannt.
Wirkung: Ist eine Adresse gegeben, z.B. in der Adressvariable p, so wird mit * p der Inhalt der Specherstelle angesprochen "auf die p zeigt", in dem Fall hier auf i.
* p = 7 ; /* schreibt die 7 dorthin, wo p hinzeigt, hier also in i */
x = * p ; /* nimmt den Inhalt der Speicherstelle wo p hinzeigt und weist ihn der Variable x zu. */
Sinnvoll, wenn die Variable nicht bekannt ist, sondern nur die Adresse.
scanf("%d\n", p) ;
printf("%d\n", * p) ; /* Gibt den Wert aus, der an der Speicherstelle
p steht.
Beispiel:
Textdatei mit Zeilennummern versehen.
/* lnr.c */
char f[81], * p ; /* gets() gibt einen Wert vom Typ char * (pointer
auf char) zurück. /
p = gets (f) ; /* p: pointer auf char, Typ: char * (expliziter
pointer, Variable) */
/* Rückgabewert wenn alles geklappt hat ist die Adresse ab der
der string abgestellt wurde, also der Aufrufparameter. Wenn gets( ) nichts
mehr lesen konnte, gibt es einen sogenannten NULL-Pointer zurück.
Alle Bits sind gelöscht, es steht also die Adresse 0000 drin. Kann
also als eine Art Fehlermeldung genutzt werden, zum beenden der Schleife.
Dies kann abgefragt werden mit
if ( p = = NULL ) ....... ;
*/
n = 1 ; /* f: pointer auf char, Typ: char * (impliziter Pointer, Konstante
) */
/*
Ein-/Ausgabe-Umleitung:
stdin und stdout sin d per Voreinstellung mit Tastatur und Bildschirm
verbunden, können jedoch beide auf beliebige andere Dateien umgelenkt
werden über den Umweg der Kommandozeile: Aufruf des Programms und
umlenken auf Datei und Ausgabe umleiten auf Textdatei: lnr < lotto.c
> lotto.txt
*/
gets ( ) ; /* lese eine Zeile aus Kanal stdin */
printf("%d", /* gibt den Zähler n und die gelesene Zeile
wieder aus Kanal stdout */
n++
/* das Ganze als Schleife, solange noch eine Zeile gelesen werden kann
*/
int i ; /* i ist ein einfacher integer zum Ablegen von ganzen Zahlen */
int f[5], *p, i ;
[0] | [1] | [2] | [3] | [4] |
Arrayindex ist der Abstand des betreffenden Arrayelements vom Arrayanfang in Anzahl Arrayelemente. Die Anzahl Arrayelemente kann ich auch in Bytes umrechnen:
Anzahl Arrayelemente * Größe eines Arrayelements
Die Größe des Arrayelements ermitteln mit:
hier: sizeoff ( int ) ;
/* f = impliziter pointer Typ int *, p expliziter pointer Typ int *.
Also wir die Konstante der Variablen zugewiesen. */
p = f ; « p = & f[0]
Mit increment auf den pointer ( p++) läuft er alle Arrayelemente durch. Im Unterschied zu den bisherigen increment jeweils um 2 Byte Schritte, sonst um einer Schritte.
hier: increment und decrement
Deklaration war: Pointer auf einen bestimmten Datentyp. Der legt auch
die Schrittweite der Pointerarithmetik fest.
1 * sizeoff ( "typ" )
Geht also immer um Datenelement weiter, je nah Typ.
p++ ; entspricht p = p + 1 ;
Allgemein gilt:
p = p + n ; /* Pointer plus ganze Zahl ergibt wieder Pointer. Wobei n « n * sizeoff ( Typ ) */
Addiert / subtrahiert man eine ganze Zahl zu / von einem Pointer, so erhält man als Ergebnis wieder einen Pointer.
Die Adresse ändert sich dabei um n * sizeoff ( Typ ) Bytes.
Pointer Typ | Schrittweite bei der Pointer-Arithmetik | Schrittweite in Byte |
char * | sizeoff (char ) | 1 |
int * | sizeoff ( int ) | 2 oder 4 |
long * | sizeoff ( long ) | 4 |
float * | sizeoff ( float ) | 4 |
double * | sizeoff ( double ) | 8 |
Der Pointer ist immer so groß, daß eine Adresse reinpasst.
Nicht verwechseln: Die Adresse des Pointers und der Wert, der dort steht.
Adresse + ganze Zahl ergibt neue Adresse verändert um die ganze Zahl entsprechend sizeoff (Typ )
Beispiel:
for ( p = f , i = 0 ; i < 5 ; i++ , p++ ) ;
printf("%5d %p \n", p, p ) ; /* gibt nacheinander den Inhalt
der einzelnen Arrayelemente aus und die Adressen */
for ( i = 0 ; i < 5 ; i++ ) f[i] = 0 ;
for ( i = 0 ; i < 5 ; i++ ) * ( f + 1 ) = 0 ;
for ( p = f , i = 0 ; i < 5 ; i++, p++ ) * = 0 ;
for ( p = f ; p < f + 5 ; p++ ) p = 0 ;
Arrayname und Index | Adresse und Abstand | ||
Datenebene | f [ i ] | « | * ( f + i ) |
Adressebene | & f [ i ] | « | ( f + i ) |
Beispiel:
Verarbeitung eines String mit einem Pointer
char f[ 81 ] , *p ; /* sizeoff (char) «
1
printf("String --> ") ;
gets ( f ) ;
/* String rückwärts ausgeben */
for ( p = f ; *p != 0 ; p++ ) ; /* 1. Pointer auf die Abschluß-Null
bringen /
/* for ( p = f ; p ; p++ ) ; entspricht oberer Zeile ! */
for ( --p ; p >= ; p-- ) putchar ( *p ) ;
putchar ('\n') ;
Das Beispiel "bubble1.c" so ändern, daß die Arrayzugriffe
über einen Pointer und nicht mehr über Index stattfinden. =>
Syntaxaustausch
int f[5] ;
f = 7 ; /* Fehlermeldung "Lvalue required": Auf eine Konstante kann
nicht zugewiesen werden! */
f [i] = 7 /* ist zulässig, wobei i immer Format integer ist. */
Zugriff über Index und über Pointer stehen gleichberechtigt nebeneinander!
double f[5] , p ; int i ;
p = f ; /* « p = & f [0] ; */
f [i] = 7
p++; /* schrittweite abhängin vom Datentyp ! Schrittweit
der Zeigerarithmentik: sizeoff (Typ ) */
Beispiel:
double f[5] , p ; int i ;
for ( p = f ; p < f+5 ; p+2 ) /* Zählt 2 Speicherstellen weiter,
d.h. jede 2. Stelle des Arrays */
p = 13 ; /* Schreibt in jede 2. Speicherstelle den Wert 13 */
printf("%d\n", *p) ; /* Gibt den Wert an der Speicherstelle
p aus */
Auf Pointer erlaubte Operationen
Addition/Subtraktion einer ganzen Zahl zu/von einem Pointer.
Typ *p ; int i ; Typ f [ 7 ] ;
p++ ; « p = p + 1 ; allgemein: p
= p + i ;
p-- ; « p = p 1 ; allgemein: p
= p i ;
Ergebnis: Eine Adresse (pointer)
Wirkung: die in p gespeicherte Adresse ändert sich um i * sizeoff
( Typ ) Bytes
p = f ; /* Pointer am Arrayanfang aufsetzen */
f [ i ] « p [ i ]
«
* ( p + i ) « * ( f + 1 )
Arrayname und Index | Adresse und Abstand | ||
Datenebene | f [ i ] bzw. p [ i] | « | * ( f + i ) bzw. * ( p + i ) |
Adressebene | & f [ i ] bzw. & p [ i ] | « | ( f + i ) bzw. & (p + i ) |
Auseinanderhalten: Habe ich es mit der Adresse oder dem Inhalt
an der jeweiligen Adresse zu tun !
Warnings auf pointer nie stehen lassen! Unter ANSI-C werden warnings
ausgegeben bei Zuweisung von pointer auf pointer bei unterschiedlichem
Datentyp! Auch wenn K&R-Compiler das akzeptieren: Gleich richtig angewöhnen.
Beispiel:
Typ *p1, *p2, f[ 7 ] ; int x ;
p1 = f ;
p2 = f + 7 ;
x = p2 p1 ; /* Resultat ist die Größe des Speicherabschnitts
zwischen p1 und p2 in Anzahl Datenelemente der Größe sizeoff
( Typ ) */
Achtung: Beide Pointer sollten auf den gleichen Datentyp deklariert sein und beide Pointer sollten in den selben zusammenhängenden Speicherbereich (Array oder dynamischer Speicherbereich) zeigen
Beispiel:
Typ *p1, *p2, f[ 7 ] ; int x ;
p1 = f ;
p2 = f + 7 ;
for ( --7 ; p2 >= f ; p2-- )
{
..... ;
}
Es ist möglich mit p = NULL dem Pointer die NULL zuzuweisen. Dies
soll signalisieren, daß der Pointer p gerade nicht auf gültige
Daten zeigt.
Laufbedingung einer Schleife:
*p ==> Solange Wert auf den pointer p zeigt TRUE ergibt, also solange bis die Abschluß Null des Srings kommt ergibt der Wert TRUE, dann mit der Null ergibt sich FALSE.
Kopieren von Speicherbereich
*q++ = *p++ ==> weist den Wert an der Stelle wo pointer q hinweist der Stelle zu wo pointer p hinweist und zählt anschließend beide Stellen einen weiter solange die Laufbedingung TRUE ist.
Wert / Inhalt an einer Speicherstelle ändern oder Speicherstelle hochzählen
*p++ ==> zählt die Speicherstelle weiter, bzw. schiebt den pointer
an die nächste Stelle.
(*p ) ++ ==> nimmt den Wert, der an der Stelle ist wo pointer p hinzeigt
und erhöht ihn um 1., d.h. der Wert wird verändert.
Operatoren: AND ( & ) XOR ( ^ ) OR ( | )
Wirkung: verknüpfen die korrespondierenden Bits mit ihren beiden Operanden nach der entsprechenden Wahrheitstabelle.
D.h. das Ganze ist auf der Dezimal-Ebene wenig sinnvoll.
Beispiel 1: bitweise XOR:
int i ; /*alles ganzzahlige ist erlaubt*/
...
....
i = i ^ i ; i ist jetzt gleich 0; XOR mit zwei gleichen Bitmustern
muß immer 0 ergeben..
Beispiel 2: Tausch des Inhalts 2er Varaiblen ohne Hilfsvariable
int a, b ;
a = 3, b = 5; (binär: a = 0011 bzw. b = 0101 )
{
a = a ^ b;
b = a ^ b;
a = a ^ b; (jetzt a = = 5 und b = = 3 )
}
Beispiel 3: bitweise AND
Wird gerne benutzt, um Bitmuster "auszumaskieren", hier: gezielt Bits auf 0 setzen.
i = 'a' ; Bitmuster: 0 1 1 0 0 0 0 1 97d
& 1 1 0 1 1 1 1 1 0xDF
0 1 0 0 0 0 0 1 65d
i = getch ( ) & 0xDF ;
Hat der Anwender einen Buchstaben (egal ob groß oder klein) getippt, so ist in i jetzt sicher ein Großbuchstabe.
Zwischen Groß- und Kleinbuchstaben wechseln durch Manipulation des 5. Bit: entweder setzen oder löschen (+ oder 32d).
Beispiel 4: bitweise OR
Wird gerne benutzt, um Bitmuster "auszumaskieren", hier: gezielt Bits auf 1 setzen.
i = 'A' ; Bitmuster: 0 1 0 0 0 0 0 1 65d
| 0 0 1 0 0 0 0 0 0x20
0 1 1 0 0 0 0 1 97d
i = getch ( ) | 0x20 ;
Hat der Anwender einen Buchstaben (egal ob groß oder klein) getippt, so ist in i jetzt sicher ein Kleinbuchstabe.
Beispiel 5:
int i;
printf("Wollen sie xxx [j/n] => ") ;
do
i = getch ( ) | 0x20 ;
while (i != 'j' && i != 'J' &&
i != 'n' && i != 'N') ;
Wenn bei getch ( ) der rote Teil ergänzt wird, so kann bei while ( ) der rote Teil weg!
Kippt alle Bits seines Operanden, d.h. 0 -> 1 und 1 -> 0
Beispiel 1:
i = 0x20; 0 0 1 0 0 0 0 0 0x20
i = ~i ; 1 1 0 1 1 1 1 1 0xDF
0x20 = = ~ 0xDF
Beispiel 2:
int i;
printf("Wollen sie xxx [j/n] => ") ;
do
i = getch ( ) | ~ 0xDF ;
while (i != 'j' && i != 'n' )
;
Schiebt den Inhalt seines linken (rechten) Operanden um soviel Positionene nach links (rechts), wie im rechten Operanden angegeben ist
Beispiel 1:
int i;
i = 2; 0 0 0 0 0 0 1 0
i = i << 1; 0 0 0 0 0 1 0 0
das linke Bit fliegt raus! Von der rechten Seite werden 0 Bits nachgeliefert.
allgemein:
i = i << n; Shift um n Positionen nach links « Multiplikation mit 2n. Wobei n ein ganzzahliger Typ ist.
Beispiel 2:
int i;
i = 32; 0 0 1 0 0 0 0 0
i = i >> 1; 0 0 0 1 0 0 0 0
das rechte Bit fliegt raus!
allgemein:
i = i >> n; Shift um n Positionen nach rechts « Division mit 2n. Wobei n ein ganz zahliger Typ ist. Das Ergebnis ist ebenfalls ganzzahlig ohne Rest.
Zu beachten ist, daß bei Shift right das linke Bit unterschiedlich nachgeliefert wird, je nach dem ob MSB ein Vorzeichen ist oder nicht. Bei Datentyp signed wird der Inhalt des MSB nachgeliefert. Wenn nicht, also ein unsigned Datentyp definiert ist, wird immer die 0 nachgeliefert.
Beispiel 3:
Binärausgabe eines 8 Bit breiten Binärmusters (-> ASCII-Tabelle)
int i, n;
for (i = 0; i <= 255; i++ )
{
Zweites Bitmuster, in dem immer genau ein Bit auf 1 steht und alle
anderen auf 0. Startwert: 128 (27)
for ( n = 0; n < 8; n++)
i & 128 >> n ? putchar ('1')
: putchar ('0') ;
putchar ('\n') ; alternativ: printf("\n") ;
Prinzip: Nimmt Textersetzungen am Quelltext vor.
Aufruf: Beginnend mit #
Eine Programmanweisung pro Zeile Quelltext ist möglich.
Die wichtigsten Preprozessoranweisungen
Anweisung | Syntax | |
#include | <stdio.h>
(Dateien werden im Standard-Iclude-Verzeichnis des Compilers gesucht. Hier: c:\bc31\include. Bei Linux/UNIX meist /usr/include) |
Die Zeile wird ersetzt durch den Inhalt der angegebenen Textdatei. Diese Dateien heißen traditionell "Header-Dateien" (*.h) oder "Include-Dateien". Die Endung ist aber nicht vorgeschrieben. |
"meine.h"
(LW- und Pfadangaben sind möglich. Erst wird die Datei aktuellen oder angegebenen Verzeichnis, erst dann im Standard-Include-Verzeichnis) |
||
#define |
Mögliche Inhalte der include-Dateien
( « Funktionsdeklarationen nach ANSI)
Deklaration: - Rückgabetyp
- Aufbau der Parameterliste
- Name der Funktion
Beispiele:
Deklaration | Beispiel ;
Fehlermeldung |
|
double sqrt (double ) | Rückgabetyp Name der Funktion Parameterliste (1 Parameter vom Typ double) | x= sqrt(3, 7.5);
"1 Parameter zuviel!" |
double pow (double, double ) | Rückgabetyp Name der Funktion Parameterliste (1 Parameter vom Typ double) | x= pow ("Hallo");
"Typ des Parameters falsch!" |
Aufruf: #define
Form: #define NAME Ersatztext
Wenn die Zeile zu lang ist, kann mit Backslash am Ende in einer neuen
Zeile weitergeschrieben werden.
Wirkung: Jedes weitere Vorkommen von NAME wird vom Preprozessor
durch den Ersatztext ersetzt.
Per Konvention werden die Namen groß geschrieben.
Beispiele:
#define BACKSPACE 8
#define TAB 9
#define RETURN 13
#define ESC 27
#define BLANK 32
#define CLS printf("\033[sJ") ;
In der Praxis zum Beispiel:
int x ;
printf("Abbruch mit ESC .. ") ;
x = getch ( ) ;
if ( x = = ESC ) exit (0) ;
#define MWST 1.16
brutto = netto * MWST ;
Aufruf: #define
Nicht zu empfehlen, da die Unterprogrammtechnik überlegen ist.
Definition: #define flaech(a,b) ( a * b )
Aufruf (1):
x = flaech (3,7) ;
Makroexpansion (1):
x = ( 3 * 7 ) ;
Makroaufruf (2):
x = flaech ( 3+2, 7+1) ;
Makroexpansion (2):
x = ( 3+2 * 7+1 ) ;
Definition: #define flaech(a,b) ( (a) * (b) ) das würde funktionieren.
Makroexpansion (2) wurde dann so aussehen:
x = ( (3+2) * (7+1) ) ;
Achtung: Jeden Platzhalter aus Sicherheitsgründen einzeln klammern!
Neue Grunddatentypen können damit nicht definiert werden !
Einem in C ohnehin schon verfügbaren Datentyp kann aber ein zusätzlicher Name gegeben werden.
Form: typedef alter_name neuer_name ;
typedef unsigned char byte ;
typedef size_t int (bei manchen auch unsigned) Typ, der von sizeoff( ) zurückgegeben wird ganzzahliger Typ auf jeden Fall.)
Hinweis: typedef kann bei Strukturen sinnvoll eingesetzt werden (sieh später)
In einer Include-Anweisung dürfen wieder #include-Anweisungen vorkommen.
Achtung: Keinen "Kreis" aus #include-Anweisungen aufbauen, so daß sich die Dateien gegenseitig "includen"!
/* hf.h */
/* standard includes */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#math <math.h>
#include <time.h>
#include <string.h>
/* benannte Konstanten */
#define BACKSPACE 8
#define TAB 9
#define RETURN 13
#define ESC 27
#define BLANK 32
#define CLS printf("\033[sJ") ;
#define MWST 1.16
/* Prototypen eigener Funktionen */
/* Quelltext eigener Funktionen */
Funktionen:
Wann endet eine Funktion?
Funktionsdefinition: void cls ( )
{
printf("\033[2J") ; //setzt Vorhandensein des ANSI-Treibers voraus
}
Anzugeben ist: Rückgabetyp, Name, Parameterliste, kein Semikolon (sonst wird Programmblock getrennt), Programmblock
Aufruf im Quelltext: cls ( ) ;
Funktionsdefinition: void curpos (int x, int y)
{
printf("\033[%d;%dH", y, x) ;
}
Die erste Angabe ist der Abstand von links (Spalte), der zweite Wert der Abstand von oben (Zeile).
Im Prototyp dürfen die Parameter-Namen angegeben werde. Sie können dort aber zur Dokumentation dienen. Der Compiler überliest sie, daher könnten sie auch ganz wegfallen.
Die Parameter x, y übernehmen die Werte aus dem Funktionsaufruf. Sie sind nur innerhalb des Programmblocks sichtbar, bzw. existent.
Im Aufruf können an der Stelle statt Konstanten auch Variablen aufgenommen werden. Einzige Bedingung ist, daß an der Stelle nur Daten vom Typ int angegeben sind.
Aufruf im Quelltext: curpos ( x , y ) ;
Beispiel: ze = 2 ; sp = 5 ;
curpos ( sp, ze++ ) ; //springt an sp5, ze2 und zählt ze immer
1 hoch
printf("......") ;
Funktionsdefinition: double pi ( )
{
return 4.0 * atan (1.0) ;
}
Aufruf im Quelltext: d = pi ( ) ; // Beispiel, wobei die Variable d vom Typ double sein sollte.
Beispiel: Printf("%lf\n", pi ( ) ) ; // Gibt den Wert von Pi
aus.
Funktionsdefinition: double viereck ( double a, double b)
{
return a * b ;
}
Aufruf im Quelltext: d = viereck ( 1.70, 2.58 ) ;
d = viereck ( 9+1, 7+2 ) ;
Konstanten im Quelltext rechnet der Compiler sofort aus. Bei Variabeln
erst beim Funktionsaufruf.
Reihenfolge im Quelltext
#include <....> Standard-Includes (Prototypen)
Prototypen für eigene Funktionen Reihenfolge ist beliebig !
Implementierung eingener Funktionen
(Funktionsdefinitionen Quelltext der Funktionen)
Für Funktionsaufrufe gilt: