Lösningsförslag och rättningsmall för tentamen i DD2387 Programsystemkonstruktion med C++
Datum: Tisdag 20 oktober 2009, 8-12 (rättning 13-14)
Rätta mycket nogrannt. Allt som är fel ska markeras. Avgör därefter om det är ett allvarligt fel eller inte. Enstaka allvarliga fel behöver inte ge underkänt. Lycka till!
1. Att söka sekvenser
(a)FRA-lagen är på tapeten igen. Man ska kunna söka igenom delar av internet efter delmängder av ännu inte helt definierade sökmängder. Detaljerna i denna “sequrity search” är höljt i dunkel.
Skriv en generell typparametriserad funktion SeqSearch som tar fyra parametrar. Parametrarna definerar två sekvenser i godtycklig mängd. Funktionen ska returnera huruvida den andra sekvensen är en delsekvens av den första. Exempel: Exempel: {1 2 3} är en delsekvens av {1 2 3 4} och {2 4} är en delsekvens av {1 2 3 4} medan varken {3 2} eller {2 2} är en delsekvens av {1 2 3 4}. Sökning efter tomma mängden ska ge falskt svar.
Algoritmen är ungefär, gå igenom det stora intervallet och kontrollera om ett element även finns i delsekvensen.
Sökningen är sann om alla delsekvensens element påträffats i ordning.
Ett sätt att definera en mängd som funktionen ska operera på är att skicka två iteratorer. Jämför med hur t.ex. stl::sort anropas.
template<class It1, class It2>
bool subseq(It1 it1, It1 end1, It2 it2, It2 end2) {
if (it2 == end2) return false;
for(; it1 != end1; ++it1) {
if(it2 != end2 && *it1 == *it2) it2++;
}
return it1 == end1 && it2 == end2;
}
Man kan även definera intervallet med en iterator och ett heltal som anger antal element. På sätt och vis kan det ge felsäkrare kod, i lösningsförslaget så förutsätts end1 komma efter it1 och någon koll görs inte. Allvarligt fel är att deklarera någon parameter som pekare (t.ex. It *) eftersom man då inte kan anropa funktionen med iteratorer. Om det är mycket fel i uppgiften att det är oklart om den överhuvudtaget gör vad den ska, gruppera felen i några stora felkoder t.ex. syntaxfel, templatefel, logikfel och markera allt som är fel.
(b)Kan man skriva funktionen så att den anropas med två olika sorters typparametrar? Motivera ditt svar.
Ja det kan man. Dels kan det vara samma slags typ i två olika containrar men det kan också vara olika typer i olika containrar. T.ex. vector<int>::iterator och list<long>::iterator. Det ställer krav på typparametrarna att de kan jämföras med operator== observera att det blir skillnad på *it1 ==
*it2 och *it2 == *it1. Det är inte allvarligt att inte kunna svaret på denna fråga men man måste ha försökt svara. Om svaret är alldeles jättetokigt kan det vara allvarligt.
1
2. Listade terrorister
Den nya lagen kan förmodligen göra det lättare att hitta terrorister och spåra deras finansieringsverksamhet. För att hålla reda på misstänkta sifferkombinationer lägger man till siffrorna i en enkellänkad lista på följande vis:
void insert(Nod * & p, int x) { if (p == 0)
p = new Nod(x);
else
insert(p->next, x);
}
(a)En java/pythonprogrammera tycker det är märkligt att koden fungerar. Förklara varför koden fungerar och varför samma kod inte fungerar i språk som python och java som inte har referensparametrar.
Om man skickar som värdeparameter blir det en lokal pekare som jämförs och tilldelas. Utanför funktionen är pekaren inte giltig och programmet kommer att läcka minne eftersom minnet som allokeras inte frigörs. Som referensparameter så ändras pekaren i anropande scope.
(b)Nu är det inte bara siffror man vill lista utan alla möjliga skumma element. Implementera en typparametri- serad Nod som fungerar med din insert nedan.
(c)Implementera en ny förbättrad rekursiv typparametriserad insert som sorterar in elementen i listan på något sätt.
template <class T>
struct Nod {
Nod(T x) : data(x), next(0) {};
T data;
Nod * next;
};
template <class T>
void insert(Nod<T> * & p, T x) { if (p == 0) {
p = new Nod<T>(x);
} else {
if (p->data > x) {
Nod<T> * tmp = new Nod<T>(x);
tmp->next = p;
p = tmp;
} else
insert(p->next, x);
} }
Allvarliga fel är grova algoritm fel som oändlig loop. Icke allvarligt fel att glömma initiera nodens medlemmar.
(d)Vilka krav ställer din kod på det typparametriserade datat?
Lösningsförsöagets kod ställer krav på operator>, se if-sats. Man kan implementera med operator<, operator<= eller operator>= det är allvarligt att inte identfiera vilken jämförelse-operator man använder. Koden använder sig också av kopieringskonstruktorn se initieringslistan för Nod. Om man istället skrivit
Nod(T x) : next(0) { data = x;
};
Så hade defaultkonstruktorn och tilldelningsoperatorn använts. Att missa vilka konstruktorer datat använder är inte ett allvarligt fel om man har rätt på Rule of three i fråga 5b. Annars är det allvarligt.
2
3. Funktorn fra Norge
I Norge undrar man hur FRA-lagen är funtad. Man pekar på suspekta funktioner som borde kräva tillstånd. Istället för att använda funktionspekare så kan man använda funktorer istället. Hitta på och implementera en helt egen exempelkod med en funktor (även kallad funktionsobjekt)som använder sig av tillstånd för att avgöra vad som ska hända.
struct A { int x;
int operator()(int y) { if (x > 1000) {
x = 0;
return 13; // Returnera 13 ibland } else {
return x*y;
x++;
} } };
Skillnaden mellan funktionspekaren och funktorn är att den senare kan använda en medlemsvariabel för att lagra tillstånd mellan körningarna. I exemplet ovan räknar funktionen fel var 1000:e gång. Det är allvarligt fel att inte definera operator()(). Om man inte använt en medlemsvariabel för sitt beroende är det inte allvarligt fel. Har man inget beroende alls (trots muntlig instruktion) så ska det markeras men det är upp till er om ni anser det vara allvarligt.
4. Att lägga ihop ett och annat
(a)Det blir mycket data att analysera i kablarna. Man måste ha huvudet på skaft för att få lägga ihop A och B.
Implementera A och B så att följande kod kompilerar. Var noga med const och referenser.
struct B {
void consider(int x) const {};
};
struct A {
int operator+=(B b) {return 3;}
};
void LeggIhop(A a, const B & b) { b.consider(a += b);
}
Returvärdet från operator+= måste matcha parametern i consider. Det är mycket allvarligt att göra fel på const. Observera att parametern till operator+= antingen är en kopia eller const-referens men absolut inte icke-const-referens. Övriga fel är inte allvarliga om de inte är allvarliga fel :)
(b)Vad används const till i allmänhet i C++?
Används till att skrivskydda minne i compile-time och ge kompileringsfel om man försöker skriva till det minnet. Används för att flagga medlemsfunktioner (metoder) som inte får skriva på medlemsva- riabler.
(c)Ge ett exempel på när det skulle kunna vara befogat att använda const_cast i C++
Man kan tänka sig slippa kodupprepning om man har identiska metoder en const och en icke-const.
Då skulle den ena kunna ropa på den andra implementationen efter att först gjort const_canst. När allt är klart kan man föra const_cast tillbaka.
3
5. På spaning efter den tid som flytt
(a) FRA-debatten har ärvt kommunikationsproblemen som fanns redan från början om vad lagen egent- ligen handlade om. Många som lever i den virtuella verkligheten som kallas internet har svårt att hålla reda på vad som gäller. Vad skriver programmet nedan ut när man kör det?
#include <iostream>
using namespace std;
struct FRA_Old_Radio {
virtual void what() { cout << "Vi spanade i luften" << endl; };
};
struct FRA_2008_kabel : FRA_Old_Radio {
virtual void what() { cout << "Vi spanar i kablar och i luften" << endl; };
int m_number;
FRA_2008_kabel() : m_number(17) {};
FRA_2008_kabel(const FRA_Old_Radio & x) : m_number(4711) {};
Observera att Fra_kabel_2008 inte har en kopiekonstruktor och en sådan kommer att skapas vid behov
};
struct FRA_2009_extra_domstol : FRA_2008_kabel {
virtual void what() { cout << "Vi spanar bara om det är OK" << endl; } };
void evaluate(FRA_2008_kabel m) { // analyseras noga m.what();
}
int main() {
FRA_2009_extra_domstol c;
FRA_Old_Radio & a = c;
a.what();
evaluate(c);
FRA_2008_kabel b1;
FRA_2008_kabel b2 = b1; // Kopiering, kopiekonstruktor skapas cout << "Antal kablar = "
<< b2.m_number << endl;
return 0;
}
UTSKRIFT:
Vi spanar bara om det är OK Vi spanar i kablar och i luften Antal kablar = 17
Markera i koden var man tänkt fel om det är fel i utskriften. T.ex. måste man vara observant på att evaluate konstruerar en FRA_kabel kopia och “skär av” c-objektet i main (slicing på engelska).
(b)Det är nog viktigt att FRA följer reglerna som definerats. Rule of three kallas en regel som handlar om metoder som implicit kan skapas även om man inte definerat dem själv. Vilka metoder är det frågan om?
Kopiekonstruktorn, tilldelningsoperatorn och destruktorn. Det är inte fel att nämna defaultkonstruktorn.
4