Im letzten Artikel wurde der Heap in seiner allgemeinen Funktionsweise vorgestellt. Bevor wir uns allerdings weiter mit Pointern beschäftigen, wollen wir zuerst ein anderes Thema besprechen, und zwar dass der strukturierten Datentypen.

Die bisher bekannten Datentypen sind lediglich dazu geeignet, um einzelne Werte (etwa Zahlen oder einzelne Buchstaben) zu speichern. In Arrays können zwar mehrere Werte gespeichert werden, jedoch nur welche vom gleichen Datentyp. Oft ist es aber auch nötig, Werte unterschiedlicher Datentypen zusammen zu speichern, wenn etwa komplexere Sachverhalte ausgedrückt werden sollen.

Zwar könnte man zu diesem Zweck natürlich mehrere Variablen unterschiedlicher Datentypen anlegen und in diesen die zusammengehörenden Werte speichern. Das hat natürlich aber zum einen den Nachteil, dass eigentlich zusammengehörende Werte nicht gebündelt gespeichert werden, und zum anderen, dass beim Übergeben dieser Werte als Argumente bei Funktionsaufrufen immer mehrere Werte übergeben werden müssen. Nehmen wir zum Beispiel an, wir wollen eine rationale Zahl als Verhältnis zweier ganzer Zahlen darstellen. Momentan haben wir dafür nur die folgende Möglichkeit, nämlich über die Verwendung zweier Variablen für Zähler und Nenner (im Beispiel wird der Bruch 1/2 in den Variablen gespeichert):

int numerator; int denominator; numerator = 1; denominator = 2;

Ein zweiter Bruch parallel dazu lässt sich nur so darstellen:

int numerator1; int denominator1; int numerator2; int denominator2;

Wir sehen, dass wir für eigentlich 2 Daten (die 2 Brüche) bereits 4 Variablen benötigen; wollen wir nun etwa eine Funktion schreiben, welche 2 rationale Zahlen miteinander multipliziert, müssen wir ihr bereits 4 Variablen übergeben. Noch schlimmer aber: welchen Wert soll die Funktion zurückliefern? Schauen wir uns einmal die Implementierung der Funktion an, deuten die Stellen mit den Fragezeichen bereits an, wo es Probleme geben könnte (C++-Kenner werden wissen, dass man das Problem natürlich auch so lösen kann, aber dazu in einem späteren Artikel mehr):

??? fractionMultiplication( int numerator1, int denominator1, int numerator2, int denominator2 ) { int numerator = numerator1 * numerator 2; int denominator = denominator1 * denominator2; return ???; }

Was wir benötigen, ist eine Möglichkeit, um die rationale Zahl mit Zähler und Nenner in einer einzigen Variable zu speichern. Die bisher bekannten Datentypen erlauben aber nicht die Speicherung von derartig notierten rationalen Zahlen; zudem können wir uns beliebige Datenstrukturen ausdenken, die aus mehreren Werten bestehen. Was wir also eigentlich benötigen, ist eine Möglichkeit, eigene, komplexe Datentypen zu beschreiben. Eine Möglichkeit, um das in C++ zu tun, ist die Verwendung sogenannter strukturierter Datentypen oder structs.

structs werden in C++ deklariert, indem sie zuerst mit einem Namen versehen und anschließend die einzelnen Bestandteile der Struktur – ebenfalls benannt – aufgezählt werden. Eine Struktur für rationale Zahlen könnte also etwa so aussehen:

struct Fraction { int numerator; int denominator; };

Sie trägt den Namen Fraction und verfügt über die beiden Bestandteile numerator und denominator. Prinzipiell kann die Struktur an (fast) beliebiger Stelle im Code deklariert werden, gilt dann aber auch nur in dem Bereich, in welchem sie deklariert wurde (also etwa innerhalb einer Funktion); idealerweise deklariert man die Struktur deshalb außerhalb von Funktionen. Außerdem müssen Strukturen vor ihrer ersten Verwendung im Code deklariert werden – das ist eine Eigenheit von C++, etwa gegenüber C# oder Java.

Hat man eine neue Struktur deklariert, kann sie wie ein normaler Datentyp zur Deklaration von Variablen benutzt werden, in unserem Fall also etwa so:

Fraction frac;

Unsere Funktion für das Multiplizieren zweier rationaler Zahlen lässt sich also schon einmal beträchtlich verbessern:

Fraction fractionMultiplication( Fraction frac1, Fraction frac2 ) { Fraction result; ??? return result; }

Bleibt nur noch die Frage, wie man denn das Ergebnis berechnet – und im Zusammenhang damit, wie man überhaupt Werte an eine Variable zuweist, die als ein strukturierter Datentyp deklariert ist. Die übliche Art der Zuweisung geschieht, indem den einzelnen Bestandteilen einer Struktur-Variablen explizit Werte zugewiesen werden; der Zugriff auf einen solchen Bestandteil erfolgt durch Verwendung des Punkt-Operators, gefolgt vom Namen des Bestandteils. Unsere Funktion lässt sich damit also so vollenden:

Fraction fractionMultiplication( Fraction frac1, Fraction frac2 ) { Fraction result; result.numerator = frac1.numerator * frac2.numerator; result.denominator = frac1.denominator * frac2.denominator; return result; }

Nutzen lässt sich die Funktion ungefähr so:

#include <cstdio> Fraction fractionMultiplication( Fraction frac1, Fraction frac2 ) { Fraction result; result.numerator = frac1.numerator * frac2.numerator; result.denominator = frac1.denominator * frac2.denominator; return result; } int main() { Fraction frac1, frac2, result; frac1.numerator = 1; frac1.denominator = 2; frac2.numerator = 3; frac2.denominator = 4; result = fractionMultiplication( frac1, frac2 ); printf( "%i / %i\n", result.numerator, result.denominator ); return 0; }

Die Ausgabe ist dann auch entsprechend 3 / 8.

Der neue C++-Standard erlaubt übrigens noch eine weitere Art der Zuweisung von Werten1. Statt den einzelnen Bestandteilen einer Struktur Werte explizit zuzuweisen, können alle Werte mit einem mal in der Reihenfolge ihrer Deklaration zugewiesen werden. Die Werte werden dazu in geschweifte Klammern geschrieben – der Compiler interpretiert diese Darstellung dann automatisch in der korrekten Art und Weise (soweit möglich). Obiges Beispiel lässt sich damit folgendermaßen beträchtlich kürzer schreiben:

1Teile dieser Darstellung funktionierten bereits in älteren C++-Versionen, jedoch nicht alle.

#include <cstdio> Fraction fractionMultiplication( Fraction frac1, Fraction frac2 ) { return { frac1.numerator * frac2.numerator, frac1.denominator * frac2.denominator }; } int main() { result = fractionMultiplication( { 1, 2 }, { 3, 4 } ); printf( "%i / %i\n", result.numerator, result.denominator ); return 0; }

Man sieht – viele Variablendeklarationen lassen sich damit komplett umgehen; ob diese Kurzschreibweise jetzt immer der Lesbarkeit dient, sei aber einmal dahingestellt. Übrigens kann man auch Variablen deklarieren und ihnen mit dieser Schreibweise Werte zuweisen, um einen Kompromiss aus Kürze und Ausdrucksstärke zu haben, etwa so (frac1 und frac2 werden hier unterschiedlich deklariert und initialisiert, um noch einmal die verschiedenen Möglichkeiten ins Gedächtnis zu rufen):

#include <cstdio> Fraction fractionMultiplication( Fraction frac1, Fraction frac2 ) { Fraction result; result = { frac1.numerator * frac2.numerator, frac1.denominator * frac2.denominator }; return result; } int main() { Fraction frac1; frac1 = { 1, 2 }; Fraction frac2 = { 3, 4 }; Fraction result = fractionMultiplication( frac1, frac2 ); printf( "%i / %i\n", result.numerator, result.denominator ); return 0; }

Soweit zur Einführung in die strukturierten Datentypen. In den nächsten Artikeln werden wir uns anschauen, wie derartige Datentypen intern im Speicher behandelt werden und wie sie im Zusammenhang mit Pointern zur Darstellung noch komplexerer Informationen genutzt werden können.

Kommentare (7)

  1. #1 rolak
    Oktober 17, 2013

    JuchHu, es geht weiter!

  2. #2 Marcus Frenkel
    Oktober 17, 2013

    Ja, endlich. Jetzt hoffentlich regelmäßiger. 😉

  3. #3 rolak
    Oktober 17, 2013

    Das wäre erfreulich – und wenn ich richtig fremdschließe, nicht nur für mich. War ja heute nicht gerade eine Erleuchtung für nen alten Dinosaurier-Dompteur, doch wer weiß was alles noch kommt…

  4. #4 MartinB
    Oktober 18, 2013

    @rolak
    Auch seinerzeit mit JCL gearbeitet (die höllischste Sprache aller Zeiten – selbst die Rechenzentrumsexperten am Desy hatten Probleme, wenn man da was komplizierteres tun wollte…)?

  5. #5 rolak
    Oktober 19, 2013

    Auch .. JCL?

    So wenig wie möglich, MartinB – zu der Physik-Zeit (OS360) war ich hauptsächlich user, Problemlösungs-Programm-Schreiber, Datenträger mit Schuhkarton voll Lochkarten unterm Arm. Später bei der Informatik wurde das siemensianische BS2000-Eisen schon ausgeschlichen gegen eine Horde MicroVaxen, Terminals zu Workstations, doch an der FAU/ER gab es als Mami für die vielen Unix-Terminals auch noch so ein Möbel mit grotesk vielen Spezialprozessoren, ein Abenteuer-Spielplatz für asm-Gefrickel…

    Damals, Mitte 80er, waren die AppleII-er und wir XT-ler noch die Freaks per se 😉

  6. #6 Dr. Webbaer
    Oktober 19, 2013

    Wann genau wird eigentlich aus einem strukturierten Datentyp ein Datensatz?

    MFG
    Dr. W (der Job- oder Stapelverarbeitungssprachen ebenfalls schätzt)

  7. #7 michael
    November 1, 2013

    @WB
    > Wann …

    Guck er hier und arbeite sich bis zur sechsten Zeile vor.