Angenommen , ich habe eine Klasse mit eigenem memebers ptr
, name
, pname
, rname
, crname
und age
. Was passiert, wenn ich sie nicht selbst initialisiere? Hier ist ein Beispiel:
class Example {
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;
public:
Example() {}
};
Und dann mache ich:
int main() {
Example ex;
}
Wie werden die Mitglieder in ex initialisiert? Was passiert mit Zeigern? Haben string
und int
erhalten 0-intialized mit Standardkonstruktoren string()
und int()
? Was ist mit dem Referenzmitglied? Was ist auch mit const Referenzen?
Was sollte ich sonst noch wissen?
Kennt jemand ein Tutorial, das diese Fälle abdeckt? Vielleicht in einigen Büchern? Ich habe in der Universitätsbibliothek Zugang zu vielen C ++ - Büchern.
Ich würde es gerne lernen, damit ich bessere (fehlerfreie) Programme schreiben kann. Jedes Feedback würde helfen!
c++
initialization
member-initialization
bodacydo
quelle
quelle
Antworten:
Anstelle einer expliziten Initialisierung funktioniert die Initialisierung von Mitgliedern in Klassen identisch mit der Initialisierung lokaler Variablen in Funktionen.
Für Objekte wird ihr Standardkonstruktor aufgerufen. Zum Beispiel setzt
std::string
der Standardkonstruktor eine leere Zeichenfolge. Wenn die Klasse des Objekts keinen Standardkonstruktor hat, handelt es sich um einen Kompilierungsfehler, wenn Sie ihn nicht explizit initialisieren.Für primitive Typen (Zeiger, Ints usw.) werden sie nicht initialisiert - sie enthalten den beliebigen Junk, der sich zuvor an diesem Speicherort befand.
Für Referenzen (z. B.
std::string&
) ist es illegal , sie nicht zu initialisieren, und Ihr Compiler wird sich beschweren und sich weigern, solchen Code zu kompilieren. Referenzen müssen immer initialisiert werden.In Ihrem speziellen Fall, wenn sie nicht explizit initialisiert werden:
quelle
foo
das hat einen Konstruktor, es ist nur implizit. Aber das ist wirklich ein Argument der Semantik.Lassen Sie mich zunächst erklären, was eine Mem-Initialisierer-Liste ist. Eine mem-Initialisierer-Liste ist eine durch Kommas getrennte Liste von mem-Initialisierern , wobei jeder mem-Initialisierer ein Mitgliedsname ist
(
, gefolgt von einer Ausdrucksliste , gefolgt von a)
. Die Ausdrucksliste gibt an, wie das Mitglied aufgebaut ist. Zum Beispiel inDie mem-initializer-Liste des vom Benutzer angegebenen Konstruktors ohne Argumente ist
name(s_str, s_str + 8), rname(name), crname(name), age(-4)
. Diese mem-initializer-Liste bedeutet, dass dasname
Member vom Konstruktor initialisiert wird, derstd::string
zwei Eingabe-Iteratoren verwendet , dasrname
Member mit einem Verweis auf initialisiert wirdname
, dascrname
Member mit einem const-Verweis auf initialisiertname
wird und dasage
Member mit dem Wert initialisiert wird-4
.Jeder Konstruktor hat seine eigene mem-initializer-Liste , und Mitglieder können nur in einer vorgeschriebenen Reihenfolge initialisiert werden (im Grunde die Reihenfolge, in der die Mitglieder in der Klasse deklariert sind). So sind die Mitglieder
Example
nur in der Reihenfolge initialisiert werden:ptr
,name
,pname
,rname
,crname
, undage
.Wenn Sie keinen Mem-Initialisierer eines Mitglieds angeben , lautet der C ++ - Standard:
Da
name
es sich hierbei um ein nicht statisches Datenelement vom Klassentyp handelt, wird es standardmäßig initialisiert, wennname
in der mem-initializer-Liste kein Initialisierer für angegeben wurde . Alle anderen Mitglieder vonExample
haben keinen Klassentyp, daher werden sie nicht initialisiert.Wenn der Standard besagt, dass sie nicht initialisiert sind, bedeutet dies, dass sie einen beliebigen Wert haben können . Da der obige Code nicht initialisiert wurde
pname
, kann es sich also um alles handeln.Beachten Sie, dass Sie noch andere Regeln befolgen müssen, z. B. die Regel, dass Verweise immer initialisiert werden müssen. Es ist ein Compilerfehler, Referenzen nicht zu initialisieren.
quelle
.h
) und Definition (in.cpp
) streng trennen möchten, ohne zu viele Interna anzuzeigen.Sie können Datenelemente auch an dem Punkt initialisieren, an dem Sie sie deklarieren:
Ich benutze dieses Formular so ziemlich ausschließlich, obwohl ich gelesen habe, dass einige Leute es als "schlechte Form" betrachten, vielleicht weil es erst kürzlich eingeführt wurde - ich denke in C ++ 11. Für mich ist es logischer.
Eine weitere nützliche Facette der neuen Regeln ist das Initialisieren von Datenelementen, die selbst Klassen sind. Angenommen, dies
CDynamicString
ist eine Klasse, die die Zeichenfolgenbehandlung kapselt. Es verfügt über einen Konstruktor, mit dem Sie den Anfangswert angeben könnenCDynamicString(wchat_t* pstrInitialString)
. Sie können diese Klasse sehr gut als Datenelement in einer anderen Klasse verwenden - beispielsweise in einer Klasse, die einen Windows-Registrierungswert kapselt, in dem in diesem Fall eine Postanschrift gespeichert ist. Um den Registrierungsschlüsselnamen, in den dies geschrieben wird, fest zu codieren, verwenden Sie geschweifte Klammern:Beachten Sie, dass die zweite Zeichenfolgenklasse, die die tatsächliche Postanschrift enthält, keinen Initialisierer hat, sodass der Standardkonstruktor bei der Erstellung aufgerufen wird - möglicherweise wird er automatisch auf eine leere Zeichenfolge gesetzt.
quelle
Wenn Ihre Beispielklasse auf dem Stapel instanziiert wird, ist der Inhalt nicht initialisierter Skalarmitglieder zufällig und undefiniert.
Für eine globale Instanz werden nicht initialisierte Skalarmitglieder auf Null gesetzt.
Für Mitglieder, die selbst Instanzen von Klassen sind, werden ihre Standardkonstruktoren aufgerufen, sodass Ihr Zeichenfolgenobjekt initialisiert wird.
int *ptr;
// nicht initialisierter Zeiger (oder auf Null gesetzt, wenn global)string name;
// Konstruktor aufgerufen, mit leerem String initialisiertstring *pname;
// nicht initialisierter Zeiger (oder auf Null gesetzt, wenn global)string &rname;
// Kompilierungsfehler, wenn Sie dies nicht initialisieren könnenconst string &crname;
// Kompilierungsfehler, wenn Sie dies nicht initialisieren könnenint age;
// Skalarwert, nicht initialisiert und zufällig (oder auf Null gesetzt, wenn global)quelle
string name
es nach dem Initialisieren der Klasse auf dem Stapel leer ist. Sind Sie sich Ihrer Antwort absolut sicher?Nicht initialisierte nicht statische Elemente enthalten zufällige Daten. Tatsächlich haben sie nur den Wert des Speicherorts, dem sie zugewiesen sind.
Natürlich
string
könnte der Konstruktor des Objekts für Objektparameter (wie ) eine Standardinitialisierung durchführen.In Ihrem Beispiel:
quelle
Bei Mitgliedern mit einem Konstruktor wird der Standardkonstruktor zur Initialisierung aufgerufen.
Sie können sich nicht auf den Inhalt der anderen Typen verlassen.
quelle
Wenn es sich auf dem Stapel befindet, ist der Inhalt von nicht initialisierten Mitgliedern, die keinen eigenen Konstruktor haben, zufällig und undefiniert. Selbst wenn es global ist, wäre es eine schlechte Idee, sich darauf zu verlassen, dass sie auf Null gesetzt werden. Ob es sich auf dem Stapel befindet oder nicht, wenn ein Mitglied einen eigenen Konstruktor hat, wird dieser aufgerufen, um es zu initialisieren.
Wenn Sie also den String * pname haben, enthält der Zeiger zufälligen Junk. Für den Zeichenfolgennamen wird jedoch der Standardkonstruktor für die Zeichenfolge aufgerufen, sodass Sie eine leere Zeichenfolge erhalten. Für Ihre Referenztypvariablen bin ich mir nicht sicher, aber es wird wahrscheinlich eine Referenz auf einen zufälligen Speicherblock sein.
quelle
Es hängt davon ab, wie die Klasse aufgebaut ist
Die Beantwortung dieser Frage erfordert das Verständnis einer riesigen Switch-Case-Anweisung im C ++ - Sprachstandard, die für bloße Sterbliche schwer zu verstehen ist.
Als einfaches Beispiel dafür, wie schwierig die Dinge sind:
main.cpp
Bei der Standardinitialisierung beginnen Sie mit: https://en.cppreference.com/w/cpp/language/default_initialization. Gehen Sie zum Teil "Die Auswirkungen der Standardinitialisierung sind" und starten Sie die case-Anweisung:
Wenn sich jemand für eine Wertinitialisierung entscheidet, gehen wir zu https://en.cppreference.com/w/cpp/language/value_initialization "Die Auswirkungen der Wertinitialisierung sind" und starten die case-Anweisung:
= delete
)Aus diesem Grund empfehle ich dringend, dass Sie sich niemals auf eine "implizite" Nullinitialisierung verlassen. Wenn keine starken Leistungsgründe vorliegen, initialisieren Sie alles explizit, entweder auf dem Konstruktor, falls Sie einen definiert haben, oder verwenden Sie die aggregierte Initialisierung. Ansonsten machen Sie die Dinge für zukünftige Entwickler sehr, sehr riskant.
quelle