Als Adobe PDF-Datei exportieren

Der HEIC-Konverter ist ein kostenloses Online-Tool für Windows und Mac, das Fotos von HEIC in JPG konvertieren kann.

Ferner wollen wir auch eine nachträgliche Initialisierung mit einem Benutzernamen zulassen sowie das Objekt auf den aktuellen Benutzer setzen können und auch die enthaltenen Daten auf den Bildschirm ausgeben. Andererseits handelt es sich dabei um potenzielle Probleme, die im einfachsten Fall ignoriert werden, im schwersten Fall aber zur Laufzeit einige unerwünschte Effekte hervorrufen können. In unserer Beispielklasse haben Sie bereits solche Methoden gesehen: Ich sprach oben davon, dass eine der Aufgaben des Konstruktors sei, Speicherplatz für die Attribute bereitzustellen. In der nächste Zeile geht es dann wieder mit Programmcode weiter.

Exportieren in PDF für den Druck

Für ein weiteres Beispiel denken Sie an die Simulationsspiele, die zurzeit sehr beliebt sind. Meistens muss der Spieler dabei eine Siedlung gründen und aufbauen und an die verschiedensten Einflüsse denken, die ihm dabei in die Quere kommen können.

Entsprechend sind Einrückungen und Ähnliches automatisch eingestellt, so dass Sie immer einen Block auch in derselben Spalte beginnen können. Wenn Sie an die Stelle hinter einer Klammer kommen, wird die zugehörige Klammer kurz durch ein rotes Quadrat markiert.

Wenn Sie also einmal einen Sachverhalt nicht als selbsterklärend erachten, finden Sie dort sicherlich Rat. Und nun kann ich Ihnen nur raten, die gezeigten Beispiel immer wieder zu laden, zu verändern und zu erweitern -- und natürlich auch eigene Programme zu schreiben. Sicher werden Sie dabei immer wieder mit den verschiedensten Fehlermeldungen des Compilers konfrontiert werden. Aber nur auf diese Weise werden Sie mit der Zeit seine Arbeitsweise verstehen und zu einem korrekten Programmierstil gelangen, mit dem Sie Ihre eigentlichen Aufgaben angehen können.

Zusammenfassung Die wichtigsten Aspekte dieses Abschnitts waren: Als Argument erwartet dieser zumindest den Namen der Quelltext-Datei. Die Option -g bewirkt, dass Informationen für den Debugger hinzugefügt werden. Was ist an folgendem Aufruf falsch? Klassen und Objekte Auf den Seiten ff.

Klassendeklaration und -definition Eine Klasse ist zunächst einmal ein von Ihnen definierter Datentyp. Sie müssen sie deklarieren, damit sie dem Compiler bekannt ist.

Eine Klasse hat im Allgemeinen folgende Bestandteile: Zugriffsbeschränkungen für ihre Elemente: Diese legen fest, ob andere Klassen auf ein Element dieser Klasse zugreifen dürfen oder nicht siehe Seite.

Diese Funktionen werden aufgerufen, wenn ein Objekt dieser Klasse erzeugt beziehungsweise vernichtet wird. Durch einen Konstruktor kann beispielsweise Speicherplatz für das Objekt bereitgestellt und es selbst initialisiert werden.

Hier sind die eigentlichen Daten gespeichert. Attribute werden wie Variablen deklariert. Sie können bei jedem Objekt andere Werte haben. Attribute können einen Standarddatentyp haben oder selbst wieder ein Objekt einer anderen Klasse sein. Methoden können auf die Attribute und Zustände des Objekts zugreifen. Deklaration Die Syntax zur Klassendeklaration besteht aus dem Schlüsselwort class , dann dem Namen der Klasse, gefolgt von den Klassenbestandteilen; diese sind von geschweiften Klammern eingeschlossen.

Kommen wir auf unser Beispiel von Seite zurück. Neu ist der Typ string ; dies ist eine Klasse für Zeichenketten, die wir später noch öfter verwenden werden. In den Zeilen 13 und 14 stehen die Deklarationen des Konstruktors und des Destruktors.

Dies sind Funktionen, die immer dann automatisch aufgerufen werden, wenn ein Objekt von dieser Klasse erzeugt beziehungsweise vernichtet wird. Aber dazu in einem der nächsten Abschnitte mehr.

Definition und Bereichsoperator Wahrscheinlich ist Ihnen nach dieser Überschrift nicht ganz klar, was jetzt schon wieder auf Sie zukommt.

Was ist denn der Unterschied zwischen Deklaration und Definition einer Klasse? Man macht ihn hauptsächlich an den Methoden fest: Eine Deklaration dient dazu, den neuen Datentyp dem Compiler bekannt zu machen. Dabei müssen Sie die Typen aller Attribute sowie die Schnittstellen Köpfe der Methoden angeben, also auch welche Parameter eine Methode benötigt und von welchem Datentyp der Rückgabewert ist.

Wie die Methoden implementiert sind, müssen Sie hier noch nicht angeben. Kurz gefasst bedeutet dies: Auf diese Weise können Sie ein Stück Prozessabstraktion verwirklichen: Andere, die Ihre Klasse verwenden wollen, müssen nur die Header-Datei, also die Deklaration kennen; die Definition kann im Verborgenen bleiben. Wie wir später sehen werden, ist die Realität leider etwas komplizierter. Es ist nämlich auch erlaubt, die Definition einer Methode gleich inmitten der Klassendeklaration zu schreiben.

Im Augenblick können Sie aber bei dieser Trennung bleiben. Dazu verwenden Sie den Bereichsoperator:: Später Seite werden wir die Möglichkeiten der Positionen, an denen Sie Deklaration und Definition unterbringen können, noch genauer fassen.

Ist sie erst einmal definiert, gilt sie als Datentyp. Wir können sie dann wie jeden anderen Datentyp verwenden und Variablen davon bilden. Man spricht bei einer Variablen, die als Typ eine Klasse hat, von einem Objekt dieser Klasse oder auch von einer Instanz der Klasse auch wenn das eine nicht ganz korrekte Übersetzung des englischen Begriffs instance ist. Zugriffsbeschränkungen Zugriffsbeschränkungen regeln, ob andere Klassen auf ein Klassenelement zugreifen dürfen oder nicht.

Dabei gibt es folgende Möglichkeiten: Die nachfolgenden Elemente und Methoden unterliegen keiner Zugriffsbeschränkung, sie können daher beliebig verändert beziehungsweise aufgerufen werden. Es gibt auch noch eine dritte Ebene, protected , die wir aber erst später -- ab Seite -- behandeln wollen.

Wenn Sie eine Zugriffsbeschränkung angeben, gilt diese für alle nachfolgenden Elemente -- solange, bis eine andere Beschränkung kommt oder die Klassendeklaration endet.

Fehlt in einer Klasse die Festlegung der Zugriffsbeschränkung ganz, so werden alle Elemente als private behandelt. In obigem Beispiel haben wir die beiden Zugriffsebenen in folgender Form verwendet: Wenn Sie in Ihrem Programm etwa die Anweisung schreiben: Anders sieht es in den Methoden der Klasse selbst aus. Hier dürfen Sie stets auf alle Elemente zugreifen, natürlich auch auf die privaten.

Betrachten Sie folgende Methode: Die Zugriffsbeschränkung gilt auf der Ebene der Klassen und nicht der Objekte. Wenn in einer Methode ein Objekt derselben Klasse auftaucht, so hat die Methode Zugang zu allen Elementen des Objekts, öffentliche wie private, zum Lesen wie zum Schreiben.

Freunde Wenn Sie einmal in die Lage geraten, dass Sie ohne die Zugriffsmöglichkeit auf private Elemente einer anderen Klasse nicht weiterkommen, deutet das meistens auf einen Designfehler hin. Allerdings gibt es in der Praxis durchaus Fälle, bei denen ein Verzicht auf den Zugriff einen sehr viel umständlicheren und damit meist langsameren Code mit sich bringen würde. Damit teilen Sie dem Compiler innerhalb der Klassendeklaration mit, welche anderen Elemente vollen Zugriff auf die Klasse erhalten.

Funktionen einzelne Methoden anderer Klassen ganze Klassen Im Englischen packt man diese Eigenschaft in ein leicht zweideutiges Wortspiel: Only friends may touch your private things. Dieses Vorrecht räumer wir damit aber nur der B:: Andere Methoden von B sofern vorhanden haben kein Zugriffsrecht auf A:: Zudem haben wir in einer erneuten Vorwärts-Deklaration auf eine Klasse C hingewiesen, die an anderer Stelle deklariert werden kann, die aber hier das volle Zugriffsrecht auf A erhalten hat.

Zusammenfassung Aus diesem Abschnitt sollten Sie sich merken: Klassen haben folgende Merkmale: Um eine Methode als zu einer bestimmten Klasse zugehörig zu kennzeichnen, verwendet man den Bereichsoperator:: Ist eine Klasse einmal definiert, kann sie wie jeder andere Datentyp verwendet werden.

Zugriffsbeschränkungen regeln, ob andere Klassen auf ein Klassenelement zugreifen dürfen oder nicht.

Alle auf public folgenden Elemente unterliegen keiner Zugriffsbeschränkung, während alle unter private nur innerhalb der Klasse selbst zugänglich sind. Das Schlüsselwort friend erlaubt es Funktionen, einzelnen Methoden anderer Klassen oder sogar ganzen Klassen, die Zugriffsbeschränkungen zu umgehen. Beachten Sie dabei die Zugriffsbeschränkungen und die erforderlichen Methoden. Was versteht man unter einer Vorwärts-Deklaration? Wozu wird sie benötigt?

Die Möglichkeit, Funktionen zu bilden, ist ein herausragendes Merkmal einer Programmiersprache. Ganz allgemein versteht man unter einer Funktion einen in sich geschlossenen Programmteil, der eine bestimmte Aufgabe erfüllt.

Eine Funktion, die zu einer Klasse gehört, nennt man Methode. Betrachten wir folgendes Beispiel: Vorher noch ein typografischer Hinweis: Es hat sich eingebürgert, in Büchern zwei Klammern hinter Bezeichner zu setzen, die sich auf eine Funktion beziehen, zum Beispiel in Sätzen wie: Mit add erreichen Sie die Addition zweier Ganzzahlen.

Das sagt noch nichts über Art und Umfang der Argumentliste aus, sondern soll Sie lediglich daran erinnern, dass es dabei um eine Funktion und nicht um eine Variable geht. Ich will mich auch in diesem Buch daran halten. Manchmal ist es aber auch gar nicht nötig oder sinnvoll, dass eine Funktion überhaupt einen Rückgabewert hat.

In diesem Fall geben Sie als Typ void an. Was macht man nun mit einem solchen Wert? Der Programmteil, der die Funktion aufruft, kann diese an allen Stellen einsetzen, wo er sonst eine Variable oder Konstante angeben würde in obiger Form allerdings nur dort, wo lediglich der Wert benötigt wird , also etwa: Selbst wenn eine Funktion einen Wert zurückgibt, müssen Sie ihn nicht beachten.

Sie dürfen auch schreiben: Argumentliste Eine Funktion kann immer nur auf den Daten arbeiten, die ihr lokal vorliegen. Von diesen Parametern auch Argumente genannt können Sie keinen, einen oder mehrere angeben, die Sie dann durch Kommas trennen.

Wenn Sie eine Funktion ohne Argumente schreiben wollen, lassen Sie den Bereich zwischen den beiden runden Klammern einfach leer -- denn die Klammern müssen Sie stets schreiben! Ansonsten geben Sie für jeden Parameter seinen Datentyp und einen Namen an, unter dem er in der Funktion bekannt sein soll.

Dieser Name kann vollkommen anders sein, als der im Hauptprogramm beim Aufruf verwendete. Funktionskörper Hier stehen die Anweisungen, die bei einem Aufruf der Funktion ausgeführt werden. Man kann darüber streiten, wie lang Funktionen sein sollten. Es gibt Experten, die fordern, dass eine Funktion aus nicht mehr als 50 Zeilen bestehen dürfe, sonst werde sie unleserlich.

Es gibt jedoch in der Praxis immer wieder Fälle, in denen längere Funktionen sinnvoll sind. Bei der objektorientierten Programmierung werden Sie allerdings ohnehin wesentlich mehr Funktionen beziehungsweise Methoden verwenden, die im Durchschnitt wesentlich kürzer sind als bei der prozeduralen Programmierung. Innerhalb des Funktionskörpers können Sie die Funktionsparameter wie normale Variablen verwenden; zusätzlich können Sie natürlich auch noch lokale Variablen definieren.

Sie dürfen sogar die Funktion selbst wieder aufrufen; man spricht dann von einer rekursiven Funktion -- aber das ist ein eigenes Thema.

Return-Anweisung Die Anweisungen im Funktionskörper werden so lange abgearbeitet, bis das Programm auf das Ende der Funktion oder eine return -Anweisung trifft. Diese erfüllt einen doppelten Zweck: Sie legen fest, welchen Wert die Funktion an das Hauptprogramm zurückliefern soll. Das kann eine Variable sein oder ein Ausdruck, eine Konstante oder der Rückgabewert einer anderen Funktion wobei Letzteres als schlechter Stil gilt.

Der Typ des angegebenen Werts muss jedoch in jedem Fall mit dem deklarierten Rückgabetyp übereinstimmen. Sie beenden die Funktion und kehren zum Hauptprogramm zurück. Jede return -Anweisung -- sei sie nun am Ende oder irgendwo inmitten des Funktionskörpers -- markiert das Ende der Abarbeitung der Funktion und den Rücksprung an die Stelle, von der aus die Funktion aufgerufen wurde. Sie können also die Funktion schon beenden, bevor alle Anweisungen ausgeführt sind, zum Beispiel wenn eine bestimmte Bedingung erfüllt ist.

Ist der Rückgabetyp void , muss am Ende der Funktion keine return -Anweisung stehen auch nicht das Schlüsselwort return , wie in folgendem Beispiel: Der Prototyp Bevor Sie eine Funktion verwenden können, müssen Sie dem Compiler zunächst mitteilen, dass es eine Funktion dieses Namens gibt, wie viele und welche Parameter sie hat und welchen Typ sie zurückliefert.

Dies geschieht mit einem so genannten Prototyp der Funktion. Der Prototyp sieht genauso aus wie die Funktion selbst bis auf den Funktionskörper; dieser fehlt und wird durch ein einfaches Semikolon ; ersetzt.

Es ist sogar erlaubt, die Namen der Argumente wegzulassen und nur ihre Typen anzugeben. Folgendes Beispiel zeigt den Umgang mit Prototypen: In main werden sie dann aufgerufen Zeilen und ab Zeile 17 definiert.

Wenn das Programm aber weiter wächst, kann der Verlass auf die Reihenfolge schnell zu Problemen führen. Am besten verwenden Sie für jede Quelltextdatei eine eigene Header-Datei.

Hier ist dies haupt. Gegenüber dem obigen Code müssen Sie bei der Implementierung in getrennten Dateien dort noch einen include -Befehl für die Header-Datei mit den Funktionsprototypen einfügen, also: Beides zusammen bezeichnet man gelegentlich auch als Signatur der Funktion.

Damit erhalten Sie die Freiheit, zwei Funktionen gleichen Namens zu verwenden, die sich aber in ihren Argumenten unterscheiden. Man bezeichnet das auch als Überladung des Funktionsnamens. Wir hatten oben Seite eine Funktion add definiert, um zwei Ganzzahlen zu addieren.

Nun kann man natürlich auch Dezimalbrüche addieren; das deckt unsere Funktion aber nicht ab. Wir definieren also eine zweite Funktion mit demselben Namen! Gerade wenn zwei Funktionen wie unsere beiden add dieselbe Anzahl von Argumenten haben und zudem nur Standardtypen verwenden, ist es für den Compiler machmal schwer, eindeutig die passende Funktion zu einem bestimmten Aufruf zu finden.

Betrachten Sie etwa folgenden Code und überlegen Sie sich vor dem Weiterlesen, welche Funktion in den jeweiligen Zeilen aufgerufen wird: Sehen wir uns die Aufrufe also genauer an: Bei den Zeilen 8 und 9 ist alles klar: Hier werden nur double - beziehungsweise int -Werte als Parameter übergeben, so dass die jeweiligen Funktionen aufgerufen werden.

In Zeile 10 ist das zweite Argument ein 'a' , also vom Typ char. Dieser Typ zählt auch zu den Ganzzahl-Werten und kann nach int konvertiert werden. Es kommt also die int,int -Variante zum Einsatz. In Zeile 11 kommt es zum Streitfall: Zum einen könnte die Zahl 3.

Was soll der Compiler also tun? Da er ratlos ist, gibt er eine Fehlermeldung aus: Hier müssen wir als Programmierer eingreifen. Eine Lösung sehen Sie in Zeile Durch die explizite Umwandlung von c nach float wird die Eindeutigkeit wiederhergestellt.

Ein ganz ähnliches Problem findet sich auch in Zeile Sie sehen daran, dass es im Grunde keinen Unterschied macht, ob Sie echte Zahlen als Argumente einsetzen im Fachjargon literale Konstanten oder Variablen.

Die Möglichkeiten zur impliziten Konvertierung und damit zum Auftreten von Doppeldeutigkeiten sind stets vorhanden. Genauso wie Funktionen können Sie auch Methoden in Klassen überladen. Folgendes Programm gibt die übergebenen Argumente auf den Bildschirm aus: Die for -Schleife in Zeile 7, zu deren Syntax wir später noch genauer kommen Seite , durchläuft einfach alle Argumente vom ersten bis zum letzten, wie in argc angegeben. Wenn Sie sehr umfangreiche Listen von Kommandozeilenargumenten verarbeiten wollen, empfehle ich Ihnen den Einsatz der Bibliotheksroutine getopt , die ich Ihnen auf Seite vorstellen werde.

Schauen wir uns ein Beispiel an: Die Funktion printNumber kann mit einem, mit zwei oder mit drei Parametern aufgerufen werden, jedoch nur in der angegebenen Reihenfolge. Somit sind folgende drei Aufrufe identisch: Alle Parameter mit Vorgabewerten müssen am Ende der Argumentliste stehen.

Es darf also nach ihnen kein Argument ohne Vorgabewert folgen. Beim Aufruf müssen Sie ebenfalls die Reihenfolge der Parameter beachten. Bei mehreren Argumenten mit Vorgabewert bedeutet das, dass Sie nicht willkürlich mal ein Argument angeben, beim nächsten aber auf den Vorgabewert vertrauen dürfen. Bei obigem Beispiel müssen Sie also immer, wenn Sie fillchar setzen wollen, auch fillsize setzen.

Es ist Ihnen sicher jetzt schon klar, dass Vorgabewerte für Parameter zuweilen recht praktisch sein können. Hat etwa eine Funktion oder eine Methode! Aber auch aus Sicht der etwas abstrakteren Objektorientierung sind Vorgabewerte willkommen -- denn sie sorgen für Erweiterbarkeit. Nun kommen Sie in die Situation, ein weiteres Argument zu der Funktion hinzufügen zu müssen.

In einer Programmiersprache ohne die Möglichkeit von Vorgabewerten müssten Sie nun an allen Stellen, an denen updateDisplay aufgerufen wird, Ihr Programm ändern und einen Standardwert hinzufügen. Neu zu schreibende Aufrufe können indessen den zusätzlichen Parameter nutzen.

Sie können damit ein und denselben Speicherplatz unter zwei verschiedenen Namen im Programm ansprechen. Jede Modifikation der Referenz ist eine Modifikation der Variablen selbst -- und umgekehrt. Wenn Ihnen das jetzt etwas abstrakt vorkommt, befinden Sie sich in guter Gesellschaft. Daher gleich ein Beispiel: Wenn Sie ihn an anderer Stelle verwenden, hat er leider eine völlig andere Bedeutung. Eine Referenz muss stets bei ihrer Deklaration sofort initialisiert werden.

Es ist also nicht möglich, eine Referenz erst einmal zu deklarieren, um ihr dann später etwas zuzuweisen. Eine Referenz einer Variable kann später nicht auf eine andere Variable umgesetzt werden. Falls Sie also einen Aliasnamen für verschiedene Variablen hintereinander benötigen, müssen Sie jeweils eine eigene Referenz verwenden. Der Datentyp der Referenz und der Variablen müssen übereinstimmen. Selbst wenn zwei Typen durch implizite Konvertierung ineinander umgewandelt werden können, ist es nicht zulässig, eine Referenz mit einer Variablen eines anderen Typs zu initialisieren.

Innerhalb einer Funktion verwendet man nicht allzu häufig Referenzen. Manchmal kommt man aber zu ziemlich langen Ausdrücken, insbesondere bei Objekten, deren Attribute wieder Objekte sind und so weiter; dann kann der Einsatz von Referenzen für solche verschachtelten Attribute die Lesbarkeit deutlich erhöhen.

Die häufigste Anwendung ist aber die Übergabe von Funktionsparametern als Referenz, wie wir sie im nächsten Abschnitt kennen lernen werden. Parameterübergabe als Referenz Bisher waren wir davon ausgegangen, dass eine Funktion die Parameter, die sie erhält, nur verwendet -- also liest -- und nicht verändert.

In einigen Fällen aber will man gerade, dass die Funktion die Parameter verändert. Hat die Funktion beispielsweise mehrere Ergebniswerte, so reicht der eine return -Wert zum Austausch der Information nicht aus. Sehen wir uns etwa folgende Funktion an: Die Antwort ist leider nein. Es findet zwar ein Austausch statt, aber das Hauptprogramm erfährt davon nichts.

Die Variablen, die dieser Funktion übergeben werden, haben hinterher immer noch dieselben Inhalte wie vorher. Ganz anders sieht die Lage aus, wenn die Parameter als Referenzen übergeben werden auf Englisch call by reference genannt.

Dann sind diese nämlich nicht im Unterprogramm eigenständige Speicherstellen, die nach dem Rücksprung wieder freigegeben werden, sondern Aliasnamen für die Variablen im Hauptprogramm. Jede Änderung, die das Unterprogramm, also die Funktion, an den Parametern vornimmt, wirkt unmittelbar auf die im Hauptprogramm definierten und der Funktion übergebenen Variablen. Um das zu erreichen, müssen wir obige Funktion nur ein klein wenig abändern: Konstanten und Referenzen Wenn Sie einer Funktion ein Objekt per Wert übergeben im Fachenglisch call by value genannt , wird eine Kopie des Objekts angelegt, mit dem die Funktion dann arbeitet.

Daher wäre es besser, diese als Referenz zu übergeben, denn dabei bekommt die Funktion nur den Verweis auf die Speicherstelle, was sehr schnell und einfach abgewickelt werden kann.

Das Problem dabei ist jedoch, dass damit der Funktion das Recht eingeräumt wird, das Objekt in jeder beliebigen Form zu verändern, was ja meist nicht beabsichtigt ist. Um dieses Problem in den Griff zu bekommen, verwenden Sie das Schlüsselwort const. Damit können Sie jede Variable und jede Referenz als konstant deklarieren, so dass sie nicht mehr verändert werden kann. Wenn Sie also in Ihrem Programm einen bestimmten Wert mehrfach benötigen, sollten Sie ihn an zentraler Stelle zum Beispiel global in der Hauptdatei des Projekts oder in einer entsprechenden Header-Datei als Konstante definieren und später dann nur noch diesen Namen für den entsprechenden Wert verwenden.

Ein Vorteil dabei ist, dass Sie später, wenn Sie das Programm mit einem anderen Wert übersetzen wollen, diesen nur einmal ändern müssen und ihn im Allgemeinen auch sofort finden. Was hilft uns das Schlüsselwort const beim Problem der Parameterübergabe? Sie bauen die Funktion so, dass sie nicht eine einfache Referenz als Argument erhält, sondern eine konstante Referenz. Dann hat sie beispielsweise folgende Form: Am Aufruf sieht man nicht, ob da ein Objekt, eine Referenz oder eine konstante Referenz übergeben wird.

Vielleicht ist Ihnen aufgefallen, dass ich die ganze Zeit von Objekten gesprochen habe, während vorher immer allgemeiner von Variablen die Rede war. Der Grund dafür ist, dass konstante Referenzen im Allgemeinen nur für Objekte benutzt werden. Konstante Methoden Obwohl es eigentlich nichts mit Referenzen zu tun hat, wollen wir uns kurz noch mit dem Thema Konstante Methoden beschäftigen. Nachdem Sie jetzt wissen, wie Sie einer Funktion ein konstantes Objekt übergeben, ist Ihnen vermutlich noch nicht so ganz klar, was Sie in der Funktion mit dem Objekt machen dürfen und was nicht.

Und wenn es Ihnen schon nicht klar ist, werden Sie es dem Compiler sicher ebenfalls kaum begreiflich machen können. Die Attribute dürfen bei einem konstanten Objekt nur gelesen, aber nicht überschrieben werden. Was ist aber mit den Methoden? Wenn Sie eine Methode eines Objekts aufrufen, wissen Sie ja nicht, was darin genau passiert.

Möglicherweise finden darin ja Modifikationen an den Attributen statt -- genau das, was wir mit dem konstanten Objekt ja verhindern wollten. Ich kann Sie beruhigen: Solche Methoden darf die Funktion nicht aufrufen, das verhindert bereits der Compiler. Hier kommt das Schlüsselwort const abermals zum Einsatz. Damit eine Methode für ein konstantes Objekt aufgerufen werden darf, müssen Sie diese in der Klasse als const deklarieren. Dieses const muss nach der Argumentliste und vor dem Semikolon beziehungsweise dem Methodenkörper stehen.

Wenn Deklaration und Definition einer Methode getrennt sind, muss const bei beiden stehen, sonst fasst der Compiler die Definition als eine andere Signatur auf. Sehen wir uns das an einem Beispiel an: Fragen Sie sich besser bei jeder Methode, ob diese eine Veränderung des Objekts bewirken soll oder nicht; im letzteren Fall deklarieren Sie sie stets als const.

Somit werden Sie übrigens auch daran gehindert, die stets fehlerträchtigen Nebeneffekte zu programmieren -- schon mancher Benutzer einer Klasse hat sich gewundert, warum sein Objekt nach einem Lesezugriff plötzlich verändert war.

Zugriffsroutinen Sie erinnern sich sicherlich, dass ich Ihnen die Datenabstraktion als wichtiges Prinzip der objektorientierten Programmierung vorgestellt habe auf Seite. Schlagen Sie am besten nochmals Abbildung 2. Dort wird gezeigt, dass im Idealfall andere Objekte nur über Nachrichten auf die Daten eines Objekts zugreifen können.

Nachrichten werden aber als Methoden des Empfängers implementiert. Für die Datenelemente Ihrer Klasse empfiehlt sich folgende Vorgehensweise: Deklarieren Sie alle Datenelemente als private und nur Methoden als public. Um den Bezug zum Attribut herzustellen sowie gleichzeitig die Bedeutung zu illustrieren, verwenden viele Programmierer auch ich für die Namen dieser Methoden immer die Vorsilben get beziehungsweise set , gefolgt vom Namen des Attributs.

Deklarieren Sie alle Lesemethoden als const. In unserer Beispielklasse haben Sie bereits solche Methoden gesehen: Inline-Funktionen Was passiert eigentlich bei einem Funktions- oder Methodenaufruf? Grob gesprochen wird der Programmfluss angehalten, die Parameter in einen speziellen Bereich genannt Stack kopiert, der Ausgangspunkt gemerkt und an den Beginn der Funktion gesprungen.

Nach deren Abarbeitung werden ihre lokalen Variablen wieder gelöscht, der Stack aufgeräumt und das Hauptprogramm fortgesetzt. Bei sehr kurzen Funktionen, wie sie bei der objektorientierten Programmierung häufiger als anderswo auftauchen zum Beispiel als Zugriffsmethoden , dauert die Verwaltung des Aufrufs länger als die Abarbeitung selbst.

Abhilfe bieten da so genannte inline -Funktionen. Durch die Angabe dieses Schlüsselworts vor einer Funktion erreichen Sie, dass für die Funktion kein Aufruf im oben genannten Sinn erzeugt wird, sondern der Funktionskörper direkt an die Stelle eingefügt wird eben in line, in der Zeile.

Es ist dann also keine echte Funktion mehr, sondern nur ein Stück Code im Hauptprogramm. Erfahrenen C-Programmierern kommt jetzt sicher der Gedanke an Makros. Auch diese bewirken eine Ersetzung schon durch den Präprozessor ; allerdings können bei diesen keine Datentypen für die Argumente definiert werden. Bei inline -Funktionen bleibt die Prüfung des Argumenttyps sowie der sonstigen Syntax genauso erhalten, als ob es sich um eine tatsächliche Funktion handeln würde.

Deklaration als inline Um eine Funktion als inline zu deklarieren, schreiben Sie dieses Schlüsselwort zur Definition, und zwar als Allererstes, also noch vor den Rückgabetyp, zum Beispiel: Sie können die Behandlung einer Funktion als inline nicht erzwingen. Allgemein sollten Sie komplexere Funktionen, die eine Reihe von Anweisungen sowie Schleifen und Ähnliches enthalten, nicht als inline deklarieren.

Selbst wenn der Compiler dies akzeptiert und umsetzt, besteht dadurch die Gefahr, dass Ihr Code stark aufgebläht und die Optimierung behindert wird. Die Implementation einer als inline deklarierten Funktion sollte immer in einer Header-Datei stehen. Wenn Sie nämlich im Header den Prototyp angeben und erst in der Implementationsdatei die Funktion als inline kennzeichnen, wird bei der Verwendung in anderen Dateien jeweils ein normaler Aufruf erzeugt, was spätestens beim Linken zu Problemen führt.

Klassenmethoden als inline Zugriffsmethoden von Klassen sind die idealen Kandidaten für die inline -Deklaration. Sie sind meist sehr kurz, werden aber ziemlich oft benötigt. Mit dem jetzigen Wissen können Sie sich auch für eine andere Stelle entscheiden, an der Sie Ihre Methode implementieren.

Deklaration und Definition innerhalb der Klassendeklaration. Wenn Sie eine Methode direkt innerhalb der Klassendeklaration implementieren, also auch den Funktionskörper angeben, wird diese automatisch als inline behandelt, auch wenn das entsprechende Schlüsselwort fehlt. Das ist bei sehr kurzen Methoden ein oder zwei Zeilen durchaus üblich. Deklaration und Definition als inline -Funktion in der Header-Datei. Wenn Sie Ihre Methode als inline gelten lassen wollen, sollten Sie sie aus oben beschriebenen Gründen innerhalb der Header-Datei implementieren.

Diese Möglichkeit wählt man oft bei etwas längeren Methoden mit mehr als zwei Zeilen , die aber noch sehr einfach sind, also beispielsweise keine Schleifen enthalten, oder wenn man auf Klassenelemente zugreifen muss, die erst später deklariert werden.

Deklaration in der Header-Datei und Definition in der Implementationsdatei. Jede davon wird dann als echte Funktion behandelt, die einmal zu Objektcode übersetzt und später dann nur noch dazugelinkt werden muss. Sehen wir uns die drei Varianten in der Praxis an. Die erste Möglichkeit führt etwa zu folgendem Code: Die zweite Variante hat beispielsweise folgende Form: Anders sieht dies beim dritten Fall aus.

Hier findet sich im Include-File nur noch: Zusammenfassung In diesem Abschnitt haben Sie eine Menge gelernt. Die wichtigsten Aspekte waren: Funktionen liefern immer einen Wert zurück, es sei denn, die Funktion ist als void definiert. Eine Funktion kann einen oder mehrere Parameter haben. Jede Funktion muss vor ihrer Verwendung deklariert werden: Sie ist der Einstiegspunkt in das Programm.

Der Rückgabewert von main kann von der Shell, die das Programm startet, ausgewertet werden. Daher können Sie zwei Funktionen gleichen Namens mit unterschiedlichen Argumenten definieren überladene Funktionen.

Sie können für einzelne Argumente einer Funktion Vorgabewerte engl. Eine Referenz ist ein Aliasname für eine Variable. Jede Modifikation der Referenz führt zu einer Modifikation der Variablen und umgekehrt. Übergibt man einer Funktion eine Referenz, so kann diese den Inhalt der übergebenen Variablen ändern.

Um zu vermeiden, dass der gesamte Datenbestand eines Objekts bei einem Funktionsaufruf kopiert wird, und um zu verhindern, dass die Funktion den Inhalt des Objekts ändern kann, übergibt man eine konstante Referenz auf das Objekt an die Funktion.

Ist eine Funktion oder Methode als inline deklariert, wird bei der Kompilierung der Aufruf vollständig durch den Funktionskörper ersetzt. Das bietet sich besonders bei Zugriffsmethoden auf Klassenelemente an. Übungsaufgaben Beantworten Sie folgende Fragen: Wie viele Rückgabewerte kann eine Funktion haben? Was ist die Signatur einer Funktion? Kann eine Klasse zwei Methoden gleichen Namens enthalten, die sich nur in ihren Rückgabewerten unterscheiden?

Was stimmt bei folgendem Programm nicht: Eine rationale Zahl wird dargestellt durch mit ganzzahligen und , wobei. Addition und Multiplikation sind wie bekannt definiert. Schreiben Sie eine Klasse Rational , die eine rationale Zahl repräsentiert. Diese soll über die Rechenfunktionen add , sub , mult und div verfügen, wobei der Bruch stets vollständig gekürzt gespeichert werden soll. Die Klasse habe folgende Struktur: Konstruktoren und Destruktoren Wenn Sie bereits mit anderen Programmiersprachen gearbeitet haben, wird Ihnen das Konzept der Konstruktoren und Destruktoren zunächst etwas merkwürdig vorkommen -- handelt es sich doch um Funktionen, die aufgerufen werden, ohne dass ihr Aufruf im Programmtext steht!

Mit der Zeit werden Sie aber die Abläufe verstehen und merken, dass auch da kein Geheimnis dahinter steckt. Besondere Eigenschaften Konstruktoren haben einige besondere Eigenschaften, die sie von allen anderen Methoden unterscheiden: Aufrufe von Konstruktoren werden automatisch in das Programm eingefügt.

Jedes Mal, wenn ein Objekt erzeugt wird und die Klasse über einen entsprechenden Konstruktor verfügt, wird dieser aufgerufen. Explizite Konstruktoraufrufe gibt es also nicht. Konstruktoren tragen denselben Namen wie die Klasse. Alle anderen Namen können keine Konstruktoren bezeichnen. Konstruktoren haben keine Rückgabewerte auch nicht void. Sie dürfen keinen Rückgabetyp bei der Deklaration eines Konstruktors angeben und auch keine void -Anweisungen mit Argument verwenden.

Ein einfaches, schon klassisches Beispiel für einen selbst definierten Datentyp ist ein Datum , das einen Tag im Kalender repräsentiert. Ich will in diesem Abschnitt eine solche Klasse mit Ihnen aufbauen, um die verschiedenen Arten von Konstruktoren deutlich zu machen. Standardkonstruktor englisch default constructor. Dieser wird bei jeder Erzeugung eines Objekts der Klasse verwendet, wenn kein anderer Konstruktor in Frage kommt.

Dieser kann beliebige Argumente haben und wie eine Methode überladen werden. Es können also mehrere Konstruktoren mit verschiedenen Argumentlisten existieren. Auch Vorgabewerte für die Argumente sind erlaubt. Dieser dient dazu, ein Objekt dieser Klasse mit einem anderen derselben Klasse zu initialisieren. Er erhält dazu als Argument eine konstante Referenz auf das Objekt. Dieser hat nur ein Argument und dient dazu, einen anderen Datentyp in die Klasse als Typ gesehen umzuwandeln.

Im Folgenden wollen wir die verschiedenen Konstruktoren genauer betrachten. Standardkonstruktor Der Standardkonstruktor, der leider viel zu oft auch im Deutschen mit dem englischen Ausdruck default constructor bezeichnet wird, hat keine Argumente.

Er wird immer dann aufgerufen, wenn ein Objekt dieser Klasse ohne weitere Angaben erzeugt wird. Bei unserem Beispiel lautet die Deklaration: Ich sprach oben davon, dass eine der Aufgaben des Konstruktors sei, Speicherplatz für die Attribute bereitzustellen. Vielleicht fragen Sie sich nun, warum Sie davon in dieser Methode nichts sehen.

Ebenso wie bei lokalen Variablen -- unabhängig davon, ob ihr Typ einfach oder eine Klasse ist -- kann das System selbstständig ermitteln, wie viel Speicher die Attribute benötigen, und diesen entsprechend reservieren.

Auch das kann innerhalb eines Konstruktors geschehen. Doch dazu kommen wir später noch. Jetzt sehen wir uns an, wo der Konstruktor aufgerufen wird. In einem Hauptprogramm steht beispielsweise: Natürlich können Sie auch mit der Klasse arbeiten und Objekte davon erzeugen, wenn diese keinen Konstruktor hat.

Allerdings sind dann die Attribute in einem unbestimmten Zustand -- genauso wie Variablen, die Sie nur deklariert, aber nicht initialisiert haben.

Ich möchte Ihnen daher empfehlen, bei allen Ihren Objekten zumindest einen Standardkonstruktor zu definieren. Da er beispielsweise keinen Rückgabewert hat, werden mögliche Fehler nicht ohne Weiteres vom Programm bemerkt.

Richten Sie im Zweifelsfall lieber eine zusätzliche Methode ein, in der dann kritische Initialisierungen vorgenommen werden können. Hintergrund Die Einfügung des Konstruktoraufrufs klappt übrigens auch bei Verschachtelungen. Wenn Sie beispielsweise eine Klasse haben wie: Auch Vorgabewerte für die Parameter sind erlaubt.

Wir wollen unsere Klasse um eine vollständige Initialisierung erweitern sowie um eine, die nur den Tag enthält. Die gerade gezeigte Vorgehensweise empfiehlt sich übrigens auch allgemein: Wenn Sie zulassen möchten, dass Ihr Objekt entweder von einem allgemeinen Konstruktor oder einem expliziten Methodenaufruf initialisiert wird, verwenden Sie einfach im Konstruktor auch diese Methode.

Auf diese Weise haben Sie den Code konsistent gehalten und die unnötige Verdopplung von Anweisungen vermieden. Unser zweiter neuer Konstruktor soll nur den Tag als Parameter haben, wobei Monat und Jahr dieselben wie für den heutigen Tag sein sollen.

Auch allgemeine Konstruktoren werden automatisch bei der Definition eines Objekts aufgerufen. Die Argumente geben Sie dabei in Klammern hinter dem Objektnamen an. Sie können dieses Ziel unter anderem dadurch erreichen, dass Sie den Standardkonstruktor weglassen. Fehlt dieser nämlich, wenn gleichzeitig ein anderer allgemeiner Konstruktor vorhanden ist, bricht der Compiler mit einem Fehler ab, sobald eine Instanz der Klasse erzeugt werden soll.

Besonders elegant ist diese Möglichkeit jedoch nicht, zumal der Anwender der Klasse die resultierende Fehlermeldung nicht sofort im Sinne des Entwicklers interpretieren würde. Ein anderer Weg ist die Deklaration des Standardkonstruktors als private. Auf diese Weise ist aus Sicht des Compilers ein solcher Konstruktor vorhanden, wird also nicht als fehlend gemeldet.

Dem Benutzer bleibt somit nichts anderes übrig, als einen der öffentlichen allgemeinen Konstruktoren zu verwenden und einen Parameter anzugeben.

Diese Vorgehensweise bietet sich beispielsweise bei so genannten Wrapper-Klassen an, also Klassen, die keine eigene Funktionalität haben, sondern nur einer anderen Klasse eine neue Schnittstelle geben. Nehmen wir etwa an, Sie hätten obige Klasse Datum fertig implementiert und auch in einigen anderen Funktionen und Klassen eingesetzt; nun wollen Sie Ihren Code in einem anderen Projekt wieder verwenden. Dummerweise schreiben die Arbeitsrichtlinien für dieses Projekt aber vor, dass alle Bezeichner auf Englisch sein müssen.

Anstatt nun die Methoden umzubenennen und dabei zu riskieren, etwas zu vergessen, schreiben Sie einfach einen Wrapper. Initialisierung mit Listen Beim Aufruf eines Konstruktors wird noch vor dem Betreten des Methodenkörpers Speicherplatz für die Datenelemente bereitgestellt.

Auf diese Weise wird aber doppelt auf die Attribute zugegriffen: Dazu verwendet man eine so genannte Initialisierungsliste. Bei unserem Beispiel hat das etwa folgende Form: Wie Sie sehen, kann in diesem Fall der Methodenkörper sogar ganz leer sein. Beim Aufruf des Konstruktors wird zuerst die Initialisierungsliste abgearbeitet, und zwar in der Reihenfolge, wie die Attribute in der Klasse deklariert sind -- und nicht, wie sie in der Liste stehen! Generell sollten Sie daher die Datenelemente in Ihren Initialisierungslisten stets in derselben Reihenfolge aufführen wie in der Klasse.

Besonders kritisch wird dies aber erst, wenn ein Datenelement auf ein anderes angewiesen ist. Hintergrund Eine Klasse darf nicht nur Variablen und Funktionen enthalten, von denen bisher immer die Rede war, sondern auch Konstanten und Referenzen.

Bei diesen stellt sich das Problem der Initialisierung noch wesentlich drängender. Denn normalerweise müssen Konstanten und Referenzen gleich bei ihrer Deklaration initialisiert werden, etwa bei bekannter Klasse Kreis: Kopierkonstruktor Ein Kopierkonstruktor hat die Aufgabe, ein Objekt mit einem anderen derselben Klasse zu initialisieren.

Dazu hat er als Parameter eine konstante Referenz auf dieses Objekt. Für unsere Datumsklasse könnte das beispielsweise lauten: Es gibt aber noch eine -- anfangs eventuell verwirrende -- Möglichkeit, den Kopierkonstruktor aufzurufen, nämlich in Form einer Zuweisung: Hintergrund Braucht eigentlich jede Klasse einen Kopierkonstruktor?

Diese Frage ist durchaus berechtigt, ist doch die Initialisierung eines Objekts mit einem anderen derselben Klasse eine gängige Anweisung. Und da die Situation so oft vorkommt, erzeugt der Compiler selbst einen Kopierkonstruktor, wenn der Autor der Klasse keinen bereitstellt.

In diesem wird das neue Objekt erzeugt, indem alle Datenelemente Bit für Bit kopiert werden. Das klappt bei Standarddatentypen immer, womit Sie sich notieren können: Bei Klassen, deren Attribute nur Standarddatentypen haben, ist ein selbst definierter Kopierkonstruktor nicht nötig.

Ist ein Attribut wiederum selbst ein Objekt, so wird beim Kopieren dessen Kopierkonstruktor aufgerufen und so weiter. Ein Klasse braucht jedoch mindestens immer dann einen Kopierkonstruktor, wenn sie dynamisch angelegten Speicherplatz verwaltet. Denn der automatisch erzeugte kopiert im Allgemeinen nur den Anfangspunkt dieses Speichers, so dass das kopierte Objekt einen Verweis auf denselben Speicherbereich erhält wie das ursprüngliche.

Und das will man beim Kopieren ja vermeiden! Daher gilt auch die Faustregel: Immer wenn für eine Klasse ein Kopierkonstruktor erforderlich ist, braucht sie auch einen Zuweisungsoperator. Mehr Details dazu erkläre ich Ihnen aber später, wenn wir den Umgang mit dynamisch angelegtem Speicher genauer unter die Lupe nehmen ab Seite.

Objekte als Rückgabewerte Manchmal ist es sinnvoll, wenn eine Funktion oder eine Methode ein Objekt zurückliefert. Dies kann einmal in Form einer Referenz geschehen, etwa: Dabei ist es doch auch möglich, ein ganzes Objekt als Ergebnis einer Funktion oder Methode zurückzugeben.

Sie müssen lediglich seine Klasse als Rückgabetyp der Funktion angeben. Sehen wir uns folgendes Beispiel an, um zu erkennen, was dabei so alles vor sich geht: Dort ist -- wie erwartet -- als Typ des Rückgabewerts die Klasse Datum vermerkt. Ein Objekt davon wird in Zeile 3 angelegt; dabei wird natürlich der entsprechende Konstruktor aufgerufen.

In der vierten Zeile startet der Rücksprung. Diesen müssen wir zusammen mit dem Aufruf in Zeile 10 betrachten, um die Vorgänge zu verstehen. Dort wird ein neues Objekt der Klasse Datum angelegt und gleichzeitig initialisiert -- ein Fall für den Kopierkonstruktor. Dieser erhält als Argument gerade das lokale Objekt d aus getLogDate.

Und am Ende von main , in Zeile 12, wird natürlich auch das Objekt date wieder entfernt. Typumwandlungskonstruktor Ein Typumwandlungskonstruktor ist eine spezielle Form des allgemeinen Konstruktors.

Er dient dazu, andere Datentypen in die jeweilige Klasse umzuwandeln. Damit ist dann eine Regel für die implizite Typkonvertierung erklärt und Sie können an allen Stellen, wo eigentlich ein Objekt der Klasse erwartet würde, einen Wert dieses Typs angeben. Sehen wir uns dazu gleich ein Beispiel an.

Bei unserer Datumsklasse ist es sehr viel einfacher, eine Zeichenkette zur Initialisierung anzugeben als drei int -Zahlen. Daher fügen wir folgenden Konstruktor hinzu: Noch deutlicher wird das an folgendem Beispiel: Diese erhält neben der Nachricht selbst noch das Datum als Argument: Hintergrund So praktisch diese automatische Umwandlung auch sein mag -- manchmal möchte man gerade diese vermeiden.

Denn dabei kann es passieren, dass der Compiler temporäre Objekte erzeugt, an die der Programmierer gar nicht gedacht hat.

Da Erzeugung und Vernichtung solcher Objekte aber auch Zeit und Speicherplatz kosten, sollte man versuchen, die vollständige Kontrolle zu behalten und unerwünschte Automatismen gar nicht erst zuzulassen. Sie schreiben es in die Klassendeklaration vor Konstruktoren, die nur ein Argument haben oder bei denen die weiteren Argumente wegen der Vorgabewerte verzichtbar sind. Beispielsweise könnten wir für unser Datumsbeispiel eine eigene Klasse Jahr einführen.

Diese hätte neben einer besseren Möglichkeit zur Typprüfung auch den Vorteil, dass wir dabei auf eine fehlende Jahrhundertangabe reagieren könnten. Destruktoren Genauso wie es Methoden gibt, die bei der Erzeugung eines Objekts aufgerufen werden, gibt es auch welche, die bei seiner Vernichtung in Aktion treten. Ein Destruktor übernimmt dann die Aufräumarbeit, wenn das Objekt nicht mehr benötigt wird.

Ebenso wie bei den Konstruktoren gilt: Gibt der Autor der Klasse keinen Destruktor vor, erzeugt der Compiler automatisch einen.

Sie werden in Ihren Klassen Destruktoren hautpsächlich dann verwenden, wenn Sie mit dynamisch verwaltetem Speicher arbeiten und das Objekt den Speicher, den es für sich in Anspruch genommen hat, am Ende seiner Lebenszeit wieder freigeben muss. Mehr dazu werde ich Ihnen daher erklären, wenn wir über dynamische Speicherverwaltung reden ab Seite.

Ein weiteres Aufgabenfeld für Destruktoren sind offene Datei- oder Datenbankverbindungen, die beim Löschen des Objekts geschlossen werden müssen. Zu diesem Zeitpunkt wird auch der Destruktor aufgerufen. Globale Objekte sind in jeder Funktion sichtbar und dort wie lokale Objekte verwendbar.

Für globale Objekte wird der Konstruktor vor der ersten Anweisung in main und der Destruktor nach der Freigabe aller in main instanziierten Objekte aufgerufen. Dazu müssen wir uns zunächst ansehen, wie Benutzer überhaupt durch Linux verwaltet werden.

Bei einem vernetzten Rechner können diese Informationen auch zentral abgelegt sein und über den Network Information Service NIS zugänglich gemacht werden früher als gelbe Seiten -- yellow pages -- bekannt. Dies geschieht in Form einer Struktur gekennzeichnet durch das Schlüsselwort struct ; das ist auch nur eine Klasse, bei der alle nicht anders gekennzeichneten Elemente public sind. Der Umgang mit diesen Funktionen erfordert etwas Zeiger-Syntax, auf die ich erst ab Seite zu sprechen kommen werde.

Sie können also entweder dort nachschlagen oder mir zunächst einmal glauben, dass die Anweisungen korrekt sind. Wenn Sie doch mehr wissen wollen: Im öffentlichen Teil statten wir die Klasse mit einem Standardkonstruktor aus, der die Daten lediglich initialisiert, und zwei allgemeinen Konstruktoren, wobei der eine eine Benutzer-ID und der andere einen Benutzernamen erwartet.

Ferner wollen wir auch eine nachträgliche Initialisierung mit einem Benutzernamen zulassen sowie das Objekt auf den aktuellen Benutzer setzen können und auch die enthaltenen Daten auf den Bildschirm ausgeben.

Unter diesen Methoden sind zwei fast identisch, was vielleicht auf den ersten Blick nicht so offensichtlich ist. Ich meine den allgemeinen Konstruktor, der die Attribute mit den Daten eines Benutzers belegt, von dem die ID bekannt ist, und das Setzen auf den aktuellen Benutzer. Denn von diesem erhält man nämlich auch die ID, wenn man die Funktion getuid ruft. Somit lohnt es sich, die Funktionalität in eine private Methode zu stecken und diese aus den beiden öffentlichen aufzurufen.

Die Initialisierungsmethoden haben als Rückgabewert true oder false , je nachdem, ob sie fehlerfrei arbeiteten oder nicht. Somit erhalten wir folgende Deklaration: Der Aufbau der anderen init -Methode ist fast identisch. Nur wird dort statt getpwuid die Funktion getpwnam aufgerufen.

Ich lasse das Listing also hier weg. Die beiden anderen Methoden der Klasse sind ziemlich elementar. Dabei ist setzeAufAktuellen so klein, dass wir sie gleich als inline deklarieren können siehe auch Seite. Einsatz der Klasse Anwenden können wir die Klasse auf verschiedene Arten: Zusammenfassung Folgende Gesichtspunkte aus diesem Abschnitt sollten Sie festhalten: Konstruktoren sind spezielle Methoden, die denselben Namen wie die Klasse haben.

Sie verfügen über keinerlei Rückgabewerte auch nicht void. Man unterscheidet zwischen Standardkonstruktor , der bei jeder Erzeugung eines Objekts der Klasse verwendet wird, wenn kein anderer angegeben ist, allgemeinem Konstruktor , der beliebige Argumente haben darf und überladen werden kann, Kopierkonstruktor , mit dem ein Objekt der Klasse durch ein anderes initialisiert wird, und Typumwandlungskonstruktor , der zur Konvertierung anderer Datentypen in die Klasse dient. Der Destruktor übernimmt die Aufräumarbeit für ein nicht mehr gültiges Objekt.

Sein häufigster Zweck ist die Freigabe von dynamisch angelegtem Speicher. In welchen Situationen braucht man einen Kopierkonstruktor? Welche Typen wandelt der Typumwandlungskonstruktor um? Wie behandeln Sie Fehler, die in Konstruktoren auftreten? Welche zusätzlichen Verwendungsmöglichkeiten ergeben sich dadurch? Beobachten Sie, wann welche Konstruktoren und Destruktoren aufgerufen werden und erklären Sie das Verhalten dieses Programms.

Vererbung und Polymorphismus Eines der bedeutsamsten Konzepte der objektorientierten Programmierung ist die Vererbung. In diesem Abschnitt werden Sie erfahren, was es damit auf sich hat und worauf Sie achten müssen. Der erste Schritt oder einer der ersten Schritte ist immer die Analyse, mit welchen Objekten das Programm eigentlich umgehen soll. Bilden Sie davon eine Abstraktion, gelangen Sie zu den Klassen.

Im mittleren Drittel der Kästen stehen die Attribute, im unteren die Methoden. Die Lösung besteht nun darin, Gemeinsamkeiten der Klassen in einer neuen Klasse zusammenzufassen, der Basisklasse oder Oberklasse.

Diese ist dann der Stammvater der anderen. Ihre Nachfahren, die abgeleiteten Klassen oder Unterklassen , besitzen alle Attribute und Methoden der Basisklasse sowie zusätzliche eigene. Ein Beispiel dazu sehen Sie in Abbildung Fig: Dort ist der Zusammenhang noch aus einem anderen Blickwinkel dargestellt.

Das bringt uns zu folgenden Merksätzen: Eine Unterklasse fügt neue Eigenschaften oder Methoden hinzu oder ersetzt bestehende. Sie ist damit eine Spezialisierung der Oberklasse. Bei Unterklassen müssen Sie lediglich die Abweichungen von der Oberklasse beschreiben; alles andere wird automatisch übernommen.

Bei der Basisklasse aus Abbildung ist noch fast alles wie gewohnt. Die abgeleitete Klasse hat eine ganz ähnliche Form. Zur Kennzeichnung als Unterklasse fügt man lediglich bei der Klassendeklaration hinter dem Klassennamen die Oberklasse an, getrennt durch einen Doppelpunkt und die Zugriffsregelung, meist public. Danach müssen Sie auch nur die Klassenelemente angeben, die Sie hinzufügen beziehungsweise ändern wollen; die ererbten sind bereits automatisch enthalten. Überschreiben von Methoden Halten wir fest: Eine Klasse erbt die Methoden ihrer Basisklasse.

Nun hat sie aber unter Umständen zusätzliche Attribute, die Einfluss auf diese Methoden haben. Beispielsweise verfügt Raumfahrzeug über die Eigenschaft hoechstgeschw , welche bei der Erhöhung der Geschwindigkeit in der Methode setGeschwindigkeit von Bedeutung ist.

Der Ausweg ist, die Methode in der abgeleiteten Klasse wie angedeutet zu modifizieren. Was ist damit gemeint? Sie haben zwei Möglichkeiten: Die Methode in der abgeleiteten Klasse ersetzt die geerbte Methode komplett. Die Methode in der abgeleiteten Klasse ruft die Methode der Basisklasse auf und fügt noch eigene Befehle hinzu.

Um eine Methode zu überschreiben, deklarieren und implementieren Sie sie einfach in der Unterklasse erneut. Achten Sie aber darauf, dass sie in Namen und Signatur mit der Basisklasse übereinstimmen muss. Typumwandlung von abgeleiteten Klassen Sie dürfen einem Objekt der Basisklasse immer ein Objekt der abgeleiteten Klasse zuweisen, zum Beispiel: Wenn die Unterklasse zusätzliche Attribute enthält, ist eine solche Umwandlung mit einem Informationsverlust verbunden, denn die zusätzlichen Werte fallen unter den Tisch.

Im umgekehrten Fall ist genau dies auch der Grund, warum keine Zuweisung und keine Konvertierung möglich ist: Eine Erzeugung von Information ist eben nicht zulässig. Die Möglichkeit zur automatischen Konvertierung von Unter- in Oberklasse erlaubt es Ihnen auch, Funktionen oder Methoden, die eigentlich ein Oberklassenobjekt erwarten, mit einem vom Typ der Unterklasse zu versorgen.

Nehmen wir beispielsweise an, es gäbe noch eine weitere Klasse namens KampfFlugzeug , die von Raumfahrzeug abgeleitet ist. Dann dürfen Sie schreiben: Vererbungshierarchien Es wäre fast langweilig, wenn Sie immer nur eine Ableitung von einer Klasse bilden könnten.

In vielen Fällen ist es sinnvoll, mehrere Stufen von Ober- und Unterklassen zu definieren. Dabei wird die Schnittstelle der Klassen in jeder Stufe um neue Elemente erweitert. Auf diese Weise gelangt man von der ganz allgemeinen zur hochspezialisierten Klasse. In Abbildung sehen Sie ein Beispiel für eine so genannte Vererbungshierarchie. In einer solchen Darstellung steht die Basisklasse an der Spitze und darunter die jeweils von ihr abgeleiteten Klassen.

Besonders in Sammlungen von Klassen, so genannten Klassenbibliotheken , kommen solche Hierarchien von Klassen häufiger vor. Besonders beliebt sind sie für Benutzeroberflächen; beispielsweise basiert die beliebte grafische Linux-Benutzeroberfläche KDE auf der Klassenbibliothek Qt, die auch eine umfangreiche Hierarchie aufweist siehe auch Seite.

Nicht immer ist Vererbung das Allheilmittel! Erfahrene Programmierer setzen sie sogar relativ sparsam ein. Nur dort, wo wirklich zwischen zwei Klassen ein enger logischer Zusammenhang besteht, sollten Sie auch eine Basisklasse der beiden definieren, um die Gemeinsamkeiten zu nützen und doppelte Implementierungen zu vermeiden.

Aber ein mehr oder weniger künstlich erzeugter oder nur auf Implementierungsaspekten begründeter Zusammenhang ist meist ein schlechter Grund für eine Hierarchiebildung.

Ein anderer Designfehler, der auch von Profis oft begangen wird, entsteht dadurch, dass beim Entwurf der Vererbungshierarchie zu stark auf die Daten und zu wenig auf die Ereignisse und Botschaften geachtet wird. Diese Herangehensweise bezeichnet man auch als data-driven design. Wenn sich eine Unterklasse nur dadurch von ihrer Oberklasse unterscheidet, dass sie zusätzliche Daten enthält, so spricht dies nicht für ein ausgeprägt objektorientiertes Denken des Entwicklers.

In diesem Fall ist die Qualität des Inhalts von höchster Priorität. Die verwendeten Schriftarten werden in Form von Untergruppen eingebettet und die Transparenz wird beibehalten bei Dateitypen, die transparenzfähig sind. Dieser Optionssatz verwendet Komprimierung, Neuberechnung und eine relativ niedrige Bildauflösung. In diesem Fall können Sie bzw. Ihr Druckdienstleister benutzerdefinierte Vorgaben erstellen.

Adobe PDF-Vorgaben werden als. Verwenden Sie zur Suche nach weiteren. Bereits vorhandene Einstellungen werden bei Bedarf überschrieben.

Im Hintergrundaufgaben-Bedienfeld wird der Fortschritt des Exportvorgangs angezeigt. Durch Auswahl einer Region ändert sich die Sprache und ggf. Dokumente werden auf Handheld-Geräten nicht korrekt angezeigt. Dokumente mit komplexen Formatierungen sind für sehbehinderte Leser schwer zugänglich. Exportieren in PDF für den Druck. Geben Sie einen Namen und Speicherort für die Datei an. Führen Sie einen der folgenden Schritte durch:.

Wenn bestimmte Ebenen nicht zusammengeführt werden sollen, geben Sie ihnen in jedem Buchdokument eindeutige Namen. Exportieren eines Buches in PDF. Soll die neue Vorgabe auf einer vorhandenen Vorgabe aufbauen, wählen Sie zunächst die gewünschte Vorgabe aus. Die Standardvorgaben können nicht bearbeitet werden. Doppelklicken Sie auf die zu konvertierende Datei.





Links:
Chancen, an der Börse reich zu werden | Diskontsatz in der Ingenieurökonomie | Wie kaufe ich Bitcoin online mit Kreditkarte? | Online kaufen mit maybank debitkarte | Freiberuflicher Sporthändler | Einfache Handelssignale | Kontrollbestand xls | Schrottpreise Silber | Kredite für die Anlage in Aktien | Kostenlose esignal emini sp500 charts |