Home » Object Pascal » Datentypen

Datentypen

Jeder Variablen liegt in Object Pascal ein Typ zugrunde. Es gibt verschiedene Arten von Datentypen. Die meisten einfachen Datentypen wurden bereits im Kapitel über Variablen beschrieben worden. Doch es gibt weitere

Teilbereichstypen

Einen weiteren Datentyp, der eigentlich gar kein eigener Datentyp ist, gibt es noch, nämlich den Teilbereichstyp, auch Unterbereichstyp genannt. Hierüber kann man Variablen z. B. zwar den Typ Integer zuordnen, aber nicht den kompletten Definitionsbereich, sondern nur einen Teilbereich davon:

var kleineZahl: 0..200;

Der Variablen kleineZahl lassen sich jetzt nur ganze Zahlen zwischen 0 und 200 zuweisen. Ähnlich lassen sich auch Strings begrenzen:

var kleinerString: string[10];

Diese Variable kann nur zehn Zeichen aufnehmen, es handelt sich nun um einen ShortString, der auch Nachteile gegenüber einem „normalen“ AnsiString hat. Mehr dazu aber in einem extra Abschnitt über Strings.

Aufzählungstypen

Um das Ganze für einen Programmierer lesbarer zu machen, können statt Zahlen auch konstante Bezeichner verwendet werden:

var farben: (blau, gelb, gruen, rot);

Intern werden die Werte bei Null beginnend durchnummeriert und haben deshalb eine feste Reihenfolge. Über die Funktion ord lässt sich die Position bestimmen.

Wichtige Funktionen zum Arbeiten mit Aufzählungstypen sind:

ord gibt die Position des Bezeichners zurück
pred gibt den Vorgänger zurück
succ gibt den Nachfolger zurück
low gibt den niedrigsten Wert zurück
high gibt den höchsten Wert zurück

Die Typen Integer und Char gehören ebenfalls zu den ordinalen (abzählbaren) Typen, d.h. die Werte lassen sich in einer Reihenfolge anordnen, weshalb o.g. Funktionen auf sie ebenfalls angewandt werden können.

Strings

String ist der Datentyp, der Texte aufnimmt. Allerdings gibt es nicht nur einen String-Typ, sondern verschiedene.

Unterschiedliche String-Typen

In Delphi gibt es verschiedene Möglichkeiten, Strings zu deklarieren:

  • String
  • ShortString
  • AnsiString
  • UnicodeString
  • UTF8String
  • WideString

String

Der Datentyp „String“ ist kein eigener Datentyp, sondern nur ein Alias, dessen wirklicher Typ sich abhängig von der Delphi-Version unterscheidet. Sofern man keine besonderen Gründe hat, wird empfohlen, immer „String“ als Datentyp für Texte zu verwenden. Der Alias steht für:

  • ShortString (in Delphi 1)
  • AnsiString (Delphi 2 bis 2007)
  • UnicodeString (seit Delphi 2009)

Die älteren String-Typen stehen nach wie vor auch in neuen Delphi-Versionen zur Verfügung. Nur reicht für ihre Verwendung nicht mehr die Angabe „String“, sondern es muss konkret z.B. „ShortString“ angegeben werden.

ShortString

Verwendet man unter Delphi 1 den Typ „String“, meinte man den Datentyp „ShortString“. Ein ShortString besteht maximal aus 255 Zeichen, für die statisch Arbeitsspeicher reserviert wird. Im ersten Byte des Strings befindet sich die Längenangabe.

AnsiString

Mit der Umstellung auf 32 Bit mit Delphi 2 fiel die 255-Zeichen-Grenze. Strings können nun bis zu 2 GB groß werden. Im Gegensatz zum ShortString wird deshalb nun nicht mehr die komplette Größe im Speicher reserviert, sondern nur so viel, wie momentan benötigt wird. Ist der String leer, so zeigt die Variable auf nil, verwendet also keinen Speicher. Für jedes Zeichen des Strings steht 1 Byte zur Verfügung, es kann also nur der ANSI-Zeichensatz verwendet werden. Der AnsiString ist 1-basiert. Will man also eine Schleife über alle Zeichen des Strings implementieren, ist zu beachten, dass das erste Zeichen sich nicht an Position 0, sondern 1 befindet.

Da AnsiStrings Zeiger sind, können mehrere AnsiString-Variablen auf den gleichen Wert im Speicher zeigen. Dazu führt jeder String einen Refernzzähler mit sich. Er wird erhöht, wenn eine weitere String-Variable auf den Wert zeigt, und erniedrigt im umgekehrten Fall. Ist der Referenzzähler 0, wird der Speicher freigegeben.

Ab Delphi 2009

UnicodeString

UnicodeStrings wurden mit Delphi 2009 eingeführt. Sie können sowohl ANSI- als auch Unicode-Zeichen (UTF-16) enthalten. Ansonsten gleicht ihr Verhalten dem Typ AnsiString. Ihre Länge ist nur vom zur Verfügung stehenden Arbeitsspeicher begrenzt. Aus Kompatibilitätsgründen basiert auch der UnicodeString auf 1.

UTF8String
Der UTF8String ist ebenfalls neu in Delphi 2009. Wie der Name sagt, sind seine Zeichen UTF-8-kodiert, während der UnicodeString mit UTF-16 arbeitet. Die String-Typen sind zuweisungskompatibel.

Nullterminierte Strings

Die folgenden String-Typen sind innerhalb eines normalen Delphi-Programms nicht erforderlich, gelegentlich werden sie jedoch zur Kommunikation mit der „Außenwelt“, der in C++ geschriebenen Win32-API benötigt.
Bei nullterminierten Strings handelt es sich um Zeichenketten, die ihr Ende durch eine ASCII-Null (#0) kennzeichnen. Man deklariert sie so:

var text: array [0..100] of Char;

Da es sich hierbei um keine normalen Pascal-Strings handelt, müssen solche nullterminierten Strings mit speziellen Funktionen bearbeitet werden, z.B. StrPCopy, um einen Pascal-String in einen nullterminierten String zu kopieren.
Bei PChar handelt es sich um einen Zeiger auf ein C-kompatibles Zeichenarray. Dieser Typ wird von einigen API-Funktionen gefordert. Man erhält ihn ganz einfach, indem man einen AnsiString mittels PChar(langerText) umwandelt.

Arbeiten mit Strings

Die Arbeit mit Strings (nicht nullterminierte Strings) ist recht einfach:

Zeichenketten zusammenhängen

var text1, text2: String;
begin
  text1 := 'toll';
  text2 := 'Ich finde Delphi '+text1+'!!';
  // text2 enthält nun den Text 'Ich finde Delphi toll!!'

Zugreifen auf ein bestimmtes Zeichen eines Strings

Der Zugriff auf ein einzelnes String-Zeichen erfolgt über dessen Index:

var text: String;
  zeichen: Char;
begin
  text := 'Ich finde Delphi toll!';
  zeichen := text[1];
  // zeichen enthält nun den Buchstaben 'I'

Vergleich zweier Strings

Das Vergleichen von zwei Strings erfolgt mit dem Gleichheitszeichen. Auch wenn es sich bei Strings intern um Zeiger handelt, wird beim Vergleich der Inhalt der Strings verglichen, nicht die Speicheradresse, auf die die Zeiger zeigen (im Gegensatz zu Java). Beim Vergleich wird Groß- und Kleinschreibung beachtet.

var text1, text2: string;
begin
  ...
  if text1 = text2 then ...

Die Delphi-Laufzeitumgebung bietet noch einige weitere Funktionen, z.B. AnsiCompareText zum Vergleich zweier AnsiStrings ohne Berücksichtigung der Groß- und Kleinschreibung. pos hilft beim Auffinden eines Teilstrings; copy zum Kopieren eines Teilstrings und delete zum Löschen eines Teilstrings sind ebenfalls wichtige Bearbeitungsmöglichkeiten.

Ab Delphi 2009

StringBuilder

Zur effektiven Arbeit mit Strings bietet die Delphi Runtime Library ab Delphi 2009 für Win32 die Klasse StringBuilder (Unit SysUtils).

var s1, s2: string;
  sb: TStringBuilder;
begin
  s1 := 'Hallo';
  s2 := 'Delphi';
  sb := TStringBuilder.Create(s1);
  s1 := sb.Append(s2).ToString;

Referenzzählung bei AnsiStrings

Um Speicherplatz und Rechenleistung zu sparen, besitzen AnsiStrings einen Referenzzähler. Dieser Referenzzähler gibt an, wie viele String-Variablen auf diese Zeichenkette zeigen. Wird einer Variable ein String zugewiesen, so wird der Referenzzähler erhöht. Ändert man nun diesen String, so ruft die Compiler-Magic je nach Zugriffsart die Funktion UniqueStr auf, die eine identische Kopie der Zeichenkette anlegt, deren Referenzzähler auf Eins setzt und den Referenzzähler der vorherigen Zeichenkette verringert. Erreicht ein Referenzzähler den Wert Null, so wird der Speicher für die Zeichenkette freigegeben und die Variable auf den Wert nil gesetzt. Bei konstanten Strings enthält der Referenzzähler den Wert -1. Man bekommt dank der Compiler-Magic von diesem Mechanismus nicht viel mit. Jedoch kann es Situationen geben, in denen das Hintergrundwissen einiges erklären kann und man den Fehler so schneller finden kann.

Wann ruft die Compiler-Magic nun UniqueStr genau auf? Zum einen wird UnitqueStr beim Schreibzugriff auf ein einzelnes Zeichen per S[x] := ‚a‘; aufgerufen und zum anderen beim Ermitteln des Zeigers eines Zeichen per offset := @S[x]; Nutzt man jedoch PChar(S[x]), so wird kein UniqueStr aufgerufen, womit auch kein const-Parameter geschützt ist.
Bei der Übergabe eines Strings als Parameter wird der String nicht kopiert, sondern nur seine Adresse an die Funktion übergeben. Diese erhöht nun je nach Modifizierer (var, out, const, „ByValue“) den Referenzzähler der Zeichenkette. Bei einem var, out und const bleibt der Referenzzähler unverändert, wohingegen bei einem „ByValue“ der Referenzzähler erhöht wird. Zu out sollte noch erwähnt werden, dass die Compiler-Magic dafür sorgt, dass der übergebene String beim Eintritt in die Funktion keinen Inhalt hat, also den Leerstring (“) enthält.

Wo wird die Referenzzählung gespeichert?

Bei Shortstrings wird die Länge im ersten Byte, auf das per s[0] zugegriffen werden kann, gespeichert.
Bei AnsiStrings ist das nicht möglich, da diese länger als 255 Zeichen sein können. Aus diesem Grund liegt vor dem eigentlichen String im Speicher noch ein Record, in dem Länge und Referenzzahl weggeschrieben werden.

TStrRec = packed record
 RefCount: Longint;
 Length: Longint;
 end;

Da eine Variable vom Typ AnsiString nur ein Pointer ist, kann man auf den Record zugreifen, indem man den Pointer um 8 Byte dekrementiert.
Eine entsprechende Funktion, die auf diesem Weg den Referenzzähler abfragt, würde so aussehen.

function GetRefCount(const S: string): Integer;
var
 P: ^TStrRec;
begin
 Result := -1;
 if Pointer(S) <> nil then
 begin
 P := Pointer(Integer(S) - SizeOf(TStrRec));
 Result := P^.RefCount;
 end;
end;

Tipps zur Optimierung

Man sollte bei String-Parametern so weit wie möglich den Modifizierer const angeben, da bei einem ByVal String der Referenzzähler erhöht und ein Exception-Block eingerichtet wird, was der Performance nicht gerade sonderlich dient.

Mengentypen

Um in einer einzigen Variablen eine unterschiedliche Menge an Werten des gleichen Typs zu speichern, gibt es Mengentypen. Es ist eine Menge an möglichen Werten vorgegeben, aus der eine beliebige Anzahl (keiner bis alle) in der Variablen abgelegt werden kann. Folgendermaßen wird eine solche Mengenvariable deklariert:

var zahlen: set of 1..10;

Damit können der Variablen zahlen Werte aus der Menge der Zahlen von 1 bis 10 zugewiesen werden – mehrere gleichzeitig oder auch gar keiner:

zahlen := [5, 9];           // zahlen enthält die Zahlen 5 und 9
zahlen := [];               // zahlen enthält überhaupt keine Werte
zahlen := [1..3];           // zahlen enthält die Zahlen von 1 bis 3
zahlen := zahlen + [5];     // zahlen enthält die Zahlen 1, 2, 3, 5
zahlen := zahlen - [3..10]; // die Zahlen von 3 bis 10 werden aus der Menge
// entfernt, es bleiben 1 und 2

Um nun zu prüfen, ob ein bestimmter Wert in der Menge enthalten ist, wird der Operator in verwendet:

if 7 in zahlen then ...

In einem Set sind nur Werte mit der Ordnungsposition von 0 bis 255 möglich.

Arrays

Müssen mehrere Werte des gleichen Typs gespeichert werden, ist ein Array (zu deutsch Feld oder Liste) eine praktische Lösung. Ein Array hat einen Namen wie eine Variable, jedoch gefolgt von einem Index in eckigen Klammern. Über diesen Index kann man auf die einzelnen Werte zugreifen. Am einfachsten lässt sich das mit einer Straße vergleichen, in der sich lauter gleiche Häuser befinden, die sich jedoch durch ihre Hausnummer (den Index) unterscheiden.
Eine Deklaration der Art

var testwert: array [0..10] of integer;

bewirkt also, dass wir quasi elf verschiedene Integer-Variablen bekommen. Man kann auf sie über den Indexwert zugreifen:

testwert[0] := 15;
testwert[1] := 234;

usw.
Vorteil dieses Indexes ist, dass man ihn durch eine weitere Variable ersetzen kann, die dann in einer Schleife hochgezählt wird. Folgendes Beispiel belegt alle Elemente mit dem Wert 1:

for i := 0 to 10 do
 testwert[i] := 1;

Auf Schleifen wird jedoch in einem gesonderten Kapitel eingegangen.
Bei dem vorgestellten Array handelt es sich genauer gesagt um ein eindimensionales, statisches Array. „Eindimensional“, weil die Elemente über nur einen Index identifiziert werden, und „statisch“, weil Speicher für alle Elemente reserviert wird. Legt man also ein Array für Indexwerte von 1 bis 10000 an, so wird für 10000 Werte Speicher reserviert, auch wenn während des Programmablaufs nur auf zwei Elemente zugegriffen wird. Außerdem kann die Array-Größe zur Programmlaufzeit nicht verändert werden.

Ab Delphi 4

Dynamische Arrays

Wenn schon so viel Wert auf die Eigenschaft statisch gelegt wird, muss es ja eigentlich auch etwas Dynamisches geben. Und das gibt es auch, zumindest seit Delphi 4: die dynamischen Arrays.
Der erste Unterschied findet sich in der Deklaration: Es werden keine Grenzen angegeben.

var dynArray: array of integer;

dynArray ist nun prinzipiell eine Liste von Integer-Werten, die bei Index Null beginnt.
Zum Hintergrundverständnis: Während statische Arrays direkt die einzelnen Werte beinhalten, enthält ein dynamisches Array nur Zeiger auf einen Arbeitsspeicherbereich. In der Anwendung ist das nicht zu merken, es ist keine spezielle Zeigerschreibweise nötig. Nur an einer Stelle bemerkt man das interne Vorgehen: Bevor man Werte in das Array stecken kann, muss man Speicher für die Elemente reservieren. Dabei gibt man an, wie groß das Array sein soll:

SetLength(dynArray, 5);

Nun kann das Array fünf Elemente (hier Integer-Zahlen) aufnehmen.
Man beachte: Da die Zählung bei Null beginnt, befindet sich das fünfte Element bei Indexposition 4!
Der Zugriff erfolgt ganz normal:

dynArray[0] := 321;

Damit es mit der Unter- und vor allem der Obergrenze, die ja jederzeit verändert werden kann, keine Probleme gibt (Zugriffe auf nicht (mehr) reservierten Speicher), lassen sich Schleifen am einfachsten so realisieren:

for i := 0 to high(dynArray) do
 dynArray[i] := 0;

Dadurch werden alle Elemente auf 0 gesetzt. high(dynArray) entspricht dem höchstmöglichen Index. Über length(a) lässt sich die Länge des Arrays ermitteln, welche immer high(dynArray)+1 ist. Dabei handelt es sich um den Wert, den man mit SetLength gesetzt hat.
Da die Länge des Arrays jederzeit verändert werden kann, könnten wir sie jetzt mit

SetLength(dynArray, 2);

auf zwei verkleinern. Die drei hinteren Werte fallen dadurch weg. Würden wir das Array dagegen vergrößern, würden sich am Ende Elemente mit undefiniertem Wert befinden.

Mehrdimensionale Arrays

Wenn es eindimensionale Arrays gibt, muss es auch mehrdimensionale geben. Am häufigsten sind hier wohl die zweidimensionalen. Man kann mit ihnen z. B. ein Koordinatensystem oder Schachbrett abbilden. Die Deklaration ist wie folgt:

var koordinate: array [1..10, 1..10] of integer;

Es werden also 10×10=100 Elemente angelegt, die jeweils einen Integer-Wert aufnehmen können. Für den Zugriff auf einzelne Werte sind zwei Schreibweisen möglich:

 koordinate[1,6] := 34;
 koordinate[7][3] := 42;

Und es gibt auch mehrdimensionale dynamische Arrays:

var koordinate: array of array of Integer;

Die Längenzuweisung erfolgt dann durch Angabe der Länge für jede Dimension:

SetLength(koordinate, 10, 10);

Records

Records entsprechen von der Struktur her einem Datensatz einer Datenbank – nur dass sie, wie alle bisher aufgeführten Variablen, nur zur Laufzeit vorhanden sind. Wenn wir unterschiedliche Daten haben, die logisch zusammengehören, können wir sie sinnvollerweise zu einem Record zusammenfassen. Beispiel: Adressen.

var Adresse: record
 name: string;
 plz: integer;
 ort: string;
end;

Die Variable Adresse wird also in drei „Untervariablen“ aufgegliedert.
Folgendermaßen greift man auf die einzelnen Felder zu:

adresse.name := 'Hans Müller';
adresse.plz := 12345;
adresse.ort := 'Irgendwo';

Damit das nicht (besonders bei vielen Elementen) in enorme Tipparbeit ausartet, gibt es dafür auch eine Abkürzung:

with adresse do begin
 name := 'Hans Müller';
 plz := 12345;
 ort := 'Irgendwo';
end;

Delphi Win32

Variante Records

Eine besondere Form der Records sind die varianten Records. Hier wird mittels case-Unterscheidung immer nur ein bestimmter Datenteil berücksichtigt.
Beispiel:

type
person = record
 name: string;
 case erwachsen: boolean of
  true: (personalausweisnr: integer);
  false: (kinderausweisnr: integer;
          erziehungsberechtigte: string);
end;

Das Record speichert in jedem Fall die Daten name und erwachsen. Abhängig von dem Wert erwachsen wird dann entweder die personalausweisnr (bei erwachsen=true) oder kinderausweisnr und erziehungsberechtigte (bei erwachsen=false) gespeichert. Ist erwachsen=false, sollte nicht auf den Wert personalausweisnr zugegriffen werden.
Dadurch hat man eindeutige Beschreibungen von Feldwerten und kommt nicht in Gefahr, die Kinderausweisnr versehentlich als Personalausweisnr zu speichern.
Wichtig ist noch zu bemerken, dass der variante Teil eines Records (also der case-Teil) immer am Ende stehen muss. Und case hat in diesem Fall auch kein schließendes end, wie sonst üblich.
Die Teile hinter case teilen sich denselben Speicherbereich. Der Compiler reserviert so viel Platz wie die größte Variante benötigt. Deshalb können auch variante Records in typisierten Dateien verwendet werden.

Nun wäre es praktisch, eine größere Menge solcher Variablen zu haben, da eine Adressverwaltung üblicherweise mehr als nur eine Adresse enthält. Die Lösung: Wir kombinieren Array und Record. Damit wir unseren neuen Adresstyp aber einfach weiterverwenden können, wollen wir zuerst einen eigenen Typ anlegen.

Ab Delphi 2006

Records mit Methoden

Seit Delphi 2006 können Records auch Methoden enthalten. Sie sind damit einer Klasse relativ ähnlich geworden. Vererbung und Polimorphismus sind allerdings nicht möglich. Während Variablen eines Klassentyps bei Funktionsaufrufen per Referenz übergeben werden, werden Records immer als Wert übergeben.
Ein Record benötigt zur Erzeugung keinen Konstruktor. Dennoch ist es möglich, zur Initialisierung von Variablen einen zu implementieren, der aber mindestens einen Parameter haben muss. Einen Destruktor hingegen kann es nicht geben, da ein Record ja auch nach Gebrauch nicht wieder freigegeben werden muss – im Gegensatz zur Instanz einer Klasse.

Eigene Typen definieren

Wir erinnern uns: Variablendeklarationen enthalten auf der linken Seite einen Variablennamen und rechts vom Doppelpunkt einen zugehörigen Datentyp. Solche Typen können wir auch selbst definieren, beispielsweise obiges Record:

type TAdressRecord = record
 name: string;
 plz: integer;
 ort: string;
end;

Eine Typdefinition besteht also aus dem Schlüsselwort type gefolgt von einer Konstruktion, die einer Variablendeklaration ähnelt. Da aber statt einer Variablen ein Typ deklariert wird, ist das Folgende auch kein Variablen- sondern ein Typname. Typnamen beginnen in Delphi mit einem großen T, wenn sich der Programmierer an den Styleguide gehalten hat.
Statt eines Doppelpunkts wird ein Gleichheitszeichen verwendet.
Auf die folgende Weise kann solch ein Typ dann verwendet werden:

var adresse: TAdressRecord;

Diese Variablendeklaration entspricht der im obigen Beispiel für Records.
Wollen wir nun noch ein Array daraus machen, dann geht das so:

var adresse: array [1..50] of TAdressRecord;

Der Zugriff erfolgt streng nach den Regeln für Arrays und Records:

adresse[1].name := 'Hans Müller';
adresse[2].name := 'Susi Sorglos';

Will man einer Funktion oder Prozedur ein Array oder ein Record als Parameter übergeben, muss hierfür auch zuerst ein Typ definiert werden:

type TMyArray = array of array of Integer;

Anschließend kann „TMyArray“ als Typ bei der Variablendeklaration oder bei der Implementierung von Funktionen und Prozeduren verwendet werden.

Zeiger

Bei Zeigern handelt es sich um Variablen, die eine Speicheradresse enthalten. Eine Zeigervariable zeigt also auf eine bestimmte Stelle im Arbeitsspeicher. Zum einen gibt es den Typ Pointer, der auf beliebige Daten zeigt und zum anderen spezialisierte Zeigertypen.
In der Delphi-Sprache kommen Zeiger (auch Referenzen genannt) häufig vor, so z.B. bei Objektreferenzen, bei langen Strings und bei dynamischen Arrays. An der Syntax fällt das an diesen Stellen nicht auf. Will man Zeiger auf selbstdefinierte Typen erstellen, muss dagegen die noch aus Pascal-Zeiten stammende Zeigersyntax mit @ und ^ verwendet werden.

Doch zunächst ein Beispiel zur Deklaration von Zeigern:

type
 PAdressRecord = ^TAdressRecord;
 TAdressRecord = record
  name: string;
  plz: integer;
  ort: string;
 end;
 
 var adresse: TAdressRecord;
  adresszeiger: PAdressRecord;

^ zur Zeigertypdefinition

Steht das Symbol ^ vor einem Typbezeichner, wird daraus ein Typ, der einen Zeiger auf den ursprünglichen Typ darstellt. ^TAdressRecord ist ein Zeigertyp auf einen Speicherbereich, an dem sich etwas vom Typ TAdressRecord befinden muss.

@ zur Ermittlung einer Speicheradresse

Anfänglich zeigt unsere Variable adresszeiger ins Leere – auf nil (not in list). Wir wollen nun, dass er auf die Variable adresse zeigt. Da ein Zeiger sich bekanntlich nur Speicheradressen merken kann, benötigen wir die Speicheradresse unserer Variable adresse. Diese ermitteln wir mit dem Operator @:

adresszeiger := @adresse;

^ zur Dereferenzierung

Nun können wir auch über den Zeiger auf den Inhalt von adresse zugreifen. Wir wollen also, dass uns der Zeiger nicht die Adresse bekannt gibt, auf die er zeigt, sondern den Wert an dieser Speicherstelle. Diesen Vorgang nennt man Dereferenzieren. Verwendet wird dafür wieder das Symbol ^ – allerdings diesmal hinter einer Zeigervariablen:

var gesuchterName: string;
begin
 gesuchterName := adresszeiger^.name;

Eine Dereferenzierung ist nur mit spezialisierten Zeigern möglich, nicht mit dem Allround-Talent Pointer. Um auch mit dem Typ Pointer entsprechend arbeiten zu können, muss dieser in einen anderen Zeigertyp umgewandelt werden.