Tilbake til startsida IKT i praksis - Pascal

Meny
 
 
 
 
Tabellar (Array)
 
 
 
 
 
 

Array (tabell)

I svært mange samanhengar er det enklare å lagre eller handtere data i form av tabellar enn som enkeltvariabler. I Pascal finst det fleire metodar for å lage slike tabellar, eller array som det heiter i programmeringsspråka. Alt avhengig av kva tabellen skal brukast til, kan ein i Pascal velje ein- eller fleir-dimensjonale tabellar. I tillegg også statiske og dynamiske utgåver av desse. Dette kapitlet vil ta føre seg noen av bruksmåtane.

Enkel array

I dette første eksemplet ønskjer eg å lagra maksimumstemperaturane for kvar dag i ein månad. Sjølvsagt kunne eg ha laga til 31 variablar med namn som dag1, dag2, dag3 osv. Men i staden definerer eg ein tabell:

var temp: array[1..31] of real;

Tala [1..31] definerer tabellen til å innehalde 31 element, eitt element for kvar dag i månaden, nummererte frå 1 til 31. Prikkane er to punktum etter kvarandre. Desse tala blir kalla «indekstal», eller i programverda helst index. Dei må alltid skrivast mellom hakeparentesar.

Den 1. januar les eg av temperaturen til å vere -5,0 grader. Dette blir skrive inn i tabellen slik:
temp[1] := -5,0;
og tilsvarande for dei andre dagane.

For å hente ut data, skriv eg t.d.
data1 := temp[1];
Skal den ferdige lista skrivast ut, er nok den enklaste måten å bruke for - to løkke:
for i := 1 to 31 do
       Form1.Canvas.TextOut(10,i*20,IntToStr(i)+'. jan. :' + FloatToStr(temp[i]));

(FloatToStr er for å gjere om desimaltalet til streng, som kan skrivast ut. IntToStr er tilsvarande for heiltal). Dette vil då resultere i ei liste nokolunde slik:
1. jan, : -5,0
2. jan, : -9,2
3. jan, : -1,9
osv.

Fleirdimensjonal array

Den definerte tabellen, temp[1..31], kan innehalde data for ein månad. Det neste steget er å lage ein tabell som kan innehalde data for kvar månad i eitt år. Dette kan gjerast på fleire måtar, men ein nokså forståeleg måte er å definere tabellen som ein tabell med 12 månader der kvar månad igjen er ein tabell med 31 dagar. Definisjonen kan då vere:
var aaret: array[1..12] of array[1..31] of Real; eller litt enklare:
var aaret: array[1..12, 1..31] of Real;
Treng du å vite temperaturen 17. mai, bruker du temp := aaret[5,17]; Sidan Pascal godtar også variablar som indekstal, kan du definere kvar månad med månadsnummeret (const januar = 1; februar = 2; osv.) og skrive temp := aaret[mai,17]; for å hente ut temperaturen 17. mai.

Du kan definere så mange dimensjonar du måtte ha bruk for. Blir temperaturen registrert kvar time i døgnet, kan du definere ein 3. dimensjon som inneheld timane: var aaret: array[1..12, 1..31,0..23] of Real;
For å hente ut temperaturen 17. mai kl 13, skriv du temp := aaret[5,17,13];
Går temperaturregistreringa over fleire år, kan du sjølvsagt utvide tabellen tilsvarande. Eksemplet nedanfor viser at ein treng ikkje byrje med 0 eller 1 for indekstalet. Her er tabellen sett opp til å gjelde frå år 2010 til år 2020.
var temperatur: array[2010..2020, 1..12, 1..31] of Real;
Skulle eg igjen ha planer om å registrere temperaturen kvar time, blir tabellen
var temperatur: array[2010..2020, 1..12, 1..31, 0..23] of Real;

Array av Record

Er du svært pirkete, vil du ha funne ut at det i grunnen ikkje er nødvendig å bruke desimaltal (Real) i temperaturlista anna enn for temperaturen. Dei andre verdiane er heiltal. I Pascal er det ikkje råd å blande element av ulike typer som t.d. Integer, String osv. i ein tabell. Difor måtte eg velje Real sidan denne kan brukast for alle variablane i dette tilfellet.

Har ein behov for å lage ein tabell med ulike typer, kan ein definere ein datatype som inneheld dei ønskte verdiane. Akkurat i temperatureksempelet er det kanskje like enkelt å bruke array nettopp slik det er gjort, men andre gonger kan andre løysingar vere enklare. Sett at du har eit kryssordprogram som mellom anna skal innehalde ein tabell med løysingsordet, stikkord og nummer for plasseringa i rutemønsteret. I tillegg om ordet er vassrett eller loddrett. For å kunne plassere ordet korrekt i diagrammet, bør tabellen også innehalde koordinata for første bokstav i ordet. Eg definerer difor ein Type som inneheld alle dei nødvenige data for kvart løysingsord i kryssordet.
Type
  svar = record
    x,y,nr: Integer; // x- og y-koordinata + nr. i kryssordet for første bokstav
    ordet, stikkord: String;
    retning: Boolean; // vassrett: True, loddrett: False
  end;

Neste steg er å definere ein tabell som kan innehalde alle orda i kryssordet. Dersom ein går ut frå at det ikkje er nødvendig med meir enn 100 løysingsord, kan tabellen definerast slik:
var xord: array[1..100] of svar;

For å fylle inn dei aktuelle verdiane i tabellen, kan dette gjerast slik:
xord[1].x :=0; // x=0, y=0 er koordinata for første rute
xord[1].y :=0;
xord[1].nr :=1; // Plassering nr. 1
xord[1].ordet := 'KRYSSORD';
xord[1].stikkord := 'Pusleoppgåve';
xord[1].retning := True; // Vassrett

For å hente ut igjen data frå tabellen er det som vanleg bare å gå andre vegen:
nr:= xord[1].nr;
tekst := xord[1].ordet;
key := xord[1].stikkord;

Dynamisk tabell

Det er slettes ikkje alltid at ein på førehand veit kor stor ein tabell må vere for å dekke alle behova. I slike tilfelle, som t.d. i eksemplet ovanfor, kan det vere greit å utvide tabellen etter kvart som behova melder seg. Då er dynamiske tabellar løysinga. Alle tabellar som kan definerast som ein- eller fleirdimensjonerte kan også definerast som dynamiske.

Først ei lita åtvaring. Når ein programmerer i Pascal kjem det gjerne opp ei melding om kva som er feil dersom ein gjer tabber i kodeinnskrivinga. Dette gjeld også køyretidsfeil (Run Time Error), som t.d. kan fortelje at ein variabel er blitt tileigna verdiar utanom det han er definert for. Når ein prøver ut dynamiske tabellar, finst det ikkje noe slik feilmelding. Du får i staden opp ei melding om at det er køyretidsfeil, utan nærare forklaring. I Lazarus kjem meldinga «Project dynamic.exe raised exception class 'External: SIGSEGV'.» opp. Dette betyr at eit eller anna har gått gale under køyringa av programmet, men seier ikkje noe om kva som er gale. Du må altså sjølv finne feilen.

Tilbake til emnet. I kryssordtabellen veit vi eigentleg ikkje kor mange innskrivingar som trengst for kvart kryssord. Det er altså naturleg å definere lengda på tabellen etter kvart som vi skriv inn nye data. Først må vi definere tabellen som «dynamic». Dette er nokså enkelt. Vi gjer det på same måten som for dimensjonerte tabellar, men sløyfer lengda. Altså:
var xord: array of svar;
Tabellen xord er nå definert, men utan lengde. Denne definerer vi med kommandoen SetLength(xord, lengde); der variabelen lengde sjølvsagt er definert på førehand med ein eller annan verdi.

Nå gjeld det bare å halde styr på kor lang tabellen må vere. For kvar gong du skriv eit nytt ord inn i tabellen, aukar du variabelen lengde med 1. Enklast med inc(lengde); Deretter må du definere ny lengde for tabellen: SetLength(xord, lengde);

I Pascal blir dei dynamiske tabellane definerte med første element som 0. Ein tabell med lengde 10 inneheld elementa tabell[0], tabell[1] osv til tabell[9]. Det siste elementet i tabellen er altså lengde - 1. Fort gjort å gløyme dette.

Det er nokså viktig å halde styr på lengda av tabellen. Difor noen eksempel på korleis vi kan kontrollere tabellen:
Sett at vi har definert tabellen vår slik:
SetLength(xord,10)
For å finne ut litt om tabellen, kan vi skrive
with Form1.Canvas do
  begin
    TextOut(10,10,'Tabellen har '+IntToStr(length(xord))+' element'); // Skal bli 10
    TextOut(10,30,'frå '+IntToStr(low(xord))+' til '+IntToStr(high(xord))); // 0 til 9
  end;

Dersom programmet inneheld fleire stader der variabelen lengde blir forandra, kan det vere lurt å definere ny lengde på tabellen som lengde := length(xord) + 1 og deretter bruke SetLength(xord,lengde);

Utskriving frå dynamiske tabellar

Greier ein å halde styr på kor lang tabellen er til ei kvar tid, kan ein bruke vanleg for - to løkke
for i:=0 to lengde do

Ein sikrare metode for å unngå køyretidsfeil, er å bruke
for i := low(tabell> to high(tabell) do
  begin
    …
  end;

Då har ein i alle fall sikra seg mot køyretidsfeil som kjem når verdien for lengde er større enn tabellengda.

Fjerne element

Det finst ingen enkeltkommando for å fjerne element frå ein dynamisk tabell. Eg måtte difor tenke litt sjølv, og kom fram til ein liten programsnutt for denne jobben. Her går eg ut frå at tabellen tabell er globalt definert, altså at han kan nåast frå alle stader i programmet.

procedure slettElement(nr: Integer);
var sist, x: Integer;
begin
  sist := high(kryssord);
  if nr in [0,sist] then
    begin
      if (nr < sist) then
        for x := nr to sist-1 do
          tabell[x] := tabell[x+1];
      setLength(tabell,length(tabell)-1);
    end;
end;

Litt forklaring. Variabelen sist blir tileigna verdien for indeksen til det siste elementet i tabellen. For å sikre seg at det elementet som skal fjernast har ein gyldig indeksverdi, vil prosedyren bli utført bare dersom verdien er innføre dei verdiane tabellen har. Dette skjer i if nr in [0,sist] som kontollerer at verdien er frå og med 0 til og med indeksen for siste element. Løkka for - to vil då flytte alle elementa som har høgare verdi enn nr eitt hakk nedover på lista. Det elementet som skal fjernast blir såleis overskrive av det elementet som ligg eitt hakk høgare. Det siste elementet får ikkje noen ny verdi, men blir fjerna likevel med kommandoen SetLength som set lengda på tabellen til 1 mindre enn den opphavlege lengda.

For spesialistane

Ein dynamisk tabell er eigentleg ein peikar. Når du endrar lengda på tabellen, vil det bli frigitt eller okupert minne i datamaskinen alt etter som tabellen blir gjort mindre eller større. Set du verdien til NIL, eller lengda til 0, vil tabellen forsvinne og heile minneplassen tabellen opptar bli frigitt. Ut frå det eg har forstått, vil programmet automatisk frigjere minneplass når det blir avslutta. Håper det stemmer.


Send melding

© Innhald og design:  Kolbjørn StuestølStuestøl heimesideSist endra 11. april 2013