Suche…


Syntax

  • Variable.Mitgliedervariable = konstant;
  • Variable.Mitgliedsfunktion ();
  • variable_pointer-> member_var = konstant;
  • variable_pointer-> member_function ();

Bemerkungen

Beachten Sie, dass der einzige Unterschied zwischen den Schlüsselwörtern struct und class besteht, dass standardmäßig die Member-Variablen, Member-Funktionen und Basisklassen einer struct public , während sie in einer class private . C ++ - Programmierer neigen dazu, sie Klasse zu nennen, wenn sie Konstruktoren und Destruktoren hat und die Möglichkeit hat, ihre eigenen Invarianten durchzusetzen. oder eine struct, wenn es sich nur um eine einfache Sammlung von Werten handelt, die C ++ - Sprache selbst unterscheidet jedoch nicht.

Klassengrundlagen

Eine Klasse ist ein benutzerdefinierter Typ. Eine Klasse wird mit dem Schlüsselwort class , struct oder union . In der umgangssprachlichen Verwendung bezieht sich der Begriff "Klasse" normalerweise nur auf nicht gewerkschaftliche Klassen.

Eine Klasse ist eine Sammlung von Klassenmitgliedern. Dies kann sein:

  • Mitgliedsvariablen (auch "Felder" genannt),
  • Elementfunktionen (auch "Methoden" genannt),
  • Elementtypen oder Typedefs (zB "verschachtelte Klassen"),
  • Elementvorlagen (beliebiger Art: Variable, Funktion, Klasse oder Aliasvorlage)

Die Schlüsselwörter class und struct , die als Klassenschlüssel bezeichnet werden , sind weitgehend austauschbar, mit der Ausnahme, dass der Standardzugriffsspezifizierer für Mitglieder und Basen für eine mit dem class deklarierte class "private" und für eine mit dem struct oder union Schlüssel deklarierte class "public" ist (siehe Zugriffsmodifizierer ).

Die folgenden Codeausschnitte sind beispielsweise identisch:

struct Vector
{
    int x;
    int y;
    int z;
};
// are equivalent to
class Vector
{
public:
    int x;
    int y;
    int z;
};

Durch die Deklaration einer Klasse wird Ihrem Programm ein neuer Typ hinzugefügt, und es ist möglich, Objekte dieser Klasse durch zu instanziieren

Vector my_vector;

Auf Mitglieder einer Klasse wird mit Punktsyntax zugegriffen.

my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;

Zugriffsspezifizierer

Es gibt drei Schlüsselwörter , die als Zugriffsspezifizierer dienen . Diese beschränken den Zugriff auf Klassenmitglieder nach dem Bezeichner, bis ein anderer Bezeichner die Zugriffsebene erneut ändert:

Stichwort Beschreibung
public Jeder hat Zugang
protected Nur die Klasse selbst, abgeleitete Klassen und Freunde haben Zugriff
private Nur die Klasse selbst und Freunde haben Zugriff

Wenn der Typ mit dem Schlüsselwort class definiert wird, ist der Standardzugriffsbezeichner private . Wenn der Typ mit dem Schlüsselwort struct definiert ist, ist der Standardzugriffsbezeichner public :

struct MyStruct { int x; };
class MyClass { int x; };

MyStruct s;
s.x = 9; // well formed, because x is public

MyClass c;
c.x = 9; // ill-formed, because x is private

Zugriffsspezifizierer werden meist verwendet, um den Zugriff auf interne Felder und Methoden zu beschränken und den Programmierer zu zwingen, eine bestimmte Schnittstelle zu verwenden, z. B. um die Verwendung von Getern und Setters zu erzwingen, anstatt eine Variable direkt zu referenzieren:

class MyClass {

public: /* Methods: */

    int x() const noexcept { return m_x; }
    void setX(int const x) noexcept { m_x = x; }

private: /* Fields: */

    int m_x;

};

Die Verwendung von protected ist nützlich, um zuzulassen, dass bestimmte Funktionen des Typs nur für die abgeleiteten Klassen zugänglich sind. Beispielsweise kann im folgenden Code auf die Methode calculateValue() nur Klassen Plus2Base , die von der Basisklasse Plus2Base , beispielsweise FortyTwo :

struct Plus2Base {
    int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
    virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
    int calculateValue() noexcept final override { return 40; }
};

Beachten Sie, dass das friend Schlüsselwort verwendet werden kann, um Zugriffsausnahmen zu Funktionen oder Typen für den Zugriff auf geschützte und private Mitglieder hinzuzufügen.

Die public , protected und private Schlüsselwörter können auch verwendet werden, um den Zugriff auf Basisklassen-Unterobjekte zu gewähren oder einzuschränken. Siehe das Vererbungsbeispiel .

Erbe

Klassen / Strukturen können Vererbungsbeziehungen haben.

Wenn eine Klasse / Struktur B von einer Klasse / Struktur A erbt, bedeutet dies, dass B übergeordnetes A . Wir sagen, dass B eine abgeleitete Klasse / Struktur von A ist und A die Basisklasse / Struktur ist.

struct A
{
public:
    int p1;
protected:
    int p2;
private:
    int p3;
};

//Make B inherit publicly (default) from A
struct B : A
{
};

Es gibt drei Arten der Vererbung für eine Klasse / Struktur:

  • public
  • private
  • protected

Beachten Sie, dass die Standardvererbung der Standardsichtbarkeit von Mitgliedern entspricht: public wenn Sie das Schlüsselwort struct , und private für das Schlüsselwort class .

Es ist sogar möglich, eine class von einer struct ableiten zu lassen (oder umgekehrt). In diesem Fall wird die Standard - Vererbung durch das Kind gesteuert, so dass eine struct , die aus einer ableitet class auf öffentliche Vererbung ausfällt, und eine class , die von einem leitet struct standardmäßig private Vererbung hat.

public Erbe:

struct B : public A // or just `struct B : A`
{
    void foo()
    {
        p1 = 0; //well formed, p1 is public in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

private Vererbung:

struct B : private A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is private in B
        p2 = 0; //well formed, p2 is private in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible

protected Vererbung:

struct B : protected A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is protected in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

Beachten Sie, dass die protected Vererbung zwar zulässig ist, die tatsächliche Verwendung jedoch selten ist. Ein Beispiel dafür, wie protected Vererbung in der Anwendung verwendet wird, ist die teilweise Spezialisierung der Basisklasse (normalerweise als "kontrollierter Polymorphismus" bezeichnet).

Wenn OOP relativ neu war, wurde häufig gesagt, dass (öffentliche) Vererbung eine "IS-A" -Beziehung modelliert. Öffentliche Vererbung ist also nur dann korrekt, wenn eine Instanz der abgeleiteten Klasse auch eine Instanz der Basisklasse ist.

Dies wurde später im Liskov-Substitutionsprinzip verfeinert: Öffentliche Vererbung sollte nur verwendet werden, wenn / wenn eine Instanz der abgeleiteten Klasse unter allen möglichen Umständen (und immer noch sinnvoll) für eine Instanz der Basisklasse eingesetzt werden kann.

Private Vererbung soll typischerweise eine völlig andere Beziehung verkörpern: "wird in Form von" implementiert (manchmal als "HAS-A" -Beziehung bezeichnet). Eine Stack Klasse kann beispielsweise privat von einer Vector Klasse erben. Private Vererbung hat eine größere Ähnlichkeit mit der Aggregation als mit der öffentlichen Vererbung.

Geschützte Vererbung wird fast nie verwendet, und es besteht keine generelle Übereinstimmung darüber, welche Art von Beziehung sie beinhaltet.

Virtuelle Vererbung

Bei der Vererbung können Sie das virtual Schlüsselwort angeben:

struct A{};
struct B: public virtual A{};

Wenn die Klasse B über die virtuelle Basis A , bedeutet dies, dass sich A in der am meisten abgeleiteten Klasse des Vererbungsbaums befindet und somit auch die am meisten abgeleitete Klasse für die Initialisierung dieser virtuellen Basis verantwortlich ist:

struct A
{
    int member;
    A(int param)
    {
        member = param;
    }
};

struct B: virtual A
{
    B(): A(5){}
};

struct C: B
{
    C(): /*A(88)*/ {}
};

void f()
{
    C object; //error since C is not initializing it's indirect virtual base `A`
}

Wenn wir /*A(88)*/ unkommentieren, erhalten wir keine Fehlermeldung, da C jetzt seine indirekte virtuelle Basis A initialisiert.

Beachten Sie auch , dass , wenn wir variabel sind die Schaffung object , die meisten abgeleitete Klasse ist C , so C verantwortlich ist für die Erstellung von (Aufruf Konstruktor) A und somit Wert von A::member ist 88 , nicht 5 (wie es wäre, wenn wir waren Objekt vom Typ B ) B .

Es ist nützlich, um das Diamantproblem zu lösen .

  A                                        A   A
 / \                                       |   |
B   C                                      B   C
 \ /                                        \ /
  D                                          D
virtual inheritance                   normal inheritance

B und C erben beide von A und D erbt von B und C , also gibt es 2 Fälle von A in D ! Dies führt zu Mehrdeutigkeiten, wenn Sie auf Member von A bis D zugreifen, da der Compiler nicht wissen kann, von welcher Klasse Sie auf dieses Member zugreifen möchten (diejenige, die B erbt oder die von C geerbt wird?). .

Die virtuelle Vererbung löst dieses Problem: Da sich die virtuelle Basis nur in den meisten abgeleiteten Objekten befindet, gibt es nur eine Instanz von A in D

struct A
{
    void foo() {}
};

struct B : public /*virtual*/ A {};
struct C : public /*virtual*/ A {};

struct D : public B, public C
{
    void bar()
    {
        foo(); //Error, which foo? B::foo() or C::foo()? - Ambiguous
    }
};

Durch das Entfernen der Kommentare wird die Mehrdeutigkeit behoben.

Mehrfachvererbung

Abgesehen von der einfachen Vererbung:

class A {};
class B : public A {};

Sie können auch mehrere Vererbungen haben:

class A {};
class B {};
class C : public A, public B {};

C wird jetzt gleichzeitig von A und B erben.

Hinweis: Dies kann zu Mehrdeutigkeiten führen, wenn in mehreren geerbten class oder struct dieselben Namen verwendet werden. Achtung!

Mehrdeutigkeit bei der Mehrfachvererbung

Mehrfachvererbung kann in bestimmten Fällen hilfreich sein, aber manchmal kommt es zu merkwürdigen Problemen bei der Verwendung von Mehrfachvererbung.

Beispiel: Zwei Basisklassen haben Funktionen mit demselben Namen, die in der abgeleiteten Klasse nicht überschrieben werden. Wenn Sie Code schreiben, um auf diese Funktion mithilfe des Objekts der abgeleiteten Klasse zuzugreifen, zeigt der Compiler einen Fehler an, da er nicht ermitteln kann, welche Funktion aufgerufen werden soll. Hier ist ein Code für diese Art der Mehrdeutigkeit bei der Mehrfachvererbung.

class base1
{
  public:
     void funtion( )
     { //code for base1 function }  
};
class base2
{
    void function( )
     { // code for base2 function } 
};

class derived : public base1, public base2
{
    
};

int main()
{
    derived obj;
    
  // Error because compiler can't figure out which function to call 
  //either function( ) of base1 or base2 .   
    obj.function( )  
}

Dieses Problem kann jedoch durch Verwendung der Bereichsauflösungsfunktion gelöst werden, um anzugeben, welche Funktion entweder base1 oder base2 klassifiziert:

int main()
{
    obj.base1::function( );  // Function of class base1 is called. 
    obj.base2::function( );  // Function of class base2 is called.
}

Zugriff auf die Klassenmitglieder

Um auf Member-Variablen und Member-Funktionen eines Objekts einer Klasse zuzugreifen, muss der . Operator wird verwendet:

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();

Beim Zugriff auf die Member einer Klasse über einen Zeiger wird häufig der Operator -> verwendet. Alternativ kann die Instanz dereferenziert werden und die . Operator verwendet, obwohl dies weniger üblich ist:

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();

Beim Zugriff auf statische Klassenmitglieder wird der Operator :: verwendet, jedoch auf den Namen der Klasse und nicht auf eine Instanz davon. Alternativ kann auf das statische Member von einer Instanz oder von einem Zeiger auf eine Instanz mit der Option zugegriffen werden . oder -> Operator mit derselben Syntax wie der Zugriff auf nicht statische Member.

struct SomeStruct {
  int a;
  int b;
  void foo() {}

  static int c;
  static void bar() {}
};
int SomeStruct::c;

SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();

Hintergrund

Der Operator -> wird benötigt, weil der Member-Zugriffsoperator . hat Vorrang vor dem Dereferenzierungsoperator * .

Man würde erwarten, dass *pa deneference p (was dazu führt, dass auf das Objekt, auf das p zeigt, verweist) und dann auf sein Element a zugreift. Tatsächlich versucht es jedoch, auf das Mitglied a von p zuzugreifen und es dann zu dereferenzieren. Dh *pa entspricht *(pa) . Im obigen Beispiel würde dies aus zwei Gründen zu einem Compiler-Fehler führen: Erstens ist p ein Zeiger und hat kein Member a . Zweitens ist a eine ganze Zahl und kann daher nicht dereferenziert werden.

Die ungewöhnlich verwendete Lösung für dieses Problem wäre die explizite Steuerung der Priorität: (*p).a

Stattdessen wird fast immer der Operator -> verwendet. Es ist eine Abkürzung, um den Zeiger zuerst dereferenzieren und dann darauf zugreifen zu können. (*p).a ist genau dasselbe wie p->a .

Der Operator :: ist der Bereichsoperator, der auf dieselbe Weise wie beim Zugriff auf ein Member eines Namespaces verwendet wird. Dies liegt daran, dass ein statischer Klassenmitglied als im Gültigkeitsbereich dieser Klasse enthalten gilt, jedoch nicht als Mitglied von Instanzen dieser Klasse betrachtet wird. Die Verwendung von normalen . und -> ist aus statischen Gründen auch für statische Mitglieder zulässig, obwohl sie keine Instanzmitglieder sind; Dies ist für das Schreiben von generischem Code in Vorlagen von Nutzen, da der Aufrufer sich nicht darum kümmern muss, ob eine bestimmte Elementfunktion statisch oder nicht statisch ist.

Private Vererbung: Einschränkung der Basisklassenschnittstelle

Die private Vererbung ist nützlich, wenn die öffentliche Schnittstelle der Klasse eingeschränkt werden muss:

class A {
public:
    int move();
    int turn();
};

class B : private A {
public:
    using A::turn; 
};

B b;
b.move();  // compile error
b.turn();  // OK

Dieser Ansatz verhindert effizient den Zugriff auf die öffentlichen A-Methoden, indem er auf den A-Zeiger oder die A-Referenz angewendet wird:

B b; 
A& a = static_cast<A&>(b); // compile error

Im Falle der öffentlichen Vererbung bietet ein solches Casting Zugang zu allen A-öffentlichen Methoden, obwohl es alternative Methoden gibt, um dies in abgeleitetem B zu verhindern.

class B : public A {
private:
    int move();  
};

oder privat mit:

class B : public A {
private:
    using A::move;  
};

dann ist es in beiden Fällen möglich:

B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK

Abschlussklassen und -strukturen

C ++ 11

Das Ableiten einer Klasse kann mit dem final Bezeichner verboten werden. Lassen Sie uns eine letzte Klasse erklären:

class A final {
};

Jeder Versuch der Unterklasse führt zu einem Kompilierungsfehler:

// Compilation error: cannot derive from final class:
class B : public A {
};

Die letzte Klasse kann an einer beliebigen Stelle in der Klassenhierarchie erscheinen:

class A {
};

// OK.
class B final : public A {
};

// Compilation error: cannot derive from final class B.
class C : public B {
};

Freundschaft

Das friend Schlüsselwort wird verwendet, um anderen Klassen und Funktionen Zugriff auf private und geschützte Mitglieder der Klasse zu gewähren, auch wenn diese außerhalb des Bereichs der Klasse definiert sind.

class Animal{
private:
    double weight;
    double height;
public:
    friend void printWeight(Animal animal);
    friend class AnimalPrinter;
    // A common use for a friend function is to overload the operator<< for streaming. 
    friend std::ostream& operator<<(std::ostream& os, Animal animal);
};

void printWeight(Animal animal)
{
    std::cout << animal.weight << "\n";
}

class AnimalPrinter
{
public:
    void print(const Animal& animal)
    {
        // Because of the `friend class AnimalPrinter;" declaration, we are
        // allowed to access private members here.
        std::cout << animal.weight << ", " << animal.height << std::endl;
    }
}

std::ostream& operator<<(std::ostream& os, Animal animal)
{
    os << "Animal height: " << animal.height << "\n";
    return os;
}

int main() {
    Animal animal = {10, 5};
    printWeight(animal);

    AnimalPrinter aPrinter;
    aPrinter.print(animal);

    std::cout << animal;
}

10
10, 5
Animal height: 5

Verschachtelte Klassen / Strukturen

Eine class oder struct kann auch eine andere class / struct enthalten, die als "verschachtelte Klasse" bezeichnet wird. In dieser Situation wird die enthaltende Klasse als "einschließende Klasse" bezeichnet. Die geschachtelte Klassendefinition wird als Mitglied der umgebenden Klasse betrachtet, ist jedoch ansonsten getrennt.

struct Outer {
    struct Inner { };
};

Von außerhalb der umgebenden Klasse wird auf verschachtelte Klassen mit dem Bereichsoperator zugegriffen. Innerhalb der umgebenden Klasse können jedoch geschachtelte Klassen ohne Qualifikationsmerkmale verwendet werden:

struct Outer {
    struct Inner { };

    Inner in;
};

// ...

Outer o;
Outer::Inner i = o.in;

Wie bei einer nicht verschachtelten class / struct können struct und statische Variablen entweder innerhalb einer verschachtelten Klasse oder im umschließenden Namespace definiert werden. Sie können jedoch nicht innerhalb der umgebenden Klasse definiert werden, da sie als andere Klasse als die verschachtelte Klasse betrachtet wird.

// Bad.
struct Outer {
    struct Inner {
        void do_something();
    };

    void Inner::do_something() {}
};


// Good.
struct Outer {
    struct Inner {
        void do_something();
    };

};

void Outer::Inner::do_something() {}

Wie bei nicht geschachtelten Klassen können verschachtelte Klassen später deklariert und definiert werden, sofern sie vor ihrer direkten Verwendung definiert werden.

class Outer {
    class Inner1;
    class Inner2;

    class Inner1 {};

    Inner1 in1;
    Inner2* in2p;

  public:
    Outer();
    ~Outer();
};

class Outer::Inner2 {};

Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
    if (in2p) { delete in2p; }
}

C ++ 11

Vor C ++ 11 hatten verschachtelte Klassen nur Zugriff auf Typnamen, static Member und Enumeratoren der umgebenden Klasse. Alle anderen Mitglieder, die in der umgebenden Klasse definiert wurden, waren gesperrt.

C ++ 11

Ab C ++ 11 werden verschachtelte Klassen und deren Mitglieder so behandelt, als wären sie friend der umgebenden Klasse, und sie können gemäß den üblichen Zugriffsregeln auf alle ihre Mitglieder zugreifen. Wenn Mitglieder der geschachtelten Klasse die Fähigkeit benötigen, ein oder mehrere nicht statische Mitglieder der umgebenden Klasse auszuwerten, müssen sie einer Instanz übergeben werden:

class Outer {
    struct Inner {
        int get_sizeof_x() {
            return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
        }

        int get_x() {
            return x; // Illegal: Can't access non-static member without an instance.
        }

        int get_x(Outer& o) {
            return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
        }
    };

    int x;
};

Umgekehrt wird die umgebende Klasse nicht als Freund der verschachtelten Klasse behandelt und kann daher nicht auf ihre privaten Mitglieder zugreifen, ohne dass ihnen explizit die Erlaubnis erteilt wurde.

class Outer {
    class Inner {
        // friend class Outer;

        int x;
    };

    Inner in;

  public:
    int get_x() {
        return in.x; // Error: int Outer::Inner::x is private.
        // Uncomment "friend" line above to fix.
    }
};

Freunde einer verschachtelten Klasse werden nicht automatisch als Freunde der umgebenden Klasse betrachtet. Wenn sie auch Freunde der umgebenden Klasse sein müssen, muss dies separat angegeben werden. Da die umschließende Klasse nicht automatisch als Freund der verschachtelten Klasse angesehen wird, werden Freunde der umschließenden Klasse auch nicht als Freunde der verschachtelten Klasse betrachtet.

class Outer {
    friend void barge_out(Outer& out, Inner& in);

    class Inner {
        friend void barge_in(Outer& out, Inner& in);

        int i;
    };

    int o;
};

void barge_in(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Good.
    int o = out.o; // Error: int Outer::o is private.
}

void barge_out(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Error: int Outer::Inner::i is private.
    int o = out.o; // Good.
}

Wie bei allen anderen Klassenmitgliedern können verschachtelte Klassen nur von außerhalb der Klasse benannt werden, wenn sie öffentlichen Zugriff haben. Sie können jedoch unabhängig vom Zugriffsmodifizierer auf sie zugreifen, sofern Sie sie nicht explizit benennen.

class Outer {
    struct Inner {
        void func() { std::cout << "I have no private taboo.\n"; }
    };

  public:
    static Inner make_Inner() { return Inner(); }
};

// ...

Outer::Inner oi; // Error: Outer::Inner is private.

auto oi = Outer::make_Inner(); // Good.
oi.func();                     // Good.
Outer::make_Inner().func();    // Good.

Sie können auch einen Typalias für eine verschachtelte Klasse erstellen. Wenn ein Typalias in der umgebenden Klasse enthalten ist, können der verschachtelte Typ und der Typalias unterschiedliche Zugriffsmodifizierer haben. Wenn sich der typedef außerhalb der umgebenden Klasse befindet, muss die geschachtelte Klasse oder ein typedef davon öffentlich sein.

class Outer {
    class Inner_ {};

  public:
    typedef Inner_ Inner;
};

typedef Outer::Inner  ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.

// ...

Outer::Inner  oi; // Good.
Outer::Inner_ oi; // Error.
ImOut         oi; // Good.

Wie bei anderen Klassen können auch verschachtelte Klassen von anderen Klassen abgeleitet oder abgeleitet werden.

struct Base {};

struct Outer {
    struct Inner : Base {};
};

struct Derived : Outer::Inner {};

Dies kann in Situationen hilfreich sein, in denen die umgebende Klasse von einer anderen Klasse abgeleitet wird, indem der Programmierer die geschachtelte Klasse bei Bedarf aktualisieren kann. Dies kann mit einem Typedef kombiniert werden, um einen konsistenten Namen für die verschachtelten Klassen jeder umgebenden Klasse bereitzustellen:

class BaseOuter {
    struct BaseInner_ {
        virtual void do_something() {}
        virtual void do_something_else();
    } b_in;

  public:
    typedef BaseInner_ Inner;

    virtual ~BaseOuter() = default;

    virtual Inner& getInner() { return b_in; }
};

void BaseOuter::BaseInner_::do_something_else() {}

// ---

class DerivedOuter : public BaseOuter {
    // Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
    struct DerivedInner_ : BaseOuter::Inner {
        void do_something() override {}
        void do_something_else() override;
    } d_in;

  public:
    typedef DerivedInner_ Inner;

    BaseOuter::Inner& getInner() override { return d_in; }
};

void DerivedOuter::DerivedInner_::do_something_else() {}

// ...

// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();

// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();

In dem obigen Fall geben BaseOuter und DerivedOuter den DerivedOuter Inner als BaseInner_ bzw. DerivedInner_ an. Dadurch können geschachtelte Typen abgeleitet werden, ohne die Schnittstelle der umgebenden Klasse zu beschädigen, und der geschachtelte Typ kann polymorph verwendet werden.

Mitgliedstypen und Aliase

Eine class oder struct kann auch Mitgliedstyp-Aliasnamen definieren, die in der Klasse selbst enthalten sind und als Mitglieder dieser Klasse behandelt werden.

struct IHaveATypedef {
    typedef int MyTypedef;
};

struct IHaveATemplateTypedef {
    template<typename T>
    using MyTemplateTypedef = std::vector<T>;
};

Auf diese Typedefs wird wie statische Member mit dem Bereichsoperator :: zugegriffen.

IHaveATypedef::MyTypedef i = 5; // i is an int.

IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.

Wie bei normalen Typ-Aliasnamen darf jeder Mitgliedstyp-Alias ​​vor jeder Definition, aber nicht nach ihrer Definition, auf jeden definierten Typ oder Alias ​​verweisen. Ebenso kann ein Typedef außerhalb der Klassendefinition auf alle zugänglichen Typedefs innerhalb der Klassendefinition verweisen, sofern diese hinter der Klassendefinition stehen.

template<typename T>
struct Helper {
    T get() const { return static_cast<T>(42); }
};

struct IHaveTypedefs {
//    typedef MyTypedef NonLinearTypedef; // Error if uncommented.
    typedef int MyTypedef;
    typedef Helper<MyTypedef> MyTypedefHelper;
};

IHaveTypedefs::MyTypedef        i; // x_i is an int.
IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.

typedef IHaveTypedefs::MyTypedef TypedefBeFree;
TypedefBeFree ii;                  // ii is an int.

Aliasnamen für Mitgliedstypen können mit jeder Zugriffsebene deklariert werden und berücksichtigen den entsprechenden Zugriffsmodifizierer.

class TypedefAccessLevels {
    typedef int PrvInt;

  protected:
    typedef int ProInt;

  public:
    typedef int PubInt;
};

TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected.
TypedefAccessLevels::PubInt pub_i; // Good.

class Derived : public TypedefAccessLevels {
    PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
    ProInt pro_i; // Good.
    PubInt pub_i; // Good.
};

Dies kann verwendet werden, um eine Abstraktionsebene bereitzustellen, die es einem Designer der Klasse ermöglicht, seine internen Funktionen zu ändern, ohne den Code zu beschädigen, der darauf beruht.

class Something {
    friend class SomeComplexType;

    short s;
    // ...

  public:
    typedef SomeComplexType MyHelper;

    MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }

    // ...
};

// ...

Something s;
Something::MyHelper hlp = s.get_helper();

Wenn in dieser Situation die SomeComplexType von SomeComplexType in einen anderen Typ geändert wird, müssen nur die typedef und die friend Deklaration geändert werden. Solange die Something::MyHelper die gleiche Funktionalität bietet, funktioniert jeder Code, der sie als Something::MyHelper anstatt sie nach Namen anzugeben, in der Regel ohne Änderungen. Auf diese Weise minimieren wir die Menge an Code, die geändert werden muss, wenn die zugrunde liegende Implementierung geändert wird, sodass der Typname nur an einem Ort geändert werden muss.

Dies kann auch mit decltype kombiniert decltype , wenn Sie dies decltype .

class SomethingElse {
    AnotherComplexType<bool, int, SomeThirdClass> helper;

  public:
    typedef decltype(helper) MyHelper;

  private:
    InternalVariable<MyHelper> ivh;

    // ...

  public:
    MyHelper& get_helper() const { return helper; }

    // ...
};

In dieser Situation ändert das Ändern der Implementierung von SomethingElse::helper automatisch den Typedef für uns aufgrund von decltype . Dies reduziert die Anzahl der erforderlichen Änderungen, wenn der helper , wodurch das Risiko menschlicher Fehler minimiert wird.

Wie bei allem kann dies jedoch zu weit gehen. Wenn der Typname beispielsweise nur einmal oder zweimal intern und null Mal außerhalb verwendet wird, muss kein Alias ​​angegeben werden. Wenn das Projekt hunderte oder tausende Male in einem Projekt verwendet wird oder einen ausreichend langen Namen hat, kann es nützlich sein, es als Typedef anzugeben, anstatt es immer in absoluten Zahlen zu verwenden. Kompatibilität und Bequemlichkeit müssen mit der Menge an unnötigem Rauschen in Einklang gebracht werden.


Dies kann auch mit Vorlagenklassen verwendet werden, um den Zugriff auf die Vorlagenparameter von außerhalb der Klasse zu ermöglichen.

template<typename T>
class SomeClass {
    // ...

  public:
    typedef T MyParam;
    MyParam getParam() { return static_cast<T>(42); }
};

template<typename T>
typename T::MyParam some_func(T& t) {
    return t.getParam();
}

SomeClass<int> si;
int i = some_func(si);

Dies wird im Allgemeinen bei Containern verwendet, die normalerweise ihren Elementtyp und andere Hilfstypen als Aliase für Mitgliedstypen angeben. Die meisten Container in der C ++ - Standardbibliothek enthalten beispielsweise die folgenden 12 Hilfstypen sowie alle anderen speziellen Typen, die sie benötigen.

template<typename T>
class SomeContainer {
    // ...

  public:
    // Let's provide the same helper types as most standard containers.
    typedef T                                     value_type;
    typedef std::allocator<value_type>            allocator_type;
    typedef value_type&                           reference;
    typedef const value_type&                     const_reference;
    typedef value_type*                           pointer;
    typedef const value_type*                     const_pointer;
    typedef MyIterator<value_type>                iterator;
    typedef MyConstIterator<value_type>           const_iterator;
    typedef std::reverse_iterator<iterator>       reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    typedef size_t                                size_type;
    typedef ptrdiff_t                             difference_type;
};

Vor C ++ 11 wurde häufig auch eine Art "Template- typedef " angegeben, da die Funktion noch nicht verfügbar war. Diese sind mit der Einführung von Alias-Templates etwas seltener geworden, sie sind jedoch in einigen Situationen immer noch nützlich (und werden in anderen Situationen mit Alias-Templates kombiniert), was sehr nützlich sein kann, um einzelne Komponenten eines komplexen Typs, z ). Sie verwenden üblicherweise den type für ihren Typalias.

template<typename T>
struct TemplateTypedef {
    typedef T type;
}

TemplateTypedef<int>::type i; // i is an int.

Dies wurde häufig bei Typen mit mehreren Vorlagenparametern verwendet, um einen Aliasnamen bereitzustellen, der einen oder mehrere Parameter definiert.

template<typename T, size_t SZ, size_t D>
class Array { /* ... */ };

template<typename T, size_t SZ>
struct OneDArray {
    typedef Array<T, SZ, 1> type;
};

template<typename T, size_t SZ>
struct TwoDArray {
    typedef Array<T, SZ, 2> type;
};

template<typename T>
struct MonoDisplayLine {
    typedef Array<T, 80, 1> type;
};

OneDArray<int, 3>::type     arr1i; // arr1i is an Array<int, 3, 1>.
TwoDArray<short, 5>::type   arr2s; // arr2s is an Array<short, 5, 2>.
MonoDisplayLine<char>::type arr3c; // arr3c is an Array<char, 80, 1>.

Statische Klassenmitglieder

Eine Klasse darf auch static Member haben, die entweder Variablen oder Funktionen sein können. Diese werden als innerhalb des Bereichs der Klasse betrachtet, aber nicht als normale Mitglieder behandelt. Sie haben eine statische Speicherdauer (sie existieren vom Beginn des Programms bis zum Ende), sind nicht an eine bestimmte Instanz der Klasse gebunden, und für die gesamte Klasse ist nur eine Kopie vorhanden.

class Example {
    static int num_instances;      // Static data member (static member variable).
    int i;                         // Non-static member variable.

  public:
    static std::string static_str; // Static data member (static member variable).
    static int static_func();      // Static member function.

    // Non-static member functions can modify static member variables.
    Example() { ++num_instances; }
    void set_str(const std::string& str);
};

int         Example::num_instances;
std::string Example::static_str = "Hello.";

// ...

Example one, two, three;
// Each Example has its own "i", such that:
//  (&one.i != &two.i)
//  (&one.i != &three.i)
//  (&two.i != &three.i).
// All three Examples share "num_instances", such that:
//  (&one.num_instances == &two.num_instances)
//  (&one.num_instances == &three.num_instances)
//  (&two.num_instances == &three.num_instances)

Statische Member-Variablen werden nicht als innerhalb der Klasse definiert, sondern nur als deklariert betrachtet und haben daher ihre Definition außerhalb der Klassendefinition. Der Programmierer darf statische Variablen in ihrer Definition initialisieren, ist aber nicht dazu verpflichtet. Bei der Definition der Elementvariablen wird das Schlüsselwort static weggelassen.

class Example {
    static int num_instances;               // Declaration.

  public:
    static std::string static_str;          // Declaration.

    // ...
};

int         Example::num_instances;         // Definition.  Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.

Aus diesem Grund können statische Variablen (abgesehen von void ) unvollständige Typen sein, sofern sie später als vollständiger Typ definiert werden.

struct ForwardDeclared;

class ExIncomplete {
    static ForwardDeclared fd;
    static ExIncomplete    i_contain_myself;
    static int             an_array[];
};

struct ForwardDeclared {};

ForwardDeclared ExIncomplete::fd;
ExIncomplete    ExIncomplete::i_contain_myself;
int             ExIncomplete::an_array[5];

Statische Memberfunktionen können wie bei normalen Memberfunktionen innerhalb oder außerhalb der Klassendefinition definiert werden. Wie bei statischen Elementvariablen wird das Schlüsselwort static bei der Definition statischer Elementfunktionen außerhalb der Klassendefinition weggelassen.

// For Example above, either...
class Example {
    // ...

  public:
    static int static_func() { return num_instances; }

    // ...

    void set_str(const std::string& str) { static_str = str; }
};

// Or...

class Example { /* ... */ };

int  Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }

Wenn eine statische Membervariable als const deklariert wird, aber nicht volatile ist und vom Integral- oder Aufzählungstyp ist, kann sie bei der Deklaration innerhalb der Klassendefinition initialisiert werden.

enum E { VAL = 5 };

struct ExConst {
    const static int ci = 5;              // Good.
    static const E ce = VAL;              // Good.
    const static double cd = 5;           // Error.
    static const volatile int cvi = 5;    // Error.

    const static double good_cd;
    static const volatile int good_cvi;
};

const double ExConst::good_cd = 5;        // Good.
const volatile int ExConst::good_cvi = 5; // Good.
C ++ 11

Ab C ++ 11 können statische Membervariablen von LiteralType Typen (Typen, die zur Kompilierzeit gemäß den constexpr Regeln erstellt werden können) auch als constexpr deklariert werden. Wenn ja, müssen sie innerhalb der Klassendefinition initialisiert werden.

struct ExConstexpr {
    constexpr static int ci = 5;                      // Good.
    static constexpr double cd = 5;                   // Good.
    constexpr static int carr[] = { 1, 1, 2 };        // Good.
    static constexpr ConstexprConstructibleClass c{}; // Good.
    constexpr static int bad_ci;                      // Error.
};

constexpr int ExConstexpr::bad_ci = 5;                // Still an error.

Wenn eine statische Membervariable const oder constexpr odr verwendet wird (informell, wenn ihre Adresse verwendet wird oder einer Referenz zugewiesen ist), muss sie außerhalb der Klassendefinition noch eine separate Definition haben. Diese Definition darf keinen Initialisierer enthalten.

struct ExODR {
    static const int odr_used = 5;
};

// const int ExODR::odr_used;

const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.

Da statische Member nicht an eine bestimmte Instanz gebunden sind, kann auf sie mit dem Bereichsoperator :: . Zugegriffen werden.

std::string str = Example::static_str;

Sie können auch so aufgerufen werden, als wären sie normale, nicht statische Member. Dies ist von historischer Bedeutung, wird jedoch weniger häufig als der Bereichsoperator verwendet, um Verwirrung darüber zu vermeiden, ob ein Member statisch oder nicht statisch ist.

Example ex;
std::string rts = ex.static_str;

Klassenmitglieder können auf statische Mitglieder zugreifen, ohne deren Gültigkeitsbereich einzuschränken, wie bei nicht statischen Klassenmitgliedern.

class ExTwo {
    static int num_instances;
    int my_num;

  public:
    ExTwo() : my_num(num_instances++) {}

    static int get_total_instances() { return num_instances; }
    int get_instance_number() const { return my_num; }
};

int ExTwo::num_instances;

Sie können weder mutable noch müssen sie sein; Da sie nicht an eine bestimmte Instanz gebunden sind, hat dies keine Auswirkungen auf statische Member.

struct ExDontNeedMutable {
    int immuta;
    mutable int muta;

    static int i;

    ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;

// ...

const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5;   // Good.  Mutable fields of const objects can be written.
dnm.i = 5;      // Good.  Static members can be written regardless of an instance's const-ness.

Statische Member berücksichtigen Zugriffsmodifizierer ebenso wie nicht statische Member.

class ExAccess {
    static int prv_int;

  protected:
    static int pro_int;

  public:
    static int pub_int;
};

int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;

// ...

int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.

Als sie auf eine bestimmte Instanz nicht gebunden sind, haben statische Elementfunktionen keinen this Zeiger; Aus diesem Grund können sie nicht auf nicht statische Member-Variablen zugreifen, wenn keine Instanz übergeben wird.

class ExInstanceRequired {
    int i;

  public:
    ExInstanceRequired() : i(0) {}

    static void bad_mutate() { ++i *= 5; }                         // Error.
    static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};

Da this Zeiger nicht vorhanden sind, können ihre Adressen nicht in Zeiger-zu-Element-Funktionen gespeichert werden, sondern werden in normalen Zeiger-zu-Funktionen gespeichert.

struct ExPointer {
           void nsfunc() {}
    static void  sfunc() {}
};

typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();

mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
    f_ptr p_sf = &ExPointer::sfunc; // Good.

Da this Zeiger nicht vorhanden ist, können sie auch nicht const oder volatile und auch keine Ref-Qualifier haben. Sie können auch nicht virtuell sein.

struct ExCVQualifiersAndVirtual {
    static void   func()                {} // Good.
    static void  cfunc() const          {} // Error.
    static void  vfunc() volatile       {} // Error.
    static void cvfunc() const volatile {} // Error.
    static void  rfunc() &              {} // Error.
    static void rvfunc() &&             {} // Error.

    virtual static void vsfunc()        {} // Error.
    static virtual void svfunc()        {} // Error.
};

Da sie nicht an eine bestimmte Instanz gebunden sind, werden statische Elementvariablen effektiv als spezielle globale Variablen behandelt. Sie werden beim Start des Programms erstellt und beim Beenden gelöscht, unabhängig davon, ob tatsächlich Instanzen der Klasse vorhanden sind. Von jeder statischen Membervariablen ist nur eine einzige Kopie vorhanden (es sei denn, die Variable ist thread_local deklariert (C ++ 11 oder höher). In diesem Fall gibt es eine Kopie pro Thread).

Statische Member-Variablen haben dieselbe Verknüpfung wie die Klasse, unabhängig davon, ob die Klasse über eine externe oder interne Verknüpfung verfügt. Lokale Klassen und unbenannte Klassen dürfen keine statischen Mitglieder haben.

Nicht statische Memberfunktionen

Eine Klasse kann über nicht statische Memberfunktionen verfügen , die einzelne Instanzen der Klasse bearbeiten.

class CL {
  public:
    void member_function() {}
};

Diese Funktionen werden wie folgt für eine Instanz der Klasse aufgerufen:

CL instance;
instance.member_function();

Sie können entweder innerhalb oder außerhalb der Klassendefinition definiert werden. Wenn sie außerhalb definiert sind, werden sie als im Klassenbereich angegeben angegeben.

struct ST {
    void  defined_inside() {}
    void defined_outside();
};
void ST::defined_outside() {}

Sie können für den Lebenslauf qualifiziert und / oder qualifiziert sein und beeinflussen, wie sie die angeforderte Instanz sehen. Die Funktion sieht die Instanz mit den angegebenen cv-qualifier (s), sofern vorhanden. Welche Version aufgerufen wird, hängt von den CV-Qualifiers der Instanz ab. Wenn es keine Version mit denselben CV-Qualifikationsmerkmalen wie die Instanz gibt, wird, falls verfügbar, eine Version mit höherem CV-Status aufgerufen.

struct CVQualifiers {
    void func()                   {} // 1: Instance is non-cv-qualified.
    void func() const             {} // 2: Instance is const.

    void cv_only() const volatile {}
};

CVQualifiers       non_cv_instance;
const CVQualifiers      c_instance;

non_cv_instance.func(); // Calls #1.
c_instance.func();      // Calls #2.

non_cv_instance.cv_only(); // Calls const volatile version.
c_instance.cv_only();      // Calls const volatile version.
C ++ 11

Member-Funktionsreferenzqualifizierer geben an, ob die Funktion für rvalue-Instanzen aufgerufen werden soll oder nicht, und verwenden dieselbe Syntax wie Funktions-CV-Qualifikationsmerkmale.

struct RefQualifiers {
    void func() &  {} // 1: Called on normal instances.
    void func() && {} // 2: Called on rvalue (temporary) instances.
};

RefQualifiers rf;
rf.func();              // Calls #1.
RefQualifiers{}.func(); // Calls #2.

CV-Qualifier und Ref-Qualifier können bei Bedarf auch kombiniert werden.

struct BothCVAndRef {
    void func() const& {} // Called on normal instances.  Sees instance as const.
    void func() &&     {} // Called on temporary instances.
};

Sie können auch virtuell sein ; Dies ist grundlegend für den Polymorphismus und ermöglicht einer untergeordneten Klasse (n), dieselbe Schnittstelle wie die übergeordnete Klasse bereitzustellen, während sie ihre eigene Funktionalität bereitstellt.

struct Base {
    virtual void func() {}
};
struct Derived {
    virtual void func() {}
};

Base* bp = new Base;
Base* dp = new Derived;
bp.func(); // Calls Base::func().
dp.func(); // Calls Derived::func().

Weitere Informationen finden Sie hier .

Unbenannte Struktur / Klasse

Unbenannte struct ist erlaubt (Typ hat keinen Namen)

void foo()
{
    struct /* No name */ {
        float x;
        float y;
    } point;
    
    point.x = 42;
}

oder

struct Circle
{
    struct /* No name */ {
        float x;
        float y;
    } center; // but a member name
    float radius;
};

und später

Circle circle;
circle.center.x = 42.f;

aber NICHT anonyme struct (unbenannter Typ und unbenanntes Objekt)

struct InvalidCircle
{
    struct /* No name */ {
        float centerX;
        float centerY;
    }; // No member either.
    float radius;
};

Hinweis: Einige Compiler lassen anonyme struct als Erweiterung zu .

C ++ 11
  • lamdba kann als eine spezielle unbenannte struct .

  • decltype erlaubt den Typ der unbenannten struct abzurufen:

    decltype(circle.point) otherPoint;
    
  • unbenannte struct kann Parameter der Vorlagenmethode sein:

    void print_square_coordinates()
    {
        const struct {float x; float y;} points[] = {
            {-1, -1}, {-1, 1}, {1, -1}, {1, 1}
        };
    
        // for range relies on `template <class T, std::size_t N> std::begin(T (&)[N])`
        for (const auto& point : points) { 
            std::cout << "{" << point.x << ", " << point.y << "}\n";
        }
    
        decltype(points[0]) topRightCorner{1, 1};
        auto it = std::find(points, points + 4, topRightCorner);
        std::cout << "top right corner is the "
                  << 1 + std::distance(points, it) << "th\n";
    }
    


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow