Przegląd rozdziału
Specyficzna sekwencja instrukcji, które procesor wykonuje w programie, nazywana jest ścieżką wykonania programu. A program liniowy za każdym razem podąża tą samą ścieżką czasie jego uruchomienia.
Instrukcje sterowania przepływem (tzw Instrukcje sterowania przepływem) umożliwiają programiście zmianę normalnej ścieżki wykonania. Kiedy instrukcja przepływu sterowania powoduje, że program rozpoczyna wykonywanie jakiejś niesekwencyjnej sekwencji instrukcji, nazywa się to odgałęzieniem.
A instrukcją warunkową jest instrukcją określającą, czy niektóre powiązane instrukcje powinny zostać wykonane, czy nie.
Instrukcje if pozwalają nam wykonać powiązaną instrukcję w zależności od tego, czy jakiś warunek jest true. Else instrukcje wykonaj, jeśli powiązany warunek to false. Możesz połączyć ze sobą wiele instrukcji if i else.
A zawieszenie else występuje, gdy jest niejednoznaczne, z którym if statement połączone jest else statement . Dangling else instrukcje są dopasowywane do ostatniej niedopasowanej if statement w tym samym bloku. W ten sposób w trywialny sposób unikamy dangling else instrukcji, upewniając się, że treść if statement jest umieszczona w bloku.
A instrukcja null jest instrukcją składającą się tylko ze średnika. Nie robi nic i jest używane, gdy język wymaga istnienia instrukcji, ale programista nie potrzebuje tej instrukcji do niczego.
Instrukcje Switch zapewniają czystszą i szybszą metodę wybierania pomiędzy wieloma pasującymi elementami. Instrukcje switch działają tylko z typami całkowitymi. Etykiety przypadków są używane do identyfikowania wartości, które mają spełniać oceniany warunek. Instrukcje znajdujące się pod domyślną etykietą są wykonywane, jeśli nie można znaleźć pasującej etykiety case.
Gdy wykonanie przechodzi z instrukcji znajdującej się pod etykietą do instrukcji znajdujących się pod kolejną etykietą, nazywa się to przejściem. A break statement (lub return statement), które można zastosować, aby zapobiec błędom. Atrybutu [[fallthrough]] można użyć do udokumentowania celowego błędu.
Instrukcje Goto pozwalają programowi na przeskoczenie do innego miejsca w kodzie, do przodu lub do tyłu. Generalnie należy ich unikać, ponieważ mogą utworzyć kod spaghetti, co ma miejsce, gdy ścieżka wykonania programu przypomina miskę spaghetti.
Pętle while pozwalają programowi na wykonywanie pętli tak długo, jak dany warunek ma wartość true. Warunek jest oceniany przed wykonaniem pętli.
An nieskończona pętla to pętla, której warunek zawsze daje wartość true. Pętle te będą działać w nieskończoność, chyba że do ich zatrzymania zostanie użyta inna instrukcja przepływu sterowania.
A zmienna pętli (zwana także licznik) jest zmienną całkowitą używaną do zliczania liczby wykonań pętli. Każde wykonanie pętli nazywane jest iteracją.
Do while pętle są podobne do pętli while, ale warunek jest oceniany po wykonaniu pętli, a nie przed.
For pętle są najczęściej używaną pętlą i idealnie nadają się, gdy trzeba wykonać pętlę określoną liczbę razy. Błąd off-by-one występuje, gdy pętla wykonuje o jedną iterację o jeden za dużo lub o jeden za mało.
Instrukcje break pozwalają nam wyjść z przełącznika, while, do while lub pętli for (również range-based for loops, o czym jeszcze nie mówiliśmy). Kontynuuj instrukcje pozwalają nam natychmiast przejść do następnej iteracji pętli.
Zatrzymania pozwalają nam zakończyć nasz program. Normalne zakończenie oznacza, że program zakończył działanie w oczekiwany sposób (a status code wskaże, czy się udało, czy nie). std::exit() jest wywoływana automatycznie na końcu main lub można go wywołać jawnie w celu zakończenia programu. Wykonuje pewne porządki, ale nie czyści żadnych zmiennych lokalnych ani nie rozwija stosu wywołań.
Nieprawidłowe zakończenie występuje, gdy program napotkał jakiś nieoczekiwany błąd i musiał zostać zamknięty. std::abort można wywołać w przypadku nieprawidłowego zakończenia.
An algorytm to skończona sekwencja instrukcji, których można użyć, aby rozwiązać jakiś problem lub dać jakiś użyteczny wynik. Algorytm uważa się za stanowy jeśli zachowuje pewne informacje podczas połączeń. I odwrotnie, algorytm bezstanowy nie przechowuje żadnych informacji (i podczas wywoływania musi otrzymać wszystkie informacje potrzebne do pracy). W zastosowaniu do algorytmów termin stan odnosi się do bieżących wartości przechowywanych w zmiennych stanowych.
Algorytm uważa się za deterministyczny jeżeli dla danego wejścia (wartość przewidziana dla start) zawsze będzie generował ten sam wynik sekwencja.
A Generator liczb pseudolosowych (PRNG) to algorytm generujący ciąg liczb, którego właściwości symulują ciąg liczb losowych. Kiedy tworzona jest instancja PRNG, można podać wartość początkową (lub zestaw wartości) zwaną losowym ziarnem (lub nasionem w skrócie), aby zainicjować stan PRNG. Kiedy PRNG zostało zainicjowane za pomocą materiału siewnego, mówimy, że zostało ono zadane. Rozmiar wartości początkowej może być mniejszy niż rozmiar stanu PRNG. Kiedy tak się dzieje, mówimy, że PRNG zostało zasiane. Długość sekwencji, zanim PRNG zacznie się powtarzać, nazywana jest okresem.
A losowym rozkładem liczb przekształcającym wynik PRNG na inny rozkład liczb. A równomierny rozkład to losowy rozkład liczb, który daje wyniki pomiędzy dwiema liczbami X i Y (włącznie) z równym prawdopodobieństwem.
Czas quizu
Ostrzeżenie: od tego momentu quizy stają się coraz trudniejsze, ale możesz to zrobić. Rozkręćmy te quizy!
Pytanie nr 1
W lekcji 4.x — Podsumowanie rozdziału 4 i quiz, napisaliśmy program symulujący spadanie piłki z wieży. Ponieważ nie mieliśmy jeszcze pętli, piłka mogła spadać tylko przez 5 sekund.
Weź poniższy program i zmodyfikuj go tak, aby piłka spadała przez tyle sekund, ile potrzeba, aż dotknie ziemi. Zaktualizuj program, aby wykorzystywał wszystkie omówione najlepsze praktyki (przestrzenie nazw, constexpr itp.).
#include <iostream>
// Gets tower height from user and returns it
double getTowerHeight()
{
std::cout << "Enter the height of the tower in meters: ";
double towerHeight{};
std::cin >> towerHeight;
return towerHeight;
}
// Returns the current ball height after "seconds" seconds
double calculateBallHeight(double towerHeight, int seconds)
{
const double gravity { 9.8 };
// Using formula: s = (u * t) + (a * t^2) / 2
// here u (initial velocity) = 0, so (u * t) = 0
const double fallDistance { gravity * (seconds * seconds) / 2.0 };
const double ballHeight { towerHeight - fallDistance };
// If the ball would be under the ground, place it on the ground
if (ballHeight < 0.0)
return 0.0;
return ballHeight;
}
// Prints ball height above ground
void printBallHeight(double ballHeight, int seconds)
{
if (ballHeight > 0.0)
std::cout << "At " << seconds << " seconds, the ball is at height: " << ballHeight << " meters\n";
else
std::cout << "At " << seconds << " seconds, the ball is on the ground.\n";
}
// Calculates the current ball height and then prints it
// This is a helper function to make it easier to do this
void calculateAndPrintBallHeight(double towerHeight, int seconds)
{
const double ballHeight{ calculateBallHeight(towerHeight, seconds) };
printBallHeight(ballHeight, seconds);
}
int main()
{
const double towerHeight{ getTowerHeight() };
calculateAndPrintBallHeight(towerHeight, 0);
calculateAndPrintBallHeight(towerHeight, 1);
calculateAndPrintBallHeight(towerHeight, 2);
calculateAndPrintBallHeight(towerHeight, 3);
calculateAndPrintBallHeight(towerHeight, 4);
calculateAndPrintBallHeight(towerHeight, 5);
return 0;
}Pytanie nr 2
Liczba pierwsza to liczba naturalna większa niż 1, która dzieli się równomiernie (bez reszty) tylko przez 1 i przez samą siebie.
Ukończ następujący program, pisząc funkcję isPrime() za pomocą pętli for. Jeśli operacja się powiedzie, program wyświetli komunikat „Success!”.
// Make sure that assert triggers even if we compile in release mode
#undef NDEBUG
#include <cassert> // for assert
#include <iostream>
bool isPrime(int x)
{
return false;
// write this function using a for loop
}
int main()
{
assert(!isPrime(0)); // terminate program if isPrime(0) is true
assert(!isPrime(1));
assert(isPrime(2)); // terminate program if isPrime(2) is false
assert(isPrime(3));
assert(!isPrime(4));
assert(isPrime(5));
assert(isPrime(7));
assert(!isPrime(9));
assert(isPrime(11));
assert(isPrime(13));
assert(!isPrime(15));
assert(!isPrime(16));
assert(isPrime(17));
assert(isPrime(19));
assert(isPrime(97));
assert(!isPrime(99));
assert(isPrime(13417));
std::cout << "Success!\n";
return 0;
}Powiązana treść
assert to makro preprocesora, które kończy działanie programu, jeśli powiązany argument ma wartość false. Kiedy więc piszemy assert(!isPrime(0)), mamy na myśli „jeśli isPrime(0) ma wartość true, zakończ program”. Bardziej szczegółowo omówimy asercję w lekcji 9.6 — Assert i static_assert.
Dodatkowe punkty:
Pętla for w powyższym rozwiązaniu jest nieoptymalna z dwóch powodów:
- Sprawdza parzyste dzielniki. Nie musimy ich testować (z wyjątkiem 2).
- Sprawdza każdą liczbę aż do
x, aby sprawdzić, czy jest dzielnikiem. Liczba inna niż pierwsza (liczba złożona) musi mieć co najmniej jeden dzielnik mniejszy lub równy pierwiastkowi kwadratowemu, więc sprawdzanie dzielników poza pierwiastkiem kwadratowym zxjest niepotrzebne.std::sqrt(x)(w nagłówku <cmath>) zwraca pierwiastek kwadratowy zx.
W tym drugim przypadku mamy dwie możliwości: oblicz std::sqrt(x) przed pętlą, a następnie przetestuj zmienną pętli względem tego wartość. Alternatywnie możemy zoptymalizować std::sqrt(x) całkowicie wyłączyć z porównania poprzez podniesienie do kwadratu obu stron porównania (h/t do czytelnika JJag za zasugerowanie tego) (zobacz wskazówkę, jeśli potrzebujesz w tym dodatkowej pomocy). Tę drugą opcję wykorzystamy w rozwiązaniu quizu.
Zaktualizowaniem powyższego rozwiązania w celu wdrożenia obu optymalizacji.
Pytanie nr 3
Zaimplementuj grę Hi-Lo. Najpierw Twój program powinien wybrać losową liczbę całkowitą z zakresu od 1 do 100. Użytkownik otrzymuje 7 prób odgadnięcia liczby.
Jeśli użytkownik nie odgadnie prawidłowej liczby, program powinien mu powiedzieć, czy zgadł za dużo, czy za mało. Jeśli użytkownik odgadnie właściwą liczbę, program powinien poinformować go o wygranej. Jeśli skończą im się domysły, program powinien poinformować ich, że przegrali i podać prawidłową liczbę. Na koniec gry użytkownik powinien zostać zapytany, czy chce zagrać jeszcze raz. Jeśli użytkownik nie wprowadzi „y” lub „n”, zapytaj go ponownie.
Na potrzeby tego quizu załóżmy, że użytkownik wprowadził prawidłową liczbę.
Użyj nagłówka Random.h z 8.15 -- Globalne liczby losowe (Random.h).
Oto, jak powinny wyglądać Twoje dane wyjściowe:
Let's play a game. I'm thinking of a number between 1 and 100. You have 7 tries to guess what it is. Guess #1: 64 Your guess is too high. Guess #2: 32 Your guess is too low. Guess #3: 54 Your guess is too high. Guess #4: 51 Correct! You win! Would you like to play again (y/n)? y Let's play a game. I'm thinking of a number between 1 and 100. You have 7 tries to guess what it is. Guess #1: 64 Your guess is too high. Guess #2: 32 Your guess is too low. Guess #3: 54 Your guess is too high. Guess #4: 51 Your guess is too high. Guess #5: 36 Your guess is too low. Guess #6: 45 Your guess is too low. Guess #7: 48 Your guess is too low. Sorry, you lose. The correct number was 49. Would you like to play again (y/n)? q Would you like to play again (y/n)? n Thank you for playing.
Aby uzyskać dodatkowe punkty: ustaw wartości minimalne i maksymalne oraz liczbę prób do skonfigurowania parametr.
Dodamy obsługę błędów do tego rozwiązania w lekcji 9.x — Podsumowanie rozdziału 9 i quiz.

