Objekter I
- Orange_Newton skrev denne artikel Tirsdag, 18 Juli 2006
Forudsætninger: Jeg går ud fra at læseren på nuværende tidspunkt har kendskab til alle simple variabeltyper (int, char og float) og arrays og pointere til samme. Det er ydermere meget smart at forstå funktioner da klasser og objekter har noget der ligner.

Hvis man ellers skal tro på alt hvad man hører er objektorienteret programmering djævlens værk, imidlertid er det nogle gange brugbart fordi det kan give MEGET simplere og væsentligt mere simpel kode. Hvis jeg ikke husker galt (og det gør jeg næsten aldrig) introducerede jeg win32 og C++ med en slags database for at vise hvordan man kunne opbevare informationer om en person, denne var meget begrænset da den kun kunne tage 1 person af gangen. Med din nuværende viden kunne du helt sikkert lave et par arrays og have flere personer i, men det ville efterhånden blive kompliceret hvis de skal have alder osv. Ind enkeltvis. Det vi har behov for er en ny type variabel, en der kan indeholde præcis de informationer vi skal bruge. Sådan en findes heldigvis i C, de kaldes structs men mere vil jeg nu ikke nævne om dem, structs har nemlig en mangel som er årsagen til C++ eksistens i C++ har man nemlig klasser og klasser har metoder i modsætning til structs i C som kun har felter (members på engelsk).  Uden nærmere forklaring end det tror jeg at det vil være på sin plads at give et eksepel her:
001   #include <windows.h>
002  
003   class person{
004   public:
005    char navn[256];
006    int alder;
007    bool mand; //1 for mand 0 for kvinde
008    int penge; //i ører
009   };
010  
011   int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nCmdShow){
012    person pers[3];
013    strcpy(pers[0].navn,"Jens");
014    pers[0].alder = 42;
015    pers[0].mand = 1;
016    pers[0].penge=1150;
017    strcpy(pers[1].navn,"Erik");
018    pers[1].alder = 25;
019    pers[1].mand = 1;
020    pers[1].penge = 125;
021    strcpy(pers[2].navn,"Karin");
022    pers[2].alder = 31;
023    pers[2].mand = 0;
024    pers[2].penge = 500;
025   }

OK, ved første øjekast ser det meget besværligt ud, men inden denne tutorial slutter, vil jeg have forkortet main delen af koden så meget ned at det næsten er utroligt. Jeg vil samtidig give alt koden en overskuelig struktur og i en senere tutorial vil jeg tilmed gøre den så magisk at man frit kan tilføje personer til listen. Men lad os se på koden:
Linie 003: Sådan starter man en klasse, man giver den et navn. Fra dette øjeblik er person en variabeltype.
Linie 004: public indikerer at vi kan benytte punktum til at skrive til de variabler der kommer bagefter. (Hvis vi havde brugt private havde alle linier i main delen været forkert fordi vi så ikke må tilgå felter på den måde mere om det senere).
Linie 5-8: forskellige variable som indgår i klassen som det man kalder felter, de indikerer de typer af data der kan være i vores nye variabel og giver dem nogle navne så man kan adskille dem. I main delen er der ikke så meget nyt udover at vi nu har en variabel der indeholder andre variable og at man kan få fat i disse med et punktum.

En mindre tilføjelse til punktummerne er at hvis det er en pointer bruger man en pil (lavet af bindestreg og større end ->) til at indikere at vi vil have indholdet. Man kan sætte Dev-cpp og VS til automatisk at vise indholdet af klasser, VS gør det vidst som standard og jeg vil ikke anbefale at slå det til i Dev-Cpp da det er yderst ustabilt i nuværende beta). I dette eksempel har jeg gjort alt public, det vil mange mene er meget dumt da det gør at der let kommer fejl hvis man er flere om at programmere på samme projekt. I stedet vil de anbefale at man bruger metoder (på engelsk methods) til at ændre indholdet af felterne. Metoder er meget ligesom funktioner, men de hører til som en del af en klasse og når man kalder dem har de adgang til de private dele af klassen, i ovenstående eksempel kunne det være man ikke var interesseret i at have negative værdier i penge, eller man ville måske have en metode til at skrive informationer om en person ind i en streng eller sådan noget. I C++ har vi derfor den mulighed at gøre informationerne sværere at tilgå på den måde kan man forhindre at de andre fra ens programmeringsprojekt ødelægger det man har lavet. Der er 2 specielle metoder nemlig constructor og destructor (jeg lader dem hedde det same på dansk). Disse er funktioner der bliver kaldt når et objekt oprettes eller slettes, en constructor skal have samme navn som klassen selv og destructor skal have et ~ foran og hedde det samme som klassen selv. Med det vi har lært nu er det tid til simplificering og forbedring af koden.

001   #include <windows.h>
002  
003   #define KOEN_MAND 1
004   #define KOEN_KVINDE 0
005   #define FEJL_ALDER -1
006  
007   #define ANTAL_PERSONER 3
008  
009   class person{
010   public:
011    person(char *navn,int alder,bool mand,int penge);
012    void setnavn(char *navn);
013    bool setalder(int alder);
014    void setmand(bool mand);
015    void setpenge(int penge);
016    int givpenge(int ekstra);
017    char* tostring(char *output);
018   private:
019    char navn[256];
020    int alder;
021    bool mand; //1 for mand 0 for kvinde
022    int penge; //i ører
023   };
024  
025   person::person(char *navn,int alder,bool mand,int penge){
026    setnavn(navn);
027    if(!setalder(alder))
028      alder = FEJL_ALDER;
029    setmand(mand);
030    setpenge(penge);
031   }
032  
033   void person::setnavn(char *navn){
034    strcpy(this->navn,navn);
035   }
036  
037   bool person::setalder(int alder){
038    if(alder<18)
039      return 0;
040    else
041      this->alder = alder;
042    return 1;
043   }
044  
045   void person::setmand(bool mand){
046    this->mand = mand;
047   }
048  
049   void person::setpenge(int penge){
050    this->penge = penge;
051   }
052  
053   int person::givpenge(int ekstra){
054    return penge+=ekstra;
055   }
056  
057   char* person::tostring(char *output){
058    if(alder!=FEJL_ALDER)
059      wsprintf(output,"%s er en %d år gammel %s, med %d.%02d kr. i lommen.",navn,alder,mand?"mand":"kvinde",penge/100,penge%100);
060    else
061      wsprintf(output,"%s er en %s, med %d.%02d kr. i lommen.",navn,mand?"mand":"kvinde",penge/100,penge%100);
062    return output;
063   }
064  
065   int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nCmdShow){
066    person *pers[ANTAL_PERSONER];
067    pers[0] = new person("Jens",42,KOEN_MAND,1150);
068    pers[1] = new person("Erik",25,KOEN_MAND,125);
069    pers[2] = new person("Karin",31,KOEN_KVINDE,50);
070    pers[1]->setmand(KOEN_KVINDE); //Erik er nu kvinde.
071    for(int i=0;i<ANTAL_PERSONER;i++){
072      char output[512];
073      pers[i]->tostring(output);
074      MessageBox(0,output,"Info:",MB_OK);
075    }
076   }


Et godt eksempel på hvordan man kan lave en simpel database, lige umiddelbart ser det ud som om det er blevet mere avanceret, men hvis vi delte det i 2 filer ville det være meget simplere, og hvis vi skule have mere end 3 personer ville det være en kort tilføjelse. Bemærk at mange af funktionerne kun benyttes i construktor, disse kunne teknisk set undværes.
Det er ret almindeligt at benytte klassens egne set funktioner i construktor, på den måde er der kun 1 metode til at sætte disse felter og hvis personer under 18 år lige pludselig er ok i databasen skal det kun skiftes et sted. Det er samme tanke der ligger til grund for #define linierne, #define er en slags variable kaldet konstanter, i C++ er der en mere fornuftig måde at erklære konstanter på, men den vil vi ikke se på lige nu. Disse defines kan så bruges steder hvor man skal have et fast tal, årsagen til at jeg bruger den her er fordi 3 skal bruges 2 gange, 1. da arrayet oprettes og 2. da alle pladser gennemløbes. De tre andre er der for at gøre det nemmere at læse, KOEN_MAND er nemmere at huske end 1 og 0, især hvis der er en situation med mange muligheder kan det være fornuftigt med defines. Bemærk at en #define sætning ikke sluttes med ’;’. Hvis du har læst tutorialen om funktioner skulle det være rimeligt tydeligt hvordan metoder virker. Jeg har valgt at lave så den ikke accepterer personer under 18, eller rettere så den ikke fortæller om deres aldrer. Ligsom med funktioner kan man returnere variable, der er en årsag til at tostring er nødt til selv at have en output array med, dette skyldes måden arrays og variabler virker på i C++, det er lidt besværligt at gå i mere detaljer end det, så bare husk at hvis noget skal returnere et array er det normalt mest fornuftigt at sende arrayet med. Det er muligt at returnere arrayet bagefter som tostring gør, dette er nyttigt hvis man vælger at bruge malloc eller new. Jeg har valgt ikke at bruge nothrow udgaven af new her, så hvis der ikke er hukommelse nok crasher programmet bare uden at sige noget til brugeren. Bemærk også at jeg har skiftet punktummet ud med en pil, og at jeg bruger noget kaldet this nogen gange men ikke andre. Årsagen til at jeg bruger this er at den medsendte variabel hedder det samme som den der hører til klassen, this er en pointer til det objekt (et objekt er en variabel lavet ud fra en klasse) som metoden er blevet kaldt på. Den er ikke nødvendig i tostring f.eks. fordi der ikke er andre variabler med samme navn. Hvis du ikke kan lide this pointeren kan du bare omdøbe variablerne i funktionskaldene. Nogle kan bedst lide det ene og andre bedst det andet. I tostring bruger jeg også % som er modulo operatoren, når man dividerer 2 int med hinanden i C og C++ bliver resultatet altid rundet ned, vil man gerne vide hvilken rest der var i en division kan man bruge modulo på den, så hvis man tager 7%3 får man 1 fordi 7/3=2 rest 1, denne operator er ofte benyttet, især i lange for løkker hvor man bare vil have et resultat en gang imellem, man kan så kigge på hvornår i%1000 giver 0 og så kun udskrive hver tusinde gang. Teknisk set burde man bruge div(7,3) hvis man vil have begge dele, dette returnerer en variabel af typen div_t dette er faktisk en struct fra C som indeholder 2 felter, quot og rem grunden til man bør benytte denne metode er at det så kan afvikles som 1 assembly instruktion, det er dog ikke et problem i nyere tid hvor compilere automatisk optimerer hvor de kan, men man ved jo aldrig om den optimerer det.div funktionen findes i stdlib.h for dem der vil lege lidt med den. Jeg tror vi slutter her for denne gang. Hvis der er nogen spørgsmål så smid mig en mail på admin@AODASoft.net.
AODASoft.net - Allan Asp Olsen & Daniel A un dal.
OBS: Du har begrænset adgang til siden.