TDDI82 - Objektorienterad problemlösning - Standardbiblioteket ITDDI82/lecture/slides/stl_I.pdf ·...

Preview:

Citation preview

TDDI82 ‐ ObjektorienteradproblemlösningStandardbiblioteket I

Christoffer Holm

Institutionen för datavetenskap

1 Kursupplägg2 Behållare3 Iteratorer4 Smartpekare

1 Kursupplägg2 Behållare3 Iteratorer4 Smartpekare

3 / 51

KursuppläggPersonal

• Kursledare: Christoffer Holm

• Etiklärare: Erik Gustavsson

• Examinator: Klas Arvidsson

• Kursassistent: Eric Ekström

• Assistent: Isak Almquist

4 / 51

KursuppläggMål

• C++: Standardbiblioteket

• C++: Mallar

• Projekt: Lösa och formulera problem medobjektorientering

• Projekt: Skapa objektorienterad design

• Etik: Redgöra och analysera etiska aspekter inomdatateknik

5 / 51

KursuppläggKursmoment

• Labserie• Etikseminarier• Projekt• Tentamen

5 / 51

KursuppläggKursmoment

• Labserie• 2 laborationer• ungefär 2 veckor• Möjlighet för bonus på tentan• Storseminarie ‐ obligatoriskt

• Etikseminarier• Projekt• Tentamen

5 / 51

KursuppläggKursmoment

• Labserie• Etikseminarier• 2 “seminarier”• diskussionsuppgifter• Pass bokade i schemat är lärarfritt arbetspass• Erik Gustavsson (erik.gustavsson@liu.se)

• Projekt• Tentamen

5 / 51

KursuppläggKursmoment

• Labserie• Etikseminarier• Projekt• Objektorienterat projekt i C++• Oftast ett spel, men kan vara annat också• 4 personer per grupp• ungefär 3 veckor• Verktyg såsom git och make

• Tentamen

5 / 51

KursuppläggKursmoment

• Labserie• Etikseminarier• Projekt• Tentamen• Zoom övervakad• 2 uppgiter: en mall‐uppgift och en STL‐uppgift• U, G eller VG per uppgift• 3 = två G, 4 = ett VG, 5 = två VG

6 / 51

KursuppläggDeadlines

13/4 STL‐seminarie20/4 Etikseminarie 129/4 Hård deadline samtliga laborationer29/4, 17:00 Anmälan till projektgrupp i webreg3/5, 17:00 Inlämning projektbeskrivning10/5, 12:00 Inlämning individuell statusrapport11/5 Etiksemiarie 225/5 Redovisning projekt

7 / 51

KursuppläggPlanering

• Denna kurs har högre tempo än TDIU20

• Planera er tid: utnyttja ingenjörsprofessionalismen

8 / 51

KursuppläggLaborationer

• Listan

• Textredigering

8 / 51

KursuppläggLaborationer

• Listan

• Mallar• Iteratorer• Smartpekare

• Textredigering

8 / 51

KursuppläggLaborationer

• Listan

• Mallar• Iteratorer• Smartpekare

• Textredigering

• Lambda funktioner• STL Algoritmer• Databehållare

9 / 51

KursuppläggLaborationer

• 12/4: Deadline för bonus, Listan

• 26/4: Deadline för bonus, Textredigering

• 29/4: Sista chansen för redovisning

10 / 51

KursuppläggÄndringar

• Ny laboration: Textredigering

• Microsoft Teams från början för laborationer

• Bonus för tentan

11 / 51

KursuppläggLaborationer

Registrera er i WebReg såsnart som möjligt!

1 Kursupplägg2 Behållare3 Iteratorer4 Smartpekare

13 / 51

BehållareIntroduktion

• Sekvensbehållare (Sequence containers)

• Sekvensadaptrar (Sequence adaptors)

• Associativa behållare (Associative containers)

13 / 51

BehållareIntroduktion

• Sekvensbehållare (Sequence containers)

• Lagrar värden av samma typ i en given sekvens

• Vanligtvis används index för hämta värden

• Sekvensadaptrar (Sequence adaptors)

• Associativa behållare (Associative containers)

13 / 51

BehållareIntroduktion

• Sekvensbehållare (Sequence containers)

• Sekvensadaptrar (Sequence adaptors)

• Anpassat gränssnittet för en given sekvensbehållare

• Representerar saker såsom stackar, köer, prioritetslistor, o.s.v.

• Associativa behållare (Associative containers)

13 / 51

BehållareIntroduktion

• Sekvensbehållare (Sequence containers)

• Sekvensadaptrar (Sequence adaptors)

• Associativa behållare (Associative containers)

• Lagrar värden associerade till givna nycklar

• Värden måste vara av samma datatyp

• Nycklar måste vara av samma datatyp

• Använder nycklarna för att komma åt värden

14 / 51

BehållareSekvensbehållare

• vector• Lagrar värden kontinuerligt i minnet• Ändrar storlek efter behov• Vanligaste behållaren

• array• deque• list• forward_list

14 / 51

BehållareSekvensbehållare

• vector• array• Lagrar värden kontinuerligt i minnet• Har en fixerad storlek som är känd under

kompilering• Är effektivare än vector

• deque• list• forward_list

14 / 51

BehållareSekvensbehållare

• vector• array• deque• Double‐ended queue• Lagrar inte värden kontinuerligt i minnet• Bra om man vill komma åt värden endast i början

OCH slutet• list• forward_list

14 / 51

BehållareSekvensbehållare

• vector• array• deque• list• En dubbellänkad lista• Lagrar inte värden kontinuerligt i minnet• Bra om man vill komma åt värden i början• Kan stega bakåt och framåt i listan

• forward_list

14 / 51

BehållareSekvensbehållare

• vector• array• deque• list• forward_list• En enkellänkad lista• Lagrar inte värden kontinuerligt i minnet• Bra om man vill komma åt värden i början• Kan endast stega framåt i listan

15 / 51

BehållareSekvensbehållare

#include <vector>#include <array>#include <deque>#include <list>#include <forward_list>int main(){// likadant för list, forward_list och dequestd::vector<int> v {1, 2, 3};

// array måste även ange storlekstd::array<int, 3> a {1, 2, 3};

}

16 / 51

• Bygger ofta på deque

• Kan endast komma åt det senaste inlagda värdet

16 / 51

BehållareAssociativa behållare

std::map<std::string, int> map{};

16 / 51

BehållareAssociativa behållare

map["c"] = 3;

16 / 51

BehållareAssociativa behållare

"c" 3

map["c"] = 3;

16 / 51

BehållareAssociativa behållare

"c" 3

map["a"] = 1;

16 / 51

BehållareAssociativa behållare

"a" 1"c" 3

map["a"] = 1;

16 / 51

BehållareAssociativa behållare

"a" 1"c" 3

map["d"] = 4;

16 / 51

BehållareAssociativa behållare

"a" 1"c" 3"d" 4

map["d"] = 4;

16 / 51

BehållareAssociativa behållare

"a" 1"c" 3"d" 4

map["b"] = 2;

16 / 51

BehållareAssociativa behållare

"a" 1"b" 2"c" 3"d" 4

map["b"] = 2;

17 / 51

BehållareAssociativa behållare

• map• set• multi*• unordered_*

17 / 51

BehållareAssociativa behållare

• map• Kopplar ett värde till en nyckel• Kräver att nycklarna går att jämföra• Sorterad efter nycklarna• Varje nyckel kan endast förekomma en gång

• set• multi*• unordered_*

17 / 51

BehållareAssociativa behållare

• map• set• Som mapmen har endast nycklar• Bra för att garantera att alla värden är unika och

sorterade• multi*• unordered_*

17 / 51

BehållareAssociativa behållare

• map• set• multi*• Finns multimap och multiset• I dessa försvinner kravet att nycklarna är unika

• unordered_*

17 / 51

BehållareAssociativa behållare

• map• set• multi*• unordered_*• I dessa försvinner kravet att nycklarna ska gå att

jämföra• Nycklarna är inte längre sorterade

18 / 51

BehållareAssociativa behållare

#include <map>#include <set>#include <string>int main(){std::map<std::string, int> m { {"a", 1},

{"b", 2} };std::set<double> s { 1.0, 3.0, -1.0 };

}

1 Kursupplägg2 Behållare3 Iteratorer4 Smartpekare

20 / 51

IteratorerIteration

• Vi vill kunna loopa igenom våra behållare

• Vore trevligt om vi kan göra det generellt

• Problemet är att alla behållare har inte samma sätt attkomma åt element

• Därför måste vi tänka om för att kunna loopa igenombehållare på ett generellt sätt

21 / 51

IteratorerIteration

int main(){vector<int> v {1, 2, 3};for (int i{0}; i < v.size(); ++i){cout << v[i] << endl;

}}

21 / 51

IteratorerIteration

int main(){set<int> v {1, 2, 3};for (int i{0}; i < v.size(); ++i){cout << v[i] << endl; // fungerar ej

}}

21 / 51

IteratorerIteration

int main(){vector<int> v {1, 2, 3};for (int e : v){cout << e << endl;

}}

21 / 51

IteratorerIteration

int main(){set<int> v {1, 2, 3};for (int e : v){cout << e << endl; // fungerar

}}

22 / 51

IteratorerRange‐baserad for‐loop

• Man kan skapa egna behållare

• Hur är det då möjligt att få denna generalla loop attfungera för den egen‐skapade behållaren?

• Det är här iteratorer kommer in

23 / 51

IteratorerRange‐baserad for‐loop

for (int e : v){cout << e << endl;

}

23 / 51

IteratorerRange‐baserad for‐loop

using iterator = std::vector<int>::iterator;

for (iterator it{v.begin()}; it != v.end(); ++it){cout << *it << endl;

}

24 / 51

IteratorerRange‐baserad for‐loop

En behållare går att loopa igenom om:

• Det finns en inre klass som heter iterator

• Det finns medlemsfunktioner begin och end somreturnerar iterator objekt

• iterator har definierat operator++, operator*,operator== och operator!=

25 / 51

IteratorerIteratorer

• Iteratorer är generaliserade pekare

• Ett generellt sätt att iterera över alla behållare påsamma sätt

• Pekar på ett element i behållaren

• Möjligt att komma åt elementet med operator*

• Gå till nästa element med operator++

26 / 51

IteratorerIteratorer

vector<int> v{1,2,3};

26 / 51

IteratorerIteratorer

vector<int> v{1,2,3};

1 2 3

26 / 51

IteratorerIteratorer

vector<int> v{1,2,3};

1 2 3

begin end

26 / 51

IteratorerIteratorer

vector<int>::iterator it{v.begin()};

1 2 3

begin end

26 / 51

IteratorerIteratorer

vector<int>::iterator it{v.begin()};

1 2 3

begin end

it

26 / 51

IteratorerIteratorer

++it;

1 2 3

begin end

it

26 / 51

IteratorerIteratorer

++it;

1 2 3

begin end

it

26 / 51

IteratorerIteratorer

int x{*it};

1 2 3

begin end

it

26 / 51

IteratorerIteratorer

int x{*it};

1 2 3

begin end

it

x = 2

26 / 51

IteratorerIteratorer

int x{*it};

1 2 3

begin end

it

x = 2

26 / 51

IteratorerIteratorer

++it;

1 2 3

begin end

it

x = 2

26 / 51

IteratorerIteratorer

++it;

1 2 3

begin end

x = 2

it

26 / 51

IteratorerIteratorer

*it = 4;

1 2 3

begin end

x = 2

it

26 / 51

IteratorerIteratorer

*it = 4;

1 2 4

begin end

x = 2

it

26 / 51

IteratorerIteratorer

++it;

1 2 4

begin end

x = 2

it

26 / 51

IteratorerIteratorer

++it;

1 2 4

begin end

x = 2

it

26 / 51

IteratorerIteratorer

it == v.end();

1 2 4

begin end

x = 2

it

27 / 51

IteratorerIteratorer

• Det är viktigt att end‐iteratorn inte pekar på det sistaelementet

• Annars kommer vi missa sista elementet i behållareneftersom att loopen avslutas då it == v.end()

• Den måste unikt kunna identifiera att nu är iterationenslut

• Därför tänker vi att den pekar på elementet efter sista

28 / 51

IteratorerIterator kategorier

Det finns olika typer av iteratorer, dessa är:

• Input• Kan läsa befintliga värden i behållaren

• Output• Forward• Bidirectional• Random Access

28 / 51

IteratorerIterator kategorier

Det finns olika typer av iteratorer, dessa är:

• Input• Output• Kan lägga till nya värden i behållaren

• Forward• Bidirectional• Random Access

28 / 51

IteratorerIterator kategorier

Det finns olika typer av iteratorer, dessa är:

• Input• Output• Forward• Kan läsa/skriva över befintliga värden i behållaren• Kan stega framåt i behållaren• Är även en Input iterator

• Bidirectional• Random Access

28 / 51

IteratorerIterator kategorier

Det finns olika typer av iteratorer, dessa är:

• Input• Output• Forward• Bidirectional• Kan stega bakåt i behållaren• Är även en Forward iterator

• Random Access

28 / 51

IteratorerIterator kategorier

Det finns olika typer av iteratorer, dessa är:

• Input• Output• Forward• Bidirectional• Random Access• Kan hoppa till godtyckligt element i behållaren• Är även en Bidirectional iterator

29 / 51

IteratorerIterator kategorier

Nedan tabell visar vilka operationer som är möjliga för deolika kategorierna

KategoriOperationer Input Output Forward Bidirectional Random Access==, != ✓ ✓ ✓ ✓ ✓*, ‐> Läsa Skriva Läsa/Skriva Läsa/Skriva Läsa/Skriva++ ✓ ✓ ✓ ✓ ✓– ‐ ‐ ‐ ✓ ✓+, +=, ‐, ‐= ‐ ‐ ‐ ‐ ✓<, <=, >, >= ‐ ‐ ‐ ‐ ✓i[n] ‐ ‐ ‐ ‐ ✓

1 Kursupplägg2 Behållare3 Iteratorer4 Smartpekare

31 / 51

SmartpekareKan det bli minnesfel här?

class My_Class{public:My_Class(int x, int y);~My_Class(){delete p1;delete p2;

}private:int* p1;int* p2;

};

31 / 51

SmartpekareKan det bli minnesfel här?

int* create(int n){if (n >= 0){return new int{n};

}throw domain_error{"Negative"};

}

My_Class::My_Class(int x, int y): p1{create(x)}, p2{create(y)}

{ }

32 / 51

SmartpekareJa, det kan bli fel!

int main(){My_Class c{0, -1};

}

33 / 51

SmartpekareVarför?

• När konstruktorn avbryts kommer objektet att tas bortutan att köra destruktorn

• All data som har allokerats innan krashen kommerdärför inte avallokeras

• Hur kan vi lösa detta?

34 / 51

SmartpekareLösning?

My_Class::My_Class(int x, int y) try: p1{create(x)}, p2{create(y)}

{ }catch (domain_errror& e){delete p1;

}int main(){My_Class c{-1, 0};

}

34 / 51

SmartpekareLösning?

My_Class::My_Class(int x, int y) try: p1{create(x)}, p2{create(y)}

{ }catch (domain_errror& e){delete p1;

}int main(){My_Class c{-1, 0};

}

Segmentation Fault

35 / 51

SmartpekareVarför?

• Nu är det p1 som kastar undantaget

• I catch‐blocket försöker vi ta bort den, men den finnsinte

• Detta ger segmentation fault

36 / 51

SmartpekareLösning?

My_Class::My_Class(int x, int y): p1{create(x)}, p2{}

{try{p2 = create(y);

}catch (domain_error& e){delete p1;throw;

}}

36 / 51

SmartpekareLösning?

My_Class::My_Class(int x, int y): p1{create(x)}, p2{}

{try{p2 = create(y);

}catch (domain_error& e){delete p1;throw;

}}

Funkar...

36 / 51

SmartpekareLösning?

My_Class::My_Class(int x, int y): p1{create(x)}, p2{}

{try{p2 = create(y);

}catch (domain_error& e){delete p1;throw;

}}

Men till vilket pris?

37 / 51

Smartpekare

Vore det inte bra ompekare kunde avallokera

sig själva?

38 / 51

Smartpekare

39 / 51

Smartpekarestd::unique_ptr

int main(){int* p1{new int{5}};cout << *p1 << endl;{int* p2{new int{3}};cout << *p2 << endl;delete p2;

}delete p1;

}

39 / 51

Smartpekarestd::unique_ptr

#include <memory>int main(){std::unique_ptr<int> p1{new int{5}};cout << *p1 << endl;{std::unique_ptr<int> p2{new int{3}};cout << *p2 << endl;

}

}

40 / 51

Smartpekarestd::unique_ptr

• är en s.k. smartpekare

• finns definierad i <memory>

• tar över ansvaret för att hantera minnet

• representerar ägarskap

• kan inte kopieras

• men det går att flytta ägarskapet

41 / 51

Smartpekarestd::unique_ptr

#include <memory>using namespace std;int main(){unique_ptr<double> p{}; // nullptrp = new double{5.0};{unique_ptr<double> q{new double{1.0}};p = std::move(q);

}}

42 / 51

Smartpekarestd::unique_ptr

• unique_ptr tar bort det gamla minnet när vi tilldelarnytt minne

• man ska nästan aldrig behöva avallokera minnetmanuellt

43 / 51

SmartpekareFälla

#include <memory>int get(std::unique_ptr<int> p){return *p;

}int main(){std::unique_ptr<int> p{new int{5}};get(p);

}

43 / 51

SmartpekareFälla

#include <memory>int get(std::unique_ptr<int> p){return *p;

}int main(){std::unique_ptr<int> p{new int{5}};get(p);

}

Kompilerar ej

44 / 51

SmartpekareFälla

test.cpp: In function ‘int main’():test.cpp:9:8: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int’>]

get(p);^

In file included from /sw/gcc-7.1.0/include/c++/7.1.0/memory:80:0,from test.cpp:1:

/sw/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:388:7: note: declaredhere

unique_ptr(const unique_ptr&) = delete;^~~~~~~~~~

test.cpp:2:5: note: initializing argument 1 of ‘int get(std::unique_ptr<int’>)int get(std::unique_ptr<int> p)

^~~

44 / 51

SmartpekareFälla

test.cpp: In function ‘int main’():test.cpp:9:8: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int’>]

get(p);^

In file included from /sw/gcc-7.1.0/include/c++/7.1.0/memory:80:0,from test.cpp:1:

/sw/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:388:7: note: declaredhere

unique_ptr(const unique_ptr&) = delete;^~~~~~~~~~

test.cpp:2:5: note: initializing argument 1 of ‘int get(std::unique_ptr<int’>)int get(std::unique_ptr<int> p)

^~~

44 / 51

SmartpekareFälla

Om du ser detta såförsöker du kopiera en

unique_ptr

45 / 51

SmartpekareMer om unique_ptr

int main(){std::unique_ptr<std::string> p{};// abstrahera bort allokeringenp = std::make_unique<std::string>("hej");// plocka ut en vanlig pekarestd::string* ptr{p.get()};// kom åt medlemmar i det som pekaren pekar påcout << p->size() << endl;

}

46 / 51

Smartpekarestd::make_unique

• Om vi använder std::make_unique snarare än newkommunicerar vi bättre vad som händer i koden

• Det blir tydligt att koden inte kräver en delete om newinte ens förekommer

• Detta tillåter kompilatorn är resonera bättre kring kodenoch kan därför göra eventuella optimeringar som inteannars går

47 / 51

Smartpekareget

• std::unique_ptr::get ska endast användas då vibehöver temporär tillgång till objektet

• den pekare som returneras av get är en icke‐ägandepekare

• detta innebär att du aldrig någonsin ska göra delete påden pekaren

• om du av någon annledning vill avallokera minnet,tilldela nullptr till smartpekaren eller anropa releasefunktionen

48 / 51

Smartpekarestd::shared_ptr

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

int main(){> std::shared_ptr<int> ptr1{std::make_shared<int>(5)};

{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){> std::shared_ptr<int> ptr1{std::make_shared<int>(5)};

{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{

> std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{

> std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

> std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

> std::shared_ptr<int> ptr3{ptr1};}

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};> }

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};> }

}}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

> }}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

> }}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}>}

48 / 51

Smartpekarestd::shared_ptr

5

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}>}

48 / 51

Smartpekarestd::shared_ptr

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}>}

48 / 51

Smartpekarestd::shared_ptr

int main(){std::shared_ptr<int> ptr1{std::make_shared<int>(5)};{std::shared_ptr<int> ptr2{ptr1};{

std::shared_ptr<int> ptr3{ptr1};}

}}

49 / 51

Smartpekarestd::shared_ptr

• Representerar delat ägarskap

• Minnet avallokeras endast då ingen längre pekar på det

• Kostar mer än std::unique_ptr och vanliga pekare

50 / 51

Smartpekare

Hur hjälper detta oss?

51 / 51

SmartpekareBättre lösning!

class My_Class{public:My_Class(int x, int y);~My_Class() = default;

private:unique_ptr<int> p1;unique_ptr<int> p2;

};

51 / 51

SmartpekareBättre lösning!

My_Class::My_Class(int x, int y): p1{create(x)}, p2{create(y)}

{ }

51 / 51

SmartpekareBättre lösning!

My_Class::My_Class(int x, int y): p1{create(x)}, p2{create(y)}

{ }

Perfektion!

www.liu.se

Recommended