Umeå universitet
Datavetenskap
Lennart Edblom
Tentamen
1997-04-30
Datavetenskap A, mom.2
Tentamen- lösningsförslag
Datavetenskap A, moment 2 (7 poäng)
16 maj 1998
Skrivtid
: 9-15
Hjälpmedel:
Appendix C och D ur Wikström:Functional Programming Using Standard ML
Maxpoäng 58 (för godkänt krävs ca 29 poäng).
Tentanden får behålla detta frågeformulär.
Läs igenom alla uppgifter,så att du upptäcker eventuella oklarheter. Jag besöker er i skrivsalen ungefär kl
12.30 , då kan ni be om hjälp. För övrigt är jag anträffbar på tel 010-652 5394 för det mesta.
Uppgifterna är slumpmässigt oordnade.Se alltså till att du hinner försöka på alla uppgifter! En del är
kanske lättare än du tror. Me se till att du inte fastnar på ML-delen! Disponera din tid så att du hinner
jobba med båda delarna!
Lämna in lösningarna i nummerordning. Endast ett problem på varje blad! Skriv namn på varje blad. Fyll
också i försättsbladet, och markera med X de uppgift du lämnar in ett lösningsförslag på.
Kommentera källkoden, så att jag kan se om du tänkt rätt men skrivit fel. Om problembeskrivningen (trots
att ni frågat mig) är oklar, bestäm dig då för en rimlig tolkning och anteckna den vid din lösning.
Även en ofullständig lösning kan ge några poäng. Om en uppgift innehåller en deluppgift som du inte kan
lösa, får du ändå i följande deluppgifter om så är lämpligt använda en funktion som antas lösa den olösta
deluppgiften.
Skriv tydligt! Försök att göra lösningarna så lättlästa som möjligt
De fördefinierade funktionerna i appendix D får användas om ej annat anges.
Uppgift 1 (6p)
a) Vilka typer har följande uttryck/funktioner:
• ([5],[8])
int list * int list
• fn f => fn x => f x div x;
(int -> int) -> int -> int
• [[(”a”,1), (”b”,2)], [(”c”,3), (”d”,4)]]
• fun f [ ] = [ ]
| f ((x,y)::t) = x::f t
(string * int) list list
(‘a * ‘b) list -> ‘a list
b) Förklara kortfattat men uttömmande följande begrepp.
1) Polymorf funktion
2) Högre ordningens funktion
a) Polymorf funktion - en funktion vars typ beror av de argument
den appliceras på. Det typuttryck som beskriver funktionens typ
innehåller typvariabler. Den fungerar på en hel ”familj” av typer
eftersom funktionen opererar på argumentets struktur, inte själva
värdena.
b) Högre ordningens funktion - en funktion som tar en annan
funktion som argument och /eller returnerar en funktion som
resultat.
Uppgift 2 (3p)
a) Visa steg för steg hur följande uttryck beräknas (reduceras) i ML. Alla inblandade
funktioner (genlist, times) finns definierade i Appendix D
genlist 2 (times 3) 3;
genlist 2 (times 3) 3 ->
2:: genlist (times 3 2) (times 3) 2 ->
2:: genlist 6 (times 3) 2 ->
2:: 6:: genlist (times 3 6) (times 3) 1 ->
2:: 6:: genlist 18 (times 3) 1 ->
2:: 6:: 18:: genlist (times 3 18) (times 3) 0 ->
2:: 6:: 18:: genlist 54 (times 3) 0 ->
2:: 6:: 18:: nil ->
[2,6,18]
Eftersom uttryck i ML alltid evalueras från vänster till höger blir det genlist som först
”försöker hitta” sina argument. Det blir inte (times 3) som tar 3 som argument.
b) Vad gör ML-funktionen volvo? (Vilket är det vanliga namnet på funktionen?)
fun volvo saab fiat nil = nil
| volvo saab fiat (opel::audi) =
saab opel (volvo saab fiat audi)
Tyvärr blev det ett fel i ovanstående funktion. Resultatet i basfallet skulle ha varit fiat, inte nil!
Då hade det möjligen varit lättare att se att detta är reduce-funktionen. (Inte map som många
hade satsat på).
Uppgift 3 (4p)
Romerska talsystemet är inte ett positionssystem som vårt talsystem, utan talen beskrivs
som en sekvens av bokstäver där bokstäver med störst värde kommer först. Bokstäverna
har följande värden:
I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000
Om en bokstav används flera gånger adderas dess värde för varje gång, utom i de fall då
bokstaven är före en bokstav med större värde; då subtraheras bokstavens värde i stället.
Ex:
II
=2
IV
=4
XIX
=19
LXXXIX
=89
Du ska skriva en ML-funktion som omvandlar en sträng som representerar en romersk
siffra till ett heltal. Du får anta att strängen är ett korrekt format romerskt tal.
Uppgift 4 (7p)
a) Definiera en datatyp FRUIT med två konstruerarkonstanter Apple och Pear.
b) Antag att tack vare framsteg inom botaniken skulle man kunna odla fruktträd med både
äpplen och päron på samma träd. Definiera en datatyp FRUITTREE! Den nya typen ska
vara en binär struktur med ett värde av typen FRUIT i "löven". Exempel:
c) Skapa och bind trädet i figuren ovan till namnet mytree med hjälp av en deklaration!
2
d) Skriv en funktion count av typ FRUITTREE -> (int *int) som räknar antalet äpplen och
antalet päron på ett träd!
e) Skriv ett uttryck som beräknar antal äpplen och antalet päron på mytree !
a)
datatype FRUIT = Apple | Pear;
b)
datatype FRUITTREE = Leaf of FRUIT | Node of FRUITTREE*FRUITTREE;
Det träd jag deklarerat kan inte vara tomt. Vill man kunna ha såna kan man lägga
till en konstruerare Empty.
Observera att man måste ha en konstruerare även för FRUIT. Flera skriver
datatype FRUITTREE = FRUIT | Node ......
men då blir FRUIT en ny konstruerare, och Apple och Pear är oåtkomliga.
Andra skriver datatype FRUITTREE = Empty | Node of (FRUITTREE * FRUITTREE),
men då får vi ett träd med bara struktur, inga frukter! En annan variant är
datatype FRUITTREE = Empty | Node of ( FRUIT * FRUITTREE * FRUITTREE), men då
får vi frukter i alla noder, även de interna.
Ytterligare andra vill gärna ha med en typvariabel och deklarera ett ‘a FRUITTREE, vilket
visserligen inte är fel, men helt poänglöst i denna uppgift, eftersom vi sagt att det ska vara
FRUITs och inget annat i trädet. Många använder dessutom en typvariabel på högersidan
utan att ha deklarerat någon på vänstersidan, dvs man har bara skrivit datatype
FRUITTREE.
Ännu en typ av fel är att använda typen FRUIT (i stället för FRUITTREE) på högersidan.
men då får vi ingen rekursiv typ, och kan ej bygga några träd!
c)
val mytree = Node(Node(Leaf Pear,Leaf Apple),
Node(Leaf Apple,Node(Leaf Pear,Leaf Apple)));
d)
fun count (Leaf Apple) = (1,0)
|
count (Leaf Pear) = (0,1)
|
count (Node (l,r)) =
let val (al,pl) = count l
and (ar,pr) = count r
in (al+ar,pl+pr)
end;
Man kan förstås också ha en underfunktion som räknar äpplen, och en som räknar
päron, som en del varit inne på.
e)
count mytree;
Uppgift 5 (6p)
Innehållet i ett lager representeras av en lista av föremål. Varje föremål representeras
av en trippel av typ (string*int*int) , där betydelsen av delarna är (namn, antal i
lager, gräns för att beställa mer).
a) Skriv en funktion som returnerar en lista av de föremål där antal i lager är mindre än
beställningsgränsen. Ex:
-getmore [("RAM",9,10),("ROM",12,10),("PROM",20,21)];
> val it = [("RAM",9,10),("PROM",20,21)]:(string*int*int)list;
b) Skriv en funktion som tar en lagerlista och en uppdateringslista av typ
(string*int)list där varje par har betydelsen (namn, antal nyinkomna), och skapar
en ny lagerlista där antal i lager = gammalt antal i lager + antal nyinkomna.
Posterna i uppdateringslistan kan vara i godtycklig ordning. Det behöver inte finnas
uppdateringsposter för alla föremål i lagerlistan. Det kan finnas flera
uppdateringsposter för samma föremål.
Exempel:
- update [("RAM",9,10),("ROM",12,10),("PROM",20,21)]
[("PROM",15),("RAM",12),("PROM",15)];
>[("RAM",21,10),("ROM",12,10),("PROM",50,21)]:(string*int*int)list;
3
a) Jag visar en lösning här där jag definierar s.k. projektionsfunktioner för att plocka fram de
olika komponenterna i tuppeln. Naturligtvis kan man göra det direkt med
mönstermatchning också.
fun item ((s:string),n,r)=s;
fun numb (s,(n:int),r) = n;
fun reord (s,n,r) = r;
fun getmore [] = []
| getmore (x::xs) = if numb x < reord x then x::getmore xs
else getmore xs;
Som vanligt kan man också använda en ackumulerande parameter där man samlar
upp listan på de varor som ska beställas i denna parameter. Detaljerna kan ni säkert
lista ut själva.
Ingen hade löst detta med en HOF, vilket ju annars kan vara ganska elegant. men
det är kanske inte den lösning man först tänker på.
local fun check (x,(y:int),(z:int)) = y<z
in
fun getmore l = filter check l
end;
b) Det som är lite knepigt med denna uppgift är att det krävs dubbel rekursion, som i mitt
lösningsförslag nedan där man för varje post i uppdateringslistan måste gå igenom (med
rekursion) lagerlistan tills man hittar den post som ska uppdateras. Några har försökt
smälta samman detta i en enda rekursiv funktion, men det är svårt...
Ett gångbart alternativ är att för varje post i lagerlistan gå igenom uppdateringslistan
för att hitta uppdateringar. Men då måste man komma ihåg att varje vara kan ha flera
uppdateringsposter, så man kan inte avsluta rekursionen när man hittar en
uppdateringspost, utan man måste gå igenom hela uppdateringslistan. Det andra
lösningsförslaget visar hur detta kan gå till.
fun uitem ((s:string),n) = s;
fun unumb (s,(n:int)) = n;
fun upd [] u = []
| upd (x::xs) u = if item x = uitem u
then (item x,numb x + unumb u,reord x)::xs
else x::upd xs u;
fun update xlist [] = xlist
| update xlist (u::us) = update (upd xlist u) us;
Alt:
fun upd v [] = v
| upd (name,ant,lim) (u::us) = if name = uitem u
then upd (name, ant+unumb u,lim) us
else upd (name,ant,lim) us;
fun update [] ulist = []
| update (x::xs) ulist = (upd x ulist)::update xs ulist;
Uppgift 6(4p)
Givet strukturen
structure MY_struct =
struct
abstype ELEM = Pair of (int * string)
with
fun mkElem (i,s) = Pair(i,s)
end
fun inc (x:int,s) = (x+1,s)
val x= Pair(1,”one”)
end
4
Vilka av följande signaturer matchar strukturen? Om en signatur inte matchar, förklara
varför!
signature ONE = sig end
signature TWO = sig
type ELEM
val inc : ELEM -> ELEM
end
signature THREE = sig
datatype ELEM
val x : ELEM
end
signature FOUR = sig
type ELEM
val mkElem : (int *string) -> ELEM
val inc : (int *string) -> (int *string)
val x : ELEM
end
ONE är OK, matchar vilken struktur som helst
TWO matchar inte, ty funktionen inc i strukturen är av typ (int * 'a) -> (int * 'a)
THREE är en felaktig signatur, en datatype i en signatur måste ha minst en konstruktor. Kan inte
heller motsvaras av en abstype i strukturen
FOUR är OK om vi antar att det står val x=mkElem(1,”one”) i strukturen
Följande uppgift ska endast lösas av de C-studenter som enbart
tenterar ML (ej programspråksdelen).
Uppgift 7(6p)
Vi har läst in ett dokument till en enda lång sträng. Nu vill vi leta reda på alla ord i
detta dokument som innehåller en annan, given sträng. De tecken som skiljer ord åt i
dokumentet är "\n", " ", "," och ".", alltså newline, blank, komma och punkt. Det kan
finnas flera av dessa ordskiljande tecken efter varann.
Din uppgift är att skriva en ML-funktion lookup som givet två strängar, dokumentsträngen och söksträngen, returnerar en lista med alla ord som innehåller söksträngen.
Tänk igenom uppgiften och skriv ner en algoritm innan du börjar koda. Dela upp den i
deluppgifter / hjälpfunktioner på lämpligt sätt. Även om du inte kan lösa uppgiften som
helhet så kan en korrekt algoritm och/eller korrekta delfunktioner ge poäng.
Ex: lookup "tom,peng. pung" "om" ska returnera ["tom"]
Denna lösning är ej testkörd. Det kan finnas nåt fel i den, det kan också finnas smartare
lösningar. Jag har inte heller skrivit ner nån algoritm fastän ni bör ha gjort det i era
lösningar.
prefix tar två listor av strängar (där varje sträng lämpligen består av bara ett tecken)
och kollar om den första listan är ett prefix till den andra
fun prefix [] ys = true
| prefix xs [] = false
| prefix (x::xs) (y::ys) = x=y andalso prefix xs ys;
substring tar två listor av strängar och kollar om den första listan finns som en dellista
av den andra. (Om man tänker sig att man sedan applicerar implode på de två
argumenten så har man alltså kollat om den första strängen är en delsträng av den
andra.)
5
fun
|
|
ys;
fun
|
|
|
|
substring [] ys = true
substring xs [] = false
substring xs (yys as y::ys) = prefix xs yys orelse substring xs
separator
separator
separator
separator
separator
" " = true
"." = true
"," = true
"\n" = true
_ = false;
words är av typen string -> string list list . Den tar en sträng, "exploderar" den till en
lista av strängar med ett tecken, delar upp denna i ord (ett ord = en lista av
enteckensträngar) och returnerar en lista av ord. Ordskiljande tecken tas bort
fun words ss =
let fun getword w [] = rev w
| getword w (x::xs) = if separator x then (rev w)::skip xs
else getword (x::w) xs
and fun skip [] = []
| skip (x::xs) = if separator x then skip xs
else getword [x] xs
in skip (explode ss)
end;
fun search key dok =
map implode (filter (substring (explode key)) (words dok));
Uppgift 8 (2p)
Para ihop rätt språk med rätt kodsnutt!
Prolog
Ada
MEAN <– (+/A) ÷ A
APL
sum = ++count
C
Fortran IV
(COND ((NULL L) 0) (T ADD1 (CDR L)))
LISP
LISP
DO 100 I=1,10
Fortran
APL
C
Uppgift 9 (6p)
a) Förklara tydligt hur var och en av följande parameteröverföringsmetoder fungerar.
Pass-by-value
Pass-by-reference
Pass-by-value-result
Pass-by-value
Den aktuella parameterns värde beräknas, och tilldelas den formella parametern. F.P.
fungerar sedan som en lokal variabel i underprogrammet, den aktuella parameterns
värde kan ej ändras.
Pass-by-reference
Den aktuella parameters adress beräknas. Om a.p. är ett uttryck allokeras en ny adress.
Den formella parametern sätts att referera till den aktuella parameterns adress. En
ändring av f.p. kommer alltså att påverka a.p.
Pass-by-value-result
Fungerar som c-b-value, med det tillägget att när återhoppet sker kopieras det värde
som den formella parametern då har tillbaka till den aktuella parametern.
b) Konstruera ett exempel som ger olika resultat för var och en av ovanstående tre metoder
6
Uppgift 10 (4p)
I boken delas arrayer upp i fyra kategorier, baserat på olika alternativ för bindning av
index och minnesallokering. Vilka är dessa fyra kategorier? Beskriv hur (när och var)
bindning av index och minnesallokering görs för var och en av dem.
• Statiska arrayer - bindning av index och minnesallokering görs statiskt, innan
exekveringen. Inga ändringar kan göras.
• ”Fixed stack-dynamic” - indexgränser bestäms statiskt och kan ej ändras, men
minne allokeras (på exekveringsstacken) när deklarationen ”elaboreras”, dvs t ex
när det underprogram där arrayen deklareras exekveras.
• Stack-dynamic” - indexgränserna bestäms först när minne allokeras (alltså när
deklarationen ”elaboreras”), men kan sedan inte ändras, dvs arrayens storlek kan
inte ändras. Minne allokeras på stacken p.s.s. som för ”fixed stack-dynamic”.
• ”Heap-dynamic” - indexgränser och arraystorlek kan ändras dynamiskt, som en följd
måste även minnesallokering göras dynamiskt och kunna ändras. Görs då på ”högen”.
Många har blandat ihop detta med de fyra typer av variabler man kan urskilja (och som
efterfrågades i första tentan). Dessa kategorier sammanfaller bara delvis med de arraykategorier som efterfrågas!
Uppgift 11 (5p)
a) Vad menas med referensomgivningen för en sats/ ett uttryck?
Referensomgivningen till en sats / ett uttryck är alla namn (identifierare, variabler) som är
”synliga” för den satsen, dvs som man kan referera till / använda i satsen.
b) Beskriv hur referensomgivningen är konstruerad / bestäms i ett språk med
• statisk räckviddsbindning
• dynamisk räckviddsbindning
Statisk rvb - referensomgivningen avgörs redan vid kompileringen, och beror på
programmets textmässiga struktur. I ett Algol/Pascal-linkande språk med blockstruktur
består referensomgivningen av alla variabler i det aktuella blocket, samt alla variabler i
textmässigt omgivande block som inte är ”dolda” av lokala (eller mer ”närliggande”)
variabler med samma namn
Dynamisk rvb - referensomgivningen avgörs dynamiskt, under exekveringen. Den består
(vanligen) av alla lokala variabler (dvs variabler i det aktuella blocket/underprogrammet)
plus alla variabler i anropande enhet som inte är ”dolda” plus alla variabler i den enhet som
anropade den anropande enheten o.s.v., dvs alla variabler som kan nås genom att följa
anropskedjan bakåt.
c) I ett språk med statisk räckviddsbindning, kan en variabels räckvidd skilja sig från dess
livslängd? Förklara / ge exempel!
En variabels räckvidd kan mycket väl skilja sig från dess livslängd? I
språk med helt statisk minnesallokering är livslängden även för
underprogram-variabler hela programexekveringen, men räckvidden är bara
det underprogram där variabeln deklarerats. Ett annat exempel är dynamiska
variabler som allokeras på heapen, som fortfarande kan vara ”levande” fast
dom inte är åtkomliga. (Men flera av er skiljer dåligt på pekaren som
pekar på denna variabel, och variabeln själv!)
d) Det finns flera andra saker/kontroller i ett programspråk som kan avgöras / göras antingen
statiskt eller dynamiskt. Nämn en sådan sak, och beskriv kort vad det är. (Du behöver inte
beskriva skillnaderna mellan statiskt och dynamiskt utförande, utan bara det övergripande
syftet)
7
Statisk / dynamisk
• typkontroll
• minnesallokering
Uppgift 12 (4p)
Förklara följande termer, dvs definiera dem och beskriv deras innebörd / betydelse (om den
inte framgår tydligt av själva definitionen):
• ”Short-circuit evaluation”
• Ett starkt typat (strongly typed) språk
• Exceptions (”undantag”)
• Garbage
• Short-circuit evaluation
Ett utrycks värde beräknas utan att alla operander evalueras. (En operand
evalueras alltså bara om dess värde behövs. Detta är alltså ett specialfall av lat
exvaluering)
• Ett starkt typat (strongly typed) språk
Bokens definition är att ett språk är starkt typat om alla typfel upptäcks,
antingen under kompilering eller exekvering. En annan vanligt förekommande
definition är att ett språk är starkt typat om alla nödvändiga typkontroller sker
under kompileringen. Inga typfel ska då kunna uppkomma under exekveringen.
• Exceptions (”undantag”)
Ett undantag är en oväntad händelse (t ex ett fel) som uppkommer/händer under
exekveringen av ett program. (Bör lämpligen hanteras på ett sådant sätt att man
slipper exekveringsavbrott.)
• Dangling pointers
En pekare som pekar på minnesceller som inte innehåller något dataobjekt. Det
objekt som tidigare fanns där har avallokerats. Dessa celler kan alltså allokeras
för något annat ändamål, men kan ändå åtkommas vi den ”dinglande pekaren”.
Uppgift 13 (4p)
Här kommer ett elakt program. (Bli inte rädda, det är inte så svårt som det kan se ut).
program P;
var a,b,c : integer;
proc Q;
begin
a:=a+2;
c:=c+2;
end {Q}
proc R;
var c:integer;
begin
c:=2;
call Q;
b:=a+b;
write (a,b,c);
end {R}
proc S;
var b,c:integer;
proc Q;
begin
a:=a+1;
c:=c+1;
end {Q}
begin
8
b:=3;
c:=1;
call Q;
call R;
end {S}
begin
a:=1;
b:=1;
c:=1;
call S;
end.
a) Visa vilka aktiveringsposter som finns på stacken när satsen a:=a+2 i Q exekveras Visa
för varje post vars dess statiska och dynamiska länk pekar. Visa också vilka variabler
som hör till posten, och vilka deras värden är.
b) Vad menas med sidoeffekter? Förekommer det några sådana i ovanstående program?
Ge exempel i så fall.
Varför har alla ritat in aktiveringsposten för anropet till Q i S på stacken?? Den
aktiveringen av Q är ju sen länge avslutad, och a-posten poppad från stacken när vi
kommer till a:=a+2 i Q i P!
Att visa vilka variabler som finns i varje a-psot, och vilka deras värden blir var ni också
dåliga på. Länkarna kunde ni bättre.
St at i sk l änk
Dynami sk l änk
Var i abel vär den
Q
c: 2
c:2 , 4 ( q
)
R
b: 3
c: 1 , 2 ( q)
b:3 , 7 ( r
)c:1 , 2 ( q
)
S
a:
1 , 2 ( q) , 4 ( q)
b:
1,1
5 ,( 3r () q)
c:
a)
a:
1 , 21 ( q) , 4 ( q)
b:
c: 1
Pv
id
b) En sidoeffekt är när ett underprogram förändrar värdet hos en icke-lokal variabel (eller, i en
mindre skala, när evalueringen av ett uttryck påverkar "tillståndet"). Sidoeffekter är oönskade
eftersom de gör det omöjligt att förstå ett underprogramanrop utan att studera
underprogrammet självt (förstör abstraktionen). Det är gott om sidoeffekter i programmet i
upppgiften; värdena på a och c ändras i Q, b ändras i R, a och c ändras i den andra Q (inuti S).
9
Uppgift 14 (3p)
Är pekare nödvändiga i ett programmeringsspråk? Ange och diskutera skäl för och emot!
(Obs att inget specifikt svar är ”rätt” på denna uppgift. Det som bedöms är allsidigheten
och ”tyngden” i din argumentation).
Enligt boken är de två huvudsakliga användningsområdena för pekare
• indirekt adressering (inklusive pekararitmetik)
• möjlighet att skapa dynamiska datastrukturer, och att överhuvudtaget allokera och
hantera minne dynamiskt.
Man skulle också kunna lägga till
• effektivitet, t ex att kunna skicka en pekare/adress som parameter i st f en hel
datastruktur.
Att skapa dynamiska datastrukturer måste definitivt vara möjligt i ett modernt språk.
Frågan är dock om det ska ske genom explicit pekarhantering, eller om det ska skötas av
runtime-systemet som i t ex ML?
Ju lägre nivå (ju ”närmare maskinen”) man programmerar på, dess mer nödvändigt är det
att kunna använda indirekt adressering. I ett språk på en hög abstraktionsnivå är det
emellertid inte nödvändigt, vilket vi t ex ser av Pascal. Det finns visserligen en del
”maskinnära programmering ” man inte kan göra i ett sådant språk, men såna uppgifter
kan man ha speciella språk för.
Om man har ett språk där alla datatyper är ”fullvärdiga medlemar”, dvs kan skickas som
parametrar, retuneras som funktionsresultat etc, så behöver man inte pekare för parameteröverföring etc. Det kan fortfarande vara adresser som skickas (av effektivitetsskäl, men det
hanteras av systemet, inte av programmeraren.
Pekare har också nackdelar. Den största är attäven en van programmerare kan råka skapa
”dangling references”, pekare till avallokerade objekt, som om de ändå används kan komma att
orsaka svårförståeliga fel. Pekare kan också vara svårförståeliga för programmeraren och
minskar programmerareffektiviteten, och ökar chansen för fel. Olika språk försöker minimera
dessa nackdelar på olika sätt, men de finns ändå där.
Sammanfattningsvis, om man har ett språk som har inbyggd ”garbage collection” och dynamisk
minneshantering, där alla typer är fullvärdiga, som innehåller tillräckligt kraftfulla datatyper
och som överhuvudtaget har goda möjligheter till dataabstraktion, då behöver man inte pekare.
Sen kan det visserligen vara så att många ändå önskar sig pekare, t ex för att
• man vill ha kontroll över vad som görs.
10