C++
Icke-statiska medlemsfunktioner
Sök…
Syntax
// Ringer:
- variable.member_function ();
- variable_pointer-> member_function ();
// Definition:
- ret_type class_name :: member_function () cv-qualifiers {
- kropp;
- }
- ret_type class_name :: member_function () cv-qualifiers {
// Prototyp:
- klass klassnamn {
- virt-specificier ret_type member_function () cv-qualifiers virt-specificier-seq;
- // virt-specificier: "virtuell", om tillämpligt.
- // cv-kval: "const" och / eller "flyktig", om tillämpligt.
- // virt-specificier-seq: "åsidosätta" och / eller "final", om tillämpligt.
- }
- klass klassnamn {
Anmärkningar
En icke- static
medlemsfunktion är en class
/ struct
/ union
medlemsfunktion, som kallas på en viss instans och fungerar på nämnda instans. Till skillnad från static
medlemsfunktioner kan det inte ringas utan att ange en instans.
För information om klasser, strukturer och fackföreningar, se moderföremålet .
Icke-statiska medlemsfunktioner
En class
eller struct
kan ha medlemsfunktioner såväl som medlemsvariabler. Dessa funktioner har syntax som oftast liknar fristående funktioner och kan definieras antingen inom eller utanför klassdefinitionen; om den definieras utanför klassdefinitionen förinställs funktionens namn med klassens namn och omfattningsoperatör ( ::
:).
class CL {
public:
void definedInside() {}
void definedOutside();
};
void CL::definedOutside() {}
Dessa funktioner anropas till en instans (eller referens till en instans) i klassen med dot ( .
) -Operatören, eller en pekare till en instans med pil ( ->
) -operatören, och varje samtal är bundet till instansen funktionen kallades på; när en medlemsfunktion anropas på en instans har den tillgång till alla instansens fält (genom this
pekare ), men kan bara få åtkomst till andra instansfält om dessa instanser tillhandahålls som parametrar.
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.
Dessa funktioner tillåter åtkomst till medlemsvariabler och / eller andra medlemsfunktioner, oavsett antingen variabeln eller funktionens åtkomstmodifierare. De kan också skrivas out-of-order, få åtkomst till medlemsvariabler och / eller ringa medlemsfunktioner som deklarerats före dem, eftersom hela klassdefinitionen måste analyseras innan kompilatorn kan börja sammanställa en klass.
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)); }
};
inkapsling
En vanlig användning av medlemsfunktioner är för inkapsling, med hjälp av en accessor (vanligtvis känd som en getter) och en mutator (allmänt känd som en setter) istället för att få åtkomst till fält direkt.
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);
}
};
Inuti klassen kan encapsulated
fritt nås av alla icke-statiska medlemsfunktioner; utanför klassen regleras tillgången till den genom medlemsfunktioner, med get_encapsulated()
att läsa den och set_encapsulated()
att ändra den. Detta förhindrar oavsiktliga ändringar av variabeln, eftersom separata funktioner används för att läsa och skriva den. [Det finns många diskussioner om getters och setters tillhandahåller eller bryter inkapsling, med goda argument för båda påståenden; en sådan het debatt ligger utanför detta exempel.]
Namn Gömmer & importerar
När en basklass tillhandahåller en uppsättning överbelastade funktioner och en härledd klass lägger till ytterligare överbelastning till uppsättningen döljer detta alla överbelastningar som tillhandahålls av basklassen.
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.
Detta beror på regler för namnupplösning: När namnet söker upp, slutar vi att titta efter rätt namn, även om vi helt klart inte har hittat rätt version av enheten med det namnet (t.ex. med hd.f(s)
); på grund av detta förhindrar överbelastning av funktionen i den härledda klassen namnsökning från att upptäcka överbelastningar i basklassen. För att undvika detta kan en användardeklaration användas för att "importera" namn från basklassen till den härledda klassen, så att de kommer att finnas tillgängliga under namnsökning.
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
Om en härledd klass importerar namn med en användardeklaration, men också deklarerar funktioner med samma signatur som funktioner i basklassen, kommer basklassfunktionerna tyst åsidosättas eller döljas.
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).
};
En användardeklaration kan också användas för att ändra åtkomstmodifierare, förutsatt att den importerade enheten var public
eller protected
i basklassen.
struct ProMem {
protected:
void func() {}
};
struct BecomesPub : ProMem {
using ProMem::func;
};
// ...
ProMem pm;
BecomesPub bp;
pm.func(); // Error: protected.
bp.func(); // Good.
På samma sätt, om vi uttryckligen vill kalla en medlemsfunktion från en viss klass i arvhierarkin, kan vi kvalificera funktionsnamnet när vi ringer till funktionen och specificerar den klassen med namn.
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.
Virtuella medlemsfunktioner
Medlemsfunktioner kan också förklaras virtual
. I det här fallet, om du anropar en pekare eller hänvisar till en instans, kommer de inte åtkomst direkt. snarare kommer de att leta upp funktionen i den virtuella funktionstabellen (en lista med pekare-till-medlem-funktioner för virtuella funktioner, mer känd som vtable
eller vftable
), och använder den för att kalla den version som är lämplig för instansens dynamik (faktisk) typ. Om funktionen kallas direkt, från en variabel i en klass, utförs ingen uppslagning.
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.
Observera att medan pd
är Base*
, och rd
är en Base&
, ringer func()
på någon av de två samtalen Derived::func()
istället för Base::func()
; detta beror på att vtable
för Derived
uppdaterar Base::func()
-posten för att istället peka på Derived::func()
. Omvänt, notera hur vidarebefordring av en instans till slicer()
alltid resulterar i att Base::func()
kallas, även när den godkända instansen är en Derived
; detta är på grund av något som kallas uppgifter skivning, där passage av en Derived
instans till en Base
parameter genom värdet gör det parti av Derived
instans som inte är en Base
instans otillgängliga.
När en medlemsfunktion definieras som virtuell, åsidosätter alla härledda klassmedlemfunktioner med samma signatur den, oavsett om den övervägande funktionen anges som virtual
eller inte. Detta kan göra härledda klasser svårare för programmerare att analysera, eftersom det inte finns någon indikation på vilken funktion (er) som är 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.
};
Observera dock att en härledd funktion endast åsidosätter en basfunktion om deras signaturer matchar; även om en härledd funktion uttryckligen förklaras virtual
, kommer den istället att skapa en ny virtuell funktion om signaturerna inte överensstämmer.
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
Och med C ++ 11, uppsåt att override kan tydligt göras med den sammanhangsberoende nyckelordet override
. Detta säger kompilatorn att programmeraren förväntar sig att den åsidosätter en basklassfunktion, vilket får kompilatorn att utelämna ett fel om den inte åsidosätter någonting.
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
Detta har också fördelen med att berätta för programmerare att funktionen är både virtuell och också deklarerad i minst en basklass, vilket kan göra komplexa klasser lättare att analysera.
När en funktion förklaras virtual
och definieras utanför klassdefinitionen måste den virtual
specifikatorn inkluderas i funktionsdeklarationen och inte upprepas i definitionen.
Detta gäller också för override
.
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
Om en basklass överbelaster en virtual
funktion är endast överbelastningar som uttryckligen anges som virtual
virtuella.
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).
För mer information, se relevant ämne .
Const Rättighet
En av de främsta användningarna för this
cv-kvalificering är const
correctness . Detta är praxis att garantera att endast åtkomst som behöver modifiera ett objekt kan modifiera objektet, och att alla (medlemmar eller icke-medlemmar) funktioner som inte behöver modifiera ett objekt inte har skrivåtkomst till det objekt (vare sig direkt eller indirekt). Detta förhindrar oavsiktliga ändringar, vilket gör att koden blir mindre felaktig. Den tillåter också alla funktioner som inte behöver ändra tillstånd för att kunna ta antingen ett const
eller non- const
objekt utan att behöva skriva om eller överbelasta funktionen.
const
correctness, på grund av dess natur, börjar längst ner: Alla klassmedlemfunktioner som inte behöver ändra tillstånd förklaras som const
, så att det kan kallas på const
instanser. Detta tillåter i sin tur att förbipasserade parametrar deklareras const
när de inte behöver modifieras, vilket gör att funktioner kan antingen ta const
eller non- const
objekt utan att klaga, och const
-ness kan sprida sig utåt i detta sätt. På grund av detta är getters ofta const
, liksom alla andra funktioner som inte behöver ändra logiskt tillstånd.
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.
Som illustreras av kommentarerna om ConstIncorrect
och ConstCorrect
cv-kvalificerade funktioner också som dokumentation. Om en klass är const
korrekt kan alla funktioner som inte const
antas säkert ändra tillstånd, och alla funktioner som är const
kan antas att inte ändra tillstånd.