6.6 — Warunek. operator

OperatorSymbolFormularzZnaczenie
Warunkowe?:c ? x : yJeśli warunkowy c Jest true to oceń x, w przeciwnym razie oceń y

Klasa operator warunkowy (?:) (czasami nazywany także operatorem arytmetycznym jeśli operator) jest operatorem trójskładnikowym (operator, który zajmuje 3 operandy). Ponieważ w przeszłości był to jedyny operator trójskładnikowy w C++, czasami nazywany jest także „operatorem trójskładnikowym”.

Klasa ?: operator udostępnia skróconą metodę wykonywania określonego typu instrukcji if-else.

Powiązana treść

Instrukcje if-else omówimy w lekcji 4.10 - Wprowadzenie do instrukcji if.

Podsumowując, instrukcja if-else ma następującą formę:

if (condition)
    statement1;
else
    statement2;

Jeśli condition wyniesie true, wówczas statement1 jest wykonywana, w przeciwnym razie statement2 jest wykonywany. Operatory else i statement2 są opcjonalne.

Klasa ?: przybierają następującą postać:

condition ? expression1 : expression2;

Jeśli condition wyniesie true, wówczas expression1 jest oceniany, w przeciwnym razie expression2 . Operator : i expression2 nie są opcjonalne.

Rozważ instrukcję if-else wyglądającą tak:

if (x > y)
    max = x;
else
    max = y;

Można to przepisać jako:

max = ((x > y) ? x : y);

W takich przypadkach operator warunkowy może pomóc w kompaktowaniu kodu bez utraty czytelność.

Przykład

Rozważ następujący przykład:

#include <iostream>

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

int main()
{
    int x { getValue() };
    int y { getValue() };
    int max { (x > y) ? x : y };
    std::cout << "The max of " << x <<" and " << y << " is " << max << ".\n";

    return 0;
}

Najpierw wprowadźmy 5 i 7 (więc x Jest 5, I y Jest 7). Po zainicjowaniu max wyrażenie (5 > 7) ? 5 : 7 jest oceniany. Ponieważ wartość 5 > 7 jest fałszywe, co daje false ? 5 : 7, co daje 7. Program wypisuje:

The max of 5 and 7 is 7.

Teraz jako dane wejściowe wprowadźmy 7 i 5 (więc x Jest 7, I y Jest 5). W tym przypadku otrzymamy (7 > 5) ? 7 : 5, czyli true ? 7 : 5, co daje 7. Program wypisuje:

The max of 7 and 5 is 7.

Operator warunkowy ocenia jako część wyrażenia

Ponieważ operator warunkowy jest oceniany jako część wyrażenia, można go używać wszędzie tam, gdzie wyrażenie jest akceptowane. W przypadkach, gdy operandy operatora warunkowego są wyrażeniami stałymi, operator warunkowy może zostać użyty w wyrażeniu stałym.

Dzięki temu operator warunkowy może być używany tam, gdzie nie można użyć instrukcji.

Na przykład podczas inicjowania zmiennej:

#include <iostream>

int main()
{
    constexpr bool inBigClassroom { false };
    constexpr int classSize { inBigClassroom ? 30 : 20 };
    std::cout << "The class size is: " << classSize << '\n';

    return 0;
}

Nie ma bezpośredniego zamiennika if-else.

Możesz pomyśleć o wypróbowaniu czegoś takiego to:

#include <iostream>

int main()
{
    constexpr bool inBigClassroom { false };

    if (inBigClassroom)
        constexpr int classSize { 30 };
    else
        constexpr int classSize { 20 }; 

    std::cout << "The class size is: " << classSize << '\n'; // Compile error: classSize not defined

    return 0;
}

Jednak to nie zostanie skompilowane i pojawi się komunikat o błędzie, który classSize nie jest zdefiniowany. Podobnie jak zmienne zdefiniowane wewnątrz funkcji umierają na końcu funkcji, zmienne zdefiniowane wewnątrz instrukcji if lub else umierają na końcu instrukcji if lub else. Zatem classSize został już zniszczony w momencie, gdy próbowaliśmy go wydrukować.

Jeśli chcesz użyć if-else, musiałbyś zrobić coś takiego:

#include <iostream>

int getClassSize(bool inBigClassroom)
{
    if (inBigClassroom)
        return 30;
    else
        return 20;
}

int main()
{
    const int classSize { getClassSize(false) };
    std::cout << "The class size is: " << classSize << '\n';

    return 0;
}

To działa, ponieważ getClassSize(false) jest wyrażeniem, a logika if-else znajduje się wewnątrz funkcji (gdzie możemy używać instrukcji). Ale to dużo dodatkowego kodu, gdy moglibyśmy zamiast tego użyć operatora warunkowego.

Umieszczanie operatora warunkowego w nawiasach

Ponieważ w C++ priorytetem jest ocena większości operatorów przed oceną operatora warunkowego, całkiem łatwo jest pisać wyrażenia przy użyciu operatora warunkowego, które nie oceniają zgodnie z oczekiwaniami.

Powiązana treść

Omówimy sposób, w jaki C++ będzie priorytetowo traktować ocenę operatorów w przyszłości lekcja 6.1 — Pierwszeństwo operatora i łączność.

Na przykład:

#include <iostream>

int main()
{
    int x { 2 };
    int y { 1 };
    int z { 10 - x > y ? x : y };
    std::cout << z;
    
    return 0;
}

Można się spodziewać, że zostanie to ocenione jako 10 - (x > y ? x : y) (co wyniesie 8), ale w rzeczywistości zostanie to wyliczone jako (10 - x) > y ? x : y (co wyniesie 2).

Oto kolejny przykład, który wykazuje częsty błąd:

#include <iostream>

int main()
{
    int x { 2 };
    std::cout << (x < 0) ? "negative" : "non-negative";

    return 0;
}

Można się spodziewać, że to zostanie wydrukowane non-negative, ale w rzeczywistości drukuje 0.

Odczyt opcjonalny

Oto co dzieje się w powyższym przykładzie. Po pierwsze, x < 0 wyniesie false. Częściowo ocenione wyrażenie jest teraz std::cout << false ? "negative" : "non-negative". Ponieważ operator<< ma wyższy priorytet niż operator?:, to wyrażenie jest obliczane tak, jakby zostało zapisane jako (std::cout << false) ? "negative" : "non-negative". Zatem std::cout << false jest oceniane, co powoduje wyświetlenie 0 (i zwrócenie std::cout).

Częściowo ocenione wyrażenie to. teraz std::cout ? "negative" : "non-negative". Ponieważ std::cout jest wszystkim, co pozostało w warunku, kompilator spróbuje przekonwertować go na bool , aby warunek mógł zostać rozwiązany. Co może zaskakujące, std::cout ma zdefiniowaną konwersję na bool, która najprawdopodobniej zwróci false. Zakładając, że zwróci false, mamy teraz false ? "negative" : "non-negative", co daje "non-negative" w pełni oceniona instrukcja to "non-negative";. Instrukcja wyrażenia będąca tylko literałem (w tym przypadku literałem łańcuchowym) nie ma żadnego efektu, więc to koniec.

Aby uniknąć problemów z ustalaniem priorytetów oceny, operator warunkowy powinien być umieszczony w nawiasach w następujący sposób:

  • Umieść w nawiasach całą operację warunkową (łącznie z operandami), gdy jest używana w wyrażeniu złożonym (wyrażenie z innymi operatorami).
  • Dla zapewnienia czytelności rozważ umieszczenie warunku w nawiasach, jeśli zawiera dowolne operatory (inne niż operator wywołania funkcji).

Operandy operatora warunkowego nie muszą być ujęte w nawiasy.

Przyjrzyjmy się niektórym instrukcjom zawierającym operator warunkowy i sposobowi ich umieszczania w nawiasach:

return isStunned ? 0 : movesLeft;           // not used in compound expression, condition contains no operators
int z { (x > y) ? x : y };                  // not used in compound expression, condition contains operators
std::cout << (isAfternoon() ? "PM" : "AM"); // used in compound expression, condition contains no operators (function call operator excluded)
std::cout << ((x > y) ? x : y);             // used in compound expression, condition contains operators

Najlepsza praktyka

Umieść w nawiasach całą operację warunkową (łącznie z operandami), gdy jest używana w wyrażeniu złożonym.

Dla zapewnienia czytelności rozważ umieszczenie warunku w nawiasach, jeśli zawiera dowolne operatory (inne niż operator wywołania funkcji).

Typ wyrażeń musi pasować lub być konwertowalny

Aby zachować zgodność z regułami sprawdzania typów C++, jeden z poniższych musi być prawdziwy:

  • Typ drugiego i trzeciego operandu muszą być zgodne.
  • Kompilator musi znaleźć sposób na konwersję jednego lub obu drugiego i trzeciego operandu na pasujące typy. Reguły konwersji używane przez kompilator są dość złożone i w niektórych przypadkach mogą dawać zaskakujące wyniki.

Dla zaawansowanych czytelników

Alternatywnie, jeden lub oba z drugiego i trzeciego operandu mogą być wyrażeniem rzutowym. Omówimy throw w lekcji 27.2 — Podstawowa obsługa wyjątków.

Na przykład:

#include <iostream>

int main()
{
    std::cout << (true ? 1 : 2) << '\n';    // okay: both operands have matching type int

    std::cout << (false ? 1 : 2.2) << '\n'; // okay: int value 1 converted to double

    std::cout << (true ? -1 : 2u) << '\n';  // surprising result: -1 converted to unsigned int, result out of range

    return 0;
}

Zakładając 4-bajtowe liczby całkowite, powyższe wypisuje:

1
2.2
4294967295

Ogólnie rzecz biorąc, można mieszać operandy z typami podstawowymi (z wyjątkiem mieszania wartości ze znakiem i bez znaku). Jeśli którykolwiek z operandów nie jest typem podstawowym, najlepiej jest samodzielnie przekonwertować jeden lub oba operandy na pasujący typ, aby dokładnie wiedzieć, co otrzymasz.

Powiązana treść

Zaskakujący przypadek powyżej związany z mieszaniem wartości ze znakiem i bez znaku wynika z reguł konwersji arytmetycznej, które omówimy w lekcji 10.5 — Konwersje arytmetyczne.

Jeśli kompilator nie będzie mógł znaleźć sposobu na przekonwertowanie drugiego i trzeciego operandu na pasujący typ, wystąpi błąd kompilacji wynik:

#include <iostream>

int main()
{
    constexpr int x{ 5 };
    std::cout << ((x != 5) ? x : "x is 5"); // compile error: compiler can't find common type for constexpr int and C-style string literal

    return 0;
}

W powyższym przykładzie jedno z wyrażeń jest liczbą całkowitą, a drugie literałem łańcuchowym w stylu C. Kompilator nie będzie w stanie sam znaleźć pasującego typu, więc wystąpi błąd kompilacji.

W takich przypadkach możesz albo wykonać jawną konwersję, albo użyć instrukcji if-else:

#include <iostream>
#include <string>

int main()
{
    int x{ 5 }; // intentionally non-constexpr for this example

    // We can explicitly convert the types to match
    std::cout << ((x != 5) ? std::to_string(x) : std::string{"x is 5"}) << '\n';

    // Or use an if-else statement
    if (x != 5)
        std::cout << x << '\n';
    else
        std::cout << "x is 5" << '\n';
    
    return 0;
}

Dla zaawansowanych czytelników

Jeśli x is constexpr, wtedy warunek x != 5 jest wyrażeniem stałym. W takich przypadkach użycie if constexpr powinno być preferowane zamiast if, a Twój kompilator może wygenerować ostrzeżenie wskazujące na to (co będzie promowane do błędu, jeśli traktujesz ostrzeżenia jako błędy).

Ponieważ nie omówiliśmy jeszcze if constexpr (robimy to na lekcji 8.4 — Constexpr if instrukcje), x nie jest to constexpr w tym przykładzie, aby uniknąć potencjalnego kompilatora ostrzeżenie.

Kiedy więc należy używać operatora warunkowego?

Operator warunkowy jest najbardziej przydatny podczas wykonywania jednej z następujących czynności:

  • Inicjowanie obiektu jedną z dwóch wartości.
  • Przypisywanie jednej z dwóch wartości do obiektu.
  • Przekazywanie jednej z dwóch wartości do funkcji.
  • Zwracanie jednej dwóch wartości z funkcji.
  • Drukowanie jednej z dwóch wartości.

W skomplikowanych wyrażeniach należy generalnie unikać stosowania operatora warunkowego, ponieważ są one podatne na błędy i trudne do odczytania.

Najlepsza praktyka

Wolę unikać operatora warunkowego w skomplikowanych wyrażeniach.

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