Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, 13.30–15.30 uur
1. deze opgave telt voor 30% van het totaal. Schrijf een compleet programma, dat door de gebruiker vanaf een console kan worden opgestart. Achter de naam van het programma specificeert de gebruiker een of meer gehele, positieve getallen. Het programma meldt wat het grootste van deze getallen is. Daarna geeft het programma een overzicht hoe vaak elk van de cijfers 0 t/m 9 in alle getallen samen voorkomen, maar alleen als dat minstens ´e´en keer is. Bijvoorbeeld: $ test.exe 123 4125 229 53 grootste getal: 4125 cijfer 1 komt 2 keer voor cijfer 2 komt 4 keer voor cijfer 3 komt 2 keer voor cijfer 4 komt 1 keer voor cijfer 5 komt 2 keer voor cijfer 9 komt 1 keer voor
Als de gebruiker vergeet om getallen te specificeren moet het programma hem daar netjes op wijzen. Het programma zal onder andere de getallen in losse cijfers uit elkaar moeten peuteren (er zijn meerdere manieren om dat te doen, je mag zelf kiezen welke manier je gebruikt). Zorg er wel voor dat elk getal maar ´e´en keer uit elkaar hoeft te worden gepeuterd. Antwoord: public class Test { public void Main(string[] args) { if (args.Length==0) Console.Writline("usage: test.exe numbers"); else { int turf[] = new int[10]; int max = 0; for (int t=0; y<args.Length; t++) { int n = int.Parse(args[t]); if (n>max) max = n; for ( ; n>0; n/=10 ) turf[ n%10 ] ++; } Console.Writeline("grootste getal: " + max); for (int t=0; t<10; t++) if (turf[t]>0) Console.Writeline("cijfer " + t + " komt " + turf[t] + " keer voor"); } } }
2. deze opgave telt voor 40% van het totaal. Bekijk het programma Huisje-boompje-beestje, waarvan hieronder een screenshot staat. Er zijn vier radiobuttons zichtbaar, waarmee de gebruiker een ‘mode’ kan kiezen. Verder kan de gebruiker overal op het scherm klikken. Op de aangeklikte plaats ontstaat dan, afhankelijk van de gekozen mode, een huisje, een boompje, een beestje, of een complete boerderij. Het aantal kliks is niet aan een maximum gebonden. Een boerderij bestaat uit een huisje met vijf beesten. In de onderste helft van het screenshot zijn twee zulke boerderijen getekend.
Er kunnen kleine en grote bomen worden gemaakt. In de klik-methode wordt door aanroep van een methode van Random steeds een random boom-hoogte bepaald. In de bijlage aan het eind van dit tentamen staat een deel van het programma gegeven. Een aantal onderdelen moet nog worden ingevuld.
(a) In de klasse Wereld ontbreken nog twee declaraties van member-variabelen. Schrijf deze declaraties. Antwoord: List
- items = new List
- (); int mode;
(b) In de klasse Wereld ontbreekt nog de methode Kies die wordt aangeroepen als de gebruiker een radiobutton indrukt. Schrijf deze methode. Antwoord: public void Kies(object o, EventArgs ea) { // een van onderstaande drie mogelijkheden: this.mode = (int)((RadioButton)o).Tag; this.mode = ((RadioButton)o).Text[0] - ’1’; this.mode = ((RadioButton)o).Location.Y / 20; }
(c) De klassen Huis en Beest hebben een gemeenschappelijke superklasse. Schrijf die klasse. Antwoord: public abstract class Item { public Point plek; public abstract void Teken(Graphics g); protected Item(Point p) { plek = p; } }
(d) De klasse Boom heeft onder andere de volgende methode: public override void Teken(Graphics g) { g.DrawLine(Pens.Green, this.plek.X, this.plek.Y , this.plek.X, this.plek.Y-5*this.takken ); for (int t=1; t<=this.takken; t++) for (int r=-1; r<=1; r+=2) g.DrawLine( Pens.Green , this.plek.X-r*(this.takken-t), this.plek.Y-5*t+this.takken-t , this.plek.X , this.plek.Y-5*t ); }
Schrijf de rest van deze klasse. (Je hoeft de gegeven methode niet over te schrijven, geef aan waar hij moet worden ingevoegd). Antwoord: public class Boom : Item { int takken = 10; public Boom(Point p, int t): base(p) { takken = t; } // hier de gegeven methode Teken }
(e) Schrijf de klasse Boerderij. Vermijd hierbij het dupliceren van code die al elders in het programma staat. Antwoord: public class Boerderij : Huis { List dieren = new List(); public Boerderij(Point p) : base(p) { for (int t=-2; t<=2; t++) dieren.Add( new Beest( p + new Size(30*t, 0))); } public override void Teken(Graphics g) { base.Teken(g); foreach (Beest b in dieren) b.Teken(g); } }
3. deze opgave telt voor 30% van het totaal. Dit is een theorie-vraag: er wordt geen code gevraagd, maar een stukje tekst. Houd het antwoord compact: het kan vaak in 1 zin, of in een tabelletje. Let wel op dat je geen deel-vragen over het hoofd ziet! (a) Bij de klassen StreamReader en StreamWriter is er een constructormethode die een Encoding als parameter krijgt. Beschrijf voor drie verschillende waarden van deze parameter wat voor consequenties dat heeft voor de omvang en de inhoud van de file die wordt gelezen of geschreven. Antwoord: Bij de encoding Latin1 kunnen alleen de 256 westeuropese lettertekens worden gebruikt, die elk 1 byte in de file kosten. Bij de encoding Unicode kunnen alle 65536 mogelijke characters worden geschreven, maar die kosten elk 2 bytes. Bij de encoding UTF8 kunnen ook alle characters worden gebruikt. De characters met code 0–127 worden in 1 byte weggeschreven, met als prijs dat sommige exotischer characters 3 bytes kosten. (b) Wat is anders aan methoden die met het woord static in de header zijn gedeclareerd? Wat heeft dit voor consequenties voor de aanroep van de methode? Antwoord: Een static methode heeft geen object onderhanden. Bij de aanroep staat er voor de punt niet een object dat de methode onder handen neemt, maar de de naam van de klasse waarin de methode is gedefinieerd. (c) Bekijk de toekenningsopdracht a.x = b.y;
Als x en y member-variabelen zijn, dan is de betekenis van deze opdracht: de member-variabele x van object a krijgt een nieuwe waarde, namelijk de waarde van member-variabele y van object b. Maar wat is de betekenis van deze opdracht als x en y properties zijn? Bij properties speelt een speciaal keyword een rol. Geef bij je beschrijving van de betekenis aan welk keyword dat is en wat daarvan de rol is. Antwoord: In dit geval wordt de mini-methode set van x aangeroepen, waarbij het keyword value het resultaat van de mini-methode get van y aanduidt. (d) In welke situatie ontstaat een null pointer exception ? Hoe kun je ervoor zorgen dat een eventueel optredende null pointer exception netjes wordt afgehandeld?
Antwoord: Als K een klasse is met een membervariabele x, en met declaratie K p heb je een variabele p van dat type gedeclareerd, maar p heeft nog geen waarde, dan ontstaat er een null pointer exception zodra je p.x gebruikt. Door de opdracht waarin p.x gebruikt wordt in de body van een try-opdracht te zetten, kun je in het catch-gedeelte aangeven hoe de exception moet worden afgehandeld. (e) Stel dat de interface Demo is gedeclareerd, en een variabele test met die interface als type: interface Demo { int Aantal(); } Demo test;
Het is niet mogelijk om deze variabele te initialiseren met test = new Demo();
• Hoe kan deze variabele toch zinvol worden ge¨ınitialiseerd (anders dan met null) ? • Waarom kan het handig zijn om test te declareren met Demo als type, in plaats van met het type van de expressie waarmee hij wordt ge¨ınitialiseerd? Antwoord: Je moet een klasse maken die Demo implementeert, door de gevraagde methode Aantal te defini¨eren: class A : Demo { int Aantal() { return 5; } }
De initialisatie kan dan met test = new A();. Het voordeel van de declaratie met Demo test; in plaats van A test; blijkt als je later toch liever een andere implementatie zou gebruiken: je hoeft dan alleen de initialisatie te veranderen, en niet alle methodes die een Demo als parameter hebben.
Bijlage bij opgave 2 static class Program { static void Main() { Application.Run(new Wereld()); } }
public class Wereld : Form { // TODO: declaraties van member-variabelen (opgave a) public Wereld() { this.Size = new Size(800, 400); this.Paint += this.Teken; this.MouseClick += this.Klik; this.Text = "Huisje, Boompje, Beestje"; this.BackColor = Color.White; string[] namen = { "1 Huisje", "2 Boompje", "3 Beestje", "4 Boerderij" }; for (int t = 0; t < namen.Length; t++) { RadioButton r = new RadioButton(); r.Location = new Point(20, t*20); r.Size = new Size(100, 20); r.Text = namen[t]; r.Click += Kies; r.Tag = t; this.Controls.Add(r); } this.DoubleBuffered = true; } public void Teken(object o, PaintEventArgs pea) { foreach (Item i in this.items) i.Teken(pea.Graphics); } public void Klik(object o, MouseEventArgs mea) { switch (this.mode) { case 0: items.Add(new Huis(mea.Location)); break; case 1: items.Add(new Boom(mea.Location, 5+new Random().Next(12))); break; case 2: items.Add(new Beest(mea.Location)); break; case 3: items.Add(new Boerderij(mea.Location)); break; } this.Invalidate(); } // TODO: methode Kies (opgave b) }
Vervolg bijlage bij opgave 2 public class Huis : Item { public Huis(Point p) : base(p) { } public override void Teken(Graphics g) { g.DrawRectangle(Pens.Black, this.plek.X, this.plek.Y-50, 50, 50); g.DrawLine(Pens.Black, this.plek.X - 10, this.plek.Y - 42, this.plek.X + 25, this.plek.Y - 70); g.DrawLine(Pens.Black, this.plek.X + 60, this.plek.Y - 42, this.plek.X + 25, this.plek.Y - 70); } }
public class Beest : Item { public Beest(Point p) : base(p) { } public override void Teken(Graphics g) { g.FillEllipse(Brushes.Red, this.plek.X, this.plek.Y-17, 20, 10); g.DrawEllipse(Pens.Red, this.plek.X+17, this.plek.Y-20, 6, 6); for (int i=5; i<=15; i+=10) for (int j=-2; j<=2; j+=4) g.DrawLine(Pens.Red, this.plek.X+i+j, this.plek.Y, this.plek.X+i, this.plek.Y-7); } }
// TODO: superklasse van Huis en Beest (opgave c)
// TODO: rest van klasse Boom (opgave d) public override void Teken(Graphics g) { g.DrawLine(Pens.Green, this.plek.X, this.plek.Y, this.plek.X, this.plek.Y-5*this.takken); for (int t=1; t<=this.takken; t++) for (int r=-1; r<=1; r+=2) g.DrawLine(Pens.Green, this.plek.X-r*(this.takken-t), this.plek.Y-5*t+this.takken-t , this.plek.X , this.plek.Y-5*t ); } }
// TODO: klasse Boerderij (opgave e)