16.x — Podsumowanie rozdziału 16 i quiz

Słowa zachęty

Ten rozdział nie jest łatwy. Omówiliśmy mnóstwo materiału i odkryliśmy kilka wad C++. Gratulujemy sukcesu!

Tablice to jeden z kluczy odblokowujących ogromne możliwości programów w języku C++.

Przegląd rozdziału

A kontener to typ danych, który zapewnia przechowywanie kolekcji nienazwanych obiektów (zwanych elementami). Zwykle używamy kontenerów, gdy musimy pracować z zestawem powiązanych wartości.

Liczba elementów w kontenerze jest często nazywana długość (lub czasami liczbą). W C++ termin rozmiar jest również powszechnie używany do określenia liczby elementów w kontenerze. W większości języków programowania (w tym C++) kontenery są jednorodne, co oznacza, że ​​elementy kontenera muszą być tego samego typu.

Klasa Biblioteka kontenerów jest częścią standardowej biblioteki C++, która zawiera różne typy klas, które implementują niektóre popularne typy kontenerów. Typ klasy implementujący kontener jest czasami nazywany klasą kontenera.

An array to typ danych kontenera, który przechowuje sekwencję wartości w sposób ciągły (co oznacza, że ​​każdy element jest umieszczony w sąsiedniej lokalizacji pamięci, bez przerw). Tablice umożliwiają szybki, bezpośredni dostęp do dowolnego elementu.

C++ zawiera trzy podstawowe typy tablic: tablice (w stylu C), std::vector klasę kontenera i std::array klasę kontenera.

std::vector jest jedną z klas kontenerów w standardowej bibliotece kontenerów C++, która implementuje tablicę. std::vector jest zdefiniowana w nagłówku <vector> jako szablon klasy z parametrem typu szablonu, który definiuje typ elementów. Zatem std::vector<int> deklaruje std::vector którego elementy są typu int.

Kontenery zazwyczaj mają specjalny konstruktor zwany konstruktorem listy który pozwala nam skonstruować instancję kontenera przy użyciu listy inicjującej. Użyj inicjalizacji listy z inicjalizującą listą wartości, aby skonstruować kontener z wartościami tych elementów.

W C++ najczęstszym sposobem uzyskiwania dostępu do elementów tablicy jest użycie nazwy tablicy wraz z operatorem indeksu dolnego (operator[]). Aby wybrać konkretny element, w nawiasach kwadratowych operatora indeksu dolnego podajemy wartość całkowitą, która określa, który element chcemy wybrać. Ta wartość całkowa nazywana jest indeksem dolnym (lub nieformalnie indeksem). Dostęp do pierwszego elementu uzyskuje się przy użyciu indeksu 0, drugi przy użyciu indeksu 1 itd. Ponieważ indeksowanie zaczyna się od 0, a nie od 1, mówimy, że tablice w C++ są liczą się zerami.

operator[] nie wykonują żadnego sprawdzania granic, co oznacza, że nie sprawdzają, czy indeks mieści się w granicach od 0 do N-1 (włącznie). Przekazanie nieprawidłowego indeksu do operator[] spowoduje niezdefiniowane zachowanie.

Tablice to jeden z niewielu typów kontenerów, które umożliwiają losowy dostęp, co oznacza, że do każdego elementu w kontenerze można uzyskać bezpośredni dostęp z taką samą szybkością, niezależnie od liczby elementów w kontenerze.

Podczas konstruowania obiektu typu klasy wybierany jest pasujący konstruktor listowy zamiast innych pasujące konstruktory. Konstruując kontener (lub dowolny typ posiadający konstruktor listowy) z inicjatorami, które nie są wartościami elementów, użyj bezpośredniej inicjalizacji.

std::vector v1 { 5 }; // defines a 1 element vector containing value `5`.
std::vector v2 ( 5 ); // defines a 5 element vector where elements are value-initialized.

std::vector można ustawić jako const, ale nie constexpr.

Każda ze standardowych klas kontenerów bibliotek definiuje zagnieżdżony element typedef o nazwie size_type (czasami zapisywane jako T::size_type), który jest aliasem typu używanego dla długości (i indeksów, jeśli są obsługiwane) kontenera. size_type jest prawie zawsze aliasem std::size_t, ale można go zastąpić (w rzadkich przypadkach), aby użyć innego typu. Możemy rozsądnie założyć size_type jest pseudonimem dla std::size_t.

Podczas uzyskiwania dostępu do size_type członkiem klasy kontenera, musimy zakwalifikować go zakresem za pomocą w pełni szablonowej nazwy klasy kontenera. Na przykład, std::vector<int>::size_type.

Możemy zapytać obiekt klasy kontenera o jego długość za pomocą metody size() funkcja członkowska, która zwraca długość bez znaku size_type. W C++ 17 możemy również użyć std::size() funkcja niebędąca członkiem.

W C++20, std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwykle std::ptrdiff_t, który jest typem zwykle używanym jako podpisany odpowiednik std::size_t).

Dostęp do elementów tablicy za pomocą metody at() funkcja członkowska sprawdza granice środowiska wykonawczego (i zgłasza wyjątek typu std::out_of_range jeśli granice są poza zakresem). Jeśli wyjątek nie zostanie przechwycony, aplikacja zostanie zakończona.

Oba operator[] i at() Funkcja członkowska obsługuje indeksowanie za pomocą indeksów innych niż stałe. Jednak obaj oczekują, że indeks będzie typu size_type, który jest typem całkowitym bez znaku. Powoduje to problemy z konwersją znaków, gdy indeksy nie są constexpr.

Obiekt typu std::vector można przekazać do funkcji, tak jak każdy inny obiekt. Oznacza to, że jeśli miniemy a std::vector według wartości zostanie wykonana kosztowna kopia. Dlatego zazwyczaj przechodzimy std::vector przez (const) odniesienie, aby uniknąć takich kopii.

Możemy użyć szablonu funkcji, aby móc przekazać a std::vector z dowolnym typem elementu w funkcję. Możesz użyć assert() aby upewnić się, że przekazany wektor ma prawidłową długość.

Termin kopiuj semantykę odnosi się do zasad określających sposób tworzenia kopii obiektów. Kiedy mówimy, że wywoływana jest semantyka kopiowania, oznacza to, że zrobiliśmy coś, co utworzy kopię obiektu.

Kiedy własność danych zostaje przeniesiona z jednego obiektu na drugi, mówimy, że dane zostały przeniesione wzruszony.

Przesuń semantykę odnosi się do reguł określających sposób przenoszenia danych z jednego obiektu do innego obiektu. Po wywołaniu semantyki przenoszenia każdy element danych, który można przenieść, zostanie przeniesiony, a każdy element danych, którego nie można przenieść, zostanie skopiowany. Możliwość przenoszenia danych zamiast ich kopiowania może sprawić, że semantyka przenoszenia będzie bardziej wydajna niż semantyka kopiowania, zwłaszcza gdy możemy zastąpić kosztowną kopię niedrogim przeniesieniem.

Zwykle, gdy obiekt jest inicjowany lub przypisany do obiektu tego samego typu, zostanie zastosowana semantyka kopiowania (zakładając, że kopia nie zostanie pominięta). Zamiast tego semantyka przenoszenia zostanie automatycznie użyta, jeśli typ obiektu obsługuje semantykę przenoszenia, a inicjator lub obiekt, z którego przypisywany jest obiekt, jest wartością.

Możemy zwracać typy umożliwiające przenoszenie (np std::vector i std::string) według wartości. Takie typy niedrogo przeniosą swoje wartości, zamiast tworzyć kosztowną kopię.

Dostęp do każdego elementu kontenera w określonej kolejności nazywany jest przechodzeniem lub przechodzeniem pojemnik. Czasami nazywane jest także przemierzaniem iteracją po lub iteracją po kontenerze.

Pętle są często używane do przechodzenia przez tablicę, a zmienna pętli jest używana jako indeks. Uważaj na błędy typu „off-by-one”, gdy ciało pętli wykonuje się o jeden raz za dużo lub o jeden za mało.

A pętla for oparta na zakresie (czasami nazywany także a dla każdej pętli) umożliwia przechodzenie przez kontener bez konieczności wykonywania jawnego indeksowania. Preferuj pętle for oparte na zakresie zamiast zwykłych pętli for podczas przechodzenia przez kontenery.

Użyj odliczenia typu (auto) z pętlami for opartymi na zakresie, aby kompilator wydedukował typ elementu tablicy. Deklaracja elementu powinna używać odniesienia (const) zawsze, gdy normalnie przekazujesz ten typ elementu przez odwołanie (const). Rozważ użycie const auto& , chyba że musisz pracować z kopiami. Dzięki temu kopie nie zostaną utworzone, nawet jeśli typ elementu zostanie później zmieniony.

Wyliczenia bez zakresu mogą być używane jako indeksy i pomagają w dostarczaniu wszelkich informacji o znaczeniu indeksu.

Dodanie dodatkowego modułu wyliczającego „count” jest przydatne, gdy potrzebujemy modułu wyliczającego reprezentującego długość tablicy. Można stwierdzić lub static_assert, że długość tablicy jest równa licznikowi count, aby zapewnić inicjalizację tablicy z oczekiwaną liczbą inicjatorów.

Tablice, w których długość tablicy musi być zdefiniowana w momencie utworzenia instancji i nie można jej zmienić, nazywane są tablicami o stałym rozmiarze lub tablicami o stałej długości. A dynamicznymi array (zwana także resizable array) to tablica, której rozmiar można zmienić po utworzeniu instancji. Ta możliwość zmiany rozmiaru sprawia, że std::vector jest wyjątkowa.

A std::vector można zmienić rozmiar po utworzeniu instancji, wywołując resize() funkcję składową z nową żądaną długością.

W kontekście instrukcji std::vector, pojemność to liczba elementów, dla których std::vector przydzielono pamięć, i długość to liczba aktualnie używanych elementów. Możemy zapytać std::vector o jego pojemność poprzez capacity() funkcja składowa.

Kiedy std::vector zmienia ilość zarządzanej pamięci, proces ten nazywa się realokacją. Ponieważ ponowna alokacja zazwyczaj wymaga skopiowania każdego elementu tablicy, ponowna alokacja jest kosztownym procesem. W rezultacie chcemy uniknąć realokacji w jak największym stopniu.

Poprawne indeksy dla operatora indeksu dolnego (operator[]) i at() funkcja składowa opiera się na długości wektora, a nie na jego pojemności.

std::vector posiada funkcję składową zwaną shrink_to_fit() która żąda, aby wektor zmniejszył swoją pojemność w celu dopasowania do swojej długości. To żądanie nie jest wiążące.

Kolejność w którym elementy są dodawane i usuwane ze stosu, można opisać jako ostatni na wejściu, pierwszy na wyjściu (LIFO). Ostatnia płyta dodana do stosu będzie pierwszą płytą, która zostanie usunięta. W programowaniu stos jest typem danych kontenera, w którym wstawianie i usuwanie elementów odbywa się w sposób LIFO o nazwie push i pop.

Klasa std::vector funkcjami składowymi push_back() i emplace_back() zwiększy długość std::vector i spowoduje ponowną alokację, jeśli pojemność nie jest wystarczająca do wstawienia wartości. Gdy naciśnięcie wyzwala ponowną alokację, std::vector zazwyczaj przydziela pewną dodatkową pojemność, aby umożliwić dodanie dodatkowych elementów bez wyzwalania kolejnej ponownej alokacji przy następnym uruchomieniu elementu. dodano.

Klasa resize() Funkcja składowa zmienia długość wektora i pojemność (jeśli to konieczne).
Klasa reserve() Funkcja składowa zmienia tylko pojemność (jeśli to konieczne)

Aby zwiększyć liczbę elementów w std::vector:

  • Użyj resize() podczas uzyskiwania dostępu do wektora poprzez indeksowanie. Zmienia to długość wektora, dzięki czemu indeksy będą ważne.
  • Użyj reserve() podczas uzyskiwania dostępu do wektora za pomocą stosu. Operacje te zwiększają pojemność bez zmiany długości wektora.

Oba push_back() i emplace_back() wpychają element na stos. Jeśli obiekt, który ma zostać wypchnięty, push_back() i emplace_back() są równoważne. Jednakże w przypadku, gdy tworzymy obiekt tymczasowy w celu umieszczenia go na wektorze, emplace_back() może być bardziej efektywne kiedy potrzebujesz dostępu do jawnego konstruktora, w przeciwnym razie wybierz push_back().

Istnieje specjalna implementacja dla std::vector<bool> który może oszczędzać miejsce w przypadku wartości logicznych poprzez podobne kompaktowanie 8 wartości logicznych w bajt.

std::vector<bool> nie jest wektorem (nie musi być ciągły w pamięci), ani nie przechowuje bool wartości (przechowuje zbiór bitów), ani nie spełnia definicji kontenera C++. Chociaż std::vector<bool> zachowuje się jak wektor w większości przypadków, nie jest w pełni kompatybilny z resztą standardowej biblioteki. Kod, który działa z innymi typami elementów, może nie działać z std::vector<bool>. W rezultacie należy ogólnie unikać std::vector<bool> .

Czas quizu

Pytanie nr 1

Napisz definicje poniższych. Jeśli to możliwe, używaj CTAD (13.14 -- Przewodniki dedukcji i dedukcji szablonu klasy (CTAD)).

a) A std::vector zainicjowana pierwszymi 6 liczbami parzystymi.

Pokaż rozwiązanie

b) Stała std::vector zainicjowana wartościami 1.2, 3.4, 5.6, I 7.8.

Pokaż rozwiązanie

c) Stała std::vector z std::string_view zainicjowana nazwami „Alex”, „Brad”, „Charles” i „Dave”.

Pokaż rozwiązanie

d) A std::vector z wartością pojedynczego elementu 12.

Pokaż rozwiązanie

e) A std::vector z 12 elementami int, zainicjowanymi do wartości domyślnych.

Pokaż wskazówkę

Pokaż rozwiązanie

Pytanie nr 2

Załóżmy, że piszesz grę, w której gracz może przechowywać 3 rodzaje przedmiotów: mikstury zdrowia, pochodnie i strzały.

> Krok #1

Zdefiniuj wyliczenie bez zakresu w przestrzeni nazw, aby zidentyfikować różne typy przedmiotów. Zdefiniuj std::vector do przechowywania liczby każdego typu przedmiotu, który nosi gracz. Gracz powinien zacząć z 1 miksturą zdrowia, 5 pochodniami i 10 strzałami. Assert, aby upewnić się, że tablica ma poprawną liczbę inicjatorów.

Wskazówka: Zdefiniuj moduł wyliczający licznik i użyj go wewnątrz potwierdzenia.

Program powinien wypisać co następuje:

You have 16 total items

Pokaż rozwiązanie

> Krok #2

Zmodyfikuj program z poprzedniego kroku, aby teraz wyświetlał:

You have 1 health potion
You have 5 torches
You have 10 arrows
You have 16 total items

Użyj pętli, aby wydrukować liczbę elementów i nazwy elementów dla każdego elementu zapasów. Obsługuj prawidłową liczbę mnogą nazw.

Pokaż rozwiązanie

Pytanie nr 3

Napisz funkcję, która przyjmuje a std::vector i zwraca a std::pair zawierające indeksy elementów z wartościami min i max w tablicy. Dokumentację std::pair można znaleźć tutaj. Wywołaj funkcję na dwóch wektorach:

    std::vector v1 { 3, 8, 2, 5, 7, 8, 3 };
    std::vector v2 { 5.5, 2.7, 3.3, 7.6, 1.2, 8.8, 6.6 };

Program powinien wypisać co następuje:

With array ( 3, 8, 2, 5, 7, 8, 3 ):
The min element has index 2 and value 2
The max element has index 1 and value 8

With array ( 5.5, 2.7, 3.3, 7.6, 1.2, 8.8, 6.6 ):
The min element has index 4 and value 1.2
The max element has index 5 and value 8.8

Pokaż rozwiązanie

Pytanie nr 4

Zmodyfikuj poprzedni program tak, aby użytkownik mógł wprowadzić dowolną liczbę liczb całkowitych. Przestań akceptować dane wejściowe, gdy użytkownik wprowadzi -1.

Wydrukuj wektor i znajdź elementy min i max.

Po uruchomieniu z danymi wejściowymi 3 8 5 2 3 7 -1 program powinien wygenerować następujące dane wyjściowe:

Enter numbers to add (use -1 to stop): 3 8 5 2 3 7 -1
With array ( 3, 8, 5, 2, 3, 7 ):
The min element has index 3 and value 2
The max element has index 1 and value 8

Zrób coś rozsądnego, gdy użytkownik wprowadzi -1 jako pierwsze wejście.

Pokaż rozwiązanie

Pytanie #5

Zaimplementujmy grę C++man (która będzie to nasza wersja klasycznej gry w lincz dla dzieci Wisielec.

Jeśli nigdy wcześniej w nią nie grałeś, oto skrócone zasady:

Wysoki poziom:

  • Komputer wybierze losowo słowo i narysuje podkreślenie każdej litery w słowie.
  • Gracz wygrywa, jeśli odgadnie wszystkie litery w słowie słowo przed dokonaniem błędnego odgadnięcia X (gdzie X jest konfigurowalne).

W każdej turze:

  • Gracz odgadnie pojedynczą literę.
  • Jeśli gracz już odgadł tę literę, nie liczy się ona i gra toczy się dalej.
  • Jeśli którykolwiek z podkreśleń reprezentuje tę literę, te podkreślenia są zastępowane tą literą i gra kontynuuje.
  • Jeśli tej litery nie reprezentują żadne podkreślenia, gracz wykorzystuje jedną ze swoich błędnych odpowiedzi.

Stan:

  • Gracz powinien wiedzieć, ile pozostało mu błędnych odpowiedzi.
  • Gracz powinien wiedzieć, jakie litery odgadł błędnie (w kolejności alfabetycznej).

Ponieważ jest to C++man, użyjemy + symbolu, aby wskazać liczbę pozostałych błędnych odpowiedzi. Jeśli skończą Ci się + symbole, przegrywasz.

Oto przykładowy wynik ukończonej gry:

Welcome to C++man (a variant of Hangman)
To win: guess the word.  To lose: run out of pluses.

The word: ________   Wrong guesses: ++++++
Enter your next letter: a
No, 'a' is not in the word!

The word: ________   Wrong guesses: +++++a
Enter your next letter: b
Yes, 'b' is in the word!

The word: b_______   Wrong guesses: +++++a
Enter your next letter: c
Yes, 'c' is in the word!

The word: b__cc___   Wrong guesses: +++++a
Enter your next letter: d
No, 'd' is not in the word!

The word: b__cc___   Wrong guesses: ++++ad
Enter your next letter: %
That wasn't a valid input.  Try again.

The word: b__cc___   Wrong guesses: ++++ad
Enter your next letter: d
You already guessed that.  Try again.

The word: b__cc___   Wrong guesses: ++++ad
Enter your next letter: e
No, 'e' is not in the word!

The word: b__cc___   Wrong guesses: +++ade
Enter your next letter: f
No, 'f' is not in the word!

The word: b__cc___   Wrong guesses: ++adef
Enter your next letter: g
No, 'g' is not in the word!

The word: b__cc___   Wrong guesses: +adefg
Enter your next letter: h
No, 'h' is not in the word!

The word: b__cc___   Wrong guesses: adefgh
You lost!  The word was: broccoli

> Krok #1

Cele:

Zadania:

  • Najpierw zdefiniuj przestrzeń nazw o nazwie WordList. Startowa lista słów to: „tajemnica”, „brokuły”, „konto”, „prawie”, „spaghetti”, „opinia”, „piękna”, „odległość”, „bagaż”. Jeśli chcesz, możesz dodać inne.
  • Napisz funkcję wybierającą losowe słowo i wyświetlającą wybrane słowo. Uruchom program kilka razy, aby upewnić się, że słowo jest losowe.

Oto przykładowy wynik tego kroku:

Welcome to C++man (a variant of Hangman)
To win: guess the word.  To lose: run out of pluses.

The word is: broccoli

Pokaż rozwiązanie

> Krok #2

Gdy tworzymy złożone programy, chcemy pracować stopniowo, dodając jedną lub dwie rzeczy na raz, a następnie upewniając się, że działają. Co ma sens dodać dalej?

Cele:

  • Umieć narysować podstawowy stan gry, pokazując słowo jako podkreślenie.
  • Zaakceptować wprowadzony tekst przez użytkownika, z podstawową walidacją błędów.

Na tym etapie nie będziemy jeszcze śledzić, jakie litery wprowadził użytkownik.

Oto przykładowy wynik tego wyniku krok:

Welcome to C++man (a variant of Hangman)
To win: guess the word.  To lose: run out of pluses.

The word: ________
Enter your next letter: %
That wasn't a valid input.  Try again.
Enter your next letter: a
You entered: a

Zadania:

  • Utwórz klasę o nazwie Session , która będzie używana do przechowywania wszystkich danych, których gra potrzebuje do zarządzania w sesji gry. Na razie musimy tylko wiedzieć, jakie jest losowe słowo.
  • Utwórz funkcję wyświetlającą podstawowy stan gry, w której słowo jest wyświetlane w postaci podkreślenia.
  • Utwórz funkcję akceptowającą list wejściowy od użytkownika. Przeprowadź podstawową weryfikację danych wejściowych, aby odfiltrować dane inne niż litery lub obce.

Pokaż rozwiązanie

> Krok #3

Teraz, gdy możemy wyświetlić stan gry i uzyskać informacje od użytkownika, zintegrujmy dane wejściowe użytkownika z grą.

Cele:

  • Śledź, które litery odgadł użytkownik.
  • Wyświetlaj poprawnie odgadnięte litery.
  • Zaimplementuj podstawowa pętla gry.

Zadania:

  • Zaktualizuj klasę Session, aby śledzić, które litery zostały do tej pory odgadnięte.
  • Zmodyfikuj funkcję stanu gry, aby wyświetlała zarówno podkreślenia, jak i poprawnie odgadnięte litery.
  • Zaktualizuj procedurę wejściową, aby odrzucać już odgadnięte litery.
  • Napisz pętlę, która wykona się 6 razy przed zakończeniem (tak możemy przetestować powyższe).

W tym kroku nie powiemy użytkownikowi, czy odgadnięta litera znajduje się w słowie (ale pokażemy to w ramach wyświetlania stanu gry).

Trudną częścią tego kroku jest podjęcie decyzji, w jaki sposób przechowywać informacje o tym, które litery odgadł użytkownik. Można to zrobić na kilka różnych, realnych sposobów. Wskazówka: liczba liter jest stała i będziesz to robić często.

Pokaż wskazówkę

Pokaż wskazówkę

Pokaż wskazówkę

Oto przykładowy wynik tego kroku:

Welcome to C++man (a variant of Hangman)
To win: guess the word.  To lose: run out of pluses.

The word: ________
Enter your next letter: a

The word: ____a___
Enter your next letter: a
You already guessed that.  Try again.
Enter your next letter: b

The word: ____a___
Enter your next letter: c

The word: ____a___
Enter your next letter: d

The word: d___a___
Enter your next letter: e

The word: d___a__e
Enter your next letter: f

The word: d___a__e
Enter your next letter: g

Pokaż rozwiązanie

> Krok 4

Cel: Zakończ grę.

Zadania:

  • Dodaj do wyświetlenia całkowitą liczbę pozostałych błędnych odpowiedzi
  • Dodaj do wyświetlania błędnych liter zgadłem
  • Dodaj warunek wygranej/przegranej i tekst wygranej/przegranej.

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:  
299 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze