Arrays, Pointer und Funktionen

http://www.sirius-net.de

Eindimensionale Arrays *

Der Operator sizeoff ( ) *

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 *



 
 
 
 

Eindimensionale Arrays


(Tabellen, Vektoren, Felder)

Array: Zusammenfassung mehrerer gleichartiger Variablen zur bessern Handhabung.
Das heißt, Array ist ein zusammengesetzter Datentyp.

C/C++ garantiert per Sprachdefinition:

  1. Der Name eines Arrays steht für die Arrayanfangsadressen d. h. die Adresse des "ersten" Arrayelements.
  2. Die Arrayelemente liegen lückenlos im Hauptspeicher hintereinander im Hauptspeicher in Richtung der aufsteigenden Adresse.
Deklarationsbeispiele int f[5] ; Array bestehend aus 5 Elementen vom Typ int. Hinter Arrayname f verbirgt sich die Anfangsadresse.

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

Der Arrayname ist eine Variable, für die die gleichen Konventionen gelten, wie für alle Variablen in C/C++.

in, char, double Typ der einzelnen Arrayelemente

f, g, h Name des Arrays « Arrayanfangsadresse

5, 81, 20 Anzahl der Arrayelemente, d.h. Größe des Arrays. Die Größe des Arrays muß zur Übersetzungszeit bekannt sein, d.h. hier steht immer eine Konstante, nie eine Variable. [ ] bildet den Array
 
 

Platzbedarf für ein Array

Beispiel:
 
int f[5] ; 5 * sizeoff (int)   => Bytes

 

Der Operator sizeoff ( )

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:

Der Zufallsgenerator von C

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 ;
x = rand ( ) ; /* x = Bereich [0 – RAND_MAX] , benannte Konstante entspricht dem größten Wert von int. Hier: 32767 */

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)
Inhalt: Anzahl der Sekunden, die seit dem 1. 1. 1970, 0 Uhr vergangen sind.

( 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.
 
0
0
0
0
0
0
0
0
0
0
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]

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

(Querverweis: ASCII-Z Format)
char f1[81], f2[81]; int i, j ;
 
'H' 'a' 'l' 'l' 'o' 0 ? ?
[0] [1] [2] [3] [4] [5] [6] [7]

Beispiel:

Stringlänge ermitteln (Stringlänge = Anzahl der gültigen Zeichen ohne die Abschluß- bzw. Stringende-Null.)
Stringende-Null « Wahrheitswert FALSE
printf("String eingeben -> ") ;
gets (f1) ;
for(i = 0 ; f1[i] != 0 ; i++ ) ; /* Semikolon als Leeranweisung */

/* for ( i = 0 ; f1[i] ; i++ ) ; entspricht der vorherigen Anweisung */

/* f1[i] ; nimmt den Inhalt des Arrayelements, das i Elemente vom Anfang des Arrays f entfernt ist. */ printf("Stringlänge: %d\n", i) ;

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] [] [] [] []

Auf das Ende des 1. string führen
Übertragung des ASCII-Codes
f1[i] = f2[j] ;

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] [] [] [] [] [] []

 
 
 

Pointer (Zeiger)
Welche Arten von Pointer gibt es in C
 
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 */
 
 

Pointer und Arrays

int f[5] ; /* Array aus 5 integer; Typ von f: int *, Typ von f[i]: integer; f ist ein Arrayname « impliziter (konstanter) Pointer. Andem Wert, der hinter f steckt kann ich nichts ändern. */ int *p; /* p ist ein expliziter Pointer vom Typ int * (Variable) */

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.

Arithmetik mit Pointern

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:

int f[5], p , i ;
for (p = f , i = 0 ; i < 5 ; i++, p++ )
{
printf("%d-ter Wert --> ", i + 1 ) ;
scanf("%d", p) ; /* kein Adressoperator nötig, da Inhalt p « Adresse /
fflush ( stdin ) ; /* pointer steht zum Schluß direkt hinter Array */
}

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 */

Möglichkeiten, alle Arrayelemente mit 0 zu füllen:


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

  1. Pointerarithmetik

  2.  

     
     
     

    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.
     
     

  3. Subtraktion zweier Pointer

  4.  

     
     
     

    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

  5. Vergleich zweier Pointer mit den relationalen Operatoren

  6.  

     
     
     

    Beispiel:

    Typ *p1, *p2, f[ 7 ] ; int x ;
    p1 = f ;
    p2 = f + 7 ;
    for ( --7 ; p2 >= f ; p2-- )
    {
    ..... ;
    }

  7. Vergleich eines Pointers mit der benannten Konstante NULL und Zuweisung von Null auf einen (expliziten) Pointer
Typ *p ;
p = funktion ( ) ;
/* Funktion, die einen Pointer zurück gibt. Viele solche Funktionen geben bei Mißerfolg einen NULL-Pointer zurück. Also eine Variable, in der alle Bit gelöscht sind. Dies kann abgefragt werden mit: */
if ( p = = NULL ) ..... ;
if ( ! p ) ... ;

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.
 
 

Praktikum
 
 

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 zur Bitmanipulation
Operatoren: ~ (Priorität 2) << und >> (Priorität 5); & (Prio 8); ^ (prio 9); | (Prio 10)
Nicht verwechseln mit logischen Operanden!
  1. Bitverknüpfungen (binäre Operatoren – mit 2 Operanden)

  2.  

     
     
     

    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!

  3. Bitweise NOT (~) unär, Priorität 2

  4.  

     
     
     

    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' ) ;
     
     

  5. Bit-Shift Operatoren << und >>
Binäre Operatoren der Priorität 5 (Shift left und Shift right)

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") ;
 
 

Der Preprozessor und die wichtigsten Preprozessoranweisungen
Definition: Software, gehört zum C/C++ Compiler
ANSI-Standard, arbeitet vor dem Compiler.
Arbeitet auf der Text-Ebene

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

  1. Prototypen

  2.  

     
     
     

    ( « 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!"

    Prototypen teilen dem Compiler mit, wie Funktionen formal richtig aufzurufen sind. Ermöglicht damit dem Compiler Fehlermeldungen zurückzugeben, wenn er formal falsche Aufrufe findet.

     
     
  3. Benannte Konstanten

  4.  

     
     
     

    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 ;
     
     

  5. Makros

  6.  

     
     
     

    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!
     
     

  7. Typdefinitionen mit typedef (reserviertes Wort)

  8.  

     
     
     

    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)

  9. Weitere include-Anweisungen

  10.  

     
     
     

    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 */
     
     

  11. Quelltext eigener Unterprogramme (Funktionen)

Modularisierung auf Quelltextebene

Funktionen:

Vorteile:
bessere Wiederverwendbarkeit bestehenden Codes
- im aktuellen Quelltext
- in späteren Quelltexten Modularisierung

bessere Übersicht
Bessere Handhabung in Projektgruppen Arbeitsteilung
 
 
 
 

Wann endet eine Funktion?

  1. Funktionen (Unterprogrammtechnik)
In der Funktionsdefinition steht was der Aufruf machen soll. Im Prototyp wird dem Compiler nur der Name und der Funktionssyntax mitgeteilt. Prototyp: void cls (void) ; //

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 ( ) ;


Prototyp: void curpos (int x, int y) ;

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("......") ;


Prototyp: double pi (void) ;

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.
 
 


Prototyp: viereck (double a, double b) ;

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: