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.
#include <stdio.h> void main(void)
{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} }; int x, y; /* setzen wir ein paar Werte */
/* und geben das Feld aus */
{
printf ("\n"); |
Nach dem starten sollten wir unser Feld mit den dort manipulierten Inhalten sehen können.
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 },}; |
...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 },}; |
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 :-)
#include <stdio.h> void main(void)
printf ("\n%s\n", samstag); |
Zeiger |
Wie immer am Anfang ein kleines Beispielprogramm:
void main ( void )
{
int *zeiger; zahl=5;
|
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:
{
float *zeiger; /* in der Folgezeile liegt der Fehler! */
|
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";} |
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.
Starten wir das Programm, so erscheint folgendes auf dem Bildschirm
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 *zeiger; zeiger=&zahl;
*zeiger=10;
|
Starten wir nun das Programm, so erhalten wir als Ausgabe:
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 )
int a=1 , b=2 ; /* Zeiger auf eine Integervariable */
/* 'zeiger' zeigt auf a */
/* 'b' wird mit dem Wert von 'a' geladen. */
/* 'a' wird mit dem Wert 2 geladen */
|
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 )
int feld [ 10 ] = { 0,1,2,3,4,5,6,7,8,9 } ; /* Variable vom Typ Integer wird erzeugt */
/* Ein Zeiger auf Integer-Werte wird erzeugt */
/* 'zeiger' zeigt auf das erste Element von 'feld' */
/* var enthält den Wert von 'feld [ 0 ]' */
/* var enthält den Wert von 'feld [ 2 ]' */
/* var enthält den Wert von 'feld [ 2 ]' */
|
Abgetippt und gestartet erhaltenwr als Ausgabe
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];} |
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.
Starten wir das Programm, so erhalten wir
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.
|
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 *text2 = "Jetzt auch als Zeiger\n\n"; /* hier wird die Feld-Variante ausgegeben */
/* hier wird die Zeiger-Variante ausgegeben */
|
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:
initialisiertes Feld von Zeigern. #include <stdio.h> void main(void)
{
"Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag" int x; for (x=0; x<7; x++)
|
Nach dem Starten erhalten wir folgendes
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 <math.h> void main ( void )
n = atof ( "2.4354" );
|
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 <stdlib.h> void main ( void )
n = atoi ( "2.4354" );
|
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!