[Inhalt][0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17]
Willkommen beim dritten Teil des C-Kurses!

Heute behandeln wir zwei Themen, welche ihr mit besonderer Sorgfalt durcharbeiten solltet, da sie euch immer wieder in C / C++ begegnen wird:  Zeiger, auf neudeutsch auch oft als Pointer bezeichnet, und Felder , auch Arrays oder Vektoren genannt..
 
Felder

Felder, also Gruppen oder Aneinanderreihungen von Variablen eines Typs werden wie folgt definiert.
 

Feldtyp feldname [ Feldgröße ] ;

Probieren wir es einfach mal mit ein paar Beispielen um besser in die Feldarbeit einzusteigen. Keine Angs, dafür müssen wir nicht zum Bauernhof.
 

int feld [ 10 ] ;

erzeugt also ein Feld von 10 Elementen des Typs Integer.
 

0 1 2 3 4 5 6 7 8 9
 Die Elemenente sind feld[ 0 ] .... feld[ 9 ] .

Richtig bemerkt, die Feldindizes fangen bei 0 an. Diese einfachen Felder werden oft auch als eindimensionale Felder bezeichnet.Doch was, wenn man ein zweidimensionales Feld erstellen möchte ?
 

int feld [ 4 ] [ 5 ];

Damit wird dann eine Feld der Größe 4 x 5 gebildet.
 

[ 0 ][ 0 ] [ 0 ][ 1 ] [ 0 ][ 2 ] [ 0 ][ 3 ] [ 0 ][ 4 ]
[ 1 ][ 0 ] [ 1 ][ 1 ] [ 1 ][ 2 ] [ 1 ][ 3 ] [ 1 ][ 4 ]
[ 2 ][ 0 ] [ 2 ][ 1 ] [ 2 ][ 2 ] [ 2 ][ 3 ] [ 2 ][ 4 ]
[ 3 ][ 0 ] [ 3 ][ 1 ] [ 3 ][ 2 ] [ 3 ][ 3 ] [ 3 ][ 4 ]

Analog können auch mehrdimensionale Felder erzeugt werden, z.B.durch feld [3][4][5] , u.s.w.. Nun können wir auch so auf jedes einzelne Feld zugreifen. Wenn wir also auf das hervorgehobene Feld zugreifen wollen, so tun wir das mit
 

feldinhalt =  feld [ 1 ] [ 2 ];

Wie wir sehen, müssen wir für jede "Dimension" eines solchen Feldes eckige Klammern setzen. Angaben wie feld [1,2] , wie man sie von anderen Sprachen her kennt, sind FALSCH! und dürfen nicht benutzt werden. Hier nun ein kleines Beispielprogramm, welches ein 5 x 5 großes Feld erzeugt, daten hineinschreibt und es dann ausgibt.
 

/*
Beispiel zur Felderzeugung
*/
#include <stdio.h>

void main(void)
{

 int feld[5][5]=

{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0} 
};
/* Hilfsvariablen zur Felddarstellung */
int x, y;

/* setzen wir ein paar Werte */
 feld[2][1] = 1;
 feld[3][1] = 2;
 feld[2][2] = 3;
 feld[3][2] = 4;

/* und geben das Feld aus */
 for (x=0; x<5; x++)
 {

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

Nach dem starten sollten wir unser Feld mit den dort manipulierten Inhalten sehen können.

Screenshot des ausgeführten Programms

Wenn sich einer über die Schleifen wundert, so bitte ich um etwas Geduld, das wird im späteren Verlauf erklärt.
Was wir gleich miteinführten, war das Initialisieren eines Feld. Der Grund ist recht einfach, da wir sonst nicht wissen, was in den Feldern enthalten ist. Die Initialisierung führten wir durch, indem wir bei der Variablendeklarierung gleich eine Zuweisung des Feldinhaltes machten. Bei eindimensionalen Feldern, wird wie folgt initialsiert.
 

int feld [7] = { 1 , 2 , 3 ,4 , 3 , 2 , 1 };

Damit wird ein Feld der Größe 7 für Int-Variablen erzeugt, das folgende Inhalte hat.
 

1 2 3 4 3 2 1

Bei Feldern mit 2 oder mehr Dimensionen, kann man diese wie folgt initialisieren.
 

int feld [3] [3] = 
{
{ 1 , 2 , 3 },
{ 4 , 5 , 6 },
{ 7 , 8 , 9 }
};

...und erhält das folgende Feld
 

1 2 3
4 5 6
7 8 9

Das gleiche Feld erhalten wir mit der Anweisung
 

int feld [] [] = 
{
{ 1 , 2 , 3 },
{ 4 , 5 , 6 },
{ 7 , 8 , 9 }
};

Wenn keine Größenangaben angegeben sind, wird das Feld mit einer Größe initialisiert, das gerade die Initialisierungsdaten Platz finden. Man kann dies aber nur während der Deklaration machen und nicht später! Dies hat den Vorteil, wenn wir vor allem Zeichenfelder anlegen wollen.Wenn natürlich jemand Lust hat, immer alle Zeichen zu zählen, so kann er das auch weiterhin tun :-)
 

/*
Feldinitialisierung
*/
#include <stdio.h>

void main(void)
{

 char samstag[]  = "Samstag";

printf ("\n%s\n", samstag);

 }

Screenshot des ausgeführten Programms


Zeiger

Wie immer am Anfang ein kleines Beispielprogramm:
 

void main ( void
{
    int zahl; 
    int *zeiger;

    zahl=5;
    zeiger=&zahl;

}

Wie wir am oberen Beispiel sehen können, zeichnet sich die Variable 'zeiger' dadurch aus, das sie mit einem Sternchen beginnt. Damit wird dem Compiler gesagt, das es sich hierbei um einen Zeiger handelt. Bevor ich nun in ewige Verdammnis gestürzt werde, weil ich immer noch nicht sagte, was ein Zeiger ist: Ein Zeiger beinhaltet nicht einen Variablenwert, sondern zeigt nur auf die Adresse, wo der Variablenwert hinterlegt ist.Das Kopieren von Adressen geht schneller, als wenn man die kompletten Daten kopiert. Arbeiten wir unser Beispiel ab, dann wird auch am ehesten die obere Darstellung, was ein Zeiger ist, klar. Wir erzeugen die Variable 'zahl' und den Zeiger 'zeiger'. Der Variablen 'zahl' wird der Wert 5 zugeordnet. Der Zeiger 'zeiger' wird die Adresse der Variable 'zahl' zugewiesen. Dies geschieht durch den Adressoperator & ! Grundsätzlich wird ein Variable durch einen vorangestellten Stern als Zeiger deklariert!

Bsp.:
 

int *pointer1;

float *pointer2;

Der Vorteil von Zeigern ist der, das in ihnen nur eine Adresse und keine Variablen stehen. Dadurch sind sie extrem flexibel und schnell. Zu beachten ist: Zeiger selber reservieren keinen Speicherplatz für die Variablen, auf die sie zeigen, sondern enthalten nur die Adresse, an der Daten liegen. Der Zeiger muß denselben Datentyp besitzen, auf den er zeigt! Folgendes funktioniert also nicht:
 

/* DIESES PROGRAMM LAEUFT SO NICHT !!! */
void main ( void
{
int zahl=5; 
float *zeiger;

/* in der Folgezeile liegt der Fehler! */
zeiger=&zahl; 

}

Wenn wir also einen Zeiger auf eine Datentyp  zeigen lassen, so enthält die Zeigervariable lediglich die Adresse des ersten Elements des Zeigertyps. Im folgenden Beispiel also würde *Zeiger auf das erste Element, den Buchstaben T zeigen.
 

/*
Beispiel für Zeiger
*/
#include <stdio.h>

void main ( void )
{

char *Zeiger = "TEXTSPEICHER";

/* Zeichenkette ausgeben */
printf ("Zeichenkette : %s \n", Zeiger );

/* Zeichenausgeben */
printf ("erstes Zeichen : %c \n", *Zeiger );

}

Zugriff auf den Variablenwert, auf den ein Zeiger zeigt erfolgt mittels des *-Operators! Man bezeichnet ihn auch als Inhalts-Operator Aber Vorsicht: NICHT MIT DEM MULTIPLIKATOR VERWECHSELN! Das sind zwei verschiedene Operatoren! Der Compiler entscheidet anhand des Zusammenhangs, ob es sich um einen Zeiger-Operator oder um eine Multiplikation handelt.Wie wir sehen,  "zeigt" ein Zeiger im wahrsten Sinne des Wortes auf die Stelle im Speicher, wo die Daten gespeichert sind. Um beim Beispiel zu bleiben zeigt hier *Zeiger auf den ersten Buchstaben des Wortes TEXTSPEICHER.

Skizze: Zeiger auf erstes Element

Starten wir das Programm, so erscheint folgendes auf dem Bildschirm

Screenshot des ausgeführten Programms

Damit wir auch Vorteile aus der Benutzung von Zeigern ziehen können, wollen wir uns einmal anschauen, was man alles mit Zeigern machen kann. Da dies im übrigen sehr viel ist, werden wir uns hier mit dem grundlegenden beschäftigen.
 

/* Beispiel für die Anwendung eines Zeigers */ 
#include <stdio.h>

void main ( void
{

    int zahl=5; 
    int *zeiger;

    zeiger=&zahl;
    printf("\nZahl = %d \n",zahl);

    *zeiger=10;
    printf("\nJetzt ist Variable Zahl = %d \n",zahl); 

}

Starten wir nun das Programm, so erhalten wir als Ausgabe:

Screenshot des ausgeführten Programms

Wie immer eine Erläuterung, was passiert ist: Dem Zeiger 'zeiger' wurde die Adresse der Variable 'zahl' zugewiesen. Somit steht in 'zeiger' die Ardresse, wo im Speicher die Variable 'zahl' steht. Als erstes geben wir unsere Variable 'zahl' aus. Nun wird in die Adresse ( ! ) , die in 'zeiger' gespeichert ist, der Wert 10 geschrieben! Dadurch, daß in dieser Adresse der Wert der Variable 'zahl' steht, schreiben wir in die Variable 'zahl' den Wert 10 hinein. Diesen Effekt sehen wir, wenn wir das Programm starten: Erst steht in der Variablen 'zahl' der Wert 5 und nach dem überscheiben der Wert 10. Man sagt auch: Durch einen Zeiger wird ein Objekt referenziert. Keine Panik :-) In diesem Satzt steht nichts anderes, als das der Zeiger auf die Variable zeigt.Wenn man ehrlich ist, klingt alles immer noch recht theoretisch, aber alles wird durch etwas Übung leichter.
 

#include <stdio.h>

void main (  void
{

    /* Variablen vom Typ Integer */ 
    int a=1 , b=2 ; 

    /* Zeiger auf eine Integervariable */
    int *zeiger; 

    /* 'zeiger' zeigt auf a */
    zeiger = &a ;

    /* 'b' wird mit dem Wert von 'a' geladen. */
    b = *zeiger ; 

    /* 'a' wird mit dem Wert 2 geladen */
    *zeiger = 2 ; 

}

Es ist wichtig, daß die oberen Beispiele verstanden werden! Klasse! Doch Zeiger müssen doch zu was gut sein, oder ? Einmal wird das übergebn von Daten stark beschleunigt, da nur eine Adresse anstatt der komplette Datensatz übergeben wird.Weiterhin lassen sich auf schnelle Art und Weise Felder verwalten und manipulieren.:
 
 
Felder und Zeiger

Schauen wir mal, was man mit Zeigern und Feldern alles machen kann:
 

/*
Beispiel, wie man auf Elemente zugreift
*/
#include <stdio.h>

void main ( void )
{

    /* Das Feld mit 10 Elementen wird erzeugt */
    int feld [ 10 ] = { 0,1,2,3,4,5,6,7,8,9 } ; 

    /* Variable vom Typ Integer wird erzeugt */ 
    int var; 

    /* Ein Zeiger auf Integer-Werte wird erzeugt */
    int *zeiger; 

    /* 'zeiger' zeigt auf das erste Element von 'feld' */
    zeiger = &feld [ 0 ] ;
    printf ("\n%d"*,zeiger);

    /* var enthält den Wert von 'feld [ 0 ]' */
    var = *zeiger; 
    printf ("\n%d",var);

    /* var enthält den Wert von 'feld [ 2 ]' */
    var = *( zeiger + 2 );
    printf ("\n%d",var);

    /* var enthält den Wert von 'feld [ 2 ]' */ 
    var = feld [ 2 ] ; 
    printf ("\n%d",var);

}

Abgetippt und gestartet erhaltenwr als Ausgabe

Screenshot des ausgeführten Programms

An diesem Beispiel ist zu sehen, das
 

zeiger = &feld [ 0 ];

var = *( zeiger + 2 );

und
 

var = feld [ 2 ];

gleichwertig sind. Wieso ? Dazu schauen wir uns mal die erste Zuweisung genauer an. 'zeiger' wird mit der Adresse von 'feld [ 0 ]' geladen, zeigt also auf 'feld [ 0 ]'.Jetzt kommst: Die Adresse von 'zeiger' wird um 2 Elemente vom Typ 'zeiger' erhöht. Damit zeigt der Ausdruck auf das zweite Feldelement, also auf 'feld [ 2 ]'. Nun wird der Inhalt von 'feld [ 2 ]' in 'var' gespeichert. Damit sind beide Ausdrücke gleichwertig. Das die Benutzung auf diese Art auch so funktioniert, kann man am folgenden Miniprogramm ausprobieren, wo beide Adressierungarten benutzt werden, um die Buchstaben a , b und c in ein Feld zu schreiben.
 

/*
Beispiel zum gezielten Feldzugriff
*/
#include <stdio.h>

void main ( void )
{

char a[3];
char *z;

/* Adresse von a[0] in z speichern */
z = &a[0];

/* Die Buchstaben in die Felder kopieren */
a[0] = 'a';
*(z+1) = 'b';
a[2] = 'c';

/* und ausgeben, damit wir sehen, ob es auch geht */
printf ("\nFeldinhalt : %s\n",a);
printf ("\nZeiger zeigt auf : %s\n",z);
 

}

Nach obiger Überlegung sollten also *z und a in dem Sinne zusammenhängen, das es egal ist, ob mit a[1] oder *(z+1) auf das Zeichenfeld zugegriffen wird.

Prinzip: Zeiger

Starten wir das Programm, so erhalten wir

Screenshot des ausgeführten Programms

Bisher hatten sich sicher viel gefragt, wie man ganze Zeichenketten oder auch Sätze in Variablen speichern kann. Wer schon andere Programmiersprachen kennt, fragt sich, ob es nicht so etwas wie Strings, also Variablentypen für Zeichenketten, gibt. Um hier mit einem Zitat zu antworten: Im Prinzip schon, in der Praxis nein :-)
 
 
Strings und deren Erzeugung

Es gibt zwei prinzipielle Arten Strings (= Zeichenketten) zu generieren.
 

/* String als Feld */ 

char zeichen [ ] = "hallo";

oder
 

/* String als Zeiger */ 

char *zeichen = "hallo";

Was machen nun beide Anweisungen ? Wird ein String durch ein Feld erzeugt, so ist das generierte Feld gerade so groß, damit der String hineinpaßt. Bei genauerer Betrachtung ist das Feld aber 5+1 Zeichen groß. Wieder ein Mysterium von C ? 5 Elemente für die 5 Buchstaben von 'hallo' - OK , wo aber kommt das 6. Element her ? Dazu müssen wir uns anschauen, wie Zeichenketten intern gespeichert werden.
Damit das System weiß, das der String zu Ende ist steht hinter der Zeichenkette immer noch ein '\0'. Das ist ein sogenanntes Nullzeichen.
 

"hallo" wird intern dargestellt durch
h a l l o \0

Wird ein String durch einen Zeiger erzeugt, so wird im Speicher die Zeichenfolge des Strings abgespeichert und der Zeiger zeigt auf das erste Element des Strings. Auch hier wird mit einem Nullzeichen abgeschlossen, da das System ja wissen muß, wo der String endet und nicht alle anderen Speicherstellen mit ausgibt. Auch hier ein kleines Beispiel:
 

/* Ein kleines Beispiel für Strings */ 
#include <stdio.h>

void main ( void )

    char text1 [ ] = "Jetzt haben wir sogar Strings\n";
    char *text2 = "Jetzt auch als Zeiger\n\n";

    /* hier wird die Feld-Variante ausgegeben */
    printf ("%s \n ",text1);

    /* hier wird die Zeiger-Variante ausgegeben */ 
    printf ("%s ",text2);

}

Screenshot des ausgeführten Programms

Als kleine Übung versuchen sie zu erklären, was passiert, wenn sie folgendes kleines Programm starten würden. Mit dem bisher gelernten, dürfte es zu schaffen sein:
 

/*
automatische Groessenzuordnung auf ein
initialisiertes Feld von Zeigern.
*/
#include <stdio.h>

void main(void)
{

 char *wochentage[] = 
{
"Montag", "Dienstag",
"Mittwoch", "Donnerstag", 
"Freitag", "Samstag", 
"Sonntag"
};

 int x;

 for (x=0; x<7; x++)
 {

  printf ("\n%s",wochentage[x]);
 }
 }

Nach dem Starten erhalten wir folgendes

Screenshot des ausgeführten Programms

Bei dem Übungsprogramm haben wir mit char *wochentage[] ein Feld von Zeigern auf char-Werte erzeugt. Wie man bisher schon lernte gibt es viele kleine Stolperfallen, über die man in C fallen straucheln kann und welche nicht auf Anhieb zu erkennen sind.
 
 
Strings in Zahlen umwandeln

Was man beim Casting ncht konnte, war Strings in eine Zahl umzuwandeln. Da aber dies eine recht häufige Angelegenheit ist, gibt es, oh Wunder, auch dafür Funktionen.
 
double n = atof ( char *c )

 Es wird ein String in eine double-Größe umgewandelt. Dabei werden Vorzeichen beachtet , sowie Exponentialschreibweisen ( E oder e mit einer angehängten Ganzzahl ). Eingebunden werden muß:
 

#include <math.h>

Als Aufrufparameter muß ein String (char* oder char[]) übergeben werden und zurückgegeben wird ein Wert vom Typ double. Ein vollständiges Beispiel so aus:
 

#include <stdio.h> 
#include <math.h> 

void main ( void

double n; 

n = atof ( "2.4354" ); 
printf ("%lf" , n );

}

Nach dem ausführen sollte auf dem Bildschirm folgendes erscheinen:


int n = atoi ( char *c )

Wieso sollte, was bei Fließkommazahlen geht, nicht auch bei Ganzzahlen gehen ? Daher gibt es atoi , welches den übergebenen String in einen Integerwert umwandelt. Im Gegensatz zu atof muß hier folgende Library eingebunden werden:
 

#include <stdlib.h>

Als Programm sieht das dann so aus:
 

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

void main ( void

int n; 

n = atoi ( "2.4354" ); 
printf ("%d" , n );

}

Als Ergebnis erhält man hier:



 
 
 
 
 
 
...das Obligatorische

Autor: Sebastian Cyris \ PCD Bascht

Dieser C-Kurs dient nur zu Lehrzwecken! Eine Vervielfältigung ist ohne vorherige Absprache mit dem Autor verboten! Die verwendete Software unterliegt der GPL und unterliegt der Software beiliegenden Bestimmungen zu deren Nutzung! Jede weitere Lizenzbestimmung die der benutzten Software beiliegt, ist zu beachten!