C++
Klasser / Konstruktioner
Sök…
Syntax
- variabel.member_var = konstant;
- variable.member_function ();
- variabel_pointer-> medlem_var = konstant;
- variable_pointer-> member_function ();
Anmärkningar
Observera att den enda skillnaden mellan struct
och class
nyckelord är att som standard, medlemsvariabler, medlemsfunktioner och basklasser av en struct
är public
, medan det i en class
de är private
. C ++ -programmerare tenderar att kalla det en klass om de har konstruktörer och förstörare och förmågan att verkställa sina egna invarianter; eller en struktur om det bara är en enkel samling värderingar, men C ++ -språket i sig gör ingen skillnad.
Grundläggande klass
En klass är en användardefinierad typ. En klass introduceras med sökordet class
, struct
eller union
. Vid användning i allmänhet avser termen "klass" vanligtvis endast klasser som inte är fackliga.
En klass är en samling klassmedlemmar som kan vara:
- medlemsvariabler (även kallad "fält"),
- medlemsfunktioner (även kallad "metoder"),
- medlemstyper eller typedefs (t.ex. "kapslade klasser"),
- medlemsmallar (av alla slag: variabel, funktion, klass eller aliasmall)
De class
och struct
sökord, så kallade klass nycklar, är i stort sett utbytbara, förutom att standardåtkomstspecifikationen för medlemmar och baser är "privat" för en klass deklareras med class
tangenten och "allmänheten" för en klass deklareras med struct
eller union
nyckel (jfr Access-modifierare ).
Till exempel är följande kodavsnitt identiska:
struct Vector
{
int x;
int y;
int z;
};
// are equivalent to
class Vector
{
public:
int x;
int y;
int z;
};
Genom att förklara en klass läggs en ny typ till ditt program, och det är möjligt att instansera objekt från den klassen av
Vector my_vector;
Medlemmar i en klass får åtkomst med dot-syntax.
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;
Åtkomstspecifikationer
Det finns tre nyckelord som fungerar som åtkomstspecifikationer . Dessa begränsar åtkomsten till klassmedlemmar som följer specifikatorn, tills en annan specifikator ändrar åtkomstnivån igen:
Nyckelord | Beskrivning |
---|---|
public | Alla har tillgång |
protected | Endast klassen själv, härledda klasser och vänner har tillgång |
private | Endast klassen själv och vänner har tillgång |
När typen definieras enligt class
nyckelordet är standardåtkomstspecifikationen private
, men om typen definieras enligt struct
nyckelordet är standardåtkomstspecifikationen 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
Åtkomstspecifikatorer används mest för att begränsa åtkomst till interna fält och metoder och tvinga programmeraren att använda ett specifikt gränssnitt, till exempel för att tvinga användning av getters och seters istället för att direkt hänvisa till en variabel:
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;
};
Att använda protected
är användbart för att tillåta att vissa funktioner av typen endast är tillgängliga för de härledda klasserna, till exempel i följande kod är metoden calculateValue()
endast tillgänglig för klasser som härrör från basklassen Plus2Base
, som 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; }
};
Observera att friend
nyckelordet kan användas för att lägga till åtkomstundantag för funktioner eller typer för åtkomst till skyddade och privata medlemmar.
De public
, protected
och private
nyckelorden kan också användas för att bevilja eller begränsa åtkomst till underobjekt av basklass. Se Arvsexemplet .
Arv
Klasser / strukturer kan ha arvsrelationer.
Om en klass / struktur B
ärver från en klass / struktur A
betyder det att B
har som förälder A
Vi säger att B
är en härledd klass / struktur från A
, och A
är basklassen / strukturen.
struct A
{
public:
int p1;
protected:
int p2;
private:
int p3;
};
//Make B inherit publicly (default) from A
struct B : A
{
};
Det finns tre former av arv för en klass / struktur:
-
public
-
private
-
protected
Observera att standardarvet är detsamma som medlemmars standardsynlighet: public
om du använder struct
nyckelordet och private
för class
sökord.
Det är till och med möjligt att få en class
härledd från en struct
(eller vice versa). I det här fallet kontrolleras standardarvet av barnet, så en struct
som härrör från en class
kommer som standard till allmän arv, och en class
som härrör från en struct
kommer att ha privat arv som standard.
public
arv:
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
arv:
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
arv:
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
Observera att även om protected
arv är tillåten är den faktiska användningen av det sällsynt. Ett exempel på hur protected
arv används vid applicering är delvis basklassspecialisering (vanligtvis benämnd "kontrollerad polymorfism").
När OOP var relativt nytt, sades ofta (offentligt) arv att modellera ett "IS-A" -förhållande. Det vill säga allmän arv är korrekt endast om en instans av den härledda klassen också är en instans av basklassen.
Detta förfinades senare i Liskovs substitutionsprincip : allmän arv ska endast användas när / om en instans av den härledda klassen kan ersättas av en instans av basklassen under alla möjliga omständigheter (och fortfarande är vettigt).
Privat arv sägs vanligtvis förkroppsliga en helt annan relation: "implementeras i termer av" (ibland kallas en "HAS-A" -förhållande). En Stack
klass kan till exempel ärva privat från en Vector
klass. Privat arv har en mycket större likhet med aggregering än med offentlig arv.
Skyddad arv används nästan aldrig, och det finns ingen allmän överenskommelse om vilken typ av relation den förkroppsligar.
Virtuell arv
När du använder arv kan du ange det virtual
nyckelordet:
struct A{};
struct B: public virtual A{};
När klass B
har virtuell bas A
betyder det att A
kommer att ligga i de flesta härledda arvsklasserna och därmed att de flesta härledda klasser också ansvarar för att initialisera den virtuella basen:
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`
}
Om vi avmarkerar kommentarer /*A(88)*/
vi inget fel eftersom C
nu initialiserar sin indirekta virtuella bas A
Observera också att när vi skapar variabelt object
är mest härledda klassen C
, så C
är ansvarig för att skapa (ringa konstruktör av) A
och därmed värdet på A::member
är 88
, inte 5
(som det skulle vara om vi skulle skapa objekt av typ B
).
Det är användbart när du löser diamantproblemet .:
A A A
/ \ | |
B C B C
\ / \ /
D D
virtual inheritance normal inheritance
B
och C
ärver från A
, och D
ärver från B
och C
, så det finns två instanser av A
i D
! Detta resulterar i tvetydighet när du går åt medlem i A
till D
, eftersom kompilatorn inte har något sätt att veta från vilken klass du vill komma åt den medlemmen (den som B
ärver eller den som ärvs av C
?) .
Virtuell arv löser detta problem: Eftersom den virtuella basen bara finns i de flesta härledda objekt kommer det bara att finnas ett exempel på A
i 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
}
};
Att ta bort kommentarerna löser tvetydigheten.
Multipel ärft
Bortsett från enstaka arv:
class A {};
class B : public A {};
Du kan också ha flera arv:
class A {};
class B {};
class C : public A, public B {};
C
kommer nu att ha arv från A
och B
samtidigt.
Obs: detta kan leda till oklarhet om samma namn används i flera ärvda class
eller struct
. Var försiktig!
Tvetydighet i multipel ärft
Flera arv kan vara till hjälp i vissa fall, men ibland udda slags problem när man använder flera arv.
Exempel: Två basklasser har funktioner med samma namn som inte åsidosätts i härledd klass och om du skriver kod för att få åtkomst till den funktionen med hjälp av objekt från härledd klass, visar kompilatorn fel eftersom den inte kan bestämma vilken funktion att ringa. Här är en kod för denna typ av tvetydighet i flera arv.
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( )
}
Men det här problemet kan lösas med hjälp av omfattningsupplösningsfunktion för att specificera vilken funktion som ska klassas antingen base1 eller base2:
int main()
{
obj.base1::function( ); // Function of class base1 is called.
obj.base2::function( ); // Function of class base2 is called.
}
Åtkomst till klassmedlemmarna
Att få tillgång till medlemsvariabler och medlemsfunktioner för ett objekt av en klass, .
operatör används:
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();
Vid åtkomst till medlemmarna i en klass via en pekare används ->
operatören vanligtvis. Alternativt kan instansen avskaffas och .
operatör som används, även om detta är mindre vanligt:
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();
Vid åtkomst till statiska klassmedlemmar används ::
-operatören, men på klassens namn istället för en instans av den. Alternativt kan den statiska medlemmen nås från en instans eller en pekare till en instans med hjälp av .
eller ->
operatör respektive med samma syntax som åtkomst till icke-statiska medlemmar.
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();
Bakgrund
Operatören ->
behövs eftersom operatören för medlemstillträde .
har företräde framför operatören för dereferencing *
.
Man kan förvänta sig att *pa
skulle leda till p
(resulterar i en referens till objektet p
pekar på) och sedan få åtkomst till dess medlem a
. Men i själva verket försöker den få åtkomst till medlemmen a
av p
och sedan återvända den. Dvs *pa
motsvarar *(pa)
. I exemplet ovan skulle detta resultera i ett kompilatorfel på grund av två fakta: För det första är p
en pekare och har inte en medlem a
. För det andra, a
är ett heltal och därmed inte kan dereferenced.
Den ovanligt använda lösningen på detta problem skulle vara att uttryckligen kontrollera förekomsten: (*p).a
Istället används ->
operatören nästan alltid. Det är en kort hand att först ställa in pekaren och sedan komma åt den. Dvs (*p).a
är exakt samma som p->a
.
Operatören ::
operatören för omfattning som används på samma sätt som åtkomst till en medlem i ett namnområde. Detta beror på att en statisk klassmedlem anses ligga inom den klassens omfattning, men inte anses vara medlem i instanser av den klassen. Användningen av normal .
och ->
är också tillåtet för statiska medlemmar, trots att de inte är instansmedlemmar, av historiska skäl; detta kan användas för att skriva generisk kod i mallar, eftersom den som ringer inte behöver vara bekymrad över om en given medlemsfunktion är statisk eller icke-statisk.
Privat arv: begränsa basklassgränssnittet
Privat arv är användbart när det krävs för att begränsa klassens offentliga gränssnitt:
class A {
public:
int move();
int turn();
};
class B : private A {
public:
using A::turn;
};
B b;
b.move(); // compile error
b.turn(); // OK
Detta tillvägagångssätt förhindrar effektivt en åtkomst till A-offentliga metoder genom att kasta till A-pekaren eller referens:
B b;
A& a = static_cast<A&>(b); // compile error
Vid offentligt arv kommer sådan gjutning att ge tillgång till alla offentliga A-metoder trots alternativa sätt att förhindra detta i härledd B, som att gömma sig:
class B : public A {
private:
int move();
};
eller privat med:
class B : public A {
private:
using A::move;
};
då är det möjligt för båda fallen:
B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK
Slutklasser och strukturer
Att hämta en klass kan vara förbjudet med den final
specifikatorn. Låt oss förklara en slutklass:
class A final {
};
Nu kommer varje försök att underklassera det att orsaka ett sammanställningsfel:
// Compilation error: cannot derive from final class:
class B : public A {
};
Slutklass kan visas var som helst i klasshierarkin:
class A {
};
// OK.
class B final : public A {
};
// Compilation error: cannot derive from final class B.
class C : public B {
};
Vänskap
Den friend
nyckelordet används för att ge andra klasser och funktioner tillgång till privata och skyddade medlemmar i klassen, även genom de definieras utanför class`s omfattning.
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
Kapslade klasser / strukturer
En class
eller struct
kan också innehålla en annan class
/ struct
definition i sig själv, som kallas en "kapslad klass"; i denna situation kallas den innehållande klassen "den slutna klassen". Definitionen av kapslad klass anses vara medlem i den slutna klassen, men är annars separat.
struct Outer {
struct Inner { };
};
Från utanför den inneslutna klassen åtkomst till kapslade klasser med hjälp av omfattningsoperatören. Inifrån den inneslutande klassen kan dock kapslade klasser användas utan kval:
struct Outer {
struct Inner { };
Inner in;
};
// ...
Outer o;
Outer::Inner i = o.in;
Som med en icke-kapslad class
/ struct
, kan medlemsfunktioner och statiska variabler definieras antingen inom en kapslad klass eller i det bifogade namnområdet. De kan emellertid inte definieras inom den bifogade klassen på grund av att den anses vara en annan klass än den kapslade klassen.
// 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() {}
Som med icke-kapslade klasser, kan kapslade klasser redovisas och definieras senare, förutsatt att de definieras innan de används direkt.
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; }
}
Innan C ++ 11 hade kapslade klasser bara tillgång till typnamn, static
medlemmar och räknare från den bifogade klassen; alla andra medlemmar som definierades i den slutande klassen var utanför gränserna.
Från C ++ 11 behandlas kapslade klasser och medlemmar därav som om de var friend
till den medföljande klassen och kan komma åt alla dess medlemmar enligt de vanliga åtkomstreglerna; om medlemmar i den kapslade klassen kräver förmågan att utvärdera en eller flera icke-statiska medlemmar i den bifogade klassen måste de därför ges en instans:
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;
};
Omvänt behandlas den slutna klassen inte som en vän till den kapslade klassen och kan därför inte få åtkomst till sina privata medlemmar utan att uttryckligen ges tillstånd.
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.
}
};
Vänner till en kapslad klass betraktas inte automatiskt som vänner till den inneslutna klassen; om de också måste vara vänner till den slutande klassen måste detta förklaras separat. Omvänt, eftersom den inneslutande klassen inte automatiskt betraktas som en vän till den kapslade klassen, kommer inte heller vännerna i den inneslutna klassen att betraktas som vänner till den kapslade klassen.
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.
}
Som med alla andra klassmedlemmar kan kapslade klasser bara namnges utanför klassen om de har allmän tillgång. Du får dock komma åt dem oavsett åtkomstmodifierare, så länge du inte uttryckligen namnger dem.
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.
Du kan också skapa ett typalias för en kapslad klass. Om ett typalias ingår i den bifogade klassen, kan den kapslade typen och typaliaset ha olika åtkomstmodifierare. Om typaliaset ligger utanför den slutna klassen kräver det att antingen den kapslade klassen eller en typedef
denna är offentlig.
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.
Som med andra klasser kan kapslade klasser både härledas från eller härledas av andra klasser.
struct Base {};
struct Outer {
struct Inner : Base {};
};
struct Derived : Outer::Inner {};
Detta kan vara användbart i situationer där den slutna klassen härrör från en annan klass, genom att låta programmeraren att uppdatera den kapslade klassen vid behov. Detta kan kombineras med en typedef för att ge ett konsekvent namn för varje innesluten klass:
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();
I ovanstående fall BaseOuter
både BaseOuter
och DerivedOuter
medlemstypen Inner
, som BaseInner_
respektive DerivedInner_
. Detta gör att kapslade typer kan härledas utan att bryta gränssnittet i den slutna klassen och tillåter att den kapslade typen används polymorf.
Medlemstyper och alias
En class
eller struct
kan också definiera alias för medlemstyp, som är typaliaser som ingår i och behandlas som medlemmar i själva klassen.
struct IHaveATypedef {
typedef int MyTypedef;
};
struct IHaveATemplateTypedef {
template<typename T>
using MyTemplateTypedef = std::vector<T>;
};
Liksom statiska medlemmar får man tillgång till dessa typedefs med hjälp av omfattningsoperatören, ::
.
IHaveATypedef::MyTypedef i = 5; // i is an int.
IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.
Liksom med alias av normal typ tillåts varje alias för medlemstyp att hänvisa till vilken typ som helst som definierats eller alias före, men inte efter, sin definition. På samma sätt kan en typedef utanför klassdefinitionen hänvisa till alla tillgängliga typdefs inom klassdefinitionen, förutsatt att den kommer efter klassdefinitionen.
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.
Alias för medlemstyp kan deklareras med valfri åtkomstnivå och respekterar lämplig åtkomstmodifierare.
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.
};
Detta kan användas för att tillhandahålla en abstraktionsnivå, så att en klassdesigner kan ändra sina interna funktioner utan att bryta kod som förlitar sig på den.
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();
I denna situation, om hjälpare klassen ändras från SomeComplexType
till någon annan typ endast typedef
och friend
skulle deklaration behöva ändras; så länge som hjälparklassen har samma funktionalitet fungerar alla koder som använder den som Something::MyHelper
stället för att ange den med namn fortfarande utan några ändringar. På detta sätt minimerar vi mängden kod som behöver modifieras när den underliggande implementeringen ändras, så att typnamnet bara behöver ändras på en plats.
Detta kan också kombineras med decltype
om man så önskar.
class SomethingElse {
AnotherComplexType<bool, int, SomeThirdClass> helper;
public:
typedef decltype(helper) MyHelper;
private:
InternalVariable<MyHelper> ivh;
// ...
public:
MyHelper& get_helper() const { return helper; }
// ...
};
I den här situationen kommer ändring av implementeringen av SomethingElse::helper
automatiskt att ändra typefef för oss på grund av decltype
. Detta minimerar antalet nödvändiga modifieringar när vi vill byta helper
, vilket minimerar risken för mänskliga misstag.
Som med allt kan man dock ta för långt. Om typnamnet bara används en eller två gånger internt och noll gånger externt, till exempel, finns det inget behov att ange ett alias för det. Om det används hundratals eller tusentals gånger i ett projekt, eller om det har ett tillräckligt länge namn, kan det vara användbart att tillhandahålla det som en typ i stället för att alltid använda det i absoluta termer. Man måste balansera framåtkompatibilitet och bekvämlighet med mängden onödigt brus som skapas.
Detta kan också användas med mallklasser för att ge åtkomst till mallparametrarna utanför klassen.
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);
Detta används vanligtvis med behållare, som vanligtvis kommer att tillhandahålla deras elementtyp, och andra hjälptyper, som alias för medlemstyp. De flesta av behållarna i C ++ -biblioteket till exempel tillhandahåller följande 12 hjälptyper, tillsammans med andra specialtyper de kan behöva.
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;
};
Före C ++ 11 användes det också ofta för att tillhandahålla en "mall typedef
", eftersom funktionen ännu inte var tillgänglig; dessa har blivit lite mindre vanliga med införandet av aliasmallar, men är fortfarande användbara i vissa situationer (och kombineras med aliasmallar i andra situationer, vilket kan vara mycket användbart för att få enskilda komponenter av en komplex typ som en funktionspekare ). De använder vanligtvis type
för deras typalias.
template<typename T>
struct TemplateTypedef {
typedef T type;
}
TemplateTypedef<int>::type i; // i is an int.
Detta användes ofta med typer med flera mallparametrar för att tillhandahålla ett alias som definierar en eller flera av parametrarna.
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>.
Statiska klassmedlemmar
En klass får också ha static
medlemmar, som kan vara antingen variabler eller funktioner. Dessa anses vara i klassens omfattning, men behandlas inte som vanliga medlemmar; de har statisk lagringsvaraktighet (de finns från början av programmet till slutet), är inte bundna till en viss instans av klassen och det finns bara en kopia för hela klassen.
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)
Statiska medlemsvariabler anses inte vara definierade i klassen, endast deklarerade och har således sin definition utanför klassdefinitionen; programmeraren får, men inte krävs, initialisera statiska variabler i sin definition. När du definierar medlemsvariablerna utelämnas nyckelordet static
.
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.
På grund av detta kan statiska variabler vara ofullständiga typer (förutom void
), så länge de senare definieras som en komplett typ.
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];
Statiska medlemsfunktioner kan definieras inom eller utanför klassdefinitionen, som med normala medlemsfunktioner. Liksom med statiska medlemsvariabler utelämnas nyckelordet static
när statiska medlemsfunktioner definieras utanför klassdefinitionen.
// 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; }
Om en statisk medlemsvariabel förklaras const
men inte volatile
och är av en integrerad eller uppräkningstyp, kan den initialiseras vid deklarationen, inom klassdefinitionen.
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.
Från C ++ 11 kan statiska medlemsvariabler av LiteralType
typer (typer som kan konstrueras vid sammanställningstid, enligt constexpr
regler) också deklareras som constexpr
; i så fall måste de initialiseras inom klassdefinitionen.
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.
Om en const
eller constexpr
statisk medlemsvariabel används odr (informellt, om den har adressen tagits eller tilldelas en referens), måste den fortfarande ha en separat definition utanför klassdefinitionen. Denna definition får inte innehålla en initialisering.
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.
Eftersom statiska medlemmar inte är bundna till en viss instans, kan de nås med hjälp av omfattningsoperatören, ::
.
std::string str = Example::static_str;
De kan också nås som om de var normala, icke-statiska medlemmar. Detta är av historisk betydelse, men används mindre vanligt än omfattningsoperatören för att förhindra förvirring om en medlem är statisk eller icke-statisk.
Example ex;
std::string rts = ex.static_str;
Klassmedlemmar har tillgång till statiska medlemmar utan att kvalificera sin räckvidd, som för icke-statiska klassmedlemmar.
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;
De kan inte vara mutable
, och inte heller behöva de vara det; eftersom de inte är bundna till någon given instans påverkar inte statiska medlemmar huruvida en instans är eller inte const.
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.
Statiska medlemmar respekterar åtkomstmodifierare, precis som icke-statiska medlemmar.
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.
Eftersom de inte är bundna till en given instans har statiska medlemsfunktioner ingen this
pekare. på grund av detta kan de inte komma åt icke-statiska medlemsvariabler om de inte har godkänt en instans.
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.
};
På grund av att inte ha en this
pekare, kan deras adresser inte lagras i pekare till medlem funktioner, och i stället lagras i vanliga pekare till funktioner.
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.
På grund av att inte ha en this
pekare, de kan inte heller vara const
eller volatile
, inte heller kan de ha REF-kval. De kan inte heller vara virtuella.
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.
};
Eftersom de inte är bundna till en given instans behandlas statiska medlemsvariabler effektivt som speciella globala variabler; de skapas när programmet startar och förstörs när det går ut, oavsett om det finns några instanser i klassen. Endast en enda kopia av varje statisk medlemsvariabel finns (såvida inte variabeln förklaras thread_local
(C ++ 11 eller senare), i vilket fall finns det en kopia per tråd).
Statiska medlemsvariabler har samma koppling som klassen, oavsett om klassen har extern eller intern länk. Lokala klasser och namngivna klasser får inte ha statiska medlemmar.
Icke-statiska medlemsfunktioner
En klass kan ha icke-statiska medlemsfunktioner , som fungerar i enskilda instanser av klassen.
class CL {
public:
void member_function() {}
};
Dessa funktioner kallas på en instans av klassen, på så sätt:
CL instance;
instance.member_function();
De kan definieras antingen inom eller utanför klassdefinitionen; om de definieras utanför, anges de som klassens omfattning.
struct ST {
void defined_inside() {}
void defined_outside();
};
void ST::defined_outside() {}
De kan vara CV-kvalificerade och / eller ref-kvalificerade , vilket påverkar hur de ser instansen de uppmanas; funktionen kommer att se instansen som att ha de angivna cv-kvalificeringarna, om några. Vilken version som kallas kommer att baseras på instansens cv-kval. Om det inte finns någon version med samma cv-kvalificeringar som instansen, kommer en mer cv-kvalificerad version att kallas om den är tillgänglig.
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.
Medlemsfunktionens ref-kvalificeringar indikerar om funktionen är avsedd att kallas på rvaluexempel eller använda samma syntax som cv-kvalifikationer för funktionen.
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-kval och ref-kval kan också kombineras vid behov.
struct BothCVAndRef {
void func() const& {} // Called on normal instances. Sees instance as const.
void func() && {} // Called on temporary instances.
};
De kan också vara virtuella ; detta är grundläggande för polymorfism och gör det möjligt för en eller flera barnklass att ge samma gränssnitt som förälderklassen, samtidigt som de tillhandahåller sin egen funktionalitet.
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().
För mer information, se här .
Namnlösa struktur / klass
Namnlösa struct
är tillåten (typen har inget namn)
void foo()
{
struct /* No name */ {
float x;
float y;
} point;
point.x = 42;
}
eller
struct Circle
{
struct /* No name */ {
float x;
float y;
} center; // but a member name
float radius;
};
och senare
Circle circle;
circle.center.x = 42.f;
men INTE anonym struct
(namngiven typ och icke namngivet objekt)
struct InvalidCircle
{
struct /* No name */ {
float centerX;
float centerY;
}; // No member either.
float radius;
};
Obs: Vissa kompilatorer tillåter anonym struct
som förlängning .
lamdba kan ses som en speciell namngiven
struct
.decltype
tillåter att hämta typen av namngivenstruct
:decltype(circle.point) otherPoint;
namngiven
struct
kan vara parameter för mallmetod: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"; }