Skrivet av Korvulla:
@perost: Tack så mycket, detta var lättare för mig att förstå, verkligen. Jag tror det sket för mig totalt när jag började lära mig c# sen insåg jag att allt ska vara till c++..
Ok, vi fortsätter på samma spår då. Du verkar redan ha koll på saker som if-satser och loopar, men vi kan ta det lite snabbt ändå. En if-else-sats skulle t.ex. kunna se ut så här (exemplet visar if(x == 5) { ... } else { ... }
, där x
har address 43):
1: JUMPEQ $43, 5, $3 // Om värdet på x (d.v.s. $43) är lika med 5, hoppa till $3.
2: JUMP $5 // Annars, hoppa till $5
3: ... // Någon kod som ska köras om x == 5.
4: JUMP $6 // Hoppa till slutet av if-satsen.
5: ... // Någon kod som ska köras om x != 5.
6: ... // Start på kod efter if-satsen.
En loop är väldigt lika, så här skulle t.ex. while(x == 5) { ... }
kunna se ut:
1: JUMPEQ $43, 5, $3 // Om värdet på x är lika med 5, hoppa till $3.
2: JUMP $5 // Annars hoppa till slutet av loopen.
3: ... // Någon kod som ska köras i loopen.
4: JUMP $1 // Hoppa tillbaka till början av loopen.
5: ... // Start på kod kod efter while-loopen.
Och en for-loop är i stort sett samma sak.
Så, låt oss tackla klasser. Vi kan börja med strukturer, som i C (och i C++/C#, men jag tar det senare) är ett sätt att gruppera variabler. T.ex.:
struct MyStruct
{
int i;
float f;
char c;
};
Att bara deklarera en sån struct gör ingenting, d.v.s. bara för att MyStruct
har en deklaration av int i;
så allokeras inget minne för någon variabel i
någonstans. Det är först när man deklarerar variabler som har typen MyStruct
som minne allokeras:
MyStruct ms;
ms.i = 44925; // 0x0000AF7D i hexadecimalt format.
ms.f = 0.24f; // 0x3E75C28F (enligt IEEE-754)
ms.c = 'a'; // 0x61 (enligt ASCII)
Detta kommer få kompilatorn att allokera minne för variabeln ms
(ms
sägs vara en instans av MyStruct
), vilket skulle se ut så här i minnet efter att ovanstående tilldelningar utförts:
1: 0x00 // Början på ms, och även ms.i
2: 0x00
3: 0xAF
4: 0x7D // Slut på ms.i
5: 0x3E // Början på ms.f
6: 0x75
7: 0xC2
8: 0x8F // Slut på ms.f
9: 0x61 // Hela ms.c, en char tar bara upp en byte.
En struct är med andra ord rätt lik en array, bara att elementen kan vara av olika typer och att elementen har unika namn. Som vanliga variabler kan man så klart deklarera flera variabler av typen MyStruct
, där alla skulle få ett eget block på 9 byte allokerat i minnet.
En klass är sen en struct på steroider, åtminstone i teorin. I C++ är det faktiskt ingen skillnad på en struct och en class, förutom att allt i en struct är public om inget annat anges medan allt i en class är private (inte så viktigt nu, du kommer stöta på det förr eller senare om du använder klasser). I C++ kan man alltså använda structs som klasser och klasser som structs, men normalt använder man struct just för att gruppera variabler och klasser för mer komplexa objekt (d.v.s. som har metoder). C# har också struct, men där är de lite mer specialiserade (vi hoppar den förklaringen, den är inte relevant för ämnet och skulle bara krångla till det).
Så i C++ skulle vi alltså kunna deklarera följande klass, som skulle fungera precis som MyStruct
ovan:
class MyClass
{
public:
int i;
float f;
char c;
};
Den har inga metoder, det enda den gör är att gruppera tre variabler. Varje instans av MyClass
vi deklarerar kommer vara som att deklarera tre separata variabler, där varje instans har sitt eget minnesblock för dess variabler:
MyClass c1;
c1.i = 4; // Sätt c1.i till 4.
MyClass c2;
c2.i = 6; // Sätt c2.i till 6, c1.i påverkas inte på något sätt och är fortfarande 4.
Ett sätt att programmera på ett objekt-orienterat sätt i C, som inte har klasser, är att använda structs och skicka dem som argument till funktioner (detta är så klart C++, C har t.ex. inte cout):
void print_i(MyStruct ms)
{
std::cout << "i has the value " << ms.i << std::endl;
}
int main()
{
MyStruct ms1;
ms1.i = 5;
MyStruct ms2;
ms2.i = 9;
print_i(ms1); // Kommer skriva ut "i has the value 5".
print_i(ms2); // Kommer skriva ut "i has the value 9".
}
Instansmetoder fungerar som sagt på samma sätt, förutom att instansen man anropar metoden på automatiskt skickas som argument till metoden (via den speciella variabeln this
):
class MyClass
{
public:
// Skriver ut värdet på i som tillhör instansen som metoden anropas via.
// Här använder jag uttryckligen this, men skulle man bara skriva i
// så skulle kompilatorn förstå att det är this->i som menas.
void print_i()
{
std::cout << "i has the value " << this->i << std::endl;
}
int i;
...
};
int main()
{
MyClass mc1;
mc1.i = 5;
MyClass mc2;
mc2.i = 9;
mc1.print_i(); // Anropar MyClass::print_i med this = mc1.
mc2.print_i(); // Anropar MyClass::print_i med this = mc2.
MyClass::print_i(); // Fel, print_i kan inte anropas utan någon instans.
}
Klasser har sen som bekant konstruktorer, för att man ska kunna tilldela värden till alla variabler i en instans och göra annat som behövs för att initiera en instans. De kan även ha destruktorer, som automatiskt anropas när en instans "dör". Tänk dig t.ex. en klass som representerar en TCP/IP-socket som kan användas för att hantera nätverkstrafik (återigen med onödig användning av this för demonstrationens skull):
class IPSocket
{
public:
// Konstruktor för IPSocket, tar en IP-address och en port.
IPSocket(int address, int port)
{
// Spara address och port, även fast de egentligen inte behövs i detta enkla exempel.
// Notera att användningen av this gör att vi kan ha samma namn på klassvariablerna
// och funktionsparametrarna här utan att få konflikter.
this->address = address;
this->port = port;
// Öppna en TCP/IP-anslutning, och spara identifieraren för anslutningen.
this->handle = ipopen(address, port);
}
// Destruktor för IPSocket.
~IPSocket()
{
// Stäng TCP/IP-anslutningen.
ipclose(this->handle);
}
// Metod för att skicka en sträng med data.
void send(string data)
{
ipsend(this->handle, data);
}
int address;
int port;
int handle;
};
int main()
{
// Denna deklaration kommer allokera minne för en IPSocket-instans,
// och sedan anropas konstruktor för IPSocket med this = socket.
IPSocket socket(2130706433, 80); // 2130706433 = 127.0.0.1
socket.send("Hello World", 11); // Anropar send med this = socket.
// Här tar main slut, och sockets allt för korta liv lika så.
// Här anropas alltså destruktorn för IPSocket med this = socket automatiskt.
}
Lite överkurs men värt att nämna är att man i C++ normalt föredrar att använda en s.k. member initializer list i konstruktorn, se t.ex. denna sida (under "Member initialization in constructors").
Och därmed var dagens test av hur långa inlägg Sweclockers tillåter slut