Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

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

Некоторые из других ответов великолепны.Но я удивлен, что никто не упомянул, что его можно использовать для обеспечения соблюдения законов. 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 гораздо более компактна и, возможно, более читабельна.

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

Look->       See?!

No
:(
Oh, well
);

Компактность и возможность встраивания конструкции if-then-else в выражение.

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

  1. пока
  2. для
  3. функции
  4. структуры

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

Иногда тернарный оператор — лучший способ выполнить работу.В частности, если вы хотите, чтобы результатом троичного числа было 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;

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

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

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

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

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

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

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

Редактировать:Вот пример, о котором я говорил, из пакета Berkeley CAD array package (glu 1.4 edition).Документация по использованию 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 ++) еще более сложное, поскольку включает несколько вложенных тернарных операторов.

Это синтатический сахар и удобное сокращение для кратких блоков if/else, содержащих только один оператор.Функционально обе конструкции должны работать одинаково.

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

как сказал dwn, производительность была одним из преимуществ во время появления сложных процессоров, блог 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/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 на моей машине!

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

троичный = простая форма if-else.Он доступен в основном для удобства чтения.

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

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

    int x = LARGEST(1,2);
    

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

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

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

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

    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 макросы".

Такой же как

if(0)
do();


if(0)
{
do();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top