Вопрос по c, operators, conditional-operator, ternary-operator – Тернарный (условный) оператор в C

52

Зачем нужен условный оператор? Функционально это избыточно, поскольку оно реализует конструкцию if-else. Если условный оператор более эффективен, чем эквивалентное присваивание if-else, почему компилятор не может интерпретировать if-else более эффективно?

@toast: на самом деле «троичный» - довольно распространенное название, если не более обычное, то условное. vittore
@vittore: Просто распространяю некоторые мелочи, которые я приобрел через Джона Скита. Пытаюсь быть крутым по ассоциации и все такое. ;) Benjamin Autin
Это не избыточно. Вы можете использовать его во многих местах, где вы не можете поместить блок if, например, в объявлениях. Leo
Есть еще несколько вопросов относительно троичного оператора для получения дополнительной информации об их использовании. Dana the Sane
И это на самом деле называется условным оператором. Это единственный троичный оператор, но, как однажды напомнил мне Джон Скит, позже всегда может быть другой. Benjamin Autin

Ваш Ответ

15   ответов
11

ение.

Подчеркивающий аспект - это одно явное отличие, которое, я думаю, упустили из виду. Man Vs Code
153

выражение вместо заявления; то есть вы можете иметь его в правой части (RHS) заявления. Таким образом, вы можете написать определенные вещи более кратко.

И, благодаря этой функции, это отличный инструмент для того, чтобы сделать код более «функциональным» и менее «процедурным». Charles Bretana
@ Чарли: +1. Я упомянул об этом в моем, но это хорошо, чтобы сделать это в явном виде. John Feminella
Это точка. Он преобразует if / else в выражение, а не в выражение. Почему-то я подозреваю, что многие люди здесь не понимают разницу (пожалуйста, воздержитесь от комментариев, что ВЫ делаете, я не говорю с вами;)). Darren Clark
-3

Такой же как

if(0)
do();


if(0)
{
do();
}
10

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

показафункцииСтруктуры

Представьте, как бы выглядел ваш код без них, и вы можете найти свой ответ. Тернарный оператор - это форма «синтаксического сахара», которая, если ее использовать с осторожностью и умением, облегчает написание и понимание кода.

«Переносимость предназначена для людей, которые не могут писать новые программы». - Линус Торвальдс Chris Lutz
Чтобы продолжить аргумент, нам вообще не нужен C, потому что мы можем сделать все необходимое с помощью ассемблера. Ether
61

а не сокращение производительности. Люди разделяются по достоинству для условных выражений различной сложности, но для кратких условий может быть полезно иметь однострочное выражение.

Более того, так как это выражение, какЧарли Мартин написалэто означает, что оно может появиться в правой части оператора в C. Это важно для краткости.

Тернарный оператор также уменьшает цикломатическую сложность кода. Akshay Immanuel D
@AkshayImmanuelD ⇒ Тернарные операторы не уменьшают цикломатическую сложность. Количество путей в коде одинаково независимо от того, используете ли вы троичный оператор или оператор if. John Feminella
Производительность была одним из ее преимуществ при появлении сложных процессоров. Вам не нужно было выгружать весь процессорный канал, чтобы взять ветвь, а затем, возможно, выполнить дополнительную копию, вместо этого часто можно просто вставить одно готовое значение в канал. Кроме того, оно часто более читабельно для многострочных выражений, чем что-то вроде 'if (A) return ret1; иначе, если (B) вернуть ret2; ... '. Там нет ничего сложного для чтения ... вернуть A? ret0: B? ret1: C? ret2: D? ret3; dwn
0

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

Допустим, мы хотим реализовать функциональный макрос, который возвращает самый большой из двух параметров. Затем он будет назван как, например:

int x = LARGEST(1,2);

Единственный способ реализовать это как функциональный макрос -

#define LARGEST(x,y) ((x) > (y) ? (x) : (y))

Это было бы невозможно сif ... else заявление, так как он не возвращает значение результата.Запись)

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

switch(something)
{
  case A: 
    if(x == A)
    {
      array[i] = x;
    }
    else
    {
      array[i] = y;
    }
    break;

  case B: 
    if(x == B)
    {
      array[i] = x;
    }
    else
    {
      array[i] = y;
    }
    break;
  ...
}

Это можно заменить гораздо более читабельным

switch(something)
{
  case A: array[i] = (x == A) ? x : y; break;
  case B: array[i] = (x == B) ? x : y; break;
  ...
}

Обратите внимание, что?: делаетникогда привести к более быстрому коду, чемif-else, Это какой-то странный миф, созданный смущенными новичками. В случае оптимизированного кода,?: дает такую же производительность, какif-else в подавляющем большинстве случаев.

Если что-то,?: может бытьпомедленнее чемif-elseпотому что это идет с обязательными неявными продвижениями типа, даже операнда, который не будет использоваться. Но?: никогда не может быть быстрее, чемif-else.

Запись) Теперь, конечно, кто-то поспорит и удивится, почему бы не использовать функцию. Действительно, если вы можете использовать функцию, этовсегда предпочтительнее, чем функциональный макрос. Но иногда вы не можете использовать функции. Предположим, например, чтоx в приведенном выше примере объявляется в области видимости файла. Тогда инициализатор должен быть константным выражением, поэтому он не может содержать вызов функции. Другие практические примеры использования функционально-подобных макросов включают в себя безопасное программирование типов с_Generic или "X макросы".

9

когда вы хотите, чтобы результат троичного числа был l-значением.

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

const char* appTitle  = amDebugging ? "DEBUG App 1.0" : "App v 1.0";

Хотя я бы предостерег одну вещь - связать троицы вместе. Они становятся настоящими
проблема во время обслуживания:

int myVal = aIsTrue ? aVal : bIsTrue ? bVal : cIsTrue ? cVal : dVal;

РЕДАКТИРОВАТЬ: Вот потенциально лучший пример. Вы можете использовать троичный оператор для назначения ссылок и константных значений, где в противном случае вам нужно было бы написать функцию для его обработки:

int getMyValue()
{
  if( myCondition )
    return 42;
  else
    return 314;
}

const int myValue = getMyValue();

...мог стать:

const int myValue = myCondition ? 42 : 314;

Что лучше, это спорный вопрос, который я предпочитаю не обсуждать.

Даже одно использование может привести к ошибкам. Показательный пример: ваша версия будет иметь название «DEBUG App 1.0». Michael Myers♦
Я согласен с поркой, но я нашел это странно читабельным. :) Конечно, в тестовом примере с выровненными по алфавиту переменными. Darren Clark
Да, это становится очень неприятно, когда вы начинаете ставить вещи в скобках. John Dibling
2

чем обычный оператор if, это может быть критично во встроенных приложениях, но также оптимизация компилятора может уменьшить эту разницу.

Вау, какая мусорная статья!(Status.data & 0x10 == 0x10) этораспространенная ошибка на уровне начинающего из-за приоритета оператора, где == имеет более высокий приоритет над &. Эта ошибка настолько распространена, что любой, кто имеет реальный опыт программирования на встроенном Си, сразу ее обнаружит. Затем он продолжает обсуждатьнеоптимизированная машинный код его глючного беспорядка. Я понятия не имею, кто такой «Джейкоб», но он некомпетентен и не должен писать блоги. Lundin
Пожалуйста, проверьте этоbeningo.com/ternary-operator-versus-the-ifelse-statement Вы найдете разницу в сгенерированной сборке для ARM. Измененная сборка приведет к разной производительности при разных рабочих нагрузках и ситуациях وليد تاج الدين
Этот ответ полная чушь. Lundin
4

lse, которые содержат только одно утверждение. Функционально обе конструкции должны работать одинаково.

1

производительность была одним из ее преимуществ во время появления сложных процессоров, блог MSDNНе классическое поведение процессора: как делать что-то быстрее, чем не делать дает пример, который четко говорит разницу между тернарным (условным) оператором и оператором if / else.

дать следующий код:

#include <windows.h>
#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>

int array[10000];

int countthem(int boundary)
{
 int count = 0;
 for (int i = 0; i < 10000; i++) {
  if (array[i] < boundary) count++;
 }
 return count;
}

int __cdecl wmain(int, wchar_t **)
{
 for (int i = 0; i < 10000; i++) array[i] = rand() % 10;

 for (int boundary = 0; boundary <= 10; boundary++) {
  LARGE_INTEGER liStart, liEnd;
  QueryPerformanceCounter(&liStart);

  int count = 0;
  for (int iterations = 0; iterations < 100; iterations++) {
   count += countthem(boundary);
  }

  QueryPerformanceCounter(&liEnd);
  printf("count=%7d, time = %I64d\n",
         count, liEnd.QuadPart - liStart.QuadPart);
 }
 return 0;
}

Стоимость за разные границы сильно отличается и странна (см. оригинальный материал). в то время как если изменить:

 if (array[i] < boundary) count++;

в

 count += (array[i] < boundary) ? 1 : 0;

Время выполнения теперь не зависит от граничного значения, поскольку:

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

но на моем настольном компьютере Intel i5 cpu / windows 10 / vs2015 мой результат теста совершенно отличается от блога msdn.

при использовании режима отладки, если / еще стоимость:

count=      0, time = 6434
count= 100000, time = 7652
count= 200800, time = 10124
count= 300200, time = 12820
count= 403100, time = 15566
count= 497400, time = 16911
count= 602900, time = 15999
count= 700700, time = 12997
count= 797500, time = 11465
count= 902500, time = 7619
count=1000000, time = 6429

и стоимость троичного оператора:

count=      0, time = 7045
count= 100000, time = 10194
count= 200800, time = 12080
count= 300200, time = 15007
count= 403100, time = 18519
count= 497400, time = 20957
count= 602900, time = 17851
count= 700700, time = 14593
count= 797500, time = 12390
count= 902500, time = 9283
count=1000000, time = 7020 

при использовании режима релиза, если / еще стоимость:

count=      0, time = 7
count= 100000, time = 9
count= 200800, time = 9
count= 300200, time = 9
count= 403100, time = 9
count= 497400, time = 8
count= 602900, time = 7
count= 700700, time = 7
count= 797500, time = 10
count= 902500, time = 7
count=1000000, time = 7

и стоимость троичного оператора:

count=      0, time = 16
count= 100000, time = 17
count= 200800, time = 18
count= 300200, time = 16
count= 403100, time = 22
count= 497400, time = 16
count= 602900, time = 16
count= 700700, time = 15
count= 797500, time = 15
count= 902500, time = 16
count=1000000, time = 16

троичный оператор работает медленнее, чем оператор if / else на моей машине!

таким образом, согласно различным методам оптимизации компилятора, оператор ternal и if / else могут вести себя по-разному.

8

что троичный оператор является выражением, а не оператором, позволяет использовать его в расширениях макросов для функционально-подобных макросов, которые используются как часть выражения. Const, возможно, не был частью оригинального C, но препроцессор макросов уходит в прошлое.

Одно из мест, где я видел его, - это пакет массивов, в котором используются макросы для доступа к проверенным массивам. Синтаксис для проверенной ссылки был что-то вродеaref(arrayname, type, index)где arrayname было фактически указателем на структуру, которая включала границы массива и массив без знака для данных, тип был фактическим типом данных, а индекс был индексом. Расширение этого было довольно сложным (и я не собираюсь делать это по памяти), но для проверки границ использовались некоторые троичные операторы.

Вы не можете сделать это как вызов функции в C из-за необходимости полиморфизма возвращаемого объекта. Таким образом, макрос был необходим для приведения типов в выражении. В C ++ вы можете сделать это как шаблонный перегруженный вызов функции (возможно, для operator []), но C не имеет таких возможностей.

Редактировать: Вот пример, о котором я говорил, из пакета массива Berkeley CAD (версия 1.4, версия 1.4). Документация по использованию array_fetch:

type
array_fetch(type, array, position)
typeof type;
array_t *array;
int position;

Получить элемент из массива. Ошибка времени выполнения возникает при попытке ссылки за пределы массива. Нет проверки типа того, что значение в данной позиции в действительности соответствует типу, используемому при разыменовании массива.

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

#define array_fetch(type, a, i)         \
(array_global_index = (i),              \
  (array_global_index >= (a)->num) ? array_abort((a),1) : 0,\
  *((type *) ((a)->space + array_global_index * (a)->obj_size)))

Раскрытие для array_insert (который при необходимости увеличивает массив, как вектор C ++) даже более сложное, включая несколько вложенных тернарных операторов.

0

чтения.

36

Это важно для обфускации кода, например:

Look->       See?!

No
:(
Oh, well
);
Примечание: чтобы компилировать приведенный выше код, просто добавьте struct {int See;} * Look; int No, Oh, хорошо; int main () {/ * приведенный выше код входит сюда * /} Artelius
8

о единственном способе стать умнымprintf Заявления заключается в использовании тернарного оператора:

printf("%d item%s", count, count > 1 ? "s\n" : "\n");

Предостережение: при переходе с C на C ++ существуют некоторые различия в приоритетах операторов, и они могут быть удивлены обнаруженными незначительными ошибками.

82

что никто не упомянул, что это может быть использовано для обеспечения соблюденияconst Корректность в компактном виде.

Что-то вроде этого:

const int n = (x != 0) ? 10 : 20;

так в основномn этоconst чье начальное значение зависит от условия условия. Самая простая альтернатива - сделатьn неconstПозволит обычномуif инициализировать это. Но если вы хотите, чтобы это былоconst, это не может быть сделано с обычнымif, Лучшая замена, которую вы могли бы сделать, это использовать вспомогательную функцию, подобную этой:

int f(int x) {
 ,   if(x != 0) { return 10; } else { return 20; }
}

const int n = f(x);

но троичная версия if гораздо более компактна и, возможно, более читабельна.

Ну констсделал Приходите через 25 лет после условного оператора. Это милый трюк. Charlie Martin

Похожие вопросы