Тернарна умовна операція

Тернарна умовна операція (лат. ternarius — «потрійний») (зазвичай записується як ?:) — у багатьох мовах програмування операція, яка повертає свій другий або третій операнд залежно від значення логічного виразу, заданого першим операндом. Як випливає з назви, тернарна операція приймає всього три вказаних операнди. Аналогом тернарної умовної операції в математичній логіці і булевій алгебрі є умовна диз'юнкція, яка записується у вигляді і реалізує алгоритм: «Якщо , то , інакше », що можна переписати як « або , залежно від або не ».

Зазвичай тернарна умовна операція асоціюється з операцією ?:, яка використовується в сі-подібних мовах програмування. Насправді, подібні операції з іншим синтаксисом є і в багатьох далеких за синтаксисом від Сі мовах програмування. До найбільш популярних мов, що містять тернарну умовну операцію, можна віднести C, C++, JavaScript, Swift, Objective-C, C#, D, Java, Perl, PHP, Python, Tcl, Ruby, Verilog, Turbo Basic та інші. Своєю появою безпосередньо в тернарній формі ця операція зобов'язана мові Алгол-60, у якій вона мала синтаксис if o1 then o2 else o3 і потім мови BCPL (o1 -> o2, o3)[1] замість звичного тепер o1 ? o2 : o3. Прототипом цієї операції також є умовна функція cond мови Лісп, яка записується за правилами Ліспа в префіксній формі і має довільну кількість аргументів.

Визначення ред.

Безвідносно до певної мови програмування тернарну операцію можна визначити так:

логічний вираз ? вираз 1 : вираз 2

Алгоритм роботи операції наступний:

  1. Обчислюється логічний вираз.
  2. Якщо логічний вираз істинний, то обчислюється значення виразу вираз 1, в іншому разі — значення виразу вираз 2.
  3. Обчислене значення повертається.

Потрібно звернути увагу, що обчислюється тільки один з виразів: вираз 1 або вираз 2. Це відповідає принципу лінивих обчислень, і зроблено не так для оптимізації, як для розширення можливостей: так, вираз x < 0 ? 0 : sqrt(x) абсолютно коректний, незважаючи на те, що корінь з від'ємних чисел не береться.

Використання і реалізація ред.

Тернарна умовна операція використовується у виразах для отримання одного з двох варіантів залежно від умови.

alarm_time = today in [SUNDAY, MONDAY] ? 12.00 : 8.00

У цьому прикладі умовного електронного будильника виставляється час, коли він повинен дзвонити, залежно від дня тижня. Потрібно зауважити, що приклад знову наведений для деякої абстрактної алгоритмічної мови програмування.

У наступному прикладі обчислюється значення найпростішого дельта-символу.

y = x == 0 ? 1 : 0

У наступному прикладі дана операція використана в ситуації, не пов'язаній з присвоюванням:

sprintf(
  Title,
  "%s %s",
  tv_system == TV_PAL ?
    "PAL" :
    "SECAM",
  tv_input ?
    Tv_Name[ tv_input - 1 ]:
    "TEST"
);

У цьому разі еквівалентна конструкція з використанням ifthenelse вимагала б запису виклику функції sprintf чотири рази. Або, як альтернатива, треба було б написати аналогічний за призначенням (але формально не еквівалентний) код з використанням двох додаткових змінних або декількох послідовних викликів sprintf.

С ред.

У Сі тернарна операція має наступний синтаксис:[2]

o1 ? o2 : o3

Як відомо,[кому?] у Сі немає логічного типу даних (у C99 з'явився логічний тип _Bool). Тому операнд o1 повинен бути числом (цілим або дійсним) або вказівником. Спочатку обчислюється саме його значення. Воно порівнюється з нулем і, якщо воно не дорівнює нулю, обчислюється і повертається o2, у разі рівності — o3. Операнди o2 і o3 можуть бути різних, кажучи загалом, незбіжних типів, зокрема void.

У наступному прикладі обчислюється мінімальне з чисел a і b:

min = (a < b) ? a : b;

C++ ред.

У C++ тернарна умовна операція має той самий синтаксис, що й у Сі.[3] Однак через наявність різниці між ініціалізацією і присвоюванням, бувають ситуації, коли операцію ?: не можна замінити конструкцією ifthenelse, як, наприклад, у наступному випадку:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(int argc, char** argv)
{
    string name;
    ofstream fout;
    if (argc > 1 && argv[1])
    {
        name = argv[1];
        fout.open(name.c_str(), ios::out | ios::app);
    }
    ostream& sout = name.empty() ? cout : fout;
    return 0;
}

Тут змінна sout ініціалізується в момент оголошення результатом роботи тернарной операції. Подібного ефекту не вдалося б досягти простим присвоюванням у тому чи іншому випадку.

Крім того, тернарна умовна операція може бути застосована в лівій частині оператора присвоєння:

0. #include <iostream>
1. int main () 
2. {
3.     int a=0, b=0;
4. 
5.     const bool cond = ...;
6.     (cond ? a : b) = 1;
7.     std::cout << "a=" << a << ','
8.               << "b=" << b << '\n';
9. }

У цьому прикладі, якщо логічна змінна cond у рядку 5 міститиме значення true, то значення 1 буде присвоєно змінній a, інакше, воно буде присвоєно змінній b.

Python ред.

a = 42
b = 41
result = a if a > b else b
assert result == 42

Також можна реалізувати через список:

[<вираз 1>, <вираз 2>][<умова>]

Повернеться результат вираз 1, якщо умова хибна; і вираз 2, якщо умова істинна. Якщо умова не буде булевими виразом, можливий вихід за межі списку з винятком.

PHP ред.

 $a = 1==0 ? "first value" :
      (2==0 ? "second value" :
      (3==3 ? "result value" : "default value"));

Тернарний оператор у PHP еквівалентний більш довгій конструкції ifelse. Наступні два приклади еквівалентні:

// Перший приклад
$result = isset($a) ? $a : 'DefaultValue';
// Дргуий приклад
if (isset($a)) {
    $result = $a;
} else {
    $result = 'DefaultValue';
}

Такі конструкції часто застосовуються, щоб у будь-якому разі ініціалізувати змінну для наступних обчислень (інакше PHP видасть помилку рівня E_NOTICE).

Починаючи з версії 5.3 з'явилася можливість не вказувати другий параметр операції. Наприклад, два наступних записи еквівалентні:

 $Variable = $_GET['Parameter'] ? $_GET['Parameter'] : 'DefaultValue';
 $Variable = $_GET['Parameter'] ?: 'DefaultValue';

JavaScript ред.

var a = 1==0 ? "first value" : 
        2==0 ? "second value" :
        3==3 ? "result value" : "default value"

Ruby ред.

Загальний синтаксис аналогічний C-подібним мовам.

print true ? "true" : "false"  # Виведе true в стандартний вивід

C# ред.

На тернарну операцію накладаються додаткові обмеження, пов'язані з типобезпекою. Вирази 1 і 2 повинні бути одного типу. Це призводить до наступного:

int a = 1;
double b = 0.0;
int nMax = (a>b) ? a : b;

Такий вихідний код не компілюватиметься незважаючи на те, що в кінцевому підсумку значення nMax дорівнюватиме а. Оскільки a і b повинні бути одного типу, a підвищиться до double, щоб відповідати b. Тип результівного значення тернарної операції виявляється double, і цей тип повинен бути знижений до int під час присвоєння:[4]

int a = 1;
double b = 0.0;
int nMax;
// Можна вчинити так:
nMax = (int) ((a>b) ? a : b) ;
// ...або так
nMax = (a>b) ? a : (int)b;

Visual Basic ред.

У класичній версії мови існує тернарний оператор у вигляді функції IIf(Expr, TruePart, FalsePart). Ця функція має певну особливість, яка полягає в тому, що під час оцінки виразу Expr, також будуть обчислюватися TruePart і FalsePart, незалежно від результату виразу: істинний він чи хибний. Це може призвести до несподіваних результатів, а іноді й до вповільнення виконання коду, якщо в ролі значень буде виклик функцій з тривалими операціями.

Dim iCount As Long

Public Sub Main()
    iCount = 1
    MsgBox IIf(1 = 1, FuncYes, FuncNo)
    
    'Змінна iCount буде містити "3", оскільки обидві функції будуть виконані
    MsgBox iCount
End Sub

Public Function FuncYes() As String
    iCount = iCount + 1
    FuncYes = "Так"
End Function

Public Function FuncNo() As String
    iCount = iCount + 1
    FuncNo = "Ні"
End Function

Для заміни функції IIf можна переписати вираз в один рядок, але це не буде аналогом функції, а буде лише коротка форма запису оператора розгалуження

If Expr Then TruePart Else FalsePart

З появою VB.NET, у синтаксис мови був доданий звичний тернарний оператор і записується він як If(Expr, TruePart, FalsePart). Цей оператор використовує скорочені обчислення, на відміну від функції IIf, яка також для сумісності з попередніми версіями доступна розробнику.[5]

Turbo Basic ред.

Синтаксис[6]: IF logic_expression [<> 0] [,] THEN statement(s) [ELSE statement(s)]

Будь-який результат logic_expression не рівний 0 вважається %FALSE, але не %TRUE, рівний тільки -1. logic_expression може бути числовим (numeric), так і символьним (string). У разі символьного виразу обчислення проводяться з ASCII-кодами символів.

%TRUE = -1
%FALSE = 0
A$ = "M"
B$ = "N"
C! = 43
D# = 44
IF A$>B$ <> %FALSE, THEN RESULT# = C! ELSE RESULT# = D#
PRINT RESULT#

За допомогою функції FN IfThenElse(X1,X2,X3), замість інфіксного виду тернарного оператора If Then X1 X2 Else X3 можна користуватися префіксним видом тернарного оператора IfThenElse:

A$ = "M"
B$ = "N"
C! = 43
D# = 44

COND = A$<B$   'COND = любое logic_expression
PRINT "FN IfThenElse(X1,X2,X3) =";FN IfThenElse(COND,C!,D#) 
END

DEF FN IfThenElse(X1,X2,X3) 
  IF X1 <> 0 THEN FN IfThenElse = X2 ELSE FN IfThenElse = X3
END DEF

Примітки ред.

  1. BCPL Ternary operator (page 15). BCPL Reference Manual. Архів оригіналу за 31 березня 2012. Процитовано 28 вересня 2017. 
  2. Ю. Ю. Громов, С. И. Татаренко. Программирование на языке СИ.
  3. Б. Страуструп. Мова програмування С++.
  4. Оператор ?: (C#) // https://msdn.microsoft.com/ru-ru/library/ty67wk28.aspx
  5. Оператор If (Visual Basic) // https://msdn.microsoft.com/ru-ru/library/bb513985.aspx
  6. Borland Turbo BASIC Owners Handbook 1987