Преимущества использования условного оператора?: (Тернарный)


101

Каковы преимущества и недостатки оператора?: По сравнению со стандартным оператором if-else. Очевидные из них:

Условный?: Оператор

  • Короче и лаконичнее при работе с прямыми сравнениями и присвоениями значений
  • Не кажется таким гибким, как конструкция if / else

Стандартный If / Else

  • Может применяться к большему количеству ситуаций (например, к вызовам функций)
  • Часто излишне длинные

Читаемость, кажется, различается для каждого в зависимости от утверждения. Некоторое время после первого знакомства с оператором?: Мне потребовалось время, чтобы понять, как именно он работает. Вы бы порекомендовали использовать его везде, где это возможно, или придерживаться if / else, учитывая, что я работаю со многими непрограммистами?


8
Вы уже поняли суть.
Байрон Уитлок,

1
@Nicholas Knight: Я предполагаю, что OP означает, что вы не можете этого сделать, например, SomeCheck() ? DoFirstThing() : DoSecondThing();вы должны использовать выражение для возврата значения.
Дэн Тао

6
Используйте его там, где это ясно , используйте if / else, если это не так. Ясность кода должна быть вашим главным соображением.
hollsk

8
Ты видел '??' все же? Серьезно, если вы думаете, что тернары - это круто ...
pdr

3
+1 за то, что он не назвал его просто «тернарным оператором», как это делают многие. Несмотря на то, что это единственный тернарный (в отличие от унарного и двоичного) оператор в C #, это не его имя.
John M Gant

Ответы:


122

Я бы в основном рекомендовал использовать его только тогда, когда результирующий оператор очень короткий и представляет собой значительное увеличение краткости по сравнению с эквивалентом if / else без ущерба для читабельности.

Хороший пример:

int result = Check() ? 1 : 0;

Плохой пример:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;

5
Хороший ответ, но для протокола, это «краткость».
mqp

6
@mquander, ты в этом уверен? merriam-webster.com/dictionary/concise
Байрон Уитлок,

39
Я всегда начинаю с простого и со временем усложняю его, пока он не станет полностью нечитаемым.
Jouke van der Maas,

9
Читаемость во втором примере может быть легко исправлена ​​с помощью лучшего форматирования. Но, как рекомендует ОП, все сводится к удобочитаемости и лаконичности по сравнению с многословием.
Натан Эрнст,

4
Это не часть вопроса OP, но важно отметить тот факт, что вы не можете returnбыть частью результата тернарной операции. Например: check() ? return 1 : return 0;не пойдет, но return check() ? 1 : 0;будет. Всегда весело находить эти маленькие причуды в программировании.
CSS

50

Это в значительной степени покрывается другими ответами, но «это выражение» на самом деле не объясняет, почему это так полезно ...

В таких языках, как C ++ и C #, вы можете определять локальные поля только для чтения (в теле метода), используя их. Это невозможно с обычным оператором if / then, потому что значение поля readonly должно быть присвоено в этом единственном операторе:

readonly int speed = (shiftKeyDown) ? 10 : 1;

не то же самое, что:

readonly int speed;  
if (shifKeyDown)  
    speed = 10;    // error - can't assign to a readonly
else  
    speed = 1;     // error  

Аналогичным образом вы можете встроить третичное выражение в другой код. Это не только делает исходный код более компактным (и в некоторых случаях более читабельным), но и делает сгенерированный машинный код более компактным и эффективным:

MoveCar((shiftKeyDown) ? 10 : 1);

... может генерировать меньше кода, чем необходимость дважды вызывать один и тот же метод:

if (shiftKeyDown)
    MoveCar(10);
else
    MoveCar(1);

Конечно, это также более удобная и краткая форма (меньше набора текста, меньше повторений и может уменьшить вероятность ошибок, если вам нужно дублировать фрагменты кода в if / else). В чистых случаях с «обычным шаблоном» вроде этого:

object thing = (reference == null) ? null : reference.Thing;

... он просто быстрее читать / анализировать / понимать (как только вы к нему привыкнете), чем многословный эквивалент if / else, поэтому он может помочь вам быстрее «разобрать» код.

Конечно, то, что это полезно , не означает, что его лучше всего использовать в каждом случае. Я бы посоветовал использовать его только для коротких фрагментов кода, где смысл ясен (или сделан более понятным) с помощью ?:- если вы используете его в более сложном коде или вкладываете тернарные операторы друг в друга, это может сделать код ужасно трудным для чтения .


@JaminGrey "это не означает, что при создании константы ей присваивается значение 10 или 1." Вы имеете в виду, что это значит? Неправильные комментарии могут вызвать большее замешательство у начинающих программистов на C ++, чем проблема, которую вы пытались
решить

5
Для будущих читателей, которые столкнутся с этим, « const int speed = (shiftKeyDown)? 10: 1; », это означает, что когда константа впервые создается , ей присваивается значение 10 или 1. Это не означает, что каждый раз к константе осуществляется доступ, она выполняет проверку. (На всякий случай, если новый программист на C ++ запутался)
Джамин Грей

2
... или, другими словами, a constявляется константой, т.е. его нельзя изменить после выполнения оператора, в котором он объявлен.
Джейсон Уильямс

1
@JaminGrey. Разве это не должно быть readonly? Я всегда думал, что это constозначает « разрешено во время компиляции и встроено везде, где используется ».
Nolonar

1
@ColinWiseman, это пример того, как можно использовать ?: . Я специально заявляю, что то, что вы можете это сделать, не означает, что это обязательно «лучший» вариант в любом конкретном случае. Чтобы решить эту проблему, читатель должен использовать свой мозг каждый раз, когда он сталкивается с ситуацией, когда он может быть им полезен.
Джейсон Уильямс

14

Я обычно выбираю тернарный оператор, если в противном случае у меня было бы много повторяющегося кода.

if (a > 0)
    answer = compute(a, b, c, d, e);
else
    answer = compute(-a, b, c, d, e);

С тернарным оператором это можно сделать следующим образом.

answer = compute(a > 0 ? a : -a, b, c, d, e); 

12
лично я бы aVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);особенно если b, c, dи eтребуется лечение тоже.
corsiKa

10
Зачем вообще использовать условное выражение в этом примере? Просто получите Abs (a) и вызовите compute () один раз.
Ash

2
Да, я создал не лучший пример. :)
Райан Брайт

Для новичка это не выглядит эквивалентным. Разве это не должно быть answer = compute (a> 0? A, b, c, d, e: -a, b, c, d, e); ?
pbreitenbach

@pbreitenbach: нет - это вопрос приоритета - первый аргумент compute(...)is a > 0 ? a : -1, который оценивается отдельно от других аргументов, разделенных запятыми. В любом случае, к сожалению, в C ++ отсутствует обозначение, которое задается в вашем вопросе для обработки «кортежей» значений, разделенных запятыми, поэтому даже a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)это недопустимо, и нет ничего похожего, что работало бы без изменений compute.
Тони Делрой

12

Я считаю, что это особенно полезно при веб-разработке, если я хочу установить для переменной значение, отправленное в запросе, если оно определено, или какое-то значение по умолчанию, если это не так.


3
Значения +1 по умолчанию в веб-разработчиках - отличный пример хорошего места для использования тернарного оператора
Байрон Уитлок,

11

Действительно классное использование:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;

10
Будьте осторожны с этим в PHP, тернарный оператор неправильно связывает в PHP. По сути, если fooложно, тогда все будет оценено до 4 без выполнения других тестов.
Том Басби

4
@TomBusby - Вау. Еще одна причина ненавидеть PHP, если вы уже ненавидите PHP.
Тодд Леман,

6

Условный оператор отлично подходит для коротких условий, например таких:

varA = boolB ? valC : valD;

Я использую его время от времени, потому что на то, чтобы написать что-то таким образом, требуется меньше времени ... к сожалению, это ветвление иногда может быть пропущено другим разработчиком, просматривающим ваш код. Кроме того, код обычно не такой короткий, поэтому я обычно улучшаю читаемость, добавляя? и: в отдельных строках, например:

doSomeStuffToSomething(shouldSomethingBeDone()
    ? getTheThingThatNeedsStuffDone()
    : getTheOtherThingThatNeedsStuffDone());

Однако большое преимущество использования блоков if / else (и почему я предпочитаю их) состоит в том, что проще зайти позже и добавить дополнительную логику в ветку,

if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else {
doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

или добавьте другое условие:

if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else if (shouldThisOtherThingBeDone()){
    doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

Итак, в конце концов, речь идет об удобстве для вас сейчас (короче:?) По сравнению с удобством для вас (и других) позже. Это суждение ... но, как и все другие проблемы с форматированием кода, единственное реальное правило - быть последовательным и быть визуально вежливым по отношению к тем, кто должен поддерживать (или оценивать!) Ваш код.

(весь код скомпилирован на глаз)


5

При использовании тернарного оператора следует понимать, что это выражение, а не утверждение.

В функциональных языках, таких как схема, различия не существует:

(если (> ab) ab)

Условный?: Оператор «Кажется, не так гибок, как конструкция if / else»

В функциональных языках это так.

При программировании на императивных языках я применяю тернарный оператор в ситуациях, когда я обычно использую выражения (присваивание, условные операторы и т. Д.).


5

Хотя приведенные выше ответы действительны, и я согласен с важностью удобочитаемости, есть еще два момента, которые следует учитывать:

  1. В C # 6 вы можете иметь методы, воплощающие выражения.

Это делает использование тернарного типа особенно кратким:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. Когда дело доходит до неявного преобразования типов, поведение отличается.

Если у вас есть типы, T1и T2оба они могут быть неявно преобразованы в T, то приведенное ниже не работает:

T GetT() => true ? new T1() : new T2();

(поскольку компилятор пытается определить тип тернарного выражения, а преобразование между T1и отсутствует T2).

С другой стороны, if/elseверсия ниже работает:

T GetT()
{
   if (true) return new T1();
   return new T2();
}

потому что T1преобразован в Tи такT2


5

Иногда это может облегчить чтение с первого взгляда:

// With
button.IsEnabled = someControl.HasError ? false : true;

// Without
button.IsEnabled = !someControl.HasError;

4

Если я устанавливаю значение и знаю, что для этого всегда будет одна строка кода, я обычно использую тернарный (условный) оператор. Если есть вероятность, что мой код и логика изменятся в будущем, я использую if / else, поскольку это более понятно другим программистам.

Возможно, вас еще интересует ?? оператор .


4

Преимущество условного оператора в том, что это оператор. Другими словами, он возвращает значение. Поскольку ifэто инструкция, она не может возвращать значение.


4

Я бы рекомендовал ограничить использование тернарного оператора (? :) простым однострочным назначением логики if / else. Что-то похожее на этот узор:

if(<boolCondition>) {
    <variable> = <value>;
}
else {
    <variable> = <anotherValue>;
}

Может быть легко преобразован в:

<variable> = <boolCondition> ? <value> : <anotherValue>;

Я бы избегал использования тернарного оператора в ситуациях, требующих if / else if / else, вложенной логики if / else или if / else, которая приводит к оценке нескольких строк. Применение тернарного оператора в этих ситуациях, вероятно, приведет к нечитаемому, запутанному и неуправляемому коду. Надеюсь это поможет.


2

Есть некоторый выигрыш в производительности от использования оператора? оператор в например. MS Visual C ++, но это действительно специфическая вещь для компилятора. В некоторых случаях компилятор может оптимизировать условную ветвь.


2

Сценарий, который я чаще всего использую, - это значения по умолчанию и особенно при возврате

return someIndex < maxIndex ? someIndex : maxIndex;

Это действительно единственные места, которые мне нравятся, но я люблю их.

Хотя, если вы ищете логическое значение, это может иногда выглядеть уместным:

bool hey = whatever < whatever_else ? true : false;

Потому что это так легко читать и понимать, но эту идею всегда следует отбрасывать в пользу более очевидного:

bool hey = (whatever < whatever_else);

2

Если вам нужно несколько веток с одним и тем же условием, используйте if:

if (A == 6)
  f(1, 2, 3);
else
  f(4, 5, 6);

Если вам нужно несколько ветвей с разными условиями, тогда, если количество операторов будет снежным комом, вы захотите использовать тройной:

f( (A == 6)? 1: 4, (B == 6)? 2: 5, (C == 6)? 3: 6 );

Кроме того, вы можете использовать тернарный оператор при инициализации.

const int i = (A == 6)? 1 : 4;

Делать это с if очень беспорядочно:

int i_temp;
if (A == 6)
   i_temp = 1;
else
   i_temp = 4;
const int i = i_temp;

Вы не можете поместить инициализацию внутри if / else, потому что это меняет область видимости. Но ссылки и константные переменные могут быть связаны только при инициализации.


2

Тернарный оператор может быть включен в rvalue, тогда как if-then-else не может; с другой стороны, if-then-else может выполнять циклы и другие операторы, тогда как тернарный оператор может выполнять только (возможно, пустые) rvalue.

В связи с этим, && и || Операторы позволяют использовать некоторые шаблоны выполнения, которые труднее реализовать с помощью if-then-else. Например, если у кого-то есть несколько функций для вызова и он хочет выполнить фрагмент кода в случае сбоя какой-либо из них, это можно сделать с помощью оператора &&. Выполнение этого без этого оператора потребует либо избыточного кода, либо goto, либо дополнительной переменной-флага.


1

В C # 7 вы можете использовать новую функцию ref locals для упрощения условного присвоения ref-совместимых переменных. Итак, теперь вы можете не только:

int i = 0;

T b = default(T), c = default(T);

// initialization of C#7 'ref-local' variable using a conditional r-value⁽¹⁾

ref T a = ref (i == 0 ? ref b : ref c);

... но также чрезвычайно замечательный:

// assignment of l-value⁽²⁾ conditioned by C#7 'ref-locals'

(i == 0 ? ref b : ref c) = a;

Эта строка кода присваивает значение aлибо bили c, в зависимости от значения i.



Примечания
1. Значение r - это правая часть присваивания, значение, которое присваивается.
2. l-значение - это левая часть присваивания, переменная, которая получает присвоенное значение.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.