5. Listor och pekare

 

 

·     Listor.

 

·     Deklarera listor.

 

·     Flera dimensioner.

·     Pekare.

 

·     Pekaren och listan.

 

·     Pekarens värde.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Listor

 

I en variabel kan man lagra ett värde. Ibland vill man dock lagra flera värden av samma typ och med samma namn. T.ex. antalet dagar i en månad. Det finns ju 12 månader, och då vill vi lagra 12 värden. Vi skulle ju kunna deklarera 12 variabler med namnen iManad1, iManad2, etc. Om vi då vill veta hur många dagar den aktuella månaden är måste vi skriva t.ex en switch på en variabel som innehåller månadens nummer. Detta blir klumpigt:

 

int iManad1 = 31, iManad2 = 28, iManad3 = 31, iManad4 = 30;
int iManad5 = 31, iManad6 = 30, iManad7 = 31, iManad8 = 31;

int iManad9 = 30, iManad10 = 31, iManad11 = 30, iManad12 = 31;

int iIndex, iAntDagar;

.

.

switch (iIndex)

{

case 1:

   iAntDagar = iManad1;

   break;

case 2:

   iAntDagar = iManad2;

   break;

case 3:

   iAntDagar = iManad3;

   break;

case 4:

   iAntDagar = iManad4;

   break;

case 5:

   iAntDagar = iManad5;

   break;

case 6:

   iAntDagar = iManad6;

   break;

case 7:

   iAntDagar = iManad7;

   break;

case 8:

   iAntDagar = iManad8;

   break;

case 9:

   iAntDagar = iManad9;

   break;

case 10:

   iAntDagar = iManad10;

   break;

case 11:

   iAntDagar = iManad11;

   break;

case 12:

   iAntDagar = iManad12;

   break;

Allt detta bara för att vi vill skapa en tabell med antalet dagar per månad, plus att hämta antal dagar för en månad när vi vet vilket nummer månaden har.

 

Det går att göra bekvämare m.h.a. en lista. En variabel är ett logiskt namn på en plats i minnet, vilket reserverats för variabelns typ. En lista är detsamma, men den reserverar utrymme för flera likadana variabler i rad, under ett och samma namn. Därigenom kan man använda samma namn, och indexera sig fram till rätt variabel. Exemplet ovan kan bli mycket lättare:

 

int iManad[12] = {
                    31, 28, 31, 30, 31, 30,
                    31, 31, 30, 31, 30, 31

                 };

int iIndex, iAntDagar;

.

.

.

iAntDagar = iManad[iIndex];

 

En liten egenhet med listor är dock att de börjar med index 0, inte ett. Skulle vi lägga äkta månadsnummer i iIndex skulle sista raden behöva ändras till:

 

iAntDagar = iManad[iIndex - 1];

 


Deklarera listor

 

Man deklarerar en lista enligt följande syntax:

 

<datatyp> <listnamn>[<antal>];

 

(Observera att hakparanteserna ingår i syntaxen.)

Ett exempel: en lista för antal dagar per månad:

 

short int manad[12];

 

Vi kan även här initiera samtidigt som vi deklarerar:

 

short int manad[12] = {31, 28, 31, 30, 31, 30,

                       31, 31, 30, 31, 30, 31};

 

Om man initierar en tabell med ett antal värden behöver man inte ange tabellens storlek, kompilatorn räknar antalet angivna värden själv:

 

short int manad[] = {31, 28, 31, 30, 31, 30,

                     31, 31, 30, 31, 30, 31};

 

char cSkola[] = ”Datortek”;

 

Det är t.o.m. så fint att kompilatorn ser att man anger typ char och skriver en textsträng, då lägger den till en position i slutet av listan vilken innehåller ett sluttecken (en NULL). Egentligen skulle man ha behövt skriva:

 

char cSkola[] = {‘D’, ‘a’, ‘t’, ‘o’, ‘r’, ‘t’, ‘e’, ‘k’, ‘\0’};

 

Övningsuppgift:

·      Skapa projektet fel.

·      Skriv följande källkod och spara som C:\CCpp\fel\fel.c.

 

         #include <stdio.h>

         void main()

         {

                 char text[] = {'a','b','c','d'};

                 puts(text);                     

         }

 

·      Kompilera och testa.

·      Visa din handledare, och förklara varför det blir som det blir.

 
Flera dimensioner.

 

Hur skulle det då se ut om man ville göra en lista som beskriver ett schackbräde? Tänk dig att vi har ett bräde med alla pjäser i startposition. Vi har kanske bestämt att alla olika typer av pjäser representeras av ett nummer, t.ex vit dam = 1, vit kung = 2 etc. Eftersom det finns sex olika typer av pjäser, i två olika färger, behöver vi 12 olika värden, alltså kan man ha en lista av typen short.

 

Rutorna numreras traditionellt med siffror i ”höjdled” och bokstäver i ”sidled”.

Nu bör vi inte indexera med bokstäver (även om det skulle gå att använda ascii-teknet minus 65), utan vi vill räkna med rad nummer 0 - 7 och kolumn 0 - 7.

 

Listan blir då en yta, den har två dimensioner. Det går alldeles utmärkt i C. Vi kan deklarera en lista med brädets rutor enligt följande exempel:

 

short int ruta[8][8];

 

Det är alltså bara att lägga till flera dimensioner genom att räkna upp dem i rad (utan kommatecken). Syntaxen för en lista av valfritt antal dimensioner blir:

 

<datatyp> <listnamn>[<antal>][[<antal>]...];

 

 


Övningsuppgift:

·      Antag att vi definierar schackpjäserna enligt följande:
                 Svart              Vit
Dam          D                   d
Kung         K                   k
Löpare      L                    l
Springare   S                    s
Torn          T                    t
Bonde       B                   b

·      Nytt projekt: schack.

·      Deklarera en lista för alla rutor och initiera enligt vanlig uppställning (tom ruta ska innehålla en ‘-’).

·      Gör en dubbel programslinga som visar brädet (som bokstäver).

·      Flytta vit kungsbonde två steg framåt och visa brädet igen.

·      Utskriften ska se ut som följer:

 

                
Som vi tidigare konstaterat finns det ingen datatyp för textsträngar i C/C++. I de exempel vi tidigare har haft har vi i stället använt oss av en lista av datatypen char, och avslutat textsträngen med en byte som innehåller värdet NULL (‘\0’).

Hur blir det då om man vill skapa en lista med texter (t.ex. veckodagarnas namn)? Svaret blir naturligtvis en lista med två dimensioner, en dimension för veckodagar och en dimension för bokstäverna i varje dags namn:

 

char cDag[][8] = {”Måndag”, ”Tisdag”, ”Onsdag”, Torsdag”,

                  ”Fredag”, ”Lördag”, ”Söndag”};

 

Här klarar inte kompilatorn riktigt att räkna variabler i flera dimensioner, utan vi blir tvugna att i alla fall ange hur många bokstäver (+1) vi använder i den längsta arean (alla blir lika långa, men inte helt utfyllda). Den första dimensionen (antalet veckodagar) räknar den i alla fall åt oss om vi inte skriver något i den.                                  

 

Övningsuppgift:

·      Gör ett program som heter dagar (ny katalog och nytt projekt).

·      Skapa två listor: en vanlig för antalet dagar per månad (glöm skottår), en för månadernas namn (eftersom textsträngar i sig är listor blir denna lista tvådimensionell). Bägge listorna ska fyllas i vid deklarationen.

·      Gör en programslinga som skapar följande utskrift (m.h.a. listorna):

 

         
Pekare.

 

En pekare är en variabel avsedd att innehålla en adress till något data i minnet. Den är därför av en viss längd, som beror av hur långt bort i minnet den ska peka. Det finns ‘långa’ pekare som kan innehålla en fullständig 32-bitars adress och vanliga pekare, vilka kan innehålla en adress inom aktuella 64 Kb. De sistnämnda är på 16 bitar.

 

Storleken på en pekare är alltså inte beroende av vad den pekar på, utan hur långt det är dit. Normalt använder vi 16-bitars pekare. Om den då är avsedd att peka på en char, vilken ju bara tar en byte i minnet, så tar pekaren ändå två bytes.

 

Man deklarerar en pekare genom att ange vilken typ av data den är avsedd att peka på, samt dess namn föregått av en stjärna:

 

char *pMinCharPekare;

int *pMinIntPekare;

 

Man anger vilken typ av data pekaren är avsedd att peka på bara för att man ska kunna peka på nästa korrekta adress i minnet genom att bara lägga till 1. Om minnet innehåller ett antal heltal som ligger efter varandra, så skulle man behöva öka adressen i pekaren med 2 för att peka på nästa heltal. Detta sker automatiskt när man lägger till 1 till en pekare som är avsedd att peka på ‘int’.

 

Skulle pekaren vara avsedd att peka på ‘double’, vilket tar 8 bytes, så motsvarar en ökning av ett steg på pekaren 8 bytes längre fram i minnet.

 

Man påverkar pekarens innehåll (adressen till det den pekar på) genom att ute­sluta stjärnan framför namnet. Vi skulle t.ex. kunna tilldela pekarna ovan adresserna till två variabler så här:

 

char cText[81];

int iTal, iTal2;

 

pMinCharPekare = &cText[0];

pMinIntPekare = &iTal;

 

Du kommer väl ihåg att ‘&’ framför variabelnamn ger just adressen till variab­eln. (Se ‘Operatorer’ i kapitel 2.)

 

Vill vi komma åt data på den position pekaren pekar på använder vi *:

 

*pMinIntPekare = 25;

*pMinCharPekare = ‘A’;

iTal2 = *pMinIntPekare;


Pekaren och listan.

 

Hittills har vi använt ett index för att välja ett element i en lista. Som vi ser kan man även använda pekare för att tala om för programmet var i minnet det ska läsa eller skriva, utan att använda variabelnamnet. Pekaren kan också vara bra när man har en lista. Kommer du ihåg det kryptiska exemplet från avsnittet om operatorer i kapitel 2?

 

void kopiera(fran, till)

char *fran, *till;               

{

    while((*till++ = *fran++) != '\0');

}

 

Funktionen ‘kopiera()’ använder två pekare till att hitta de strängvariabler (d.v.s. char-listor) som den anropande funktionen vill ha kopierad från/till.

 

Kom ihåg att när man deklarerar en pekare anger man också vilken datatyp pekaren är avsedd att peka på, därigenom vet pekaren hur många bytes längre fram den ska peka om man ökar pekarens värde med t.ex. 1.

 

Övningsuppgift:

·      I headerfilen string.h som medföljer kompilatorn finns en funktion som heter strcpy(). Den ser ut precis som funktionen kopiera() ovan.

·      Skapa ett nytt projekt kopiera.

·      Skriv ett program som innehåller två strängar vilka initieras till ”Här står det” respektive ” samma sak!”. Strängarna ska deklareras som följer:

         char cText1[] = ”Här står det”;

         char cText2[] = ” samma sak!”;

·      Programmet ska först skriva ut bägge texterna på en rad, sedan anropa strcpy() med andra variabel som första parameter och första variabeln som andra parameter:

         strcpy(cText2, cText1);


Pekarens värde.

 

Om nu t.ex ‘text[3]’ använder ett index = 3 till att peka på det fjärde elementet i variabeln text, vad är det då för skillnad på att använda index från att använda en pekare? Tekniskt sett är det ingen alls, men naturligtvis är det en skillnad: du skriver på ett annat sätt. Dock kommer kompilatorn och länkaren i slutändan att översätta det till en adress, och det är just vad en pekare innehåller.

 

Det första elementet i en lista är t.ex. iDagar[0], d.v.s. alltid en nolla inom hakparanteserna. Adressen till en variabel får man m.h.a. adressoperatorn, ‘&’. T.ex. &iDagar[0]. Nu har man hittat på att förkorta detta i C. Man kan i stället skriva bara t.ex iDagar. Detta tolkas som &iDagar[0].

 

Detta är precis vad vi gjort när vi bett printf() och puts() att skriva ut textsträngar! Se tidigare exempel! Även i övningsuppgiften ovan anropade vi strcpy() på samma sätt. Skulle inte ovannämnda förkortning ha funnits borde vi i stället skrivit:

 

strcpy(&cText2[0], &cText1[0]);

 

Låt oss nu deklarera en pekare som ska peka på någon textsträng:

 

char *pcMinPekare;

 

Man kan nu ge pekaren ett värde (en adress), d.v.s. man ger den något att peka på. Då måste vi ha en adress. Vi kanske deklarerar en charlista:

 

char cMinText[] = ”Detta är min text!”;

 

Då kan man få adressen på ett av nedanstående sätt:

 

pcMinPekare = &cMinText[0];

pcMinPekare = cMinText;

 

Observera att pcMinPekare är en variabel som innehåller en adress nu. Om man i stället skriver *pcMinPekare så menar man datat på den adress som pekaren pekar på. Om man t.ex. skriver:

 

printf(”%d”,*pcMinPekare);

 

Så skriver programmet ut siffran 68, vilket är ASCII-koden för bokstaven ‘D’.


Man kan även ändra i texten:

 

pcMinPekare += 4;

*pcMinPekare = 65;

puts(cMinText);

 

Detta resulterar i utskriften:

 

DettA är min text!

 

Att komma ihåg:

 

·     Ett variabelnamn är ett logiskt namn på en viss plats i minnet. Denna plats har en fysisk adress, och den adressen är en siffra.

 

·     Ett &-tecken framför ett variabelnamn ger variabelns adress i stället för det data variabeln innehåller.

 

·     En pekare är en variabel som är avsedd att innehålla en adress.

 

·     Skriver man pekarens namn avser man att läsa/skriva den adress som pekaren innehåller.

 

·     Skriver man en stjärna före pekarens namn avser man att läsa/skriva på den adress som pekaren innehåller (d.v.s. den variabel pekaren pekar på).

 

·     Pekaren kan peka på vilken adress som helst, vare sig det finns en deklarerad variabel på den adressen eller ej.

 

Övningsuppgift:

·      Skriv ett program backword som har en initierad charlista med texten ”SALLAD OCH SIRAP I PARIS OCH DALLAS”, samt en lika stor charlista som initierats med blanka.

·      Använd två pekare för att kopiera texten i den första charlistan till den andra, men gör så att texten kommer baklänges!

·      Skriv ut bägge texterna.