2009-04-17 42 views
42

¿Cuál es la necesidad del operador condicional? Funcionalmente es redundante, ya que implementa una construcción if-else. Si el operador condicional es más eficiente que la asignación if-else equivalente, ¿por qué el compilador no puede interpretar if-else de manera más eficiente?El operador ternario (condicional) en C

+0

Hay muchas otras preguntas sobre el operador ternario para obtener más información sobre su uso. –

+7

Y en realidad se llama el operador condicional. Resulta ser el único operador ternario, pero como Jon Skeet me recordó una vez, siempre podría haber otro más tarde. –

+1

@toast: en realidad 'ternario' es un nombre bastante común para él, si no es más habitual que el condicional – vittore

Respuesta

57

El operador ternario es una conveniencia sintáctica y de legibilidad, no un atajo de rendimiento. Las personas se dividen en los méritos por condicionales de complejidad variable, pero para condiciones breves, puede ser útil tener una expresión de una línea.

Además, dado que es una expresión, como Charlie Martin wrote, eso significa que puede aparecer en el lado derecho de una declaración en C. Esto es valioso por ser conciso.

+4

El rendimiento fue uno de sus beneficios durante el surgimiento de los procesadores complejos. No tenía que volcar toda la tubería del procesador para tomar una rama y luego posiblemente realizar una copia adicional, en su lugar, a menudo solo podía insertar un solo valor listo en la tubería. Además, a menudo es más fácil de leer para expresiones de líneas múltiples que algo como 'si (A) return ret1; de lo contrario, si (B) devuelve ret2; ... '. No hay nada difícil de leer en ... return A? ret0: B? ret1: C? ret2: D? ret3; – dwn

+0

El operador ternario también reduce la Complejidad ciclomática del código. –

+1

@AkshayImmanuelD ⇒ Los operadores terciarios no reducen la complejidad ciclomática. El número de rutas a través del código es el mismo ya sea que use un operador ternario o una instrucción if. –

11

Compacidad y la capacidad de alinear un constructo if-then-else en una expresión.

+0

El aspecto inicial es una diferencia distinta de las otras que creo que han pasado por alto. –

4

Es azúcar sintáctica y una abreviatura útil para bloques breves if/else que solo contienen una declaración. Funcionalmente, ambos constructos deberían funcionar de manera idéntica.

10

Hay muchas cosas en C que no son técnicamente necesarias porque se pueden implementar más o menos fácilmente en términos de otras cosas. Aquí es una lista incompleta:

  1. mientras
  2. para
  3. funciones
  4. estructuras

imaginar lo que su código se vería como sin ellos y usted puede encontrar su respuesta. El operador ternario es una forma de "azúcar sintáctico" que, si se usa con cuidado y habilidad, facilita la escritura y el entendimiento del código.

+3

Para continuar con el argumento, realmente no necesitamos C en absoluto porque podemos hacer todo lo necesario con el ensamblador. – Ether

+0

"La portabilidad es para las personas que no pueden escribir nuevos programas". - Linus Torvalds –

146

En C, la verdadera utilidad de esto es que es una expresión en lugar de una declaración; es decir, puede tenerlo en el lado derecho (RHS) de una declaración. Entonces puedes escribir ciertas cosas más concisamente.

+17

Este es EL punto. Convierte un if/else en una expresión, NO una declaración. De alguna manera, sospecho que algunas personas aquí no entienden la diferencia (por favor, abstente de comentar que TÚ lo haces, no te estoy hablando;)). –

+3

@Charlie: +1. Mencioné esto en el mío, pero es bueno hacer de esto un punto explícito. –

+1

Y, debido a esta característica, es una gran herramienta para hacer que el código sea más "funcional" y menos "de procedimiento". –

0

ternary = forma simple de if-else. Está disponible principalmente para legibilidad.

9

A veces, el operador ternario es la mejor manera de hacer el trabajo. En particular, cuando quiere que el resultado del ternario sea un valor l.

Esto no es un buen ejemplo, pero me estoy quedando en blanco en algo mejor. Una cosa es cierta, no es frecuente cuando realmente necesitas usar el ternario, aunque todavía lo uso bastante.

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

Una cosa que me gustaría advertir es atar ternaries juntos. Se convierten en un verdadero problema a la hora de
maintennance:

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

EDITAR: He aquí un ejemplo potencialmente mejor. Se puede utilizar el operador ternario para asignar referencias & valores const donde de otro modo debería escribir una función para manejarlo:

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

const int myValue = getMyValue(); 

...podría convertirse en:

const int myValue = myCondition ? 42 : 314; 

Cuál es mejor es una pregunta debatible que elegiré no debatir.

+0

Estoy de acuerdo con la flagelación, pero me pareció extrañamente legible. :) Seguramente en el ejemplo de prueba con variables alineadas alfabéticamente. –

+0

Sí, se pone realmente desagradable cuando comienzas a poner las cosas entre paréntesis. –

+0

Incluso un solo uso puede provocar errores. Caso en cuestión: su versión de lanzamiento tendrá el título "DEBUG App 1.0". –

33

Es crucial para la ofuscación de código, así:

Look->  See?! 

No 
:(
Oh, well 
); 
+5

Nota: para hacer que el código anterior se compile, simplemente agregue struct {int See;} * Look; int No, Oh, bueno; int main() {/ * código anterior ingresa aquí * /} – Artelius

73

Algunas de las otras respuestas dadas son grandes. Pero me sorprende que nadie haya mencionado que se puede usar para ayudar a aplicar la corrección const de forma compacta.

Algo como esto:

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

así que básicamente es un nconst cuyo valor inicial es dependiente de una instrucción de condición. La alternativa más fácil es hacer que n no sea const, esto permitiría que un if ordinario lo inicialice. Pero si quiere que sea const, no se puede hacer con un if ordinario. El mejor sustituto que podría hacer sería utilizar una función de ayuda de esta manera:

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

const int n = f(x); 

pero el ternario si la versión es mucho más compacto y podría decirse que sea más legible.

+2

Bueno, const * did * vengan aproximadamente, oh, 25 años después del operador condicional. Aunque es un lindo truco. –

8

Puesto que nadie ha mencionado esto, sin embargo, la única manera de conseguir printf declaraciones inteligentes es utilizar el operador ternario:

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

Advertencia: Hay algunas diferencias en la prioridad de los operadores cuando se pasa de C a C++ y puede sorprenderse por la (s) falla (s) sutil (s) que surgen de la misma.

8

El hecho de que el operador ternario sea una expresión, no un enunciado, le permite ser utilizado en macroexpansiones para macros funcionales que se usan como parte de una expresión. Es posible que Const no haya formado parte de C original, pero el preprocesador macro se remonta a mucho tiempo atrás.

Un lugar donde lo he visto utilizado está en un paquete de matriz que usaba macros para accesos de matriz verificados. La sintaxis para una referencia comprobada era algo así como aref(arrayname, type, index), donde arrayname era en realidad un puntero a una estructura que incluía los límites de la matriz y una matriz de caracteres sin signo para los datos, el tipo era el tipo real de los datos y el índice era el índice. La expansión de esto fue bastante peluda (y no voy a hacerlo de memoria), pero usó algunos operadores ternarios para hacer la verificación encuadernada.

No se puede hacer esto como una llamada de función en C debido a la necesidad de polimorfismo del objeto devuelto. Por lo tanto, se necesitaba una macro para hacer el tipo de conversión en la expresión. En C++ puede hacer esto como una llamada de función con sobrecarga de plantilla (probablemente para el operador []), pero C no tiene tales características.

Edición: Este es el ejemplo del que estoy hablando, del paquete de matriz CAD de Berkeley (edición glu 1.4). La documentación del uso array_fetch es:

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

Fetch un elemento de una matriz. Se produce un error de tiempo de ejecución en un intento de referencia fuera de los límites de la matriz .No hay verificación de tipo que el valor en la posición dada sea en realidad del tipo utilizado cuando desreferencia la matriz.

y aquí está la Defintion macro de array_fetch (nótese el uso del operador ternario y el operador secuenciación coma para ejecutar todas las subexpresiones con los valores correctos en el orden correcto como parte de una sola expresión):

#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))) 

La expansión de array_insert (que hace crecer la matriz si es necesario, como un vector de C++) es incluso más peluda, involucrando múltiples operadores ternarios anidados.

-2

El mismo que

if(0) 
do(); 


if(0) 
{ 
do(); 
} 
2

operador ternario puede ser de más rendimiento que una normal si cláusula else, esto puede ser crítico en aplicaciones embebidas, sino también la optimización del compilador puede colapsar esta diferencia.

+0

Esta respuesta es una tontería completa. – Lundin

+0

Consulte esta https://www.beningo.com/ternary-operator-versus-the-ifelse-statement/ y encontrará una diferencia en el ensamblaje generado para ARM. El ensamblaje cambiado dará lugar a un rendimiento diferente en diferentes cargas de trabajo y situaciones. –

+0

Guau, ¡qué artículo tan sucio! '(Status.data & 0x10 == 0x10)' es ** un error común de nivel principiante ** debido a la precedencia del operador, donde == tiene una precedencia más alta que &. Este error es tan común que cualquier persona con experiencia real de programación en C incrustada lo detectará instantáneamente. Luego continúa hablando sobre el código de máquina no optimizado de su buggy mess. No tengo idea de quién es "Jacob", pero él es incompetente y no debería escribir blogs. – Lundin

1

como dwn dijo, el rendimiento fue uno de sus beneficios durante el aumento de los procesadores complejos, MSDN blog Non-classical processor behavior: How doing something can be faster than not doing it da un ejemplo que indica claramente la diferencia entre el operador ternario (condicional) y la instrucción if/else.

dar el siguiente código:

#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; 
} 

el costo para diferentes límites son muy diferentes y extraño (ver el material original). mientras que si el cambio:

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

a

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

El tiempo de ejecución es ahora independiente del valor límite, ya que:

el optimizador fue capaz de eliminar la rama de la expresión ternaria .

pero en mi computadora de escritorio intel i5 cpu/windows 10/vs2015, el resultado de mi prueba es bastante diferente con msdn blog.

cuando se utiliza el modo de depuración, si/costo más:

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 

y el costo operador ternario:

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 

al utilizar el modo de liberación, si/costo más:

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 

y ter nario costo del operador:

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 

el operador ternario es más lento que si/else en mi máquina!

de acuerdo con las diferentes técnicas de optimización del compilador, el operador ternal y if/else pueden comportarse de forma muy diferente.

0
  • Algunos de los más oscuros operadores en C existen solamente porque permiten la implementación de diversas macros tipo función como una sola expresión que devuelve un resultado. Diría que este es el objetivo principal por el cual los operadores ?: y , pueden existir, incluso si su funcionalidad es redundante.

    Digamos que deseamos implementar una macro de función que devuelve el mayor de dos parámetros. Sería entonces ser llamado como por ejemplo:

    int x = LARGEST(1,2); 
    

    La única forma de implementar esto como una macro-función como sería

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

    que no sería posible con una declaración if ... else, ya que no devuelve un valor de resultado Nota)

  • El otro propósito de ?: es que en algunos casos realmente aumenta la legibilidad. Lo más frecuente es que if...else sea más legible, pero no siempre. Tomemos, por ejemplo, las declaraciones de cambio largas y repetitivas:

    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; 
        ... 
    } 
    

    Esto se puede sustituir por el extremo más legible

    switch(something) 
    { 
        case A: array[i] = (x == A) ? x : y; break; 
        case B: array[i] = (x == B) ? x : y; break; 
        ... 
    } 
    
  • Tenga en cuenta que nunca se ?: hace resultado en el código más rápido que if-else. Ese es un mito extraño creado por principiantes confundidos. En el caso de un código optimizado, ?: ofrece el mismo rendimiento que if-else en la gran mayoría de los casos.

    En todo caso, ?: puede ser más lenta de if-else, ya que viene con promociones obligatorias de tipo implícitas, incluso del operando que no va a ser utilizado. Pero ?: nunca puede ser más rápido que if-else.


Nota) Ahora, por supuesto, alguien va a discutir y se preguntan por qué no utilizar una función. De hecho, si puede usar una función, es siempre preferible sobre una macro de función. Pero a veces no puedes usar funciones. Supongamos, por ejemplo, que x en el ejemplo anterior se declara en el alcance del archivo. El inicializador debe ser una expresión constante, por lo que no puede contener una llamada a función. Otros ejemplos prácticos de donde debe usar macros similares a funciones implican una programación segura con _Generic o "X macros".

Cuestiones relacionadas