C++ klasser
En klass i C++ är en användardefinierad typ eller datastruktur som deklareras med nyckelordsklass som har data och funktioner (även kallade medlemsvariabler och medlemsfunktioner ) som sina medlemmar vars åtkomst styrs av de tre åtkomstspecifikationerna
privat , skyddad eller offentlig . Som standard är åtkomst till medlemmar i en C++-klass privat . De privata medlemmarna är inte tillgängliga utanför klassen; de kan endast nås genom klassens metoder. De offentliga medlemmarna bildar ett gränssnitt till klassen och är tillgängliga utanför klassen.
Förekomster av en klassdatatyp kallas objekt och kan innehålla medlemsvariabler, konstanter , medlemsfunktioner och överbelastade operatorer definierade av programmeraren.
Skillnader mellan en struktur och en klass i C++
I C++ har en klass som definieras med klassnyckelordet
privata medlemmar och basklasser som standard . En struktur är en klass som definieras med nyckelordet struct .
Dess medlemmar och basklasser är offentliga som standard. I praktiken är strukturer vanligtvis reserverade för data utan funktioner. När man härleder en struktur från en klass/struktur, är standardåtkomstspecifikationen för en basklass/struktur offentlig. Och när man härleder en klass är standardåtkomstspecifikationen privat.
Sammanlagda klasser
En aggregerad klass är en klass utan användardeklarerade konstruktörer, inga privata eller skyddade icke-statiska datamedlemmar, inga basklasser och inga virtuella funktioner. En sådan klass kan initieras med en klammerinnesluten kommaseparerad lista med initialiseringssatser. Följande kod har samma semantik i både C och C++.
struct C { int a ; dubbel b ; }; struct D { int a ; dubbel b ; Cc ; _ }; // initiera ett objekt av typ C med en initieringslista C c = { 1 , 2.0 }; // D har ett underaggregat av typ C. I sådana fall kan initialiseringssatser kapslas D d = { 10 , 20.0 , { 1 , 2.0 }};
POD-strukturer
En POD-struktur (Plain Old Data Structure) är en aggregerad klass som inte har några icke-statiska datamedlemmar av typen icke-POD-struktur, icke-POD-union (eller array av sådana typer) eller referens, och som inte har någon användar- definierad tilldelningsoperatör och ingen användardefinierad destruktor . En POD-struktur kan sägas vara C++-ekvivalenten till en C- struktur
. I de flesta fall kommer en POD-struktur att ha samma minneslayout som en motsvarande struktur som deklareras i C. Av denna anledning brukar POD-strukturer ibland kallas "C-style structs".
- Datamedlemmar tilldelas så att senare medlemmar har högre adresser inom ett objekt, utom där de är åtskilda av en åtkomstspecificerare.
- Två POD-strukturtyper är layoutkompatibla om de har samma antal ickestatiska datamedlemmar, och motsvarande ickestatiska datamedlemmar (i ordning) har layoutkompatibla typer.
- En POD-struktur kan innehålla namnlös utfyllnad .
- En pekare till ett POD-strukturobjekt, lämpligt konverterat med hjälp av en omtolkning av cast , pekar på dess initiala medlem och vice versa, vilket antyder att det inte finns någon utfyllnad i början av en POD-struktur.
- En POD-struktur kan användas med makrot offset .
Deklaration och användning
C++-klasser har sina egna medlemmar. Dessa medlemmar inkluderar variabler (inklusive andra strukturer och klasser), funktioner (specifika identifierare eller överbelastade operatorer) kända som metoder, konstruktörer och destruktörer. Medlemmar förklaras vara antingen offentligt eller privat tillgängliga med hjälp av de offentliga: respektive
privata :
åtkomstspecifikationerna. Varje medlem som påträffas efter en specificator kommer att ha tillhörande åtkomst tills en annan specificator påträffas. Det finns också arv mellan klasser som kan använda sig av den skyddade:
specificeraren.
Global och lokal klass
En klass definierad utanför alla metoder är en global klass eftersom dess objekt kan skapas var som helst i programmet. Om det är definierat inom en funktionskropp är det en lokal klass eftersom objekt i en sådan klass är lokala för funktionsomfånget.
Grunddeklaration och medlemsvariabler
Klasser deklareras med nyckelordet class
eller struct
. Medlemmarnas förklaring placeras i denna förklaring.
struct Person { strängnamn ; _ int ålder ; };
|
class Person { public : strängnamn ; _ int ålder ; };
|
Ovanstående definitioner är funktionellt likvärdiga. Båda koderna kommer att definiera objekt av typen Person
som att ha två offentliga datamedlemmar, namn
och ålder
. Semikolon efter de avslutande klammerparenteserna är obligatoriska .
Efter en av dessa deklarationer (men inte båda), kan Person
användas enligt följande för att skapa nydefinierade variabler av persondatatypen
:
#include <iostream> #include <string> struct Person { std :: strängnamn ; _ int ålder ; }; int main () { Person a ; Person b ; a . name = "Calvin" ; b . name = "Hobbes" ; a . ålder = 30 ; b . ålder = 20 ; std :: cout << a . namn << ": " << a . ålder << std :: endl ; std :: cout << b . namn << ": " << b . ålder << std :: endl ; }
Om du kör ovanstående kod matas ut
Calvin: 30 Hobbes: 20
Medlemsfunktioner
En viktig funktion i C++-klassen och -strukturen är medlemsfunktioner . Varje datatyp kan ha sina egna inbyggda funktioner (kallade metoder) som har tillgång till alla (offentliga och privata) medlemmar av datatypen. I kroppen av dessa icke-statiska medlemsfunktioner kan nyckelordet detta
användas för att referera till objektet för vilket funktionen anropas. Detta implementeras vanligtvis genom att skicka objektets adress som ett implicit första argument till funktionen. persontypen
ovan som ett exempel igen:
#include <iostream> class Person { public : void Skriv ut () const ; privat : std :: strängnamn_ ; _ int age_ = 5 ; //C++ 11 }; void Person::Print () const { std :: cout << name_ << ':' << age_ << '\n' ; // "name_" och "age_" är medlemsvariablerna. Nyckelordet "detta" är ett //-uttryck vars värde är adressen till objektet för vilket medlemmen // anropades. Dess typ är "const Person*", eftersom funktionen deklareras // const. }
I exemplet ovan deklareras Print -funktionen i klassens brödtext och definieras genom att kvalificera den med namnet på klassen följt av
::
. Både name_
och age_
är privata (standard för klass) och Print
deklareras som offentligt vilket är nödvändigt om det ska användas utanför klassen.
Med medlemsfunktionen Skriv ut
kan utskriften förenklas till:
a . Skriv ut (); b . Skriv ut ();
där a
och b
ovan kallas avsändare, och var och en av dem kommer att referera till sina egna medlemsvariabler när Print()-
funktionen körs.
Det är vanligt att separera klass- eller strukturdeklarationen (kallas dess gränssnitt) och definitionen (kallas dess implementering) i separata enheter. Gränssnittet, som behövs av användaren, hålls i en header och implementeringen hålls separat i antingen käll- eller kompilerad form.
Arv
Layouten för icke-POD-klasser i minnet är inte specificerad av C++-standarden. Till exempel implementerar många populära C++-kompilatorer enstaka arv genom sammanlänkning av de överordnade klassfälten med de underordnade klassfälten, men detta krävs inte av standarden. Detta val av layout gör att referera till en härledd klass via en pekare till den överordnade klasstypen till en trivial operation.
Tänk till exempel
struct P { int x ; };
struktur C : P { int y ; };
En instans av P
med en P* p
som pekar på den kan se ut så här i minnet:
┏━━━━┓ ┃P::x┃ ┗━━━━┛ ↑ p
En instans av C
med en P* p
som pekar på den kan se ut så här:
┏━━━━┳━━━━┓ ┃P::x┃C::y┃ ┗━━━━┻━━━━┛ ↑ p
Därför kan vilken kod som helst som manipulerar fälten i ett P
-objekt manipulera P
-fälten inuti C
-objektet utan att behöva överväga något om definitionen av C
:s fält. Ett korrekt skrivet C++-program bör i alla fall inte göra några antaganden om layouten av ärvda fält. omvandlingsoperatorerna static_cast eller dynamic_cast kommer att säkerställa att pekare konverteras korrekt från en typ till en annan.
Multipelarv är inte lika enkelt. Om en klass D
ärver P
och C
måste båda föräldrarnas fält lagras i någon ordning, men (högst) en av föräldraklasserna kan placeras längst fram i den härledda klassen. Närhelst kompilatorn behöver konvertera en pekare från D
-typen till antingen P
eller C
, kommer kompilatorn att tillhandahålla en automatisk konvertering från adressen för den härledda klassen till adressen för basklassfälten (vanligtvis är detta en enkel offsetberäkning) .
För mer om multipelarv, se virtuellt arv .
Överbelastade operatörer
I C++ kan operatörer , såsom + - * /
, överbelastas för att passa programmerares behov. Dessa operatörer kallas överbelastningsbara operatörer .
Enligt konvention bör överbelastade operatörer bete sig nästan likadant som de gör i inbyggda datatyper ( int
, float
, etc.), men detta krävs inte. Man kan deklarera en struktur som kallas Integer
där variabeln verkligen lagrar ett heltal, men genom att anropa Integer * Integer
kan summan, istället för produkten, av heltal returneras:
0
struct Integer { Heltal () = default ; Heltal ( int j ) : i { j } { } Heltalsoperator * ( const Heltal & k ) const { returnera Heltal ( i + k . i ); } int i = ; };
Koden ovan använde en konstruktor för att "konstruera" returvärdet. För en tydligare presentation (även om detta kan minska programmets effektivitet om kompilatorn inte kan optimera satsen till motsvarande ovan), kan ovanstående kod skrivas om som:
Heltalsoperator * ( const Heltal & k ) const { Heltal m ; _ m . i = i + k . jag ; returnera m ; }
Programmerare kan också lägga in en prototyp av operatören i struct-
deklarationen och definiera operatörens funktion i det globala omfånget:
0
struct Integer { Heltal () = default ; Heltal ( int j ) : i { j } { } Heltalsoperator * ( const Heltal & k ) const ; int i = ; }; Heltal Heltal :: operator * ( const Heltal & k ) const { returnera Heltal ( i * k . i ); }
i
ovan representerar avsändarens egen medlemsvariabel, medan ki
representerar medlemsvariabeln från argumentvariabeln k
.
Nyckelordet const
förekommer två gånger i ovanstående kod. Den första förekomsten, argumentet const heltal& k
, indikerade att argumentvariabeln inte kommer att ändras av funktionen. Den andra förekomsten i slutet av deklarationen lovar kompilatorn att avsändaren inte skulle ändras av funktionskörningen.
I const heltal& k
betyder et-tecken (&) "pass genom referens " . När funktionen anropas kommer en referens till variabeln att skickas till funktionen, snarare än värdet på variabeln.
Samma överbelastningsegenskaper ovan gäller även för klasser.
Observera att aritet , associativitet och prioritet för operatorer inte kan ändras.
Binära överbelastningsbara operatörer
Binära operatorer (operatorer med två argument) överbelastas genom att deklarera en funktion med en "identifierare" operator (något) som anropar ett enda argument. Variabeln till vänster om operatorn är avsändaren medan den till höger är argumentet.
Heltal i = 1 ; /* vi kan initiera en strukturvariabel på detta sätt som om vi anropar en konstruktor med endast det första argumentet specificerat. */ Heltal j = 3 ; /* variabelnamn är oberoende av namnen på medlemsvariablerna i strukturen. */ Heltal k = i * j ; std :: cout << k . i << '\n' ;
'3' skulle skrivas ut.
Följande är en lista över binära överbelastningsbara operatorer:
Operatör | Allmänt bruk |
---|---|
+ - * / % | Aritmetisk beräkning |
^ & ! << >> | Bitvis uträkning |
< > == != <= >= | Logisk jämförelse |
&& | Logisk konjunktion |
|| | Logisk disjunktion |
= <<= >>= | Sammansatt uppdrag |
, | (ingen allmän användning) |
Operatorn '=' (tilldelning) mellan två variabler av samma strukturtyp är som standard överbelastad för att kopiera hela innehållet i variablerna från en till en annan. Den kan skrivas över med något annat om det behövs.
Operatörer måste överbelastas en efter en, med andra ord är ingen överbelastning förknippad med varandra. Till exempel <
inte nödvändigtvis motsatsen till >
.
Ovanligt överbelastade operatörer
Medan vissa operatorer, som specificerats ovan, tar två termer, avsändare till vänster och argumentet till höger, har vissa operatorer bara ett argument - avsändaren, och de sägs vara "unära". Exempel är det negativa tecknet (när inget står till vänster om det) och det "logiska INTE " ( utropstecken , !
).
Avsändaren av unära operatörer kan vara till vänster eller till höger om operatören. Följande är en lista över unär överbelastningsbara operatörer:
Operatör | Allmänt bruk | Avsändarens position |
---|---|---|
+ - | Positivt/negativt tecken | höger |
* & | Referens | höger |
! ~ | Logisk / bitvis INTE | höger |
++ -- | Föröka / minska | höger |
++ -- | Efterökning/minskning | vänster |
Syntaxen för en överbelastning av en unär operator, där avsändaren är till höger, är följande:
return_type operator@ ()
När avsändaren är till vänster är deklarationen:
return_type operator@ (int)
@
ovan står för att operatören ska vara överbelastad. Ersätt return_type
med datatypen för returvärdet ( int
, bool
, strukturer etc.)
Parametern int
betyder i huvudsak ingenting annat än en konvention för att visa att avsändaren är till vänster om operatören.
const-
argument kan läggas till i slutet av deklarationen om tillämpligt.
Överbelastningsfästen
Den fyrkantiga parentesen []
och den runda parentesen ()
kan överbelastas i C++-strukturer. Hakparentesen måste innehålla exakt ett argument, medan den runda parentesen kan innehålla valfritt antal argument, eller inga argument.
Följande deklaration överbelastar hakparentesen.
return_type operator[] ( argument )
Innehållet inom parentes anges i argumentdelen
.
Runt fäste överbelastas på liknande sätt.
return_type operator() ( arg1, arg2, ... )
Innehållet i parentesen i operatörsanropet anges i den andra parentesen.
Förutom de operatorer som anges ovan kan piloperatorn ( ->
), den stjärnmärkta pilen ( ->*
), det nya
nyckelordet och nyckelordet delete
också överbelastas. Dessa minnes- eller pekarrelaterade operatörer måste bearbeta minnesallokeringsfunktioner efter överbelastning. Liksom tilldelningsoperatorn ( =
) är de också överbelastade som standard om ingen specifik deklaration görs.
Konstruktörer
Ibland kanske programmerare vill att deras variabler ska ta ett standardvärde eller specifikt värde vid deklaration. Detta kan göras genom att deklarera konstruktörer .
Person :: Person ( strängnamn , int ålder ) { name_ = name ; _ age_ = ålder ; }
Medlemsvariabler kan initieras i en initialiseringslista, med användning av ett kolon, som i exemplet nedan. Detta skiljer sig från ovan genom att det initieras (med hjälp av konstruktorn), snarare än att använda tilldelningsoperatorn. Detta är mer effektivt för klasstyper, eftersom det bara behöver konstrueras direkt; medan med tilldelning måste de först initieras med standardkonstruktorn och sedan tilldelas ett annat värde. Vissa typer (som referenser och const -typer) kan inte heller tilldelas och måste därför initieras i initialiseringslistan.
Person ( std :: strängnamn , int ålder ) : name_ ( namn ), ålder_ ( ålder ) { }
Observera att de lockiga hängslen inte kan utelämnas, även om de är tomma.
Standardvärden kan ges till de sista argumenten för att hjälpa till att initiera standardvärden.
0 Person ( std :: strängnamn = " " , int ålder = ) : name_ ( namn ), ålder_ ( ålder ) {}
När inga argument ges till konstruktorn i exemplet ovan, motsvarar det att anropa följande konstruktor utan argument (en standardkonstruktor):
0 Person () : name_ ( "" ), age_ ( ) {}
Deklarationen av en konstruktor ser ut som en funktion med samma namn som datatypen. Faktum är att ett anrop till en konstruktör kan ta formen av ett funktionsanrop. I så fall kan en initierad persontypvariabel
ses som returvärdet:
int main () { Person r = Person ( "Wales" , 40 ); r . Skriv ut (); }
En alternativ syntax som gör samma sak som exemplet ovan är
int main () { Person r ( "Wales" , 40 ); r . Skriv ut (); }
Specifika programåtgärder, som kan eller inte kan relatera till variabeln, kan läggas till som en del av konstruktorn.
Person () { std :: cout << "Hej!" << std :: endl ; }
Med ovanstående konstruktör, ett "Hej!" kommer att skrivas ut när standardpersonkonstruktorn anropas
.
Standardkonstruktör
Standardkonstruktörer anropas när konstruktörer inte är definierade för klasserna.
struktur A { int b ; }; // Objekt skapat med parenteser. A * a = nytt A (); // Anropar standardkonstruktorn och b kommer att initialiseras med '0'. // Objekt skapat utan parentes. A * a = nytt A ; // Allokera minne, anropa sedan standardkonstruktorn, och b kommer att ha värdet '0'. // Objektskapande utan nytt. A a ; // Reservera plats för a på stacken, och b kommer att ha ett okänt skräpvärde.
Men om en användardefinierad konstruktor definierades för klassen, kommer båda ovanstående deklarationer att anropa denna användardefinierade konstruktor, vars definierade kod kommer att exekveras, men inga standardvärden kommer att tilldelas variabeln b.
Förstörare
En destruktor är det omvända till en konstruktör. Den anropas när en instans av en klass förstörs, t.ex. när ett objekt av en klass skapat i ett block (uppsättning av krulliga klammerparenteser "{}") raderas efter den avslutande klammerparentesen, då anropas destruktorn automatiskt. Det kommer att anropas vid tömning av minnesplatsen som lagrar variablerna. Destruktorer kan användas för att frigöra resurser, såsom heap-allokerat minne och öppnade filer när en instans av den klassen förstörs.
Syntaxen för att deklarera en destruktor liknar den för en konstruktor. Det finns inget returvärde och namnet på metoden är detsamma som namnet på klassen med en tilde (~) framför.
~ Person () { std :: cout << "Jag tar bort " << namn_ << " med ålder " << age_ << std :: endl ; }
Likheter mellan konstruktörer och destruktörer
- Båda har samma namn som klassen där de deklareras.
- Om de inte deklareras av användaren är båda tillgängliga i en klass som standard, men de kan nu bara allokera och avallokera minne från objekten i en klass när ett objekt deklareras eller tas bort.
- För en härledd klass: Under körtiden för basklasskonstruktorn har den härledda klasskonstruktorn ännu inte anropats; under körtiden för basklassförstöraren har den härledda klassförstöraren redan anropats. I båda fallen är de härledda klassmedlemsvariablerna i ett ogiltigt tillstånd.
Klass mallar
I C++ kan klassdeklarationer genereras från klassmallar. Sådana klassmallar representerar en familj av klasser. En faktisk klassdeklaration erhålls genom att instansiera mallen med ett eller flera mallargument. En mall instansierad med en viss uppsättning argument kallas en mallspecialisering.
Egenskaper
Syntaxen för C++ försöker få varje aspekt av en struktur att se ut som de grundläggande datatyperna . Därför tillåter överbelastade operatorer att strukturer kan manipuleras precis som heltal och flyttal, arrayer av strukturer kan deklareras med hakparentessyntaxen ( some_structure variable_name [size] )
, och pekare till strukturer kan avläsas på samma sätt som pekare till inbyggda datatyper.
Minnesförbrukning
Minnesförbrukningen för en struktur är åtminstone summan av minnesstorlekarna för ingående variabler. Ta TwoNums-
strukturen nedan som ett exempel.
struct TwoNums { int a ; int b ; };
Strukturen består av två heltal. I många nuvarande C++-kompilatorer är heltal 32-bitars heltal som standard , så var och en av medlemsvariablerna förbrukar fyra byte minne. Hela strukturen förbrukar därför åtminstone (eller exakt) åtta byte minne, enligt följande.
+----+----+ | en | b | +----+----+
Dock kan kompilatorn lägga till utfyllnad mellan variablerna eller i slutet av strukturen för att säkerställa korrekt datajustering för en given datorarkitektur, ofta utfyllnadsvariabler för att vara 32-bitars justerade. Till exempel strukturen
struct BytesAndSuch { char c ; kol C ; kol D ; kort int s ; int i ; dubbel d ; };
kunde se ut
+-+-+-+-+--+--+----+--------+ |c|C|D|X|s |XX| jag | d | +-+-+-+-+--+--+----+--------+
i minnet, där X representerar vadderade byte baserat på 4 byte justering.
Eftersom strukturer kan använda pekare och arrayer för att deklarera och initiera dess medlemsvariabler, är minnesförbrukningen för strukturer inte nödvändigtvis konstant . Ett annat exempel på icke-konstant minnesstorlek är mallstrukturer.
Bitfält
Bitfält används för att definiera de klassmedlemmar som kan uppta mindre lagringsutrymme än en integraltyp. Detta fält är endast tillämpligt för integraltyper (int, char, short, long, etc.) och exkluderar float eller double.
0
struct A { unsigned a : 2 ; // Möjliga värden 0..3, upptar de första 2 bitarna av int unsigned b : 3 ; // Möjliga värden 0..7, upptar nästa 3 bitar av int unsigned : ; // Flyttar till slutet av nästa integraltyp osignerad c : 2 ; osignerad : 4 ; // Pads 4 bitar mellan c & d osignerad d : 1 ; osignerad e : 3 ; };
- Minnesstruktur
4 byte int 4 byte int [1][2][3][4][5][6][7][8] [1] [2] [3] [4] [a][a] [b][b][b][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [5] [6] [7] [8] [c][c][ ][ ][ ][ ][d][e] [e][e] ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ]
Bitfält är inte tillåtna i en fackförening. Det är endast tillämpligt för de klasser som definieras med nyckelordet struct eller klass.
Passera genom referens
Många programmerare föredrar att använda et-tecken (&) för att deklarera argumenten för en funktion som involverar strukturer. Detta beror på att genom att använda dereferens-et-tecknet krävs endast ett ord (vanligtvis 4 byte på en 32-bitarsmaskin, 8 byte på en 64-bitarsmaskin) för att överföras till funktionen, nämligen minnesplatsen till variabeln. Annars, om pass-by-value används, måste argumentet kopieras varje gång funktionen anropas, vilket är kostsamt med stora strukturer.
Eftersom pass-by-reference exponerar den ursprungliga strukturen som ska modifieras av funktionen, bör nyckelordet const
användas för att garantera att funktionen inte ändrar parametern (se const-correctness ), när detta inte är avsett.
Det här nyckelordet
För att underlätta strukturers förmåga att referera sig själva, implementerar C++ detta
nyckelord för alla medlemsfunktioner. Detta nyckelord fungerar som en pekare till det aktuella objektet .
Dess typ är en pekare till det aktuella objektet.
Detta nyckelord är särskilt viktigt för medlemsfunktioner med själva strukturen som returvärde :
Complex & operator += ( const Complex & c ) { real_part_ += c . verklig_del_ ; imag_part_ += c . bild_del_ ; returnera * detta ; }
Som nämnts ovan är detta
en pekare, så användningen av asterisken (*) är nödvändig för att konvertera den till en referens som ska returneras.
Se även
- Åtkomstmodifierare
- Virtuellt arv
- Klass (datorprogrammering)
- Klassbaserad programmering
- Objektsammansättning
- Typkonvertering
Allmänna referenser:
- Cplusplus.com tutorial lektion 5.2 , tillgänglig i januari 2006
- Cplusplus.com tutorial lektion 2.5, tillgänglig i februari 2006