Suche…


Syntax

  • // Anrufen:

    • Variable.Mitgliedsfunktion ();
    • variable_pointer-> member_function ();
  • // Definition:

    • ret_type klassenname :: member_function () cv-qualifiers {
      • Karosserie;
    • }
  • // Prototyp:

    • class class_name {
      • virt-specifier ret_type member_function () cv-qualifiers virt-spezifier-seq;
      • // virt-spezifier: "virtuell", falls zutreffend.
      • // cv-qualifiers: "const" und / oder "volatile", falls zutreffend.
      • // virt-specifier-seq: "override" und / oder "final", falls zutreffend.
    • }

Bemerkungen

Eine nicht static Mitgliedsfunktion ist eine class / struct / union , die für eine bestimmte Instanz aufgerufen wird und für diese Instanz arbeitet. Im Gegensatz zu static Memberfunktionen kann sie nicht ohne Angabe einer Instanz aufgerufen werden.

Informationen zu Klassen, Strukturen und Vereinigungen finden Sie im übergeordneten Thema .

Nicht statische Elementfunktionen

Eine class oder struct kann sowohl Member-Funktionen als auch Member-Variablen haben. Diese Funktionen haben eine weitgehend eigenständige Syntax und können entweder innerhalb oder außerhalb der Klassendefinition definiert werden. Wenn die Funktion außerhalb der Klassendefinition definiert ist, wird dem Namen der Funktion der Name der Klasse und der Gültigkeitsbereich-Operator ( :: :) vorangestellt.

class CL {
  public:
    void  definedInside() {}
    void definedOutside();
};
void CL::definedOutside() {}

Diese Funktionen werden für eine Instanz (oder einen Verweis auf eine Instanz) der Klasse mit dem Punkt ( . ) Oder einem Zeiger auf eine Instanz mit dem Pfeil ( -> ) aufgerufen. Jeder Aufruf ist an die Instanz der Funktion gebunden wurde angerufen; Wenn eine Member-Funktion für eine Instanz aufgerufen wird, hat sie Zugriff auf alle Felder dieser Instanz (über this Zeiger ), kann jedoch nur auf die Felder anderer Instanzen zugreifen, wenn diese Instanzen als Parameter angegeben werden.

struct ST {
    ST(const std::string& ss = "Wolf", int ii = 359) : s(ss), i(ii) { }

    int get_i() const { return i; }
    bool compare_i(const ST& other) const { return (i == other.i); }

  private:
    std::string s;
    int i;
};
ST st1;
ST st2("Species", 8472);

int  i = st1.get_i(); // Can access st1.i, but not st2.i.
bool b = st1.compare_i(st2); // Can access st1 & st2.

Diese Funktionen können auf Member-Variablen und / oder andere Member-Funktionen zugreifen, unabhängig von den Zugriffsmodifizierern der Variablen oder Funktionen. Sie können auch außerhalb der Reihenfolge geschrieben werden, um auf Member-Variablen und / oder aufrufende Member-Funktionen zuzugreifen, die vor ihnen deklariert wurden, da die gesamte Klassendefinition analysiert werden muss, bevor der Compiler mit der Kompilierung einer Klasse beginnen kann.

class Access {
  public:
    Access(int i_ = 8088, int j_ = 8086, int k_ = 6502) : i(i_), j(j_), k(k_) {}

    int i;
    int get_k() const { return k; }
    bool private_no_more() const { return i_be_private(); }
  protected:
    int j;
    int get_i() const { return i; }
  private:
    int k;
    int get_j() const { return j; }
    bool i_be_private() const { return ((i > j) && (k < j)); }
};

Verkapselung

Member-Funktionen werden häufig für die Verkapselung verwendet. Sie verwenden einen Accessor (allgemein als Getter bezeichnet) und einen Mutator (allgemein als Setter bezeichnet), anstatt direkt auf Felder zuzugreifen.

class Encapsulator {
    int encapsulated;

  public:
    int  get_encapsulated() const { return encapsulated; }
    void set_encapsulated(int e)  { encapsulated = e; }

    void some_func() {
        do_something_with(encapsulated);
    }
};

Innerhalb der Klasse kann encapsulated von jeder nicht statischen encapsulated frei zugegriffen werden. Außerhalb der Klasse wird der Zugriff darauf durch Member-Funktionen geregelt. Verwenden Sie get_encapsulated() , um sie zu lesen, und set_encapsulated() , um sie zu ändern. Dies verhindert unbeabsichtigte Änderungen an der Variablen, da zum Lesen und Schreiben separate Funktionen verwendet werden. [Es gibt viele Diskussionen darüber, ob Getter und Setter die Einkapselung anbieten oder brechen, mit guten Argumenten für beide Ansprüche; Eine solche hitzige Debatte fällt nicht in den Rahmen dieses Beispiels.]

Name ausblenden & importieren

Wenn eine Basisklasse einen Satz überladener Funktionen bereitstellt und eine abgeleitete Klasse dem Satz eine weitere Überladung hinzufügt, werden alle von der Basisklasse bereitgestellten Überladungen ausgeblendet.

struct HiddenBase {
    void f(int) { std::cout << "int" << std::endl; }
    void f(bool) { std::cout << "bool" << std::endl; }
    void f(std::string) { std::cout << "std::string" << std::endl; }
};

struct HidingDerived : HiddenBase {
    void f(float) { std::cout << "float" << std::endl; }
};

// ...

HiddenBase hb;
HidingDerived hd;
std::string s;

hb.f(1);    // Output:  int
hb.f(true); // Output:  bool
hb.f(s);    // Output:  std::string;

hd.f(1.f);  // Output:  float
hd.f(3);    // Output:  float
hd.f(true); // Output:  float
hd.f(s);    // Error: Can't convert from std::string to float.

Dies ist auf Namensauflösungsregeln zurückzuführen: Während der Namenssuche, sobald der richtige Name gefunden wurde, hören wir auf zu suchen, selbst wenn wir eindeutig nicht die richtige Version der Entität mit diesem Namen gefunden haben (z. B. mit hd.f(s) ); Aus diesem Grund verhindert das Überladen der Funktion in der abgeleiteten Klasse, dass die Namenssuche die Überladungen in der Basisklasse erkennt. Um dies zu vermeiden, können mithilfe einer using-Deklaration Namen aus der Basisklasse in die abgeleitete Klasse "importiert" werden, sodass sie während der Namenssuche verfügbar sind.

struct HidingDerived : HiddenBase {
     // All members named HiddenBase::f shall be considered members of HidingDerived for lookup.
    using HiddenBase::f;

    void f(float) { std::cout << "float" << std::endl; }
};

// ...

HidingDerived hd;

hd.f(1.f);  // Output:  float
hd.f(3);    // Output:  int
hd.f(true); // Output:  bool
hd.f(s);    // Output:  std::string

Wenn eine abgeleitete Klasse Namen mit einer using-Deklaration importiert, aber Funktionen mit derselben Signatur wie Funktionen in der Basisklasse deklariert, werden die Funktionen der Basisklasse unbemerkt überschrieben oder ausgeblendet.

struct NamesHidden {
    virtual void hide_me()      {}
    virtual void hide_me(float) {}
    void hide_me(int)           {}
    void hide_me(bool)          {}
};

struct NameHider : NamesHidden {
    using NamesHidden::hide_me;

    void hide_me()    {} // Overrides NamesHidden::hide_me().
    void hide_me(int) {} // Hides NamesHidden::hide_me(int).
};

Eine using-Deklaration kann auch verwendet werden, um Zugriffsmodifizierer zu ändern, vorausgesetzt, die importierte Entität war public oder in der Basisklasse protected .

struct ProMem {
  protected:
    void func() {}
};

struct BecomesPub : ProMem {
    using ProMem::func;
};

// ...

ProMem pm;
BecomesPub bp;

pm.func(); // Error: protected.
bp.func(); // Good.

Wenn wir explizit eine Member-Funktion von einer bestimmten Klasse in der Vererbungshierarchie aufrufen möchten, können wir den Funktionsnamen beim Aufruf der Funktion angeben, indem Sie diese Klasse nach Namen angeben.

struct One {
    virtual void f() { std::cout << "One." << std::endl; }
};

struct Two : One {
    void f() override {
        One::f(); // this->One::f();
        std::cout << "Two." << std::endl;
    }
};

struct Three : Two {
    void f() override {
        Two::f(); // this->Two::f();
        std::cout << "Three." << std::endl;
    }
};

// ...

Three t;

t.f();      // Normal syntax.
t.Two::f(); // Calls version of f() defined in Two.
t.One::f(); // Calls version of f() defined in One.

Virtuelle Elementfunktionen

Elementfunktionen können auch als virtual deklariert werden. Wenn in diesem Fall ein Zeiger oder eine Referenz auf eine Instanz aufgerufen wird, wird nicht direkt auf sie zugegriffen. vtable suchen sie die Funktion in der virtuellen Funktionstabelle (eine Liste von Zeiger-zu-Member-Funktionen für virtuelle Funktionen, die üblicherweise als vtable oder vftable ) und verwenden sie, um die für die Dynamik der Instanz geeignete Version aufzurufen (tatsächlicher) Typ. Wenn die Funktion direkt aus einer Variablen einer Klasse aufgerufen wird, wird keine Suche durchgeführt.

struct Base {
    virtual void func() { std::cout << "In Base." << std::endl; }
};

struct Derived : Base {
    void func() override { std::cout << "In Derived." << std::endl; }
};

void slicer(Base x) { x.func(); }

// ...

Base b;
Derived d;

Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d;   // References.

b.func();   // Output:  In Base.
d.func();   // Output:  In Derived.

pb->func(); // Output:  In Base.
pd->func(); // Output:  In Derived.

rb.func();  // Output:  In Base.
rd.func();  // Output:  In Derived.

slicer(b);  // Output:  In Base.
slicer(d);  // Output:  In Base.

Beachten Sie, dass während pd Base* und rd eine Base& , der Aufruf von func() bei einem der beiden Aufrufe Derived::func() anstelle von Base::func() ; Dies liegt daran, dass die vtable für Derived den Eintrag Base::func() aktualisiert und stattdessen auf Derived::func() . Beachten Sie umgekehrt, dass das Übergeben einer Instanz an slicer() immer dazu führt, dass Base::func() aufgerufen wird, auch wenn die übergebene Instanz ein Derived . dies ist wegen etwas , als Daten - Slicing bekannt, wo eine vorübergehenden Derived Instanz in eine Base von Wertparametern den Teil der macht Derived Instanz , die sie nicht um eine Base - Instanz nicht zugegriffen werden .

Wenn eine Member-Funktion als virtuell definiert wird, überschreiben alle abgeleiteten Klassen-Member-Funktionen mit derselben Signatur diese Funktion, unabhängig davon, ob die überschreibende Funktion als virtual oder nicht. Dies kann dazu führen, dass abgeleitete Klassen für Programmierer schwieriger zu analysieren sind, da es keine Hinweise darauf gibt, welche Funktion (en) virtual .

struct B {
    virtual void f() {}
};

struct D : B {
    void f() {} // Implicitly virtual, overrides B::f.
                //  You'd have to check B to know that, though.
};

Beachten Sie jedoch, dass eine abgeleitete Funktion eine Basisfunktion nur überschreibt, wenn ihre Signaturen übereinstimmen. Selbst wenn eine abgeleitete Funktion explizit als virtual deklariert wird, erstellt sie stattdessen eine neue virtuelle Funktion, wenn die Signaturen nicht übereinstimmen.

struct BadB {
    virtual void f() {}
};

struct BadD : BadB {
    virtual void f(int i) {} // Does NOT override BadB::f.
};
C ++ 11

Mit C ++ 11 kann die Absicht zum Überschreiben explizit mit dem kontextsensitiven Schlüsselwort override . Dies teilt dem Compiler mit, dass der Programmierer erwartet, dass er eine Basisklassenfunktion überschreibt, wodurch der Compiler einen Fehler auslässt, wenn er nichts außer Kraft setzt.

struct CPP11B {
    virtual void f() {}
};

struct CPP11D : CPP11B {
    void f() override {}
    void f(int i) override {} // Error: Doesn't actually override anything.
};

Dies hat auch den Vorteil, den Programmierern mitzuteilen, dass die Funktion sowohl virtuell ist als auch in mindestens einer Basisklasse deklariert ist, wodurch komplexe Klassen einfacher zu analysieren sind.

Wenn eine Funktion als virtual deklariert und außerhalb der Klassendefinition definiert wird, muss der virtual Bezeichner in der Funktionsdeklaration enthalten sein und nicht in der Definition wiederholt werden.

C ++ 11

Dies gilt auch für das override .

struct VB {
    virtual void f(); // "virtual" goes here.
    void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.

Wenn eine Basisklasse eine virtual Funktion überlastet, werden nur virtual Überladungen, die explizit als virtual werden, verwendet.

struct BOverload {
    virtual void func() {}
    void func(int) {}
};

struct DOverload : BOverload {
    void func() override {}
    void func(int) {}
};

// ...

BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).

Weitere Informationen finden Sie im entsprechenden Thema .

Const Korrektheit

Eine der Hauptanwendungen für this cv-Qualifier ist die const Richtigkeit . Das ist die Praxis zu gewährleisten , dass nur dieses Bedürfnis greift auf ein Objekt in der Lage , zu modifizieren , um das Objekt, und dass jedes (Mitglied oder Nicht-Mitglied) Funktion, braucht nicht zu ändern ist , ein Objekt zu modifizieren keinen Schreibzugriff auf das hat Objekt (direkt oder indirekt). Dies verhindert unbeabsichtigte Änderungen und macht Code weniger fehleranfällig. Es ermöglicht auch jede Funktion, die den Status nicht ändern muss, um entweder ein const oder ein non- const Objekt zu übernehmen, ohne die Funktion neu schreiben oder überladen zu müssen.

const Korrektheit beginnt naturgemäß von unten nach oben: Jede Klassen-Member-Funktion, die den Status nicht ändern muss, wird als const deklariert , sodass sie für const Instanzen aufgerufen werden kann. Auf diese Weise können übergebene Parameter als const deklariert werden, wenn sie nicht geändert werden müssen. Dadurch können Funktionen entweder const oder non- const Objekte aufnehmen, ohne sich zu beschweren, und const -ness kann sich nach außen ausbreiten Weise. Getter sind daher häufig const , ebenso wie alle anderen Funktionen, die den logischen Zustand nicht ändern müssen.

class ConstIncorrect {
    Field fld;

  public:
    ConstIncorrect(const Field& f) : fld(f) {}     // Modifies.

    const Field& get_field()       { return fld; } // Doesn't modify; should be const.
    void set_field(const Field& f) { fld = f; }    // Modifies.

    void do_something(int i) {                     // Modifies.
        fld.insert_value(i);
    }
    void do_nothing()        { }                   // Doesn't modify; should be const.
};

class ConstCorrect {
    Field fld;

  public:
    ConstCorrect(const Field& f) : fld(f) {}       // Not const: Modifies.

    const Field& get_field() const { return fld; } // const: Doesn't modify.
    void set_field(const Field& f) { fld = f; }    // Not const: Modifies.

    void do_something(int i) {                     // Not const: Modifies.
        fld.insert_value(i);
    }
    void do_nothing() const  { }                   // const: Doesn't modify.
};

// ...

const ConstIncorrect i_cant_do_anything(make_me_a_field());
// Now, let's read it...
Field f = i_cant_do_anything.get_field();
  // Error: Loses cv-qualifiers, get_field() isn't const.
i_cant_do_anything.do_nothing();
  // Error: Same as above.
// Oops.

const ConstCorrect but_i_can(make_me_a_field());
// Now, let's read it...
Field f = but_i_can.get_field(); // Good.
but_i_can.do_nothing();          // Good.

Wie aus den Kommentaren zu ConstIncorrect und ConstCorrect , dienen die richtigen Qualifizierungsfunktionen auch der Dokumentation. Wenn eine Klasse const korrekt ist, kann davon ausgegangen werden, dass jede Funktion, die nicht const ist, den Status ändert, und es kann davon ausgegangen werden, dass jede Funktion, die const ist, den Status nicht ändert.



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