Alle Daten, die ein Computer verarbeitet, werden in Speicherzellen abgelegt, auf die der Mikroprozessor über deren Adressen zugreifen kann. Man kann sich das wie eine lange Straße vorstellen, bei der die aneinandergereihten Häuser die Speicherzellen darstellen; die Adressen entsprechen dann den Hausnummern. Bei Byte-Maschinen - und zu denen zählt die Joyce - besteht eine Speicherzelle aus acht Bit oder einem Byte. Der Z80-Prozessor kann 64 kByte, also 65.535 einzelne Adressen verwalten. Das sind 2 hoch 16 Adressen; daraus folgt, dass eine Adresse 16 Bit lang ist.
char ist ein Datentyp, der aus einem einzelnen Byte besteht. Eine Variable, die als char ch deklariert wurde, kann also ein Byte speichern. Greift man im Programmcode auf ch zu, dann ist der Inhalt dieses Bytes gemeint, von dem der C-Compiler weiß, wo im Speicher es sich befindet. Wenn wir wissen müssen, wo im Speicher sich ch befindet, z.B. um ch mit der C-Funktion scanf zu füllen, dann müssen wir die Adresse von ch ermitteln. Wir haben das bereits bei der Verwendung von scanf erfahren: der &-Operator ermittelt die Adresse einer Variablen, d.h. &ch liefert die 16-Bit-Adresse der Variablen ch.
Speichert man diese Adresse wiederum in einer Variablen ab, dann nennt man diese Variable einen Zeiger oder englisch Pointer. Pointer-Variablen werden mit einem Stern und dem Typ der Ziel-Variablen gekennzeichnet. Um einen Pointer für ch zu deklarieren, schreiben wir z.B. char *p;. Damit p auf ch zeigt, weisen wir p die Adresse von ch zu: p = &ch;. Um nun auf das Zeichen in ch über den Pointer zuzugreifen, müssen wir den Pointer "dereferenzieren". Das geschieht über den *-Operator, z.B. putchar(*p);.
Ein kleines Beispielprogramm soll das veranschaulichen:
Code: Alles auswählen
#include <stdio.h>
int main(void) {
char ch;
char *p;
ch = 'X'; // ch enthaelt jetzt das Zeichen 'X'
putchar(ch); // gibt X auf der Konsole aus
p = &ch; // p erhaelt die Adresse von ch, d.h. p zeigt auf ch
*p = 'Y'; // das Zeichen, auf das p zeigt, also ch, wird auf 'Y' gesetzt
putchar(*p); // gibt Y auf der Konsole aus
putchar(ch); // gibt ebenfalls Y auf der Konsole aus, da ch ueber p veraendert wurde
return 0;
}
Arrays sind Speicherstrukturen mit mehreren Elementen gleichen Typs; auf die Elemente kann man über einen Index zugreifen. Es gibt eindimensionale Arrays, die auch als Vektoren bezeichnet werden, und mehrdimensionale Arrays. Die Anzahl der Dimensionen bestimmt, wieviele Indizes man benötigt, um auf ein einzelnes Element zuzugreifen.
Einen Array mit fünf int-Elementen deklariert man etwa so: int a[5];. Damit erhält man eine Array-Variable a, deren Elemente man über die Indizes 0 bis 4 erreicht: a[0], a[1], ..., a[4]. Der kleinste Index ist in C-Arrays immer 0, der größte ist die Anzahl der Elemente minus eins. Da die Indizes auch über Variablen bereitgestellt werden können, hat man damit eine sehr bequeme Speicherstruktur für den Zugriff auf gleichartige Elemente.
Code: Alles auswählen
#include <stdio.h>
int main(void) {
int a[5];
int i;
// Array mit Vielfachen von 3 belegen
for (i = 0; i < 5; i++) {
a[i] = i * 3;
}
// Array ausgeben
for (i = 0; i < 5; i++) {
printf("Zahl an Index %d: %d\n", i, a[i]);
}
return 0;
}
Zahl an Index 0: 0
Zahl an Index 1: 3
Zahl an Index 2: 6
Zahl an Index 3: 9
Zahl an Index 4: 12
In C sind Arrays gleichzeitig Pointer-Strukturen. a ist also in unserem Beispiel ein int-Pointer. Um das erste Element, also das Element mit dem Index 0, auf den Wert 17 zu setzen, schreiben wir normalerweise a[0] = 17;. Genausogut können wir aber auch *a = 17; schreiben, da a eben auch ein Pointer ist. Um das vierte Element auf 21 zu setzen, schreiben wir a[3] = 21; oder *(a + 3) = 21;.
Die Pointer-Schreibweise ist noch erklärungsbedürftig. a ist ein Pointer; die Variable enthält also die Adresse des Arrays bzw. die Adresse des Elements mit dem Index 0. (a + 3) ist die Adresse des Elements mit dem Index 3, wobei aber nicht 3, sondern 6 zur Adresse des Arrays addiert wird, denn a ist vom Typ int und ein int beansprucht 2 Bytes Speicherplatz. Die Multiplikation mit der Typgröße macht der Compiler automatisch. Bei Zeigerarithmetik muss man immer beachten, von welchem Typ die Pointer sind. Die Pointer-Variablen selbst belegen dagegen immer 2 Bytes, weil jede Adresse 16 Bits = 2 Bytes lang ist, egal, ob der Pointer auf einen char, einen int oder sonst etwas zeigt.