Gestructureerde types

We hebben al een aantal keer (gedwongen) gebruik gemaakt van een type-declaratie voor strings.
Pascal kent het principe van zelfgemaakte types.
In hoofdstuk 8 (of 9) hebben we hier een paar voorbeelden van gezien.
In dit hoofdstuk gaan we ons bezig houden met gestructureerde types.
Deze versie van Pascal kent twee gestructureerde types, nl. de array en de record.
In latere versies (waaronder Delphi) is daar een derde bijgekomen, nl. de class.

Array’s.

Een string is (wederom in deze versie van Pascal) een array van karakters.
Het voordeel van een array is dat je een (groot) aantal bij elkaar horende waardes efficiënt kan opslaan terwijl iedere waarde afzonderlijk benaderbaar blijft.
Laten we maar vlug naar het eerste voorbeeld gaan kijken:

program p12_1;

type
  TDobbelsteen = array[1..6] of integer;

var
  Dobbelsteen: TDobbelsteen;

procedure InitDobbelsteen(var ds: TDobbelsteen);
  var x: byte;
begin
  for x := 1 to 6 do
    ds[x] := 0;
end;

procedure Gooi(aantal: integer;var ds: TDobbelsteen);
  var
    x: integer;
    w: byte;
begin
  for x := 1 to aantal do
    begin
      w := trunc(random * 6);
      ds[w] := ds[w] + 1;
    end;
end;

procedure Toon(ds: TDobbelsteen);
  var x: byte;
begin
  for x := 1 to 6 do
    write(x:8);
  writeln;
  for x := 1 to 6 do
    write(ds[x]:8);
  writeln;
end;

begin
  randomize;
  InitDobbelsteen(Dobbelsteen);
  Gooi(1000,Dobbelsteen);
  Toon(Dobbelsteen);
end.

Het programma simuleert het 1000 keer gooien met een dobbelsteen en toont vervolgens hoe vaak iedere kant (1 t/m 6) is gevallen.
In dit voorbeeld wordt eerst een type TDobbelsteen gedeclareerd als een array met 6 elementen van het type integer.
Vervolgens wordt een variabele Dobbelsteen van het zojuist gemaakte type TDobbelsteen gedeclareerd.
De variabele Dobbelsteen is eigenlijk een bundeling van 6 integer-variabelen.
Wanneer je een specifieke variabele uit Dobbelsteen wilt benaderen dan geef je z’n rangnummer tussen blokhaken aan.
Als ik hier bv. de waarde van 4-e variabele wil weten dan benader ik die met Dobbelsteen[4].
Met de functie random wordt een pseudo-willekeurig reëel getal tussen de 0 en 1 gekozen, waarbij 0 wel en 1 niet meedoet.
Met de functie trunc([getal]) wordt alles achter de komma van getal afgekapt (dus niet afgerond!).
Met het commando randomize wordt de ‘randomizer’ geschud, anders zou je bij iedere run van het programma dezelfde serie getallen krijgen.
De rest van het programma moet duidelijk zijn.
Overigens is het in Turbo Pascal toegstaan om het array-type in de variabele-decalratie op te nemen.
Hierboven zou je dus ook deze declaratie kunnen gebruiken: var Dobbelsteen: array[1..6] of integer.
Dit is weer niet toegestaan bij parameter-declaraties!

Het voorbeeld hierboven maakte gebruik van een 1-dimensionale-array. Dit zeggende begrijpt de aandachte lezer dat er dus ook meer-dimensionale-array’s bestaan. En dat is juist.
In het volgende voorbeeld gaan we kijken naar een 2-dimensionale array:

program p12_2;

const
  dim = 5;

type
  TMatrix = array[1..dim,1..dim] of integer;

var
  Matrix: TMatrix;

procedure ToonMatrix(m: TMatrix);
  var x,y: byte;
begin
  writeln;
  for x := 1 to dim do
    begin
      for y := 1 to dim do
        write(m[x,y]:4);
      writeln;
    end;
end;

procedure VulMatrix(var m: TMatrix);
  var x,y: Byte;
begin
  for x := 1 to dim do
    for y := 1 to dim do
      m[x,y] := trunc(random*100);
end;

begin
  randomize;
  VulMatrix(Matrix);
  ToonMatrix(Matrix);
end.

Het enige wat dit programmaatje doet is een matrix van 5×5 vullen met willekeurige getallen en vervolgens deze matrix op het scherm tonen.
In dit programma heb ik gebruik gemaakt van een constante-declaratie.
De syntax luidt:

const [naam] = [waarde];

Vervolgens kun je overal waar je iets van [waarde] wilt gebruiken revereren aan [naam].
Mocht later blijken dat [naam] toch een andere waarde moet hebben, dan hoef je dat alleen maar in de constante-declaratie-regel aan te passen.
In het programma hierboven werken we dus met een variabele Matrix van 5×5. Wat hier de x-dimensie en de y-dimensie is laat Pascal graag aan onze fantasie over.
In principe kent Pascal geen beperkingen aan het aantal dimensies van array’s.
De rest moet weer voor zich spreken.

Record’s.

Een record is een type met variabelen (in deze context velden genoemd) die logischerwijs bij elkaar horen.
De syntax luidt:

type
[naam] = record
[veld1]: [type_a];
[veld2],[veld3]: [type_b];
[veld4]: [type_c];
end;

Dus een record moet een naam hebben, en wordt omsloten met record en end;.
Hier binnen declareer je de velden (=variabelen) die je nodig hebt. De type’s van die velden mogen op hun beurt ook weer gestructureerd zijn.
In het volgende voorbeeld gaan we stoeien met een NAW-record waar we NAW-gegevens van een persoon in gaan opslaan.

program P12_3;

type
  s80 = string[80];
  s40 = string[40];
  s20 = string[20];
  s15 = string[15];
  s7 = string[7];
  TNAW =
    record
      achternaam: s40;
      voorletters: s15;
      voornaam: s20;
      adres: s40;
      postcode: s7;
      plaats: s20;
      leeftijd: byte;
      geslacht: char;
      naamcombi: array[1..2] of s80
    end;

var
  NAW: TNAW;

procedure LeesNAWIn(var naw: TNAW);
  var w: char;
begin
  write(‘achternaam : ‘);
  readln(naw.achternaam);
  write(‘voorletters: ‘);
  readln(naw.voorletters);
  write(‘voornaam : ‘);
  readln(naw.voornaam);
  write(‘adres: ‘);
  readln(naw.adres);
  write(‘postcode: ‘);
  readln(naw.postcode);
  write(‘plaats: ‘);
  readln(naw.plaats);
  write(‘leeftijd: ‘);
  readln(naw.leeftijd);
  write(‘geslacht (M/V): ‘);
  repeat
    read(kbd,w);
  until w in [‘m’,’M’,’v’,’V’];
  writeln(w);
  naw.geslacht := w;
  naw.naamcombi[1] := naw.voorletters + naw.achternaam;
  naw.naamcombi[2] := naw.voornaam + naw.achternaam;
end;

procedure ToonNAW(naw: TNAW);
begin
  with naw do
    begin
      writeln(‘achternaam: ‘,achternaam);
      writeln(‘voorletters: ‘,voorletters);
      writeln(‘voornaam: ‘,voornaam);
      writeln(‘adres: ‘,adres);
      writeln(‘postcode: ‘,postcode);
      writeln(‘plaats: ‘,plaats);
      writeln(‘leeftijd: ‘,leeftijd);
      write(‘geslacht: ‘);
      if (geslacht=’m’) or (geslacht=’M’) then
        writeln(‘Man’)
      else
        writeln(‘Vrouw’);
      writeln(‘combi 1: ‘,naamcombi[1]);
      writeln(‘combi 2: ‘,naamcombi[2]);
    end;
end;

begin
  LeesNAWIn(NAW);
  writeln;
  writeln;
  ToonNAW(NAW);
end.

Bij de type-declaratie is record-type TNAW gemaakt, waarin bepaalde waardes behorend bij een persoon logischerwijs bij elkaar zijn gezet.
Let even op het type van naamcombi, dit is een array.
Vervolgens maken we een variabele NAW, waarin de gegevens daadwerkelijk kunnen worden opgeslagen.
Om bij een specifiek veld van NAW te komen, gebruiken we de punt-selector. Dus met NAW.achternaam bereik je het veld achternaam.
Bij de procedure LeesNAWIn wordt hiervan bij de read’s gebruik gemaakt.
Bij de procedure ToonNAW maken we gebruik van de with-constructie. Hiermee onderdrukken we het gebruik van de punt-selector.
De syntax luidt:

with [variabele] do [opdracht];

Hierbij is [variabele] de naam van de variabele en [opdracht] de opdracht waarmee je iets met de [variabele] kunt doen. Ook hier mag [opdracht] een samengestelde opdracht zijn.

Het zou natuurlijk prettiger zijn als we de gegevens van meer dan één persoon zouden kunnen verwerken.
En dat is dan ook de volgende uitbreiding van het programma.

program P12_4;

const
  Max = 2;

type
  s80 = string[80];
  s40 = string[40];
  s20 = string[20];
  s15 = string[15];
  s7 = string[7];
  TNAW =
    record
      achternaam: s40;
      voorletters: s15;
      voornaam: s20;
      adres: s40;
      postcode: s7;
      plaats: s20;
      leeftijd: byte;
      geslacht: char;
      naamcombi: array[1..2] of s80;
    end;
TNAWs = array[1..Max] of TNAW;

var
  NAW: TNAWs;

procedure DrukToets;
  var c: char;
begin
  write(‘Druk op een toets…’);
  read(kbd,c);
  writeln;
end;

procedure LeesNAWIn(var naw: TNAW);
  {dezelfde implementatie als in P12_3}

procedure ToonNAW(naw: TNAW);
  {dezelfde implementatie als in P12_3}

procedure LeesNAWsIn;
  var x: byte;
begin
  for x := 1 to Max do
    begin
      writeln(‘Voer de ‘,x,’-e persoon in:’);
      writeln;
      LeesNAWIn(NAW[x]);
      writeln;
    end;
end;

procedure ToonNAWs;
  var x: byte;
begin
  for x := 1 to Max do
    begin
      writeln(‘Gegevens van ‘,x,’-e persoon:’);
      writeln;
      ToonNAW(NAW[x]);
      writeln;
      DrukToets;
      writeln;
    end;
end;

begin
  LeesNAWsIn;
  writeln;
  writeln;
  ToonNAWs;
end.

Bij de type-declaraties is TNAWs toegevoegd als array van TNAW. Het aantal NAW’s hangt af van de constante Max.
De variabele NAW is nu van het type TNAWs.
De procedure LeesNAWsIn leest nu Max NAW’s in.
De procedure ToonNAWs laat de Max NAW’s per NAW zien, daartoe gebruik makend van de procedure DrukToets.

Tot slot willen we de ingevoerde gegevens natuurlijk op schijf kunnen bewaren.
Zoals in hoofdstuk 10 reeds vermeld kunnen we dat doen d.m.v. een binair bestand dat gekoppeld is aan een type.

program P12_5;

const
  Max = 2;

type
  s80 = string[80];
  s40 = string[40];
  s20 = string[20];
  s15 = string[15];
  s7 = string[7];
  TNAW =
    record
      achternaam: s40;
      voorletters: s15;
      voornaam: s20;
      adres: s40;
      postcode: s7;
      plaats: s20;
      leeftijd: byte;
      geslacht: char;
      naamcombi: array[1..2] of s80;
    end;
TNAWs = array[1..Max] of TNAW;

var
  NAW: TNAWs;
  bNAW: file of TNAW;

function Bestaat(bn: s80): Boolean;
  var b: file;
begin
  assign(b,bn);
  {$I-}
    reset(b);
  {$I+}
  if IOResult = 0 then
    begin
      close(b);
      Bestaat := true;
    end
  else
    Bestaat := false;
end;

procedure DrukToets;
  {dezelfde implementatie als in P12_4}

procedure LeesNAWIn(var naw: TNAW);
  {dezelfde implementatie als in P12_3}

procedure ToonNAW(naw: TNAW);
  {dezelfde implementatie als in P12_3}

procedure LeesNAWsIn;
  {dezelfde implementatie als in P12_4}

procedure ToonNAWs;
  {dezelfde implementatie als in P12_4}

procedure NAWsNaarSchijf;
  var x: byte;
begin
  assign(bNAW,’NAWS.GSP’);
  rewrite(bNAW);
  for x := 1 to Max do
    write(bNAW,NAW[x]);
  close(bNAW);
end;

procedure NAWsVanSchijf;
  var x: byte;
begin
  assign(bNAW,’NAWS.GSP’);
  reset(bNAW);
  for x := 1 to Max do
    read(bNAW,NAW[x]);
  close(bNAW);
end;

begin
  if Bestaat(‘NAWS.GSP’) then
    begin
      NAWsVanSchijf;
      ToonNAWs;
    end
  else
    begin
      LeesNAWsIn;
      writeln;
      writeln;
      ToonNAWs;
      NAWsNaarSchijf;
    end;
end.

Bij de variabele-declaratie is een variabele bNAW: file of TNAW gedeclareerd. Met deze variabele kunnen we een bestand maken of lezen die als inhoud NAW-reocrds heeft. Deze records worden binair opgelagen.
De functie Bestaat is al in hoofdstuk 10 besproken.
De procedure NAWsNaarSchijf zet de NAW-records op schijf. Hierbij wordt het write-statement gebruikt en niet writeln.
De procedure NAWsVanSchijf leest de NAW-records van schijf. Hierbij wordt het read-statement gebruikt en niet readln.

Tot zover dit uitgebreide hoofdstuk.
Tot zover ook de basis van het programmeren.
De eventuele verdere hoofdstukken behandelen andere gevorderde programmeer-zaken.

Oefeningen.

12.1
Schrijf een programma dat twee matrices (maximale dimensie 10) vermenigvuldigd.
De gebruiker moet eerst de dimensies van de 1e matrix ingeven. Hierna moeten de twee matrices worden ingelezen.
Ten slotte moet de resultaat-matrix worden getoond.
De matrix vermenigvuldiging gaat als volgt:
Het element (x,y) van de resultaat matrix is de som van de producten van de oplopende elementen van de x-e rij van de eerste matrix met de oplopende elementen van de y-e kolom van de tweede matrix.
Dat betekend dat wanneer de eerste matrix de dimensie axb heeft de tweede als dimensie bxa moet hebben.
Verder zal de resultaat-matrix een vierkante dimensie hebben van axa als a groter is dan b, anders bxb.
Voorbeeldje:

|1|           |3 4|
| | x (3 4) = |   |
|2|           |6 8|

[Antwoord]

12.2
Schrijf een programma dat een datum als string inleest en deze middels een procedure omzet naar een variabele van het type TDatum.
Uiteraard moet de procedure testen of de string wel een datum is! Zo niet dan worden alle velden (zie volgende regel) op 0 gezet.
Het type TDatum is een, zelf te maken, record-type met de volgende integer-velden: jaarmaand en dag.
Vervolgens moeten er drie functies bestaan die uit een TDatum-variabele resp. het JaarMaand en Dag als integer moeten halen.
Test alles door de invoer middels de functies als uitvoer te geven. [Antwoord]

12.3
De meest complexe tot nog toe, maar laat je daardoor niet van de wijs brengen. Met alle kennis die je tot nog toe hebt opgedaan en met nog enkele tips kom je er wel uit.
Deze oefening luidt: Schrijf een programma dat de volgende handelingen met breuken kan uitvoeren:
– twee breuken optellen
– twee breuken aftrekken
– twee breuken vermenigvuldigen
– twee breuken delen
– een breuk vereenvoudigen
– een breuk decimaal kan maken.

Tevens moet het programma in staat zijn om optioneel de resultaten in een bestand op schijf te bewaren.

Eén en ander moet in een menu aan de gebruiker worden aangeboden.

Tips:
Gebruik een record-type TBreuk om een breuk te representeren.
Om breuken te vereenvoudigen is het handig om de ggd van teller en noemer te bepalen, schrijf daartoe een functie Ggd.
Hierbij kun je gebruik maken van het algoritme van Euclides:
1. Bepaal de gehele deling (div) en rest (mod) van teller en noemer,
2. Als de rest 0 is, dan is de ggd=noemer,
3. Anders is de ggd= ggd van de noemer en de rest (dus recursief).
Om een breuk in te lezen maak je gebruik van een procedure die teller en noemer apart vraagt.
Om een breuk op het scherm (of in het bestand) te schrijven maak je gebruik van een functie.
Daarbij is het handig om een functie IntToStr te hebben die een integer omzet naar een string.
Maak die functie en maak daarbij gebruik van de ingebouwde functie chr([waarde]), die [waarde] omzet naar een char met ascii-waarde [waarde] (‘0’=48, ‘9’=57). [Antwoord]

[Recursie] <– –> [Pointers]