8.3 — Typowe problemy z instrukcjami if

Ta lekcja jest kontynuacją lekcji 8.2 -- Instrukcje i bloki if. Podczas tej lekcji przyjrzymy się niektórym typowym problemom, które pojawiają się podczas używania instrukcji if.

Zagnieżdżone instrukcje if i problem wiszącego else

Możliwe jest zagnieżdżanie instrukcji if w innych instrukcjach if:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x >= 0) // zewnętrzna instrukcja if
        // zagnieżdżanie instrukcji if w ten sposób jest złym stylem
        if (x <= 20) // wewnętrzna instrukcja if
            std::cout << x << " is between 0 and 20\n";

    return 0;
}

Rozważmy teraz następujący program:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x >= 0) // zewnętrzna instrukcja if
        // zagnieżdżanie instrukcji if w ten sposób jest złym stylem
        if (x <= 20) // wewnętrzna instrukcja if
            std::cout << x << " is between 0 and 20\n";

    // do której instrukcji if należy to jeszcze?
    else
        std::cout << x << " is negative\n";

    return 0;
}

Powyższy program wprowadza źródło potencjalnej niejednoznaczności zwane zawieszenie else problem. Czy instrukcja else w powyższym programie jest dopasowana do zewnętrznej lub wewnętrznej instrukcji if?

Odpowiedź jest taka, że ​​instrukcja else jest sparowana z ostatnią niedopasowaną instrukcją if w tym samym bloku. Zatem w powyższym programie instrukcja else jest dopasowywana do wewnętrznej instrukcji if, tak jakby program został napisany w ten sposób:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x >= 0) // zewnętrzna instrukcja if
    {
        if (x <= 20) // wewnętrzna instrukcja if
            std::cout << x << " is between 0 and 20\n";
        else // dołączony do wewnętrznej instrukcji if
            std::cout << x << " is negative\n";
    }

    return 0;
}

To powoduje, że powyższy program generuje nieprawidłowe dane wyjściowe:

Enter a number: 21
21 is negative

Aby uniknąć takich niejasności podczas zagnieżdżania instrukcji if, dobrym pomysłem jest jawne zamknięcie wewnętrznej instrukcji if w bloku. Dzięki temu możemy bez dwuznaczności dołączyć instrukcję else do wewnętrznej lub zewnętrznej instrukcji if:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x >= 0)
    {
        if (x <= 20)
            std::cout << x << " is between 0 and 20\n";
        else // dołączony do wewnętrznej instrukcji if
            std::cout << x << " is greater than 20\n";
    }
    else // dołączony do zewnętrznej instrukcji if
        std::cout << x << " is negative\n";

    return 0;
}

Instrukcja else w bloku jest dołączana do wewnętrznej instrukcji if, a instrukcja else na zewnątrz bloku jest dołączana do zewnętrznej instrukcji if.

Spłaszczanie zagnieżdżonych instrukcji if

Zagnieżdżone instrukcje if mogą często być spłaszczane przez albo restrukturyzację logiki lub użycie operatorów logicznych (omówionych w lekcji 6.8 -- Operatory logiczne). Kod mniej zagnieżdżony jest mniej podatny na błędy.

Na przykład powyższy przykład można spłaszczyć w następujący sposób:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x < 0)
        std::cout << x << " is negative\n";
    else if (x <= 20) // wykonuje się tylko wtedy, gdy x >= 0
        std::cout << x << " is between 0 and 20\n";
    else // wykonuje się tylko wtedy, gdy x > 20
        std::cout << x << " is greater than 20\n";

    return 0;
}

Oto kolejny przykład użycia operatorów logicznych do sprawdzenia wielu warunków w ramach jednej instrukcji if:

#include <iostream>

int main()
{
    std::cout << "Enter an integer: ";
    int x{};
    std::cin >> x;

    std::cout << "Enter another integer: ";
    int y{};
    std::cin >> y;

    if (x > 0 && y > 0) // && jest logiczna i -- sprawdza, czy oba warunki są spełnione
        std::cout << "Both numbers are positive\n";
    else if (x > 0 || y > 0) // || jest logiczne lub -- sprawdza, czy którykolwiek z warunków jest prawdziwy
        std::cout << "One of the numbers is positive\n";
    else
        std::cout << "Neither number is positive\n";

    return 0;
}

Instrukcje null

instrukcja null to instrukcje wyrażeń składające się tylko ze średnika:

if (x > 10)
    ; // to jest instrukcja null

Instrukcje null nic nie robią. Są one zwykle używane, gdy język wymaga istnienia instrukcji, ale programista jej nie potrzebuje. Aby zapewnić czytelność, instrukcje null są zwykle umieszczane w osobnych wierszach. Przykłady zamierzonych instrukcji null zobaczymy w dalszej części tego rozdziału, kiedy będziemy omawiać pętle.

Instrukcje null rzadko są celowo używane z instrukcjami if. Mogą jednak w sposób niezamierzony sprawić problemy nowym (lub nieostrożnym) programistom. Rozważmy następujący fragment:

if (nuclearCodesActivated()); // zwróć uwagę na średnik na końcu tej linii
    blowUpTheWorld();

W powyższym fragmencie programista przypadkowo umieścił średnik na końcu instrukcji if (częsty błąd, ponieważ wiele instrukcji kończy się średnikami). Ten skromny błąd kompiluje się poprawnie i powoduje, że fragment kodu jest wykonywany tak, jakby został napisany w ten sposób:

if (nuclearCodesActivated())
    ; // średnik pełni funkcję instrukcji zerowej
blowUpTheWorld(); // a ta linia zawsze zostanie wykonana!

Ostrzeżenie

Uważaj, aby nie „zakończyć” instrukcji if średnikiem, w przeciwnym razie instrukcje, które chcesz wykonać warunkowo, zostaną zamiast tego wykonane bezwarunkowo (nawet jeśli znajdują się w bloku).

Wskazówka

W Pythonie słowo kluczowe pass służy jako instrukcja zerowa. Często jest używany jako symbol zastępczy dla kodu, który zostanie zaimplementowany później. Ponieważ jest to słowo, a nie symbol, pass jest mniej podatny na niezamierzone niewłaściwe użycie i łatwiejszy do przeszukiwania (co pozwala później łatwo znaleźć te symbole zastępcze).

for x in [0, 1, 2]:
  pass               # To be completed in the future

W C++ możemy naśladować pass używając preprocesora:

#define PASS

void foo(int x, int y)
{
    if (x > y)
        PASS;
    else
        PASS;
}

int main()
{
    foo(4, 7);

    return 0;
}

W celu zapewnienia spójności z innymi instrukcjami C++, nasze PASS wymaga zakończenia średnik. PASS jest usuwany przez preprocesor, a końcowy średnik jest interpretowany przez kompilator jako instrukcja zerowa.

Operator== vs Operator= wewnątrz warunku

Wewnątrz warunku, którego powinieneś użyć operator== podczas testowania równości, a nie operator= (co jest przypisaniem). Rozważmy następujący program:

#include <iostream>

int main()
{
    std::cout << "Enter 0 or 1: ";
    int x{};
    std::cin >> x;
    if (x = 0) // ups, użyliśmy tutaj przypisania zamiast testu dla równość
        std::cout << "You entered 0\n";
    else
        std::cout << "You entered 1\n";

    return 0;
}

Ten program zostanie skompilowany i uruchomiony, ale w niektórych przypadkach da błędny wynik:

Enter 0 or 1: 0
You entered 1

W rzeczywistości ten program zawsze wygeneruje wynik You entered 1. Dzieje się tak, ponieważ x = 0 najpierw przypisuje wartość 0 Do x, następnie ocenia wartość x, która jest teraz 0, czyli Boolean false. Ponieważ warunek jest zawsze false, instrukcja else zawsze jest wykonywana.

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