Wprowadzenie do Warunków
Dotychczas tworzyliśmy programy, które za każdym razem działały tak samo. Nasze kreacje nie potrafiły podejmować decyzji. Nadszedł czas, aby to zmienić. W tej lekcji nauczymy się jak sprawić, by programy szły innymi ścieżkami, zależnie od tego, co wpisze użytkownik.
Stworzymy aplikację, która sprawdzi, czy użytkownik może legalnie uzyskać prawo jazdy. 😎
Przygotowanie kodu
Najpierw zapytajmy użytkownika o jego rok urodzenia.
#include <iostream>
int main()
{
std::cout << "Welcome to the Driver's License Oracle 2000\n";
std::cout << "Please enter your year of birth: ";
int year_of_birth;
std::cin >> year_of_birth;
// W momencie pisania tej lekcji mamy 2022 rok
int age = 2022 - year_of_birth;
// Przypadek A:
std::cout << "You can legally get a driver's license\n";
// Przypadek B:
std::cout << "You cannot legally get a driver's license\n";
}
Zaznaczyłem tutaj dwa kawałki kodu - jeden, który powinien się wykonywać, jeśli użytkownik może legalnie dostać prawo jazdy, oraz drug, kiedy nie może. Chcem, aby tylko jeden przypadek się wykonał, nie oba. Jak możemy to zrealizować? Najpierw powinniśmy zastanowić się jakie mamy "przypadki" i pod jakim warunkiem powinny się wykonać.
- Przypadek A — Legalny — Powinien się wykonywać tylko, jeśli
age
jest większy lub równy18
- Przypadek B — Nielegalny — Powinien się wykonywać tylko, jeśli
age
jest mniejszy od18
Teraz zobaczmy jak możemy przełożyć te przypadki na kod.
Instrukcja warunkowa
Najprostszą formą warunku w C++ jest instrukcja if
, której podstawowa forma wygląda następująco:
if (/* warunek logiczny */)
{
// Kod w środku wykonuje się tylko wtedy, gdy warunek jest prawdziwy
}
// Kod poza wykonywany jest niezależnie od tego, czy warunek jest prawdziwy czy fałszywy
Aby użyć instrukcji if
, potrzebujemy dwóch rzeczy:
- Warunek, który po obliczeniu daje wartość logiczną typu
bool
-true
/false
- Kod, który wykona się, jeśli warunek będzie prawdziwy (wartość
true
)
Po słowie kluczowym if
, umieszczamy warunek logiczny wewnątrz nawiasów.
Następnie, nawiasy, które otaczają #2 tworzą tak zwany blok kodu. Wszystko wewnątrz tego bloku kodu zostanie
wykonane wtedy i tylko wtedy, gdy warunek jest prawdziwy. Zmienne, które są tworzone w bloku kodu, żyją i umierają razem z tym blokiem - są niszczone, gdy blok się kończy (blok kończy się na nawiasie zamykającym }
) i są niedostępne
spoza niego.
Zastosujmy to do naszego przykładu z prawem jazdy, łącząc instrukcję if z przypadkami, które wcześniej ustaliliśmy:
#include <iostream>
int main()
{
std::cout << "Welcome to the Driver's License Oracle 2000\n";
std::cout << "Please enter your year of birth: ";
int year_of_birth;
std::cin >> year_of_birth;
// W momencie pisania tej lekcji mamy 2022 rok
int age = 2022 - year_of_birth;
// Przypadek A:
if (/* zmienna age jest większa lub równa 18 */)
{
std::cout << "You can legally get a driver's license\n";
}
// Przypadek B B:
if (/* zmienna age jest mniejsza od 18 */)
{
std::cout << "You cannot legally get a driver's license\n";
}
}
Zauważ, że mamy tu dwie instrukcje if
, po jednym dla każdego przypadku.
Pierwsza z nich obsługuje przypadek A, w którym użytkownik może legalnie uzyskać prawo jazdy.
Druga obsługuje nasz przypadek B, w którym nie może tego zrobić.
Po bliższej analizie, jedna rzecz powinnna szybko stać się oczywista - te przypadki są wzajemnie wykluczające się.
Oznacza to, że nie jest możliwe, aby oba przypadki były prawdziwe na raz; tylko jeden lub drugi może się wykonać.
W C++ mamy słowo kluczowe else
, które służy do definiowania wzajemnie wykluczających się warunków.
Podczas gdy powyższy kod będzie działał zgodnie z oczekiwaniami, możemy go ulepszyć w następujący sposób:
if (/* warunek logiczny */)
{
// Kod w środku wykonuje się tylko wtedy, gdy warunek jest prawdziwy
}
else
{
// Kod w środku wykonuje się tylko wtedy, gdy warunek jest fałszywy
}
// Kod poza wykonywany jest niezależnie od tego, czy warunek jest prawdziwy czy fałszywy
Blok else
jest opcjonalny i powinieneś go używać tylko wtedy, kiedy chcesz obsłużyć
wzajemnie wykluczący się warunki.
Teraz możemy wrócić do naszego przykładu i zobaczyć, że tylko jeden warunek jest konieczny, ponieważ jeśli jeden jest prawdziwy, to drugi musi być fałszywy.
#include <iostream>
int main()
{
std::cout << "Welcome to the Driver's License Oracle 2000\n";
std::cout << "Please enter your year of birth: ";
int year_of_birth;
std::cin >> year_of_birth;
// W momencie pisania tej lekcji mamy 2022 rok
int age = 2022 - year_of_birth;
if (/* zmienna age jest większa lub równa 18 */)
{
std::cout << "You can legally get a driver's license\n";
}
else
{
std::cout << "You cannot legally get a driver's license\n";
}
}
;
Nigdy nie dawaj średnika (';') po nawiasach instrukcji if.
Nie spowoduje to błędu kompilacji, ale sprawi to, że Twój kod
będzie działać niepoprawnie.
Blok z kodem, który powinienm się wykonywać tylko zależnie od warunku będzie się wykonywać zawsze!.
- ✔ Poprawnie
- ❌ Niepoprawnie
if (age >= 18)
// ...
else
// ...
if (age >= 18);
// ...
else;
// ...
Podsumowując, nauczyliśmy się jak rozdzielić warunkowo wykonywane przypadki w naszym kodzie na dedykowane instrukcje if. Dzięki temu możemy wybrać, jaki kod zostanie uruchomiony w zależności od różnych wartości w naszym programie. Teraz, gdy mamy już podstawową strukturę naszej instrukcji if-else, możemy zobaczyć jak zakończyć nasz program poprzez stworzenie warunku logicznego.
Wyrażenia logiczne
Wewnątrz nawiasu instrukcji if
znajduje się wyrażenie logiczne, czyli wyrażenie
które ocenia się na true
lub false
.
C++ posiada kilka operatorów, które pozwalają nam stworzyć takie wyrażenia.
Operatory logiczne
Poniżej znajduje się tabela przedstawiająca operatory logiczne dostępne w C++.
Operator logiczny (boolowski) zwraca wartość logiczną (boolowską) true
/false
zależną od operandów.
Są prawie takie same jak operatory matematyczne, które mogłeś zobaczyć w poprzednich lekcjach,
jednak zamiast ewaulować się do liczby (jak int
), ewaluują się do typu bool
(true
/false
).
Operator | Odpowiednik matematyczny | Opis |
---|---|---|
a == b | a = b | Czy a równa się b ? |
a != b | a ≠ b | Czy a nie równa się b ? |
a > b | a > b | Czy a jest większe od b ? |
a >= b | a ≥ b | Czy a jest większe lub równe b ? |
a < b | a < b | Czy a jest mniejsze od b? |
a <= b | a ≤ b | Czy a jest mniejsze lub równe b? |
Operatory równości
Pierwsze dwa wiersze naszej tabelkie ==
i !=
to operatory równości.
Te dwa operatory pozwalają nam sprawdzić, czy dwie wartości są takie same.
Operatory równości działają na większości typów C++, w tym na liczbach, napisach, boolach (typ bool
) i innych.
Pojedynczy znak =
różni się od podwójnego =
. Pojedynczy =
jest operatorem przypisania, np. gdy tworzysz zmienną lub zmieniasz jej wartośc.
Podwójny =
jest operatorem równości, używanym gdy chcesz sprawdzić czy dwie zmienne mają tę samą wartość.
W instrukcjach if
, jeśli chcemy sprawdzić czy dwie zmienne są takie same zawsze należy użyć ==
:
- ✔
if (a == b)
- ❌
if (a = b)
Operatory relacyjne
Operatory relacyjne działają na niektórych typach C++, takich jak liczby i ciągi znaków; jednakże, wiele innych typów, których jeszcze nie omówiliśmy, nie może być porównywanych w ten sposób.
Wiemy teraz wystarczająco dużo, aby pierwsza wersja naszego programu zaczęła działać. Dla przypomnienia,
musimy stworzyć warunek, który sprawdzi, czy wiek użytkownika jest większy niż
lub równy 18. Korzystając z powyższej tabeli, najbliższym dopasowaniem jest age >= 18
.
Zapiszmy więc ten warunek:
#include <iostream>
int main()
{
std::cout << "Welcome to the Driver's License Oracle 2000\n";
std::cout << "Please enter your year of birth: ";
int year_of_birth;
std::cin >> year_of_birth;
// W momencie pisania tej lekcji mamy 2022 rok
int age = 2022 - year_of_birth;
if (age >= 18)
{
std::cout << "You can legally get a driver's license\n";
}
else
{
std::cout << "You cannot legally get a driver's license\n";
}
}
Jako wyzwanie bonusowe, spróbuj utworzyć trzy inne warunki, równoważne do age >= 18
, używając każdego pozostałego operatora relacyjnego.
Złożone wyrażenia logiczne
Możesz przekształcać i łączyć wyrażenia logiczne za pomocą tak zwanych operatorów logicznych. Są one podzbiorem operatorów logicznych wprowadzonych powyżej, ale są przeznaczone do przyjmowania wartości logicznych jako dane wejściowe
C++ obsługuje dwie równoważne formy tych operatorów, wersję tekstową oraz wersję symboliczną. Chociaż symboliczna reprezentacja jest bardziej powszechna, reprezentacja tekstowa może być dla Ciebie łatwiejsza do zrozumienia i zapamiętania. Możesz używać dowolnej reprezentacji, ale staraj się być konsekwentny!
Operator | Alternatywna reprezentacja | Opis |
---|---|---|
a and b | a && b | Czy a i b są oba prawdziwe? |
a or b | a || b | Czy którykolwiek z a lub b jest prawdziwe? |
not c | !c | Czy c jest fałszywe? |
Logiczny operator I (and)
Logiczny operator I and
/&&
bierze dwie wartośći logiczne i zwraca prawdę wtedy i tylko wtedy, jeśli
oba są prawdziwe. Najlepiej używać go, kiedy chcesz sprawdzić,
czy kilka warunków na jest na raz prawdziwych lub fałszywych (używając operatora negacji, not
/!
).
Dla przykładu, false && true
to false
, ale true and true
to true
.
Logiczny operator LUB (or)
Podobnie jak I, logiczny operator LUB or
/||`` przyjmuje dwa wejścia logiczne i zwraca
truejeśli jedno lub oba jego wejścia są
true`. Najlepiej używać go, gdy chcemy sprawdzić, czy któryś z wielu
warunków jest prawdziwy, lub gdy jedna zmienna jest jedną z wielu możliwości
Dla przykładu, false or false
to false
, ale false || true
to true
Logiczny operator NEGACJI (not)
Zwróć szczególną uwagę na ostatni wiersz tabeli wyżej; not
/!
jest znany jako logiczny operator negacji
Ten operator zmienia wartość pojedynczego wejścia na przeciwną, więc w wyrażenii !c
, c
jest wartością logiczną
( true
/ false
), a !
zamieni true
na false
, a false
na true
.
Jest to równoważne z pytaniem "czy c
jest false
". Ten operator przydaje się, gdy chcesz
sprawdzić, czy jakiś warunek nie został spełniony.
Dla przykładu, !(a == b)
to to samo co a != b
.
Oraz, not (a <= b)
to to samo co a > b
.
Zwróc uwagę, że używamy nawiasów, aby najpierw wykonywać wyrażenie logiczne wewnątrz, a dopiero potem je zanegować.
Ulepszanie naszego programu
Zostały uchwalone nowe przepisy i teraz musimy modernizować nasz program, aby je obsłużyć. Nowe prawo określa maksymalny wiek dla kogoś, kto chce mieć prawo jazdy - osoby w wieku 65 lat i starsze nie mogą już prowadzić pojazdów mechanicznych.
Musimy rozszerzyć nasz warunek, aby spełnić ten nowy wymóg.
Obecnie, age >= 18
określa dolną granicę dla zakresu dopuszczalnych wartości.
Brakuje nam czegoś, co ustawi górną granicę, którą powinno być 65
.
Wyrażenie logiczne, które sprawdza czy wiek jest mniejszy niż 65
może wyglądać tak: age < 65
.
Zauważ, że używamy <
zamiast <=
, ponieważ 65-latkowie mają zakaz prowadzenia pojazdów.
Teraz mamy age >= 18
i age < 65
, które muszą być w jakiś sposób połączone.
Musimy jakoś określić, że OBA muszą być prawdziwe - osoba musi być
18 lub starsza ORAZ młodsza niż 65, aby otrzymać prawo jazdy.
Patrząc na tabelę w Złożone wyrażenia logiczne,
widzimy, że operator and
/&&
najlepiej pasuje do tej sytuacji.
Możemy zapisać ten warunek jako age >= 18 and age < 65
.
Zauważ że kolejność lewo/prawo nie ma tutaj znaczenia, co oznacza, że
age < 65 and age >= 18
jest warunkiem równoważnym.
#include <iostream>
int main()
{
std::cout << "Welcome to the Driver's License Oracle 3000\n";
std::cout << "Please enter your year of birth: ";
int year_of_birth;
std::cin >> year_of_birth;
// W momencie pisania tej lekcji mamy 2022 rok
int age = 2022 - year_of_birth;
if (age >= 18 and age < 65)
{
std::cout << "You can legally get a driver's license\n";
}
else
{
std::cout << "You cannot legally get a driver's license\n";
}
}
Może Cię kusić, aby warunek użyty powyżej zapisać jako 18 <= age < 65
.
To nie zadziała. Skompiluje się, aczkolwiek będzie dawać niepoprawny wynik - zawsze true
.
Taki sam problem możemy zaobserwować w przypadku operatorów równości.
Dla przykładu, age1 == age2 == 18
lub age1 != age2 != 35
, oba będą zachowywać się podobnie,
zawsze zwracajać true
lub false
.
Nigdy nie próbuj łączyć w taki sposób operatorów równości lub operatorów relacyjnych, zamiast tego
zawsze używaj operatorów and
/&&
lub or
/||
, aby łączyć całe warunki.
Wyjaśnienie dlaczego działa to w taki sposób znajdziej tu Typ bool
» konwersja pomiędzy int
a bool
.
Kolejność operacji
Podobnie jak operatory matematyczne, operatory logiczne również mają swój priorytet, którego ściśle przestrzegają. Podobnie jak w przypadku "PEMDAS", nawiasy są zawsze liczone peirwsze, a pozostałe operatory wg priorytetów wymienionych niżej. Najwyższy priorytet jest na górze.
PEMDAS z angielskiego to skrótowiec od Parentheses, Exponents, Multiplication and Division, Addition and Subtraction, co tłumaczy się na Nawiasy, Potęgi, Mnożenie i Dzielenie, Dodawanie i Odejmowanie.
Służy on do zapamiętania poprawnej kolejności podstawowych operacji matematycznych.
Operator | Nazwa | Łączność |
---|---|---|
! not | Logiczna NEGACJA | Prawo-do-lewo 🡠 |
* / % | Mnożenie/Dzielenie | Lewo-do-prawo 🡢 |
+ - | Dodawanie/Odejmowanie | Lewo-do-prawo 🡢 |
< <= > >= | Operatory relacyjne | Lewo-do-prawo 🡢 |
== != | Operatory równości | Lewo-do-prawo 🡢 |
&& and | Logiczne I | Lewo-do-prawo 🡢 |
|| or | Logiczne LUB | Lewo-do-prawo 🡢 |
Użyjmy tej tabelki do przeanalizowania następujących przykładów:
a > 10 and a < 100 or a == 150
// równoważne z
(a > 10 and a < 100) or a == 150
a * 4 == b + 5
// równoważne z
(a * 4) == (b + 5)
not a or b < 3701
// równoważne z
(not a) or (b < 3701)
not a != false
// równoważne z
(not a) != false
Dobrą praktyką jest nie polegać zbytnio na regułach pierwszeństwa operatorów. Programistom może być trudno pamiętać wszystkie priorytety operatoró, więc pisanie dużej ilości kodu zależnego od tych reguł może sprawić, że będzie on mnij czytelne i trudniejszy do zrozumienia. Jeśli chcesz, aby jakieś podwyrażenie było obliczane jako pierwsze, zastanów się, czy użycie nawiasów nie będzie dobrym pomysłem.
Więc, zamiast pisać a and b and c or a and not b
, zastanów się czy lepiej nie będzie napisać (a and b and c) or (a and not b)
.