ZFX
ZFX Neu
Home
Community
Neueste Posts
Chat
FAQ
IOTW
Tutorials
Bücher
zfxCON
ZFXCE
Mathlib
ASSIMP
NES
Wir über uns
Impressum
Regeln
Suchen
Mitgliederliste
Membername:
Passwort:
Besucher:
4440682
Jetzt (Chat):
19 (0)
Mitglieder:
5239
Themen:
24223
Nachrichten:
234554
Neuestes Mitglied:
-insane-
ZFX - C++ TutorialsDruckversion

Einführung in Zeiger und Referenzen
Das C++ Tutorials von Julian aka Vertexwahn

© Copyright 2004 [whole tutorial] by Julian aka Vertexwahn
Die Texte der Tutorials unterliegen dem Copyright und drfen ohne schriftliche Genehmigung vom Author weder komplett noch auszugsweise vervielfltigt, auf einer anderen Homepage verwendet oder in sonst einer Form verffentlicht werden. Die zur Verfgung gestellten Quelltexte hingegen stehen zur freien Weiterverwendung in Software Projekten zur Verfgung.
Die Quelltexte dieses Tutorials werden auf einer "as is" Basis bereit gestellt. Der Autor bernimmt keinerlei Garantie fr die Lauffhigkeit der Quelltexte. Fr eventuelle Schden die sich aus der Anwendung der Quelltexte ergeben wird keinerlei Haftung bernommen.


Zeiger

 

Vorwort

 

Dieses Dokument soll keine vollstndige Abhandlung ber Zeiger darstellen, sondern nur Anfnger einen berblick ber dieses Thema geben und ein besseres Verstndnis von Zeiger vermitteln.

 

Grundlagen

Zeiger werden nach folgendem Schema deklariert:

 

ZEIGERTYP *Zeigername;

 

ZEIGERTYP: Variablentyp der Variable, deren Adresse in der Zeigervariablen gespeichert werden soll

 

Beispiel:

int i = 5; // Variable

int *Zeiger; // Zeiger - Variable die die Adresse einer Anderen Variablen enthlt

 

Wie weit man einem Zeiger eine Adresse einer andern Variablen zu?

Antwort: Mit dem Adressoperator &

 

Beispiel:

int i = 5;

int *Zeiger;

Zeiger = &i;

 

Variablen stellen einmal einen Wert dar (auch als rvalue bezeichnet) (z. B. i stellt den Wert 5 dar) und zum anderen die (Anfangs)adresse zu einem Speicherbereich wo dieser Wert binr gespeichert ist.

 

Es gibt auerdem einen Dereferenzierungsoperator: *

Mit diesem kann man wieder auf den Wert der an einer Adresse gespeichert ist zugreifen

 

Beispiel:

int i = 5;

int *Zeiger = &i;

*Zeiger = 10; /* in der Variablen i steht jetzt der Wert 10 */

 

Zeiger unterscheiden sich von normalen Variablen nur indem das sie Adressen von Variablen speichern, statt irgendwelche Werte.

 

Ein Zeiger hat genauso wie eine Variable eine rvalue und einen lvalue - rvalue ist die Adresse, an der Variablen auf die ein Zeiger zeigt lvalue ist der Speicherort an dem sich der Zeiger selbst im Speicher befindet.

 

Vorteile von Zeigern

-          dynamische Speicherverwaltung

-          direkte bergabe von Objekten an eine Funktion statt der bergabe von Kopien

-          rekursive Datenstrukturen (Bume, dynamische Listen (engl. LinkedList), usw.)

 

es gibt noch weitere Mglichkeiten, die Zeiger bieten

 

Stellung des Sternchens *

 

int* ptr;

 

ist das gleiche wie

 

int *ptr;

 

int* ptr1, ptr2;

 

ptr1 ist ein Zeiger

ptr2 ist kein Zeiger, sondern eine normale Integer Variable

 

Zeiger auf beliebige Datentypen

Grundstzlich gilt: Die Deklaration eines Zeigers sieht genauso aus wie die Deklaration der Variablen, auf die der Zeiger zeigt.

 

Variable

Zeiger

int i;

int *i;

char array[10];

char *zeiger[10];

void funktion(int);

void (*zeiger_auf_funktion)(int);

 

Bei dem Zeiger auf die Funktion fllt die Klammer auf diese sind erforderlich, weil sonst der Compiler annehmen wrde das der Rckgabewert ein Zeiger auf einen void ist.

 

Zeiger auf char kann man auch mit einem String-Literal initialisieren (Dies stellt eine AUSNAHME dar String-Literale sind die einzigen Konstanten, fr die Speicher bereit gestellt wird.)

 

char *ptr;

ptr = "Hallo Welt!";

 

Zeiger, die auf kein Objekt gerichtet sind, sollten mit NULL initialisiert werden:

 

int *ptr = NULL;

 

Namen von Arrays und Funktionen werden ebenfalls als Adressen interpretiert

 

int array[5];

int *ptr[5];

ptr = array;

 

int funk(void);

int (*funk_ptr)(void);

funk_ptr = funk;

 

Die Funktion malloc sowie der Operator new dienen dazu, Speicher zu reservieren und die Adresse an den Zeiger zu bergeben. Auf diese Weise werden Objekte eingerichtet, die nicht mit einer Variablennamen verbunden sind, sondern nur ber Zeiger erreichbar sind.

 

int *ptr = NULL;

ptr = new int;// Speicher reservieren

*ptr = 7;

 

cout<<*ptr<<endl;

 

delete ptr;// reservierten Speicher wieder freigeben

 

Derefernzierungsoperatoren

 

Es gibt verschiede Dereferenzierungsoperatoren:

*n;

bereits bekannt

n->

ermglich Zugriff auf Elemente einer Struktur bzw. Klasse

n.*

???

n->*

???

 

Zeigerarithmetik

 

Nehmen wir an int *ptr; ist ein Zeiger, der auf 10 hintereinander folgende Integerwerte im Speicher verweit wollte man nun den ersten Integer Wert ausgeben wrde man das wie folgt machen:

 

cout<<*ptr<<endl;

 

Wie gibt man aber den zweiten Integer Wert im Speicher aus? Ganz einfach: Man ermittelt einfach die nchst hhere Adresse (die nchst folgende Adresse im Speicher) an dieser sollte sich dann der zweite Integerwert befinden das stimmt aber nicht ganz da ein Integerwert ber mehrere Adressen (um genau zu sein ber zwei Adressen eine Adresse zeigt auf 8 Bits = 1 Byte ein Integer ist gleich 2 Bytes gro) geht. Dies macht aber der Compiler automatisch wenn man schreibt:

 

ptr + 1;

 

Die ausgabe des zweiten Integerwerts she damit wie folgt aus:

cout<<*(ptr + 1)<<endl;

 

Die Anwendung des Indizierungsoperators ([]) wird intern in die Dereferenzierung eines Zeigers umgewandelt.

 

int Array[10];

Array[5] = 4;

 

wird zu:

 

*(Array + 5) = 4;

 

Nun wei man auch was passieren wrde, wenn man folgendes versuchen wrde:

 

int Array[10];

Array[15] = 4;

 

Der Wert 4 oder besser gesagt die Binrfolge davon wrde an die Adresse Array + 15 geschrieben was zu Fehlern fhren kann, ja nachdem welche Informationen an dieser Adresse ursprnglich standen und je nachdem fr welchen Zweck diese Daten bentigt wurden.

 

Zeichenketten

 

char *String = "Das ist ein String";

 

Dies Funktioniert, weil der Compiler fr konstante String Literale automatisch Speicher reserviert. String enthlt eine Adresse, die auf einen char verweit. Dabei enthlt der Zeiger String die Adresse des ersten char Wertes im Speicher in diesem Fall verweit der Zeiger auf die Adresse, an der der Wert D (der erste Buchstabe des Stings) zu finden ist.

 

C++ verwaltet Strings als hintereinander folgende chars im Speicher der letzte char in dieser Folge enthlt den Wert \0 dieser Wert hat auch einen Namen, nmlich Nullterminierungszeichen.

 

Damit wei der Compiler wo der String anfngt und wo er aufhrt.

 

Nice to know: Jede Funktion in C die mit Strings arbeitet erwartet einen Zeiger auf den ersten Buchstaben des Strings dann wird automatisch bis zum Nullterminierungszeichen gelesen.

 

Ein Zeichenkette knnte man auch wie folgt anlegen:

 

char my_string[40];

my_string[0] = 'T';

my_string[1] = 'e';

my_string[2] = 'd':

my_string[3] = '\0';

 

Alternativ gibt es noch diese Mglichkeit:

 

char my_string[40] = {'T', 'e', 'd', '\0',};

 

Da beide Schreibweisen etwas umstndlich sind gibt es auch folgende Mglichkeit:

 

char my_string[40] = "Ted";

 

Bei der Verwendung von doppelten Gnsefsschen wird das Nullterminierungszeichen automatisch angehngt.

 

Will man nur einen String der den Namen Ted beinhaltet kann man auch folgendes machen:

char my_string[] = "Ted";

 

Hier wird ein Speicher fr 4 Chars reserviert im 4ten char steht das Nullterminierungszeichen. In den ersten drei chars steht der Name Ted. Der Compiler zhlt einfach ab aus wie vielen Zeichen der String besteht und stellt dann automatisch ein entsprechend groen Speicher bereit.

 

Es wurden 2 Mglichkeiten aufgezeigt einen String einfach zu speichern:

 

char String[] = "Ted"; // 1. Mglichkeit

char *String = "Ted"; // 2. Mglichkeit

 

Nun stellt sich die Frage welchen Unterschied es zwischen beiden Mglichkeiten gibt die erste Mglichkeit bentigt 4 Bytes fr 4 char Werte die zweite Mglichkeit bentigt ebenfalls 4 Bytes fr 4 char Wert und zustzlich noch 2 bzw. 4 Bytes (je nach Compiler) fr die Adresse des Strings.

 

Dafr ist die 2. Mglichkeit dynamisch und die 1. Mglichkeit statisch.

 

Weiterer Unterschied: Bei 1. wird Speicher im Stack reserviert.

Bei 2. kann auch Speicher auerhalb des Stacks reserviert werden.

 

Call by value Call by refernce

 

bergibt man Kopien von Werten von Variablen an eine Funktion nennt man das Call by value.

 

bergibt man statt einer Kopie des Wertes einer Variablen die Adresse einer Variablen an eine Funktion nennt man dies Call by refernce.

 

Vorteil von Call by reference (Prototyp eine Funktion die Call by reference nutzt: void fkt(int *i);) ist das man Variablen direkt bearbeiten kann und groe Datenbestnde (z. B. Array mit vielen Elementen) nicht erst kopiert werden mssen, was CPU Zeit kostet.

 

Zeiger auf Strukturen

 

Beispiel einer Struktur:

 

struct person

{

       char *nachname;

       char *vorname;

       int alter;

};

 

Einen Zeiger erhalten wir wie folgt auf die Struktur:

 

person *Zeiger;

 

Wie wird aber dieser Zeiger dereferenziert?

 

#include <iostream.h>

 

struct person

{

       char *nachname;

       char *vorname;

       int alter;

};

 

person *Zeiger;

 

void main(void)

{     

       person A;

       A.alter = 19;

       A.nachname = "Mayer";

       A.vorname = "Michael";

 

       Zeiger = &A;

 

       cout<<(*Zeiger).alter<<endl; // Dereferenzeirung des Zeigers

}

 

Alternativ gibt es folgende Schreibweise zur Dereferenzierung eines Zeigers auf eine Struktur:

 

cout<<Zeiger->alter<<endl;

 

Zeiger auf Funktionen

 

An Arrays kann man keine Funktionen als Werte bergeben, jedoch Adressen von Funktionen.

 

Genauso verhlt es sich mit Arrays Arrays knnen keine Funktionen, aufnehmen, jedoch Adressen von Funktionen.

 

Dies ermglich interessante syntaktische Gebilde:

 

#include <stdio.h>

#include <string.h>

 

struct sFUNKTION

{

       char *command_String;

       void (*funktion)(void); // Zeiger auf eine Funktion

};

 

const int Anzahl_der_Funktionen = 1;

 

sFUNKTION FunktionsManager[Anzahl_der_Funktionen];

 

void printText(void)

{

       printf("Hallo Welt!\n");

}

 

void main(void)

{

       FunktionsManager[0].command_String = "printText";

       FunktionsManager[0].funktion = printText;

 

       char *Console_Text = "printText";

 

       for(int i = 0; i < Anzahl_der_Funktionen; i++)

       {

             if(strcmp(Console_Text,FunktionsManager[i].command_String)==0)

             {

                    // Funktion ausfuehren

                    FunktionsManager[i].funktion();

             }

       }

}

 

Stellen wir uns ein Computerspiel vor nennen wir es Quake, HL oder DOOM drckt man auf die TAB Taste erscheint im Regelfall eine Konsole, in die der Benutzer einen Befehl eintippen kann ja nachdem Befehl wird eine bestimmte Aktion ausgelst obiger Code soll nur vereinfacht darstellen wie man so etwas mithilfe von Zeigern auf Funktionen verwalten knnte.

 

Ich habe ein weitere Konsolenanwendung geschrieben Code ist Quick & Dirty - zeigt aber obige Konsolenanwendung erweitert mit den Befehlen exit (verlsst das Programm) und printText (gibt den String) "Hallo Welt! aus:

 

#include <stdio.h>

#include <string.h>

#include <iostream.h>

 

bool bExit = false;

 

struct sFUNKTION

{

       char *command_String;

       void (*funktion)(void); // Zeiger auf eine Funktion

};

 

const int Anzahl_der_Funktionen = 2;

const int Command_Buffer_Size = 200;

char Console_Text_Buffer[Command_Buffer_Size];

 

sFUNKTION FunktionsManager[Anzahl_der_Funktionen];

 

// Funktionen

void printText(void)

{

       printf("Hallo Welt!\n");

}

 

void Exit(void)

{

       bExit = true;

}

 

void InitFunktionsManager(void)

{

       FunktionsManager[0].command_String = "printText";

       FunktionsManager[0].funktion = printText;

 

       FunktionsManager[1].command_String = "exit";

       FunktionsManager[1].funktion = Exit;

}

 

void main(void)

{

       InitFunktionsManager();

 

       while(bExit==false)

       {

      

             cin>>Console_Text_Buffer; // liet Tastatureingabe des Benutzer ein

 

 

             for(int i = 0; i < Anzahl_der_Funktionen; i++)

             {

                    if(strcmp(Console_Text_Buffer,FunktionsManager[i].command_String)==0)

                    {

                           // Funktion ausfuehren

                           FunktionsManager[i].funktion();

                    }

             }

       }

}

 

Abstecher zu dynamischen Listen

 

Dynamische Listen gehren zu den Rekursiven Datenstrukturen im englischen bezeichnet man dynamische Listen als so genannte LinkedLists.

 

Beispiel:

struct  a

{

       int value;

       a *next;

};

 

Strukturen, die sich selbst wiederum als Element enthalten sind nicht erlaubt ein Zeiger, der auf die Struktur selbst verweit ist jedoch schon erlaubt (s. o.).

 

Virtuelle Adressen

 

Man knnte annehmen Adressen sind physikalisch greifbar, d. h. jedes Byte im Arbeitsspeicher hat seine eigene Adresse. Das stimmt aber nicht was eine Adresse wirklich reprsentiert hngt vom Betriebssystem ab. Schlaue Betriebssysteme vergeben jeder Anwendung einen Adressraum eine Anwendung kann nur auf Adressen in diesem Raum zugreifen diese Adressen bezeichnet man als virtuelle Adressen, weil diese Adressen nicht mit den physikalischen Adressen bereinstimmen mssen damit wird  z. B. verhindert, das mit einem Zeiger auf die Daten einer anderen Anwendung bzw. auf Daten des Betriebssystem zugegriffen werden kann.

 

Dynamische Speicherreservierung

 

#include <iostream.h>

 

void main(void)

{

       int *Zeiger = NULL;

 

       Zeiger = new int;

 

       *Zeiger = 5;

 

       cout<<*Zeiger<<endl;

 

       delete Zeiger;

}

 

Mit new wird ein Speicherbereich fr den angegebenen Datentyp reserviert (im Beispiel ist der angegebene Datentyp ein int). Wenn es keine Fehler gab (z. B. zu wenig Speicherplatz) liefert new die Adresse eines gltigen Speicherbereichs zurck also einen Speicherbereich in den geschrieben werden darf.

 

Dynamisch reservierter Speicher muss auch wieder freigegeben werden mit dem delete Operator sonst knnte es passieren das irgendwann der Speicher ausgeht. Beim beenden des Programms wird normalerweise der mit new reservierte Speicherplatz automatisch freigegeben aber man sollte trotzdem per Hand den belegten Speicher ber den Operator delete wieder freigeben, weil man sich nicht unbedingt darauf verlassen sollte das der belegte Speicherplatz wieder freigegeben wird.

 

Braucht man mehr als einen Integer Wert z. B. 20 Integer Werte geht dies wie folgt:

 

#include <iostream.h>

 

void main(void)

{

       int *Zeiger = NULL;

 

       Zeiger = new int[20];

 

       for(int i = 0; i < 20; i++)

       {

             Zeiger[i] = i;

       }

 

       for(i = 0; i < 20; i++)

       {

             cout<<Zeiger[i]<<endl;

       }

 

       delete [] Zeiger;

}

 

Die Adresse NULL

 

NULL ist ein Makro das vom Compilerhersteller definiert wurde es ist die Adresse eines Speicherbereichs, in den nie geschreiben wird sich also niemals wichtige Daten befinden. NULL ist nicht gleichwertig mit der Zahl 0 (meistens aber schon aber man sollte sich nicht darauf verlassen). Zeiger die nicht bei der Deklaration gleich initialisiert werden knnen, sollten mit NULL initialisiert werden, da der Zeiger auf einen zuflligen Speicherbereich zeigt und bei falscher Anwendung dies zu schwerwiegenden Fehlern fhren knnte.

 

Nachwort

 

Ich kann nicht fr die Richtigkeit dieser Informationen garantieren ich hoffe jedoch, dass sich nicht zu viele Fehler eingeschlichen haben.

 

Quellen

-          C/C++ Kompendium (verarbeitet)

-          A TUTORIAL ON POINTERS AND ARRAYS IN C (Ted Jensen)

-          Volkard C++ Kurs (verarbeitet)

-          OOP fr Dummies

 

Kontakt:

 

vertexwahn@gmx.de

 

Referenzen

 

Was sind Referenzen?

 

Referenzen sind ein Sprachelement von C++ - in C gibt es diese Referenzen nicht. Referenzen sind hnlich wie Zeiger. Sie ermglichen call by reference das heit der Inhalt einer Variablen bei der Angabe als Funktionsparameter wird nicht kopiert, sondern es wird auf sie nur verwiesen wie ein Lesezeichen, das sagt schau her die Informationen befindet sich da und dort.

 

Ihr Vorteil gegenber Zeiger liegt darin, dass sie nicht dereferenziert werden mssen und somit die Lesbarkeit des Quellcodes verbessern.

 

Deklaration

 

Beispiel:

variablentyp variable;

variablentyp &referenz = variable;

 

Eine Referenz stellt ein Alias fr eine Variable dar. Referenzen mssen bei der Deklaration initialisiert werden. Eine Referenz kann spter (nach der Initialisierung, die bei der Deklaration stattfindet) nicht mehr auf eine andere Variable verweien.

 

Referenzen zur Parameterbergabe

 

Referenzen werden blicherweise bei Parameterbergaben verwendet:

 

void addition(int& summe, int& operant1, int& operant2)

{

       summe = operant1 + operant2;

}

 

Besonderheiten

 

Referenzen knnen auch mit konstanten Werten initialisiert werden.

 

const int& referenz = 123;

 

Dabei muss aber beachtet werden, dass die Referenz als Konstante deklariert wird.

 

Der Compiler legt automatisch fr den Konstanten Wert (auch Literal genannt) eine Variable an und verbindet die Referenz mit dieser Variablen diese automatisch angelegte Variable hat die gleiche Lebensdauer wie die Referenz, was nur logisch erscheint.

 

Quellen

 

- C/C++ Kompendium



Informationen: " ); ?> " ); ?> " ); ?> " ); ?>
WWW :$wwwtitel
Data:Projektdateien
Mail:$author ($email)
Nick:$nick