Teil 1: Grundlagen


Zeiger

Definition

Ein im Zeiger abgelegter Wert wird als eine Speicheradresse interpretiert. D.h., über einen Zeiger kann indirekt auf den Speicher, z.B. auf eine Variable oder einen Peripheriebaustein, zugegriffen werden.

Ein Zeiger wird wie folgt definiert:

DTYP *NAME;

DTYP bei der Zeigerdefinition gibt an, wie die Daten zu interpretieren sind, deren Adresse im Zeiger abgelegt ist. Ist DTYP z.B. ein short, so werden sizeof(short) Bytes ausgelesen bzw. geschrieben.

Adressberechnung

Nachdem ein Zeiger definiert ist, kann ihm eine Adresse, z.B. die einer Variablen, zugewiesen werden. Um die Adresse einer Variable zu erhalten, ist vor dem Variablennamen den Adressoperator & anzugeben. Hierbei muss DTYP des Zeigers gleichen dem Datentyp der Variable sein.

Soll ein Zeiger auf eine bestimmte Adresse im Speicher verweisen, so kann dem Zeiger auch ein Literal oder eine benannte Konstante zugewiesen werden. In diesem Fall muss aber eine entsprechende Typkonvertierung mittels reinterpret_cast<...> durchgeführt werden (reinterpret_cast wird auf der nächsten Seite erklärt).

Zeigerzugriffe

Um auf den Inhalt der Speicherstelle zuzugreifen, deren Adresse im Zeiger abgelegt ist, ist vor dem Zeigernamen der Dereferenzierungsoperator * (wiederum das Sternchen) anzugeben. Die Anzahl der Bytes, die bei einem solchen Zugriff transferiert werden, ist vom Datentyp des Zeigers abhängig.

String-Literale und Zeiger

Der Datentyp eines String-Literals ist standardgemäß const char[n] und damit kann ein String-Literal einem const char* zugewiesen werden.

Der nullptr

nullptr ist ein Literal für einen Zeiger, dessen Inhalt nicht definiert ist. Wird ein Zeiger definiert, dem bei seiner Definition keine Adresse zugewiesen werden kann, so sollte dieser Zeiger immer mit einem nullptr initialisiert werden.

void Zeiger

Eine Besonderheit im Zusammenhang mit Zeigern spielt der Datentyp void. Ein Zeiger vom Datentyp void* ist ein Zeiger, der an keinen Datentyp gebunden ist. Soll über einen solchen void-Zeiger auf Daten zugegriffen werden, so muss dieser zuerst in einen entsprechenden typisierten Zeiger (char*, short* usw.) konvertiert werden.

Zeiger in cout-Anweisungen

Steht in einer cout-Anweisung als auszugebendes Datum ein Zeiger, so wird in der Regel der Inhalt des Zeigers, d.h. die in ihm abgelegte Adresse, ausgegeben. Soll stattdessen der Inhalt der Speicherstelle, die durch den Zeiger adressiert wird, ausgegeben werden, so muss der Zeiger dereferenziert werden (*-Operator).

Eine Ausnahme davon bilden alle Typen von char-Zeigern. Bei char-Zeigern innerhalb einer cout-Anweisung wird davon ausgegangen, dass der Zeiger auf einen String zeigt und somit der String auszugeben ist.

#include <iostream>
using std::cout;
using std::endl;

// main() Funktion
int main()
{
    // Variable definieren und initialsieren
    short sVar = 10;
    // Zeiger definieren, hier im Beispiel uninitialisiert
    short *pVar = nullptr;
    // Dem Zeiger die Adresse von sVar zuweisen
    pVar = &sVar;
    // Inhalt ueber Zeiger ausgeben
    cout << "*pVar = " << *pVar << endl;
    // Inhalt der Speicherstelle, auf die pVar verweist, erhoehen
    // In diesem Fall also sVar
    *pVar += 1;
    // sVar nun ausgeben
    cout << "sVar = " << sVar << endl;
    // Zeiger auf konstanten String definieren
    const char* pText;
    // Ueber Zeiger einen Text ausgeben
    pText = "Erster Text";
    cout << pText << endl;
    // Weiteren Text ueber den selben Zeiger ausgeben
    pText = "Zweiter Text"; cout << pText << endl;
}

Operationen mit Zeigern

Die Zuweisung eines Zeigers an einen anderen Zeiger ist nur zulässig, wenn beide Zeiger den gleichen Datentyp besitzen. Soll eine Zuweisung mit einem anderen Datentyp erfolgen, so ist dieser entsprechend zu konvertieren (siehe Beispiel hierzu).

Für arithmetische Operationen mit Zeigervariablen gelten einige Besonderheiten:

  • Eine Addition eines Ganzzahlwertes X erhöht den Zeiger vom Typ DTYP* um sizeof(DTYP). Für die Subtraktion gilt entsprechendes.
  • Außerdem können zwei Zeiger desselben Datentyps nur subtrahiert werden. Das Ergebnis ist vom Datentyp size_t.

Außer der erwähnten Addition und Subtraktion sind nur noch Vergleichsoperationen mit Zeigern erlaubt.

#include <iostream>
using std::cout;
using std::endl;

int main()
{
    // long Variable definieren und initialisieren
    unsigned long lVar = 0x11223344L;
    // Einen short definieren und mit der Adresse der long Variable
    // initialisieren. Hierzu muss der Zeiger auf die long Variable
    // in einen short Zeiger mittels reinterpret_cast konvertiert werden
    unsigned short* pShortPtr = reinterpret_cast<unsigned short*>(&lVar);
    // Inhalt der long Variable nun als 2 short-Werte ausgeben
    cout << std::hex << "1. short Wert: " << *pShortPtr;
    // nun short Zeiger auf die hoeherwertigen Bytes verweisen
    pShortPtr++;
    cout << ", 2. short Wert: " << *pShortPtr << endl;
    // long Zahl in Bytes ausgeben
    unsigned char* pCharPtr = reinterpret_cast<unsigned char*>(&lVar);
    cout << "Und als einzelne Bytes: " << int(*pCharPtr);
    pCharPtr++;
    cout << ',' << int(*pCharPtr);
    pCharPtr++;
    cout << ',' << int(*pCharPtr);
    pCharPtr++;
    cout << ',' << int(*pCharPtr);
}

const und Zeiger

Wie bereits weiter vorne weiter ausgeführt, werden für nicht veränderbare Werte Konstanten verwendet. Bei Zeigern müssen 3 Fälle unterscheiden:

  • Der Zeiger ist konstant.
  • Das, worauf der Zeiger zeigt, ist konstant.
  • Sowohl der Zeiger als auch das, worauf er zeigt, ist konstant.

Sehen wir uns die entsprechenden Zeiger-Definitionen einmal an:

Zeigerdefinition
Bedeutung
const DTYP *ptr;
Zeiger ptr zeigt auf eine Konstante vom Typ DTYP; der Zeiger kann verändert werden.
DTYP *const ptr;
Zeiger ptr zeigt auf Variable vom Typ DTYP; der Zeiger selbst ist konstant.
const DTYP *const ptr;
Sowohl der Zeiger ptr wie auch das, worauf er zeigt, ist konstant.

Eine Übung aus dem C++ Buch hierzu:



Copyright © cpp-tutor.de
Impressum &Datenschutz