8.2 — If instrukcje i bloki

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.

  1. 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 execute

Ups, właśnie pozwoliliśmy nieletnim grać. Miłej zabawy w więzieniu!

  1. 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 executes

Powiedzmy, że podejrzewamy, że coś jest nie tak z funkcją addBeerToCart() , więc to skomentujemy:

if (age >= minDrinkingAge)
//    addBeerToCart();

checkout(); // conditionally executes now

Teraz 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.

  1. if constexpr (wariant instrukcji if dodany w C++23) wymaga użycia bloków. Zatem używanie bloków zapewnia spójność pomiędzy if i if 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 true warunku.
  • Użyj if-if, gdy chcesz wykonać kod po spełnieniu wszystkich true warunkó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.

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