#include #include // random_device, uniform_int_distribution, ... #include // swap, sort, for_each, random_shuffle, ... #include // ostream_iterator... #define V 1 using namespace std; // ---------------------------------------------------- // Deklarationer (i filen deck_of_cards.h) class Card { public: // "getter" funktioner för att kunna läsa värde och färg int getValue() const { return value; } string getSuit() const { return suit; } // Jämför kort baserat på enbart kortets värde bool operator<(Card const& c) const; bool operator==(Card const& c) const; // Textuell beskrivning av ett kort string toString() const; // För utskrift av ett kort på strömmen "os" ostream& print(ostream& os) const; private: // Deck är den enda som får skapa kort friend class Deck; // Funktionsobjekt som behöver jämföra kort // Kan dock använda getValue() istället! // friend class Greater_Card; Card(int v, string s); // Bara Deck kan skapa kort! int value; // 1 - 13 string suit; // hearts, diamonds, clubs, spades }; // ---------------------------------------------------- // Implementation (i filen deck_of_cards.cc) class Greater_Card { public: bool operator()(Card const& a, Card const& b) { // return a.value > b.value; // Använd getter istället! return a.getValue() > b.getValue(); } }; // Konstruktor med initieringslista Card::Card(int v, string s) : value(v), suit(s) { // Inget behövs här för denna klass } string Card::toString() const { return to_string(value) + " of " + suit; } ostream& Card::print(ostream& os) const { return os << toString(); } bool Card::operator<(Card const& c) const { return value < c.value; } bool Card::operator==(Card const& c) const { return value == c.value; } // operator<< för att kunna mata ut ett kort på en utström // (t.ex. cout). Observera att detta inte är en medlemsfunktion, utan // bara starkt relaterad till klassen Card. Skulle även kunna // implementeras som medlem av klassen ostream: // ostream& ostream::operator<<(Card const& c); // (Men bara om vi hade tillgång till källkoden för ostream...) ostream& operator<<(ostream& os, Card const& c) { return c.print(os); } // ---------------------------------------------------- // Deklarationer (i filen deck_of_cards.h) class Deck { public: Deck(); void shuffle(); // Vet inte när denna behövs, men bra exempel på STL Deck& makeOrdered(); // Titta på översta kortet... Card const& top() const; Card const& pop(); private: vector deck; // För att mata ut en hel kortlek på valfri utström // Observera att detta inte är en medlemsfunktion, endast vän! friend ostream& operator<<(ostream& os, Deck const& d); // Hälpfunktion för ett slumpmässigt kort int randomIndex() const { return rand() % deck.size(); } // Kortleken behåller alla kort internt, det går bara att titta på // dem, inte att slarva bort dem eller ändra dem! int topmost; // static: En gemensam för alla instanser av klassen! static bool rand_initiated; }; // ---------------------------------------------------- // Implementation (i filen deck_of_cards.cc) // Så initieras en static-medlemsvariabel bool Deck::rand_initiated = false; Deck::Deck() : topmost(0) { // Ser till att initiera slumpgeneratorn bara en gång // Går göra först i main istället (enklare)... if ( ! rand_initiated ) { srand(time(0)); // Kommer bara ske en gång! rand_initiated = true; } for (int v = 1; v <= 13; ++v) { deck.push_back(Card(v, "hearts")); /* deck.push_back(Card(v, "diamonds")); deck.push_back(Card(v, "spades")); deck.push_back(Card(v, "clubs")); */ } } // Ett funktionsobjekt för att blanda en vector // Varför göra ett funktionsobjkt? // - Kan göras till friend av andra klasser // - Kan skrivas en gång - användas på många ställen // - Kan lagra egna variabler i objektet // Oftast: lambdafunktion klarar biffen! class Shuffler { public: // Konstruktor som initerar referensmedlem! Enda sättet! Shuffler(vector& v) : deck(v) {} // Anropas när en variabel av klassen anropas som funktion! void operator()(Card& c) { swap(c, deck.at( rand() % deck.size() )); } private: vector& deck; }; // Tre versioner att blanda en lek void Deck::shuffle() { #if V == 1 // Normal for-loop. for (unsigned i = 0; i < deck.size(); ++i) { swap(deck.at(i), deck.at( randomIndex() )); } #elif V == 2 // Algoritmen for_each med funktionsobjekt. for_each(deck.begin(), deck.end(), Shuffler(deck)); #else // Algoritmen for_each och lambdafunktion. for_each(deck.begin(), deck.end(), [this](Card& c)->void { // Diskussion om referenser sist i filen! swap(c, deck.at( randomIndex() )); }); #endif #if 0 // Enklast och bäst - men inte för att lära sig ovan! random_shuffle(deck.begin(), deck.end()); #endif } Deck& Deck::makeOrdered() { #if V == 1 // Använd std::sort och ett funktionsobjekt och sortera i fallande // ordning sort(deck.begin(), deck.end(), Greater_Card()); #elif V == 2 // Använd std::sort och en lambdafunktion för att sortera i fallande // ordning sort(deck.begin(), deck.end(), [](Card const& a, Card const& b)->bool { return a.value > b.value; // Ärver friend från omgivning! }); #else // Använd den normala jämförelse operator< för att sortera i // stigande ordning sort(deck.begin(), deck.end()); #endif // this är en variabel som innehåller objektets adress i minnet. * // "går till" adressen så referens till själva objektet returneras return *this; } Card const& Deck::top() const { return deck.at(topmost); } Card const& Deck::pop() { int top = topmost++; topmost %= deck.size(); return deck.at(top); } // Fristående funktion - inte medlem av Deck! ostream& operator<<(ostream& os, Deck const& d) { // Kopiera hela vectorn till utsrömmen med en utströmiterator! copy(d.deck.begin(), d.deck.end(), ostream_iterator(os, "\n")); return os; } //----------------------------------------------- // Huvudprogram int main() { // C++11 (Används inte av Card eller Deck) // Skapa en slumpgenereringsmotor i C++11 // Om det finns hårdvara för "äkta" slump används den! // Slumpfrö initieras automatiskt. Skapa bara EN! random_device rnd; // Skapa en distrubution av slumptal i C++11 // Denna genererar alltid tal i intervallet [1, 51] // Talen blir jämnt fördelade i intervallet. uniform_int_distribution uni(0, 51); cout << "-------------------- Slumpade tal med jämn fördelning: " << endl; for (int i = 0; i < 5; ++i) { // Genererar jämnt fördelade slumpade tal med random_device cout << uni(rnd) << endl; } // Skapa en annan typ av slumpgenereringsmotor i C++11 // Endast en skall skapas - inte fler. // Initieras med lämpligt frö "som vanligt". default_random_engine def(time(nullptr)); // Skapa en normalfördelad distribution. Denna genererar tal som // oftast hamnar nära 10. Utäses som att 68% av de slumpade talen // hamnar mindre än 2 steg från 10. (Inte så viktigt om man inte är // matematiker?) normal_distribution norm(10, 2); cout << "-------------------- Slumpade tal med normalfördelning: " << endl; for (int i = 0; i < 5; ++i) { // Genererar normalfördelade slumptal med standardmotor cout << norm(def) << endl; } Deck my_deck; // Min kortlek. (Dålig kommentar bland andra...) // Använd lite av funktionerna. Fungerar de? my_deck.shuffle(); cout << "-------------------- Fyra översta korten: " << endl; cout << my_deck.pop() << endl; cout << my_deck.pop() << endl; cout << my_deck.pop() << endl; cout << my_deck.pop() << endl; cout << "-------------------- Hela leken: " << endl; cout << my_deck << endl; my_deck.makeOrdered(); cout << "-------------------- Hela leken sorterad: " << endl; cout << my_deck << endl; return 0; } #if 0 // Diskussion om referenser! // Den "enkla" bilden av en referens är helt enkelt att det är ett // alias till en annan variabel - till något som ligger lagrat i // minnet. Om detta räcker för att förstå hur en referens fungerar är // hela diskussionen nedan onödig. void Deck::shuffle() { for_each(deck.begin(), deck.end(), [this](Card& c)->void { swap(c, deck.at( randomIndex() )); }); } // Hur funkar det? // Är c en referens till positionen i vectorn eller till kortet? // Svaret är: JA! // Varje position i vectorn ÄR ju ett kort, så positionen och kortet // kan ses som samma sak. // En variabel är en "låda" i minnet som har ett namn och innehåller // ett värde. "Lådan" har egenskapen att den kan tilldelas, d.v.s. vi // kan stoppa in nya värden i "lådan". Rättigheten (möjligheten) att // STOPPA IN något i EN VISS "låda" är det som är en referens. Med // rättigheten att stoppa in något följer automatiskt rättigheten att // läsa vad som redan finns. (D.v.s referensen blir ett alias för en // viss variabel.) // En vector är en sekvens med intilliggande "lådor" som istället för // namn har ett nummer. vectorns medlemsfunktion "at" returnerar // rättigheten att stoppa in något i en viss av vectorns // "lådor". Vilken låda som avses anges som bekant genom att skicka // ett nummer som argument till "at". // T.ex. returnerar deck.at(5) rättigheten att stoppa in något i lådan // med positionsnummer 5 - referensen till lådan med positionsnummer 5 // i vectorn - slarvigt uttryckt "referensen till position 5 i vectorn". // En referens - rättigheten att tilldela en variabel - kan skickas // vidare hur långt som helst genom att använda referensparametrar och // referensreturvärden. Så långt att det kan vara struligt att till // slut se vilken variabel som egentligen tilldelas när referensen // används. // I koden för shuffle ovan kommer for_each att gå igenom varje // positionsnummer i vectorn och plocka fram en referens till "lådan" // med det positionsnumret. Denna referens skickas som parameter till // lambda-funktionen. Lambdafunktionen skickar den vidare till // swap-funktionen. Swap gör följande: swap(Card& a, Card& b) { // 'a' har nu fått rättigheten att tilldela en låda med ett visst // positionsnummer i vectorn. // 'b' har också fått rättigheten att tilldela en låda med ett visst // positionsnummer i vectorn (kanske samma låda!). Card save = a; // Här läsas den låda som 'a' har rättighet att tilldela // och innehållet kopieras in i nya lådan 'save' a = b; // Här läsas den låda som 'b' har rättighet att tilldela och // innehållet kopieras in i den låda 'a' har rättighet tilldela b = save; // Här läsas lådan 'save' och innehållet kopieras in i den // låda som 'b' har rättighet att tilldela } // Rent tekniskt kommer en referensvariabel att initieras med faktiska // minnesadressen till den variabel ("låda") som anges när referensen // skapas. Om referensen skickas vidare som ny referens kopieras helt // enkelt den adressen till nya referensen. Om något tilldelas till // referensen kommer det stoppas in på den adress referensen // lagrar. Och om referensen läses kommer data hämtas från adressen // referensen lagrar. Den adress referensen lagrar kan inte ändras på // något sätt. Dock går det att få tag i adressen om man så vill. // Läs detta igen när du förstår pekare: Referenser är i C++ uppfunna // för att skapa en slags automatisk och säker (alltid giltig) // pekare. Man har nästan lyckats! Hade man helt förbjudit // möjligheten att returnera referenser hade man nog faktiskt lyckats // helt. #endif