17.x — podsumowanie rozdziału 17 i quiz

Przegląd rozdziału

Tablice o stałym rozmiarze (lub tablicami o stałej długości) wymagają, aby długość tablicy była znana w momencie tworzenia instancji i ta długość nie można później zmienić. Tablice w stylu C i std::array są tablicami o stałym rozmiarze. Rozmiar tablic dynamicznych można zmieniać w czasie wykonywania. std::vector jest tablicą dynamiczną.

Długość std::array musi być wyrażeniem stałym. Najczęściej podaną wartością długości będzie literał całkowity, zmienna constexpr lub moduł wyliczający bez zakresu.

std::array jest agregatem. Oznacza to, że nie ma konstruktorów i zamiast tego jest inicjowany przy użyciu inicjalizacji agregowanej.

Zdefiniuj swój std::array jako constexpr, jeśli to możliwe. Jeśli std::array nie jest constexpr, rozważ użycie std::vector .

Użyj odliczenia argumentów szablonu klasy (CTAD), aby kompilator wywnioskował typ i długość std::array z jej inicjatorów.

std::array jest zaimplementowany jako struktura szablonu, której deklaracja wygląda następująco:

template<typename T, std::size_t N> // N is a non-type template parameter
struct array;

Nietypowy parametr szablonu reprezentujący długość tablicy (N) ma typ std::size_t.

Aby uzyskać długość std::array:

  • Możemy zapytać std::array obiekt o jego długość za pomocą size() funkcji składowej (która zwraca długość jako bez znaku size_type).
  • W C++17 możemy użyć std::size() nieelementowej (która for std::array po prostu wywołuje funkcję składową size() , zwracając w ten sposób długość bez znaku size_type).
  • W C++20 możemy użyć std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwyklestd::ptrdiff_t).

Wszystkie trzy funkcje zwrócą długość jako wartość constexpr, z wyjątkiem sytuacji, gdy zostaną wywołane w std::array przekazanej przez referencję. Ten defekt został rozwiązany w C++23 przez P2280.

Aby zaindeksować std::array:

  • Użyj operatora indeksu dolnego (operator[]). W tym przypadku nie jest wykonywane żadne sprawdzanie granic, a przekazanie nieprawidłowego indeksu spowoduje niezdefiniowane zachowanie.
  • Użyj at() funkcji składowej, która wykonuje indeks dolny ze sprawdzaniem granic w czasie wykonywania. Zalecamy unikać tę funkcję, ponieważ zazwyczaj chcemy sprawdzić granice przed indeksowaniem lub chcemy sprawdzić granice w czasie kompilacji.
  • Użyj std::get() szablonu funkcji, który przyjmuje indeks jako argument szablonu niebędący typem i sprawdza granice w czasie kompilacji.

Możesz przekazywać std::array z różnymi typami elementów i długościami do funkcji, używając szablonu funkcji z parametrem szablonu deklaracja template <typename T, std::size_t N>. Lub w C++20 użyj template <typename T, auto N>.

Zwrócenie std::array by wartością spowoduje utworzenie kopii tablicy i wszystkich elementów, ale może to być w porządku, jeśli tablica jest mała, a kopiowanie elementów nie jest drogie. W niektórych kontekstach lepszym wyborem może być użycie parametru out.

Podczas inicjalizacji std::array w przypadku struktury, klasy lub tablicy i bez podawania typu elementu przy każdym inicjatorze, będziesz potrzebować dodatkowej pary nawiasów klamrowych, aby kompilator właściwie zinterpretował to, co należy zainicjować. Jest to artefakt inicjowania agregacji, a inne standardowe typy kontenerów bibliotecznych (które używają konstruktorów list) nie wymagają w takich przypadkach podwójnych nawiasów.

Agregaty w C++ obsługują koncepcję zwaną elizja nawiasów, która określa pewne zasady dotyczące sytuacji, gdy może występować wiele nawiasów klamrowych. ogólnie rzecz biorąc, można pominąć nawiasy klamrowe podczas inicjowania std::array za pomocą wartości skalarnych (pojedynczych) lub podczas inicjowania z typami klasowymi lub tablicami, gdzie typ jest jawnie nazwany przy każdym elemencie.

Nie możesz mieć tablicy odniesień, ale możesz mieć tablicę std::reference_wrapper, która zachowuje się jak modyfikowalne odwołanie do wartości.

Jest kilka rzeczy, o których warto pamiętać std::reference_wrapper:

  • Operator= ponowne osadzenie a std::reference_wrapper (zmiana obiektu, do którego się odwołuje).
  • std::reference_wrapper<T> będzie niejawnie konwertowana do T&.
  • Klasa get() funkcji składowej, której można użyć do uzyskania T&. Jest to przydatne, gdy chcemy zaktualizować wartość obiektu, do którego się odwołujemy.

Klasa std::ref() i std::cref() funkcje zostały udostępnione jako skróty do tworzenia std::reference_wrapper i const std::reference_wrapper owinięte obiektów.

Użyj static_assert jeśli to możliwe, upewnij się, constexpr std::array przy użyciu CTAD ma poprawną liczbę inicjatorów.

Tablice w stylu C zostały odziedziczone z języka C i są wbudowane w rdzeń języka C++. Ponieważ są częścią języka podstawowego, tablice w stylu C mają własną składnię deklaracji specjalnych. W deklaracji tablicy w stylu C używamy nawiasów kwadratowych ([]), aby poinformować kompilator, że zadeklarowany obiekt jest tablicą w stylu C. Wewnątrz nawiasów kwadratowych możemy opcjonalnie podać długość tablicy, która jest wartością całkowitą typu std::size_t, która informuje kompilator, ile elementów znajduje się w tablicy. Długość tablicy w stylu C musi być wyrażeniem stałym.

Tablice w stylu C są agregowane, co oznacza, że ​​można je inicjować przy użyciu inicjalizacji agregowanej. Używając listy inicjatorów do inicjowania wszystkich elementów tablicy w stylu C, lepiej pominąć długość i pozwolić kompilatorowi obliczyć długość tablicy.

Tablice w stylu C można indeksować za pomocą operator[]. Indeks tablicy w stylu C może być liczbą całkowitą ze znakiem lub bez znaku, albo wyliczeniem bez zakresu. Oznacza to, że tablice w stylu C nie podlegają wszystkim problemom z indeksowaniem konwersji znaków, które występują w standardowych klasach kontenerów bibliotek!

Tablice w stylu C mogą być const lub constexpr.

Aby uzyskać długość tablicy w stylu C:

  • W C++17 możemy użyć std::size() funkcji niebędącej składową, która zwraca długość jako unsigned std::size_t.
  • W C++20 możemy użyć std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwyklestd::ptrdiff_t).

W większości przypadków, gdy w wyrażeniu używana jest tablica w stylu C, tablica zostanie niejawnie przekonwertowana na wskaźnik do typu elementu, zainicjowany adresem pierwszego elementu (z indeksem 0). Potocznie nazywa się to rozpadem tablicy (lub po prostu w skrócie rozpadu).

Arytmetyka wskaźników to funkcja, która pozwala nam zastosować pewne operatory arytmetyczne na liczbach całkowitych (dodawanie, odejmowanie, zwiększanie lub zmniejszanie) do wskaźnika w celu wygenerowania nowego adresu pamięci. Biorąc pod uwagę pewien wskaźnik ptr, ptr + 1 zwraca adres następnego obiektu w pamięci (w zależności od typu, na który wskazuje).

Użyj indeksu dolnego podczas indeksowania od początku tablicy (element 0), tak aby indeksy tablicy pokrywały się z elementem.
Użyj arytmetyki wskaźników podczas wykonywania względnych pozycjonowanie z danego elementu.

Ciągi w stylu C to po prostu tablice w stylu C, których typ elementu to char lub const char. W związku z tym ciągi w stylu C będą się rozpadać.

Klasa wymiar tablicy to liczba indeksów potrzebnych do wybrania elementu.

Tablica zawierająca tylko jeden wymiar nazywana jest tablicą jednowymiarową lub a tablicą jednowymiarową (czasami w skrócie 1d tablica). Tablica tablic nazywana jest tablicą dwuwymiarową (czasami w skrócie tablicą 2d), ponieważ ma dwa indeksy dolne. Tablice z więcej niż jednym wymiarem nazywane są tablicami wielowymiarowymi. Spłaszczanie tablica to proces zmniejszania wymiarowości tablicy (często do jednego wymiaru).

W C++23 std::mdspan to widok zapewniający wielowymiarowy interfejs tablicy dla ciągłej sekwencji elementów.

Czas quizu

Pytanie nr 1

Co jest nie tak z każdym z tych fragmentów i jak to naprawić?

A)

#include <array>
#include <iostream>

int main()
{
    std::array arr { 0, 1, 2, 3 };

    for (std::size_t count{ 0 }; count <= std::size(arr); ++count)
    {
        std::cout << arr[count] << ' ';
    }

    std::cout << '\n';

    return 0;
}

Pokaż rozwiązanie

B)

#include <iostream>

void printArray(int array[])
{
    for (int element : array)
    {
        std::cout << element << ' ';
    }
}

int main()
{
    int array[] { 9, 7, 5, 3, 1 };

    printArray(array);

    std::cout << '\n';

    return 0;
}

Pokaż rozwiązanie

C)

#include <array>
#include <iostream>

int main()
{
    std::cout << "Enter the number of test scores: ";
    std::size_t length{};
    std::cin >> length;

    std::array<int, length> scores;

    for (std::size_t i { 0 } ; i < length; ++i)
    {
        std::cout << "Enter score " << i << ": ";
        std::cin >> scores[i];
    }
    return 0;
}

Pokaż rozwiązanie

Pytanie nr 2

W tym quizie będziemy uruchom sklep z eliksirami Roscoe, najlepszy sklep z eliksirami w kraju! To będzie większe wyzwanie.

Zaimplementuj program, który wyświetli następujące wyniki:

Welcome to Roscoe's potion emporium!
Enter your name: Alex
Hello, Alex, you have 85 gold.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: a
That is an invalid input.  Try again: 3
You purchased a potion of invisibility.  You have 35 gold left.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 4
That is an invalid input.  Try again: 2
You purchased a potion of speed.  You have 23 gold left.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 2
You purchased a potion of speed.  You have 11 gold left.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 4
You can not afford that.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: q

Your inventory contains: 
2x potion of speed
1x potion of invisibility
You escaped with 11 gold remaining.

Thanks for shopping at Roscoe's potion emporium!

Gracz zaczyna z losową ilością złota od 80 do 120.

Brzmi zabawnie? Zróbmy to! Ponieważ trudno będzie wdrożyć to wszystko na raz, będziemy rozwijać to stopniowo.

> Krok #1

Utwórz Potion przestrzeń nazw zawierająca wyliczenie o nazwie Type zawierające typy mikstur. Utwórz dwa std::array: jakiś int tablica do przechowywania kosztów mikstur, oraz a std::string_view tablica do przechowywania nazw mikstur.

Napisz także funkcję o nazwie shop() który wylicza poprzez listę Potions i drukuje ich numery, nazwy i koszt.

Program powinien wypisać co następuje:

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50

Pokaż wskazówkę

Pokaż rozwiązanie

> Krok #2

Utwórz Player klasa do przechowywania imienia gracza, ekwipunku mikstur i złota. Dodaj tekst wprowadzający i pożegnalny dotyczący emporium Roscoe. Zdobądź imię gracza i losuj jego złoto.

Wykorzystaj na lekcji plik „Random.h”. 8.15 -- Globalne liczby losowe (Random.h) aby ułatwić randomizację.

Program powinien wypisać co następuje:

Welcome to Roscoe's potion emporium!
Enter your name: Alex
Hello, Alex, you have 84 gold.

Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50

Thanks for shopping at Roscoe's potion emporium!

Pokaż rozwiązanie

> Krok #3

Dodaj możliwość zakupu mikstur i obsługi nieprawidłowych danych wejściowych (każde obce dane wejściowe traktuj jako awarię). Wydrukuj ekwipunek gracza po jego wyjściu. Po tym kroku program powinien być gotowy.

Upewnij się, że testujesz następujące przypadki:

  • Użytkownik wprowadza nieprawidłowy numer mikstury (np. „d”)
  • Użytkownik wprowadza prawidłowy numer mikstury, ale z dodatkowymi danymi (np. 2d, 25)

Na lekcji omówimy obsługę nieprawidłowych danych wejściowych 9.5 — std::cin i obsługa nieprawidłowych danych wejściowych.

Pokaż wskazówkę

Pokaż wskazówkę

Pokaż rozwiązanie

Pytanie nr 3

Załóżmy, że chcemy napisać grę karcianą wykorzystującą standardową talię kart. Aby to zrobić, będziemy potrzebować sposobu na przedstawienie tych kart i talii kart. Zbudujmy tę funkcjonalność.

Wykorzystamy go w następnym pytaniu quizu, aby faktycznie zaimplementować grę.

> Krok #1

Talia kart składa się z 52 unikalnych kart (13 szeregów kart po 4 kolory). Twórz wyliczenia dla rang kart (as, 2, 3, 4, 5, 6, 7, 8, 9, 10, walet, dama, król) i kolorów (trefl, karo, kier, pik).

Pokaż rozwiązanie

> Krok #2

Każda karta będzie reprezentowana przez strukturę o nazwie Card który zawiera rangę i członka koloru. Utwórz strukturę i przenieś do niej wyliczenia.

Pokaż rozwiązanie

> Krok #3

Następnie dodajmy kilka przydatnych funkcji do naszej struktury Card. Po pierwsze, przeciążenie operator<< aby wydrukować rangę i kolor karty w postaci dwuliterowego kodu (np. walet pik zostanie wydrukowany jako JS). Można to zrobić wypełniając następującą funkcję:

struct Card
{
    // Your other stuff here

    friend std::ostream& operator<<(std::ostream& out, const Card &card)
    {
        out << // print your card rank and suit here
        return out;
    }
};

Po drugie dodaj funkcję zwracającą wartość karty. Traktuj asa jako wartość 11. Na koniec dodaj a std::array rangi i koloru (o nazwie allRanks i allSuits odpowiednio), aby można było je iterować. Ponieważ są one częścią struktury (nie przestrzeni nazw), uczyń je statycznymi, aby były tworzone tylko raz (nie z każdym obiektem).

Poniższe powinno się skompilować:

int main()
{
    // Print one card
    Card card { Card::rank_5, Card::suit_heart };
    std::cout << card << '\n';

    // Print all cards
    for (auto suit : Card::allSuits)
        for (auto rank : Card::allRanks)
            std::cout << Card { rank, suit } << ' ';
    std::cout << '\n';

    return 0;
}

i wygenerowany zostanie następujący wynik:

5H
AC 2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC AD 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD AH 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH KH AS 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS 

Pokaż rozwiązanie

> Krok 4

Następnie stwórzmy naszą talię kart. Utwórz klasę o nazwie Deck zawierającą a std::array Kart. Możesz założyć, że talia składa się z 52 kart.

Talia powinna spełniać trzy funkcje:

Po pierwsze, konstruktor domyślny powinien zainicjować tablicę kart. Aby przeglądać wszystkie kolory i stopnie, możesz użyć pętli for ranged podobnej do tej w funkcji main() z poprzedniego przykładu.

Po drugie, dodaj a dealCard() funkcja, która zwraca następną kartę w talii według wartości. Od std::array jest tablicą o stałym rozmiarze, zastanów się, w jaki sposób będziesz śledzić, gdzie znajduje się następna karta. Ta funkcja powinna zostać potwierdzona, jeśli zostanie wywołana, gdy Deck przejdzie przez wszystkie karty.

Po trzecie napisz A shuffle() funkcja członkowska, która tasuje talię. Aby to ułatwić, skorzystamy z pomocy std::shuffle:

#include <algorithm> // for std::shuffle
#include "Random.h"  // for Random::mt

    // Put this line in your shuffle function to shuffle m_cards using the Random::mt Mersenne Twister
    // This will rearrange all the Cards in the deck randomly
    std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);

Klasa shuffle() funkcja powinna również zostać zresetowana, jednakże śledzisz miejsce, w którym znajduje się następna karta na początek talii.

Powinien uruchomić się następujący program:

int main()
{
    Deck deck{};
    std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';

    deck.shuffle();
    std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';

    return 0;
}

i otrzymasz następujący wynik (ostatnie 3 karty powinny zostać losowane):

AC 2C 3C
2H 7H 9C

Pokaż rozwiązanie

Pytanie nr 4

W porządku, teraz użyjmy naszej karty i talii, aby wdrożyć uproszczoną wersję blackjacka! Jeśli nie znasz jeszcze Blackjacka, w Wikipedii znajdziesz artykuł na temat Blackjack , który zawiera podsumowanie.

Oto zasady naszej wersji Blackjacka:

  • Kurier otrzymuje na początek jedną kartę (w prawdziwym życiu krupier otrzymuje dwie, ale jedna jest zakryta, więc w tym momencie nie ma to znaczenia).
  • Gracz otrzymuje dwie karty do rozdania. start.
  • Gracz zaczyna.
  • Gracz może wielokrotnie „trafić” lub „wstać”.
  • Jeśli gracz „wstanie”, jego tura dobiega końca, a jego wynik jest obliczany na podstawie kart, które otrzymał.
  • Jeśli gracz „trafia”, otrzymuje kolejną kartę, a wartość tej karty jest dodawana do jego całkowitego wyniku.
  • Zwykle as liczy się jako 1 lub 11 (w zależności od tego, co jest lepsze dla całkowitego wyniku). Dla uproszczenia policzymy to tutaj jako 11.
  • Jeśli gracz przekroczy wynik 21, odpada i natychmiast przegrywa.
  • Gdy gracz skończy, nadchodzi kolej krupiera.
  • Krucznik wielokrotnie dobiera, aż osiągnie wynik 17 lub więcej, w którym to momencie musi przestać dobierać.
  • Jeśli krupier przekroczy wynik 17 21, odpada i gracz natychmiast wygrywa.
  • W przeciwnym razie, jeśli gracz ma wyższy wynik niż krupier, wygrywa. W przeciwnym razie gracz przegrywa (dla uproszczenia uznamy remis za wygraną krupiera).

W naszej uproszczonej wersji Blackjacka nie będziemy śledzić, jakie konkretne karty otrzymali gracz i krupier. Będziemy śledzić jedynie sumę wartości kart, które otrzymali gracz i krupier. To upraszcza sprawę.

Zacznij od kodu, który napisałeś w poprzednim quizie (lub skorzystaj z naszego rozwiązania referencyjnego).

> Krok #1

Utwórz strukturę o nazwie Player która będzie reprezentować uczestnika naszej gry (albo krupiera, albo gracza). Ponieważ w tej grze interesuje nas tylko wynik gracza, ta struktura potrzebuje tylko jednego członka.

Napisz funkcję, która (ostatecznie) rozegra rundę blackjacka. Na razie ta funkcja powinna losować jedną losową kartę dla krupiera i dwie losowe karty dla gracza. Powinien zwrócić wartość bool wskazującą, kto ma większy wynik.

Kod powinien wyprowadzić następującą treść:

The dealer is showing: 10
You have score: 13
You win!
The dealer is showing: 10
You have score: 8
You lose!

Pokaż rozwiązanie

> Krok #2

Dodaj Settings przestrzeń nazw zawierającą dwie stałe: wartość, powyżej której gracz odpada, oraz wartość, przy której krupier musi przestać dobierać karty.

Dodaj logikę obsługującą turę krupiera. Krupier będzie dobierać karty, dopóki nie osiągnie 17, po czym musi się zatrzymać. Jeśli odpadną, gracz wygrywa.

Oto przykładowe wyniki:

The dealer is showing: 8
You have score: 9
The dealer flips a 4D.  They now have: 12
The dealer flips a JS.  They now have: 22
The dealer went bust!
You win!
The dealer is showing: 6
You have score: 13
The dealer flips a 3D.  They now have: 9
The dealer flips a 3H.  They now have: 12
The dealer flips a 9S.  They now have: 21
You lose!
The dealer is showing: 7
You have score: 21
The dealer flips a JC.  They now have: 17
You win!

Pokaż rozwiązanie

> Krok #3

Na koniec dodaj logikę dla tury gracza. To zakończy grę.

Oto przykładowe wyniki:

The dealer is showing: 2
You have score: 14
(h) to hit, or (s) to stand: h
You were dealt KH.  You now have: 24
You went bust!
You lose!
The dealer is showing: 10
You have score: 9
(h) to hit, or (s) to stand: h
You were dealt TH.  You now have: 19
(h) to hit, or (s) to stand: s
The dealer flips a 3D.  They now have: 13
The dealer flips a 7H.  They now have: 20
You lose!
The dealer is showing: 7
You have score: 12
(h) to hit, or (s) to stand: h
You were dealt 7S.  You now have: 19
(h) to hit, or (s) to stand: h
You were dealt 2D.  You now have: 21
(h) to hit, or (s) to stand: s
The dealer flips a 6H.  They now have: 13
The dealer flips a QC.  They now have: 23
The dealer went bust!
You win!

Pokaż rozwiązanie

Pytanie #5

a) Opisz, w jaki sposób możesz zmodyfikować powyższy program, aby obsługiwał przypadek, w którym asy mogą być równe 1 lub 11.

Ważne jest, aby pamiętać, że śledzimy tylko sumę kart, a nie konkretne karty posiadane przez użytkownika.

Pokaż rozwiązanie

b) W prawdziwym blackjacku, jeśli gracz i krupier mają ten sam wynik (a gracz nie przegrał), wynikiem jest remis i żaden nie wygrywa. Opisz, jak zmodyfikowałbyś powyższy program, aby to uwzględnić.

Pokaż rozwiązanie

c) Dodatkowy kredyt: zaimplementuj powyższe dwa pomysły w swojej grze w blackjacka. Pamiętaj, że będziesz musiał pokazać początkową kartę krupiera i początkowe dwie karty gracza, aby wiedzieli, czy ma asa, czy nie.

Oto przykładowy wynik:

The dealer is showing JH (10)
You are showing AH 7D (18)
(h) to hit, or (s) to stand: h
You were dealt JD.  You now have: 18
(h) to hit, or (s) to stand: s
The dealer flips a 6C.  They now have: 16
The dealer flips a AD.  They now have: 17
You win!

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
1.6K Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze