Pointere
- skrev denne artikel Lørdag, 14 Januar 2006
Forudsætninger: for at forstå pointere er det nødvendigt at forstå variabler og arrays. Det anbefales at lære om funktioner først, men det er ikke nødvendigt.

Pointere er et vigtigt element i C og endnu mere i C++, denne tutorial vil kun gennemgå den mest simple pointer teori, og der vil senere blive skrevet om mere advancerede anvendelser af pointere.
en pointer er en type variabel, den minder en hel del om int men i stedet for et heltal indeholder den en adresse til en anden variabeltype, dette lyder måske i første omgang som en fjollet ide, men det viser sig at have fornuftige anvendelser.
For at lave en pointer i C og C++ sættes en stjerne foran variabelnavnet, f.eks. int *i; dette laver pointeren i af typen int, skal man bruge værdien der står på i's plads skriver man *i, og skal man ændre adressen skrives der blot i. ofte vil man gerne have en variabels adresse som man kan sætte i til og så sætter man bare et & foran dennes navn. f.eks. int *i=&j; nu indeholder i så adressen på integer værdien j og *i og j er herefter synonyme.
Den første og mest åbenlyse anvendelse er med funktioner, når man kalder en funktion kopieres argumenterne over til den nye funktion, men nogle gange vil man godt have lov til at ændre en variabel i en anden funktion en der hvor den er oprettet. Hvis man så i stedet bruger en pointer laves der en kopi af pointeren som jo er en reference til en adresse og funktionen kan så skrive der hvor adressen peger hen. Eksemplet på hvordan en pointer sendes til en funktion er dog lagt under funktioner.
En anden anvendelse af pointere er arrays, når man laver et array laver computeren plads til arrayets størelse i hukommelsen og der laves en pointer til element 0, man kan derfor også bruge en pointer til at bladre gennem et array, dette er fornuftigt i tilfælde hvor man er i tvivl om hvorvidt ens compiler optimerer ordentligt og speed er et absolut 'must have'(burde ikke betyde noget på en almindelig moderne computer). Jeg tror dette kræver et eksempel og en efterfølgende forklaring (hvis ikke du behøver forklaringen er det ret sejt :-P).
001   #include <windows.h>
002  
003   void strcpyP(char *ud,const char *ind);
004   void strcpyA(char *ud,const char *ind);
005  
006   int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nFunsterStil){
007    char alf[]="jaaaaa det virker";
008    char test1[512];
009    char test2[512];
010    strcpyP(test1,alf);
011    MessageBox(0,alf,test1,MB_OK);
012    strcpyA(test2,alf);
013    MessageBox(0,alf,test2,MB_OK);
014    return 0;
015   }
016  
017   void strcpyP(char *ud,const char *ind){
018    do{
019      *ud++=*ind;
020    }while(*ind++);
021   }
022  
023   void strcpyA(char *ud,const char *ind){
024    int i;
025    for(i=0;ind[i];i++)
026      ud[i]=ind[i];
027    ud[i]=0;
028   }


ok så teknisk set gør de 2 funktioner det samme, men måden de gør det på er ikke helt ens, pointer udgaven løber igennem arrayet med 2 pointere i stedet for 1 int, årsagen til at dette er mere effektivt er at der for hver gang der laves en [] indekserings operator regnes forfra dvs. den lægger tallet i klammerne til pointeres værdi og ser hvad der står på denne plads, dette er selvfølgelig ikke store beregninger og de kører kun med heltal, udover dette optimeres der lidt i koden når den compileres. Så i sidste ende er det ligegyldigt, begge måder er korrekte, og jeg vil mest benytte array metoden, i hvert fald indtil det handler om kædede lister.
Jeg går udfra på dette tidspunkt at læseren er indforstået med at alle strenge slutter med 0 og at 0 er det eneste tal der regnes for falskt i en betingelse.
Der er også en tredje anvendelse af pointere der hører til i denne del, uheldigvis er det også der hvor folk ofte begynder at lave fejl. En pointer kan nemlig også sættes til at pege på nye elementer (altså elementer der ikke er variabler i forvejen). Måden dette gøres på er lidt forskellig fra C til C++ årsagen er at C++ indfører objekter i C og dette gjorde det nødvendigt at have en fornuftig måde at allokere nye objekter på. Objekter vil ikke blive beskrevet her da de foreløbig ikke er nødvendige og da de fortjener adskillige tutorials for at komme ordentligt omkring dem, og ud i alle hjørner.
Når man beslutter sig for at lave et nyt element i C og C++ er det også nødvendigt at slette det igen, årsagen er at C og C++ som standard ikke ved hvornår et element ikke skal bruges mere, dette 'problem' kan 'nemt' løses men forståelsen af måden dette foregår på ligger endnu et stykke uden for læserens nuværende formodede forståelse af C++.
Jeg vil først forklare hvordan det gøres i C og derefter hvordan det gøres i C++.
i C kan man reservere en plads i hukommelsen med funktionen malloc, malloc tager 1 argument som er antallet af byte der skal reserveres. Når man er færdig med at bruge elementet skal man så huske at kalde free, free tager også 1 argument nemlig pladsen hvor der skal slettes en reservation fra. Jeg tror et eksempel er en god ide.
001   #include <windows.h>
002  
003   int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nFunsterStil){
004    char fil_buffer[] = "Dette her skal forstille at være indlæst fra en fil!";
005    char *tekst;
006   
007    tekst = (char *)malloc(sizeof(fil_buffer)); //det kunne så erstattes med størrelsen af filen
008    strcpy(tekst,fil_buffer);                  //dette ville så være en indlæsning fra filen
009    MessageBox(0,tekst,"tekst:",MB_OK);
010    free(tekst);
011   }

Jeg bør måske lige nævne af sizeof finder størrelsen af et element, f.eks. sizeof(int) eller sizeof(double), på den måde kan man sikre sig at der er plads til variablen. grunden til at der står (char *) mellem = og malloc er at malloc returnerer en pointer til en void (void *) og man er derfor nødt til at tvinge den til den rigtige type. Denne måde at tvinge en type på kaldes et cast.
Fordelen her er åbenlys, i hvert fald hvis det havde været en fil, for så havde det ikke været nødvendigt at vide på forhånd hvor stort et array der skal laves. Ulempen er at hvis filen er for stor fejler malloc og vi har ikke noget fejlcheck her, hvis malloc fejler returnerer den 0 som adresse, nogle foretrækker null eller NULL men disse er i teorien det samme som 0. I andre situationer kan dynamisk hukommelses allokering også have fordele, men de må blive gemt til det bliver relevant.
I C++ laves dynamisk hukommelses allokering med new operatoren, når den så skal slettes igen bruges delete, fordelen ved at bruge new frem for malloc vil først blive tydelig senere, så for nu vil jeg blot give et eksempel på anvendelse.
001   #include <windows.h>
002  
003   int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nFunsterStil){
004    char fil_buffer[] = "Dette her skal forstille at være indlæst fra en fil!";
005    char *tekst;
006    tekst = new char[sizeof(fil_buffer)];
007    strcpy(tekst,fil_buffer);
008    MessageBox(0,tekst,"tekst:",MB_OK);
009    delete[] tekst;
010   }

Dette er igen et array, men metoden er den samme for et enkelt element, [] skal bare fjeren sammen med det der står indeni. Hvis new fejler kaster den en exception, dette vil først blive forklaret senere, så for nu bør du skrive new(nothrow), på denne måde fejler den på samme måde som malloc og man kunne så have en if(tekst){ lige efter allokeringen og så slutte med }else MessageBox(0,"Der er ikke plads i hukommelsen","Fejl",MB_ICONERROR); det færdige resultat bliver:
001   #include <windows.h>
002   #include <new>
003  
004   using namespace std;
005  
006   int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nFunsterStil){
007    char fil_buffer[] = "Dette her skal forstille at være indlæst fra en fil!";
008    char *tekst;
009    tekst = new(nothrow) char[sizeof(fil_buffer)];
010    if(tekst){
011      strcpy(tekst,fil_buffer);
012      MessageBox(0,tekst,"tekst:",MB_OK);
013      delete[] tekst;
014    }else
015      MessageBox(0,"Der er ikke plads i hukommelsen","Fejl",MB_ICONERROR);
016   }

linie 2 og 4 er der for at få new(nothrow) med, linie 4 siger at den skal bruge std namespace, dette blev indført i C++ for at undgå at overskrive de gamle C header filer med nye, man kaldte så C++ headerne det samme men undlader .h og så skal man huske at bruge std som namespace. Namespace vil blive forklaret i nærmere detaljer senere.
Jeg lader det være op til læseren at skrive en fejlhåndtering til C udgaven, da den ligner C++ udgaven fuldstændig.
Som altid: Hvis der er nogen spørgsmål så send mig en mail på:
admin@AODASoft.net