Strona główna » c++

Tag: c++

Bartosz Gala autor bloga devhobby.eu – programowanie z kotem na kolanach. Zapraszam do przeczytania moich artykułów o C++ na blogu.

std::default_random_engine – opis podstawowy

Do tej pory w wielu publikacjach spotykamy się z opisem generatora liczb pseudolosowych w wersji dla języka C, czyli korzystamy z generatora „ziarna” srand (),a następnie generatora  rand(). Natomiast od wersji C++11 powinno się korzystać z dobrodziejstwa biblioteki random. W tym artykule opiszę mechanizm:

std::default_random_engine 

Samo korzystanie z nowej metody generowania liczb losowych w pierwszym momencie może wydawać się mało zrozumiałe, jednak postaram się w prostych słowach wyjaśnić same podstawy, niezbędne, aby można w pełni zamienić stary kod nowym. 

Komenda default_random_engine w przeciwieństwie do srand() nie jest generatorem ziarna – czyli prościej nie jest mechanizmem, który zapobiega otrzymywaniu identycznych ciągów liczb pseudolosowych, tylko jest inicjatorem generatora, i wskazuje jaki algorytm generowania liczb będziemy używać. Oczywiście możemy skorzystać z innych algorytmów, a nawet (jeśli posiadamy) ze sprzętowego generatora liczb losowych (wszystko opisane pod tym adresem).  tak więc komenda std::default_random_engine generator oznacza, że od teraz generator jest naszą liczbą losową a dokładniej całym ciągiem liczb, zmieniających się przy każdym wykorzystaniu i na razie zupełnie dla nas nieprzydatnych. Poniżej krótki program, który pozwoli to w pełni zrozumieć:

Kolejną komendą, jaka jest potrzebna to: 

std::uniform_int_distribution

Dzięki tej komendzie zamienimy ciąg liczb losowych na liczby z zadanego nam przedziału. Komendy używamy w następujący sposób:  std::uniform_int_distribution<int> distribution(a, b) gdzie: <int> określa typ zmiennej, jaki będzie zwracać generator, distribution to nazwa dla naszego generatora, natomiast (a,b) to zakres liczb, jaki będzie zwracany (na przykład od 0 do 20).

Teraz możemy już korzystać z komendy distribution(generator) aby wygenerować liczbę pseudolosową z zadanego zakresu. Poniżej krótki programik, który pokazuje cały mechanizm w działaniu. Oczywiście zamiast "generator" i "distribution" możemy skorzystać z dowolnych innych określeń (niebędących już istniejącymi komendami języka lub nazwami zmiennych) co pozwoli na płynne stosowanie wielu zakresów liczb losowych oraz kilku algorytmów generowania tych liczb (jeśli byłoby to potrzebne).

Mam nadzieję, że ten kawałek kodu wyjaśnia wszelkie wątpliwości, jeśli jakieś się pojawiły, i od teraz podstawowe mechanizmy związane z biblioteką random stały się proste i oczywiste.

*W artykule używam zamiennie nazw liczba losowa i pseudo losowa – w większości przypadków chodzi mi o liczby pseudo losowe, poza wzmianką o sprzętowym generatorze liczb losowych.

** Tak stworzone liczby pseudo losowe będą identyczne za każdym razem (taka sama sekwencja). W następnym artykule napiszę jak to zmienić, oraz opiszę prawidłowo wykonywany pomiar czasu w C++11.

Projekt: Tworzymy własną grę – Po konsultacji :)

Pierwsza część projektu             Poprzednia część projektu      

Dzisiaj tylko krótka informacja techniczna: Kod projektu został rzucony na pastwę zawodowców, co oczywiście obnażyło jego słabe strony.

Po konsultacji

z rewelacyjną ekipą Cpp Polska kod gry jest w trakcie zamieszczania często dość istotnych zmian. Z jednej strony jest co poprawiać, ale to także świetny kawałek nauki. Poniżej umieszczam listę zaleconych poprawek, nad którymi pracuje, natomiast mam nadzieję, do tygodnia pojawi się pierwsza część Projekt: Tworzymy własną grę – refactoring/poprawki. Natomiast krótka przerwa jest potrzebna, na to, żebym mógł zapoznać się z kilkoma kwestiami zawartymi w uwagach do kodu i je ciut przećwiczyć. 

Uwagi do kodu: 

1. Za dużo komentarzy. Poleciłbym Ci lepsze nazewnictwo kosztem mniejszej ilości komentarzy.
2. Mam wrazenie, ze sporo ifow moglbys zamienic na switche.
W logikę nie chcę się wtrącać, bo nie znam modelu projektu.

Main.cpp
9 – w nowym c++ powinno używać się std::random_device do generowania ziarna. Tutaj więcej szczegółów: http://en.cppreference.com/w/cpp/numeric/random
17 – musisz popracować nad nazewnictwem, w linii 35 masz „endGame = startGame” ….
27 – nie potrzebujesz dwuwymiarowej dynamicznej tablicy. Znasz rozmiar w czasie kompilacji, możesz użyć std::array
32 – memory leak. Nie usuwasz pamięci po stworzeniu obiektu. Nie potrzebujesz dynamicznej alokacji, możesz stworzyć Player na stosie.
Da się uniknąć wszystkich new/delete w tym pliku

Player.h
dużo magicznych wartości, możesz zrobić klasy wyliczeniowe „enum class”

Player.cpp
9 – domyślny konstruktor tylko po to, żeby wypisać błąd. Nie potrzebujesz domyślnego konstruktora
16 – membery class możesz zainicjalizować w pliku .hpp
58 – gameOver?:)
63 – aż się prosi o switch/case i enum class

ScoreBoard.h
8 – możesz użyć std::array

ScoreBoard.cpp
25 – używaj algorytmów z std::algorithm, w szczególności std::sort (który również działa na tablicach)
52 – uchwyt do pliku jest automatycznie zamykany w destruktorze. I robisz close() nawet jak się nie uda otworzyć pliku
57 – użytkownika chyba nie interesuje czy plik istniał czy nie, jak jest pierwszy na pewno będzie o tym wiedział
60 – Nie potrzebujesz takich sprytnych algorytmów. Potrzebujesz użyć dobrej struktury danych. Użyj std::set, który jest posortowany (std::map jak będziesz chciał dodać nazwy), będziesz mógł łatwo wstawiać elementy
Może warto dodać do klasy nazwę użytkownika, nie tylko najlepszy wynik?

Ogólnie
Sformatuj kod clang-formatem, bardzo ciężko się czyta.
Nie masz żadnych testów, a to bardzo niedobrze 🙂

Tak więc czekają mnie dwie linie artykułów: będą to 2-3 artykuły związane z poprawkami do kodu oraz zupełnie osobna cześć bloga, czyli testy jednostkowe. Tak więc zaraz po majowym długim weekendzie będzie się sporo działo.

Na zakończenie wpisu, chcę bardzo podziękować ekipie z CPP Polska, a szczególnie osobą, które poświęciły bezinteresownie swój czas na code review – Jesteście wielcy!!! 🙂 

 

Visualki – ReShaper, pierwsze wrażenie.

Jeśli ktoś zapyta mnie, jaki addon do Visual Studio uważam za niezbędny, to wskażę go bez chwili zawahania się. Tak więc dzisiaj chcę napisać kilka słów o dodatku, o którym bardzo chcę za jakiś czas (gdy go znacznie lepiej poznam)  napisać dłuższy artykuł. Oto:

ReShaper C++ for Visual Studio.

Narzędzie znanej i dość lubianej, szczególnie w środowisku programistów Java firmy Jet Brains, na pierwszy rzut oka tylko powiela wbudowane funkcje Visual Studio, takie jak InteliSense. Na samym początku podchodziłem do tego rozszerzenia dość nieufnie i bez entuzjazmu. Jednak po kilku dniach korzystania moje zdanie o tym produkcie zmieniło się całkowicie. ReShaper nawet jeszcze dość nieudolnie wykorzystywany, czasem wręcz (o zgrozo) wspierany myszką, bardzo mocno przyśpiesza samo „klepanie” kodu, wspaniale pokazuje błędy i stylistyczne potknięcia, a co najważniejsze dla amatora: wspiera „ładne” kodowanie, dbając o spójność nazewnictwa, potrafi wskazać wiele złych stylistycznie miejsc kodu, podpowiada ciekawe rozwiązania. 

Dodatkowo jest to nieoceniony przyjaciel przy re faktoringu kodu. Pozwala pewnie zmieniać nazwy zmiennych, klas czy funkcji w całym kodzie, przerabiać w kilka sekund całe bloki kodu i jednocześnie uniknąć głupich błędów i literówek. Opcje takie jak Rearrange code, potrafią wprowadzić co prawda zarówno sporo porządku, jak i totalny chaos w kodzie, ale czasem warto spróbować, jak wygląda porządek, według automatycznych skryptów :). Obecnie powoli nabieram wprawy w wykorzystywaniu podstawowych funkcji rozszerzenia, jednak obiecuje, że ten wpis to tylko „zajawka” kolejnych, już zdecydowanie praktycznych artykułów o tym narzędziu.

Narzędzie możemy pobrać na stronie producenta i przez 30 dni cieszyć się darmową wersją. Potem musimy albo zapłacić 89 euro za pierwszy rok (lub 8.9 za miesiąc), albo sprawdzić, czy nie możemy się ubiegać o licencję darmową lub zniżkową, w ramach programów edukacyjnych lub Open Source. 

Osobiście bardzo polecam przetestowanie ReShapera, bo na prawdę warto.

Tym razem wyjątkowo: Ważne skróty klawiaturowe dla ReShaper:

  • Alt + Enter          szybka naprawa, “Swiss Army knife”
  • Alt + Insert         generowanie kodu
  • Ctrl + Shift + R     kontekstowy refactoring
  • Ctrl + N             nawigacja/wyszukiwanie
  • Ctrl + Alt + G       nawigacja, zależnie od kontekstu
  • Alt + P              parametry metod (wszystkie przeciążenia)
  • Ctrl + Alt + H       hierarchia obiektów, dziedziczenia, itp
  • Ctrl + F11           struktura pliku: sortowanie ręczne, refaktoring, otaczanie fragmentów,
  • Ctrl + Space         uzupełnianie składni, dodatkowo podpowiada nazwy zmiennych
  • Ctrl + Shift + Space kontekstowe podpowiedzi
 

Projekt: tworzymy własną grę w (6)-1: przygotowanie obszaru gry.

Pierwsza część projektu      Poprzednia część projektu

Tym razem artykuł zabrał mi ciut więcej czasu… po pierwsze święta, ale przede wszystkim dość przełomowy moment, podczas którego trzeba by omówić bardzo dużo kodu, za dużo jak na jeden wpis. Tak więc po raz pierwszy dzielę jeden wpis na dwie dość nierozerwalne części i umieszczę je w odstępie tylko kilku dni od siebie. Dzisiaj pozmieniamy to i owo w klasach, i przedstawimy reguły gry obowiązujące w pierwszej, uproszczonej wersji. Niestety w brew obietnicą z poprzedniego wpisu, poruszanie się wprowadzimy dopiero w drugiej części tego artykułu.

Przygotowanie obszaru gry.

Najpierw dodamy nowe metody do klasy player. Nie są one konieczne, ale bardzo uproszczą kod zarówno teraz, jak i później, podczas dodawania „eventów” do gry. Nowe metody to bardzo proste odmiany seterów, jednak nie ustawiają one nową wartość zmiennej, a ją modyfikują:

We właściwym kodzie obie metody zostaną zapisane w jednej linijce, co prawda przy obecnych kompilatorach nie ma to większego znaczenia, ale wizualnie będzie wyglądać znacznie ładniej.

Teraz informacja o celu gry. Jest wyświetlana tylko raz, na samym początku rozgrywki i jeśli gracz, mimo ostrzeżenia nie zapamięta opisu wyjścia, będzie się poruszać po świecie gry zupełnie po omacku.

Zaraz po opisie, w tej samej funkcji wprowadzam  zmiany podczas wejścia na obszar, oraz to tutaj, w następnym artykule dopisze wszystkie pozostałe wywołania funkcji w grze. Dodaje też metodę:  void Player::change_addOne_PlayerRounds() { playerRounds++; }Jak wynika z kodu, jest to metoda klasy Player (i tam ją też zapisuje). Na końcu pojawia się górny „pasek statusu” gry, na którym wyświetlane są najważniejsze parametry gracza.

Przy okazji znalazłem błąd w pliku player.cpp: w konstruktorze były ustawione w 2 ostatnich linijkach do zapisu komórki tablicy 4,5 zamiast 6,7, co powodowało wyjście poza zakres w aktualnie pisanym kodzie i błąd dopiero podczas wykonywania programu. 

Na koniec najdłuższa funkcja całej gry: wyświetlanie opisu aktualnego obszaru gry. Pierwsza część opisowa nie wymaga większego wyjaśnienia. Część „graficzna” przedstawia szkic pola gry z ukazaniem tego, które drzwi są otwarte, a które zamknięte. Osoby chętne, mogą wprowadzić trzeci poziom trudności, w którym opis graficzny nie będzie wyświetlany (to 2-3 linijek kodu więcej). Na sam koniec, kilka linijek opisu wpływu pomieszczenia na gracza. 

Link do funkcji opisującej obszar.

 

Proszę pamiętać, że przez samą zmianę treści napisów, można bardzo poważnie zmienić klimat gry. Ja koncentruje się na bardzo sterylnych i krótkich opisach, jednak nic nie stoi na przeszkodzie, aby to zmienić samemu. Można też w ramach wprawy tworzyć grę np. po angielsku.

Wyjątkowo w tym odcinku nie podaje linka do GitHuba z kodem końcowym, dopiero w następnym odcinku umieszczę tam kod wstępnie grywalnej gry (alfa 1). Jednocześnie zachęcam do samodzielnego eksperymentowania i połączenia podanych dzisiaj fragmentów kodu w całość.

Zapraszam do następnej części projektu już wkrótce.

Dodawanie liczby do string

Proste dodanie int do string jest niemożliwe i powoduje błąd. Takie operacje są często bardzo pożądane i wiele początkujących osób ma z tym problem. Często kończy się to obchodzeniem problemu przez korzystanie z danych tekstowych i liczbowych osobno, i łączeniu ich dopiero na poziomie zwykłego składania podczas wyświetlania na ekran. Na szczęście jednak istnieją bardzo proste sposoby na realizację tego zadania.

to_string

Pierwsza metoda to zawarta w bibliotece string, bardzo prosta w użyciu  komenda to_string().Ten sposób wykorzystałem w opisywanej na tym blogu grze konsolowej. Działanie polecenia jest bardzo proste: string += to_string(int)Przykład poniżej: 

stringstream

Inny sposób na realizację zadania dodania int do string znajdziemy w bibliotece sstream , pozwalająca nam na korzystanie z ciągów znaków jak z strumienia tekstu. W zadanej operacji potrzebny jest  odrębny rodzaj zmiennej tekstowej: strumień napisu stringstream, który wykorzystamy do zespolenia danych w jednym ciągu tekstowym.  Ta metoda wygląda bardziej skomplikowanie:

  1. tworzymy zmienną (strumień) typu stringstream
  2. korzystając z operatora << dodajemy do strumienia kolejne fragmenty 
  3. przepisujemy do zwykłej zmiennej string zawartość strumienia stringstream przy pomocy metody .str().

Tak wygląda gotowy kawałku kodu (nie zapomnijmy o #include<sstream>;):

Osobiście najczęściej wykorzystuje prostszą pierwszą metodę, ale biblioteka sstream daje nam jeszcze wiele innych ciekawych narzędzi konwersji napisów, także w przeciwną stronę (na przykład string do int), oraz tworzenie strumienia teksu zawierającego w sobie komendy formatujące jak na przykład std::endl. Tak więc warto się z nią zapoznać i zaprzyjaźnić, zamiast szukać innych skomplikowanych metod na konwersje powiązane z typem string. Dodatkową zaletą strumieni napisów jest możliwość tworzenia ich w wersji tylko do odczytu, tylko do zapisu lub do zapisu i odczytu, co może nam dodatkowo wesprzeć hermetyzację kodu. 

Ten artykuł nie ma na celu przedstawienia w pełni możliwości biblioteki sstream, a tylko jednej, wybranej funkcjonalności. Jeśli chcesz wiedzieć więcej, zapraszam do dokumentacji w linku poniżej, albo poinformuj mnie o tym w komentarzu, to postaram się napisać znacznie bardziej rozbudowany przewodnik po sstream.

Pełna dokumentacja biblioteki sstream

If – else if: to samo, ale inaczej, czyli o „?:”

Nie jeden raz zdarza się tworzyć kolejne linijki if - else if - else if...  sporo klepania w klawiaturę, masa kodu, i nie koniecznie to wszystko  ładnie wygląda.

Za przykład można wziąć  proste sterowanie grą z klawiatury, pobieramy pojedynczy znak przy pomocy _getch(), następnie sprawdzamy, czy naciśnięty został klawisz sterujący (przyjmijmy standard wsad), a może wprowadzono dużą literę, na koniec może gracz chce wyjść i nacisną Q lub q, albo nacisną cokolwiek innego, więc należy ponowić pętlę wprowadzania…  i otrzymujemy piękny kawałek kodu w stylu:

Niedoceniany operator  „?:”

Jednocześnie ten sam kod można zapisać zupełnie inaczej, korzystając z często nie docenianej notacji „?:” – co to jest? oto definicja:

Operator warunkowy ?:, powszechnie znany jako trójargumentowy operator warunkowy zwraca jedną z dwóch wartości w zależności od wartości wyrażenia logicznego. Zastosowanie wygląda następująco: x = a ? b : c co tłumaczymy jako: if (a = true) x = b else x = c.

Z tą wiedzą przepiszmy podany powyżej kod wykorzystując operator „?:„.

I tak to niedoceniany „?:” zwykle stosowany sporadycznie w układzie data = (x)? a:b jeśli x to prawda zwracaj, a jeśli fałsz zwracaj b został wykorzystany do napisania przejrzystego i zrozumiałego kodu.

Bardzo zachęcam do eksperymentowania z operatorem „?:” w bardziej zaawansowanych niż podstawowe „jedno liniowe” fragmenty kodu. Proponuję też na własną rękę wypróbować działanie następującego schematu: x = a ? b : c ? d : e. Gwarantuje, że można spędzić nad tym nieco czasu i wyciągnąć bardzo dużo wiedzy i ciekawych pomysłów na przyszłość.