| Operator | Symbol | Formularz | Znaczenie |
|---|---|---|---|
| Warunkowe | ?: | Jeś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 operatorsNajlepsza 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.

