Pierwszą kategorią instrukcji przepływu sterowania, o której będziemy mówić, są instrukcje warunkowe. A instrukcją warunkową jest instrukcją określającą, czy niektóre powiązane instrukcje powinny zostać wykonane, czy nie.
C++ obsługuje dwa podstawowe rodzaje warunków warunkowych: if statements (które przedstawiliśmy w lekcji 4.10 - Wprowadzenie do instrukcji if i omówimy dalej tutaj) oraz switch statements (które omówimy w kilku lekcjach).
Szybkie podsumowanie instrukcji if
Najbardziej podstawowym rodzajem instrukcji warunkowej w C++ jest if statement. An if statement przybiera postać:
if (condition)
true_statement;
lub z opcjonalnym else statement:
if (condition)
true_statement;
else
false_statement;
Jeśli condition wyniesie true, instrukcja true_statement wykonuje. Jeśli condition wyniesie false i opcjonalny else statement istnieją, false_statement wykonuje się.
Oto prosty program, który używa if statement z opcjonalnymi else statement:
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x{};
std::cin >> x;
if (x > 10)
std::cout << x << " is greater than 10\n";
else
std::cout << x << " is not greater than 10\n";
return 0;
}Ten program działa tak, jak można się spodziewać:
Enter a number: 15 15 is greater than 10
Enter a number: 4 4 is not greater than 10
If lub else z wieloma instrukcjami warunkowymi
Nowi programiści często próbuj czegoś takiego:
#include <iostream>
namespace constants
{
constexpr int minRideHeightCM { 140 };
}
int main()
{
std::cout << "Enter your height (in cm): ";
int x{};
std::cin >> x;
if (x >= constants::minRideHeightCM)
std::cout << "You are tall enough to ride.\n";
else
std::cout << "You are not tall enough to ride.\n";
std::cout << "Too bad!\n"; // focus on this line
return 0;
}Rozważ jednak następujące uruchomienie programu:
Enter your height (in cm): 180 You are tall enough to ride. Too bad!
Ten program nie działa zgodnie z oczekiwaniami, ponieważ true_statement i false_statement może być tylko pojedynczą instrukcją. Wcięcie nas tutaj oszukuje - powyższy program wykonuje się tak, jakby był napisany w następujący sposób:
#include <iostream>
namespace constants
{
constexpr int minRideHeightCM { 140 };
}
int main()
{
std::cout << "Enter your height (in cm): ";
int x{};
std::cin >> x;
if (x >= constants::minRideHeightCM)
std::cout << "You are tall enough to ride.\n";
else
std::cout << "You are not tall enough to ride.\n";
std::cout << "Too bad!\n"; // focus on this line
return 0;
}To wyjaśnia, że „Szkoda!” zawsze zostanie wykonane.
Jednak często zdarza się, że chcesz wykonać wiele instrukcji w oparciu o jakiś warunek. Aby to zrobić, możemy użyć instrukcji złożonej (bloku):
#include <iostream>
namespace constants
{
constexpr int minRideHeightCM { 140 };
}
int main()
{
std::cout << "Enter your height (in cm): ";
int x{};
std::cin >> x;
if (x >= constants::minRideHeightCM)
std::cout << "You are tall enough to ride.\n";
else
{ // note addition of block here
std::cout << "You are not tall enough to ride.\n";
std::cout << "Too bad!\n";
}
return 0;
}Pamiętaj, że bloki są traktowane jako pojedyncza instrukcja, więc teraz działa to zgodnie z oczekiwaniami:
Enter your height (in cm): 180 You are tall enough to ride.
Enter your height (in cm): 130 You are not tall enough to ride. Too bad!
Bloki niejawne
Jeśli programista nie zadeklaruje bloku w części instrukcji if statement lub else statement, kompilator niejawnie go zadeklaruje. Zatem:
if (condition)
true_statement;
else
false_statement;
jest właściwie odpowiednikiem:
if (condition)
{
true_statement;
}
else
{
false_statement;
}
W większości przypadków nie ma to znaczenia. Jednak nowi programiści czasami próbują zdefiniować zmienne w ukrytym bloku, w ten sposób:
#include <iostream>
int main()
{
if (true)
int x{ 5 };
else
int x{ 6 };
std::cout << x << '\n';
return 0;
}To się nie skompiluje, a kompilator generuje błąd, że identyfikator x nie jest zdefiniowany. Dzieje się tak, ponieważ powyższy przykład jest odpowiednikiem:
#include <iostream>
int main()
{
if (true)
{
int x{ 5 };
} // x destroyed here
else
{
int x{ 6 };
} // x destroyed here
std::cout << x << '\n'; // x isn't in scope here
return 0;
}W tym kontekście jaśniejsze jest, że zmienna x ma zasięg blokowy i jest niszczona na końcu bloku. Zanim dotrzemy do std::cout wiersza, x nie istnieje.
Blokowanie czy nie blokowanie pojedynczych instrukcji
W społeczności programistów toczy się debata na temat tego, czy pojedyncze instrukcje następujące po if lub else powinny być wyraźnie ujęte w bloki, czy nie.
Istnieje wiele powodów uzasadniających używanie bloków.
- Nieużywanie bloków ułatwia aby przypadkowo dodać instrukcje, które wyglądają na warunkowe, ale nimi nie są. Rozważ:
if (age >= minDrinkingAge)
purchaseBeer();Załóżmy, że się spieszymy i zmodyfikuj ten program, dodając kolejną umiejętność:
if (age >= minDrinkingAge)
purchaseBeer();
gamble(); // will always executeUps, właśnie pozwoliliśmy nieletnim grać. Miłej zabawy w więzieniu!
- Nieużywanie bloków może utrudnić debugowanie programów. Załóżmy, że mamy następujący fragment kodu:
if (age >= minDrinkingAge)
addBeerToCart(); // conditionally executes
checkout(); // always executesPowiedzmy, że podejrzewamy, że coś jest nie tak z funkcją addBeerToCart() , więc to skomentujemy:
if (age >= minDrinkingAge)
// addBeerToCart();
checkout(); // conditionally executes nowTeraz stworzyliśmy checkout() warunkowy, czego z pewnością nie mieliśmy na myśli.
Żadne z powyższych nie wystąpi, jeśli zawsze będziesz używać bloków po an if lub else.
if constexpr(wariant instrukcji if dodany w C++23) wymaga użycia bloków. Zatem używanie bloków zapewnia spójność pomiędzyifiif constexpr.
Najlepszym argumentem za niestosowaniem bloków wokół pojedynczych instrukcji jest to, że dodanie bloków sprawia, że możesz zobaczyć mniejszą część kodu za jednym razem poprzez rozmieszczenie go w pionie, co sprawia, że kod jest mniej czytelny i może prowadzić do innych, poważniejszych błędów.
Społeczność wydaje się bardziej opowiadać się za ciągłym używaniem bloków, chociaż to zalecenie z pewnością nie jest wszechobecne.
Najlepsza praktyka
Rozważ umieszczenie pojedynczych wypowiedzi powiązane z if lub else w blokach (szczególnie podczas nauki). Bardziej doświadczeni programiści C++ czasami ignorują tę praktykę na rzecz mniejszych odstępów w pionie.
Rozsądną alternatywą jest umieszczenie pojedynczych wierszy w tym samym wierszu, co if lub else:
if (age >= minDrinkingAge) purchaseBeer();
else std::cout << "No drinky for you\n".Pozwala to uniknąć obu powyższych wad wymienionych powyżej przy niewielkim koszcie utraty czytelności.
Uzasadniona krytyka metody jednowierszowej polega na tym, że generuje ona kod trudniejszy do debugowania:
- Ponieważ instrukcja warunkowa i powiązana z nią instrukcja zostaną wykonane w ramach tego samego kroku, trudniej jest stwierdzić, czy instrukcja faktycznie wykonana lub została pominięta.
- Ponieważ instrukcja warunkowa i skojarzona z nią instrukcja znajdują się w tej samej linii, nie można przerwać tylko powiązanej instrukcji (aby zatrzymać wykonywanie tylko wtedy, gdy instrukcja faktycznie zostanie wykonana).
Jeśli jednak którykolwiek z powyższych punktów utrudnia debugowanie, możesz wstawić nową linię pomiędzy warunkową a instrukcją (aby znajdowały się w oddzielnych liniach), przeprowadzić debugowanie, a następnie usunąć znak nowej linii później.
If-else vs if-if
Nowi programiści czasami zastanawiają się, kiedy powinni użyć if-else (jeśli po nim następuje jedna lub więcej instrukcji else) czy if-if (jeśli następuje jedna lub więcej dodatkowych instrukcji if).
- Użyj if-else, jeśli chcesz wykonać kod tylko po pierwszym
truewarunku. - Użyj if-if, gdy chcesz wykonać kod po spełnieniu wszystkich
truewarunków.
Oto program, który to demonstruje:
#include <iostream>
void ifelse(bool a, bool b, bool c)
{
if (a) // always evaluates
std::cout << "a";
else if (b) // only evaluates when prior if-statement condition is false
std::cout << "b";
else if (c) // only evaluates when prior if-statement condition is false
std::cout << "c";
std::cout << '\n';
}
void ifif(bool a, bool b, bool c)
{
if (a) // always evaluates
std::cout << "a";
if (b) // always evaluates
std::cout << "b";
if (c) // always evaluates
std::cout << "c";
std::cout << '\n';
}
int main()
{
ifelse(false, true, true);
ifif(false, true, true);
return 0;
}W wywołaniu ifelse(false, true, true), a Jest false, więc nie wykonujemy powiązanej instrukcji, a zamiast tego wykonywana jest powiązana else . b jest prawdziwe, więc drukujemy b. Ponieważ ten if warunek był prawdziwy, powiązane else nie zostaną wykonane (ani żadne inne else instrukcje bezpośrednio następujące po nich). Zauważ, że wykonaliśmy kod dopiero po pierwszym true warunku (b).
W wywołaniu ifif(false, true, true), a jest fałszywe, więc nie wykonujemy powiązanej instrukcji i przechodzimy do następnego if. b jest prawdziwe, więc drukujemy b i przechodzimy do następnego if. c jest prawdziwe, więc drukujemy c. że wykonaliśmy kod po wszystkich true warunkach (b i c).
Spójrzmy teraz na tę nieco podobną funkcję:
char getFirstMatchingChar(bool a, bool b, bool c)
{
if (a) // always evaluates
return 'a';
else if (b) // only evaluates when prior if-statement condition is false
return 'b';
else if (c) // only evaluates when prior if-statement condition is false
return 'c';
return 0;
}Ponieważ używamy if-else, jasne jest, że chcemy wykonać kod tylko po pierwszym true warunku. Ale kiedy każda powiązana instrukcja zwraca wartość, możemy zamiast tego napisać to:
char getFirstMatchingChar(bool a, bool b, bool c)
{
if (a) // always evaluates
return 'a'; // returns when if-statement is true
if (b) // only evaluates when prior if-statement condition is false
return 'b'; // returns when if-statement is true
if (c) // only evaluates when prior if-statement condition is false
return 'c'; // returns when if-statement is true
return 0;
}Chociaż może to wyglądać powierzchownie tak jak chcemy wykonać kod po każdym true warunku, gdy tylko znajdziemy pierwszy true warunek, powiązana instrukcja spowoduje zwrócenie funkcji. Pozostałe instrukcje if nigdy nie mają szansy na wykonanie. Dlatego zachowuje się to tak samo, jak w poprzedniej wersji. Gdy każda powiązana instrukcja zwraca wartość, wielu programistów woli pominąć słowa kluczowe else , ponieważ pozwala to uniknąć niepotrzebnego bałaganu i linii warunków. lepiej.
Kluczowa informacja
Gdy wszystkie powiązane instrukcje zwracają wartość, zawsze możesz po prostu użyć if-if, ponieważ else nie dostarcza żadnej wartości.
Będziemy kontynuować naszą eksplorację instrukcji if w następnej lekcji.

