Konstruktor-Initialisierungslisten
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 22 von 104.
Du hast die Doppelpunktsyntax in vorherigen Lektionen gesehen, etwa bei Player(std::string n, int h) : name(n), health(h) {}. Dies wird als Member-Initialisiererliste bezeichnet und ist die bevorzugte Methode, Member in C++ zu initialisieren.
Die Initialisierungsliste steht nach der Parameterliste des Konstruktors und beginnt mit einem Doppelpunkt. Jedes Mitglied wird direkt mit seinem Wert initialisiert, wobei die Einträge durch Kommas getrennt werden:
class Rectangle {
int width;
int height;
const int id;
int& reference;
public:
Rectangle(int w, int h, int i, int& r)
: width(w), height(h), id(i), reference(r) {
// Konstruktorrumpf (kann leer sein)
}
};Warum Initialisierungslisten anstelle von Zuweisungen im Konstruktorrumpf verwenden? Bei Zuweisungen werden Member zunächst standardmäßig konstruiert und anschließend neue Werte zugewiesen. Bei Initialisierungslisten werden Member direkt mit den korrekten Werten konstruiert – effizienter und manchmal erforderlich.
Bestimmte Member müssen mithilfe der Liste initialisiert werden:
const-Mitglieder – können nach der Konstruktion nicht zugewiesen werden
- Referenzmitglieder – müssen bei der Initialisierung gebunden werden
- Mitglieder ohne Standardkonstruktoren
class Player {
const int maxHealth; // Muss Initialisierungsliste verwenden
std::string name;
public:
// Das funktioniert
Player(int max, std::string n) : maxHealth(max), name(n) {}
// Das würde NICHT kompilieren:
// Player(int max) { maxHealth = max; } // Fehler!
};Member werden in der Reihenfolge initialisiert, in der sie in der Klasse deklariert sind, nicht in der Reihenfolge der Initialisierungsliste. Schreibe deine Initialisierungsliste immer in der Reihenfolge der Deklarationen, um Verwirrung zu vermeiden.
Aufgabe
EinfachErstellen wir ein Konfigurationssystem für eine Game-Engine, das zeigt, wann und warum Member-Initialisierungslisten unverzichtbar sind. Du erstellst eine GameConfig-Klasse mit Membern, die mithilfe der Syntax für Initialisierungslisten initialisiert werden müssen.
Du erstellst zwei Dateien, um deinen Code zu organisieren:
GameConfig.h: Definiere eineGameConfig-Klasse, die Spieleinstellungen speichert. Deine Klasse sollte Folgendes enthalten:- Ein
const std::string gameName: der Titel des Spiels, der nach der Erstellung nicht geändert werden kann - Ein
const int maxPlayers: die maximale Spieleranzahl, die bei der Konstruktion festgelegt wird - Ein
int& difficultyRef: eine Referenz auf eine externe Schwierigkeitseinstellung - Ein
int screenWidthund einint screenHeight: reguläre Member für die Anzeigeeinstellungen - Einen Konstruktor, der alle erforderlichen Parameter übernimmt und jeden Member mithilfe der Initialisierungsliste initialisiert. Die Initialisierungsliste sollte der Reihenfolge folgen, in der die Member in der Klasse deklariert sind
- Eine
display()-Methode, die die Konfiguration in diesem Format ausgibt:Game: <gameName> Max Players: <maxPlayers> Difficulty: <difficultyRef value> Resolution: <screenWidth>x<screenHeight>
- Ein
main.cpp: Lies Konfigurationswerte aus der Eingabe und erstelle ein GameConfig-Objekt. Du musst:- Den Spielnamen (String), die maximale Spieleranzahl (int), den Schwierigkeitsgrad (int), die Breite (int) und die Höhe (int) aus der Eingabe lesen: jeweils in einer eigenen Zeile
- Eine lokale
int-Variable für den Schwierigkeitsgrad erstellen, auf die die Konfiguration verweisen wird - Ein
GameConfig-Objekt mithilfe des Konstruktors mit Initialisierungsliste erstellen display()aufrufen, um die anfängliche Konfiguration anzuzeigen- Die Variable für den Schwierigkeitsgrad direkt ändern (sie mit 2 multiplizieren)
"After difficulty change:"ausgebendisplay()erneut aufrufen, um zu zeigen, dass die Referenz den aktualisierten Wert widerspiegelt
Diese Aufgabe verdeutlicht, warum Initialisierungslisten wichtig sind: Deine const-Member und dein Referenz-Member können nicht im Konstruktorrumpf zugewiesen werden. Sie müssen in der Liste initialisiert werden. Der Referenz-Member zeigt außerdem, wie Änderungen an der ursprünglichen Variable durch das Konfigurationsobjekt widergespiegelt werden.
Binde deine Header-Datei in main.cpp mithilfe von #include "GameConfig.h" ein.
Probier es selbst
#include <iostream>
#include <string>
#include "GameConfig.h"
using namespace std;
int main() {
// Eingabewerte lesen
string gameName;
getline(cin, gameName);
int maxPlayers;
cin >> maxPlayers;
int difficulty;
cin >> difficulty;
int width;
cin >> width;
int height;
cin >> height;
// TODO: Erstelle ein GameConfig-Objekt mit dem Initialisierungslisten-Konstruktor
// Der Konstruktor sollte empfangen: gameName, maxPlayers, difficulty (per Referenz), width, height
// TODO: Rufe display() auf, um die Anfangskonfiguration anzuzeigen
// TODO: Ändere die Variable difficulty direkt (multipliziere sie mit 2)
// TODO: Print "After difficulty change:"
// TODO: Rufe display() erneut auf, um die aktualisierte Konfiguration anzuzeigen
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler