web-dev-qa-db-fra.com

L'opérateur ternaire (conditionnel) en C

Quelle est la nécessité pour l'opérateur conditionnel? Sur le plan fonctionnel, il est redondant, car il implémente une construction if-else. Si l'opérateur conditionnel est plus efficace que l'affectation if-else, pourquoi ne pourrait-il pas être interprété plus efficacement par le compilateur?

51
Bongali Babu

L'opérateur ternaire est une commodité de syntaxe et de lisibilité, pas un raccourci de performance. Les gens sont divisés sur les avantages de conditionnels de complexité variable, mais pour des conditions courtes, il peut être utile de disposer d'une expression d'une ligne.

De plus, puisque c’est une expression, comme l’a écrit Charlie Martin , cela signifie qu’elle peut figurer à droite d’une déclaration en C. C’est un élément précieux pour la concision.

61
John Feminella

En C, son utilité réelle est que c’est une expression expression au lieu d’une déclaration; c'est-à-dire que vous pouvez l'avoir à droite (RHS) d'une déclaration. Ainsi, vous pouvez écrire certaines choses de manière plus concise.

152
Charlie Martin

Certaines des autres réponses données sont excellentes. Mais je suis surpris que personne ne mentionne que cela peut être utilisé pour aider à appliquer la correction const de manière compacte.

Quelque chose comme ça:

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

donc, fondamentalement, n est unconstdont la valeur initiale dépend d'une instruction de condition. L’alternative la plus simple est de faire n pas une const, cela permettrait à une if ordinaire de l’initialiser. Mais si vous voulez qu'elle soit const, vous ne pouvez pas utiliser une if ordinaire. Le meilleur substitut que vous puissiez faire serait d’utiliser une fonction d’aide comme celle-ci:

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

const int n = f(x);

mais la version ternaire si est beaucoup plus compacte et peut-être plus lisible.

81
Evan Teran

C'est crucial pour l'obscurcissement du code, comme ceci:

Look->       See?!

No
:(
Oh, well
);
36
Artelius

Compacité et capacité à intégrer une construction if-then-else dans une expression.

11
tvanfosson

Il y a beaucoup de choses en C qui ne sont pas techniquement nécessaires car elles peuvent être plus ou moins facilement implémentées. Voici une liste incomplète:

  1. tandis que
  2. for
  3. les fonctions
  4. structures

Imaginez à quoi votre code ressemblerait sans cela et vous pourriez trouver votre réponse. L'opérateur ternaire est une forme de "sucre syntaxique" qui, utilisé avec soin et habileté, facilite la rédaction et la compréhension du code.

10
1800 INFORMATION

Parfois, l'opérateur ternaire est le meilleur moyen de faire le travail. En particulier lorsque vous voulez que le résultat du ternaire soit une valeur-l.

Ce n’est pas un bon exemple, mais je tire au blanc sur quelque chose de mieux. Une chose est certian, ce n’est pas souvent quand vous avez vraiment besoin d’utiliser le ternaire, même si je l’utilise encore assez souvent.

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

Une chose contre laquelle j’aimerais toutefois mettre en garde est de lier les ternaires. Ils deviennent un réel
problème au moment de la maintenance:

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

EDIT: Voici un exemple potentiellement meilleur. Vous pouvez utiliser l'opérateur ternaire pour attribuer des valeurs de référence et const où vous auriez sinon besoin d'écrire une fonction pour la gérer:

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

const int myValue = getMyValue();

...pourrait devenir:

const int myValue = myCondition ? 42 : 314;

Ce qui est meilleur est une question discutable que je choisirai de ne pas débattre.

9
John Dibling

Comme personne n’a encore mentionné cela, le seul moyen d’obtenir des instructions intelligentes printf consiste à utiliser l’opérateur ternaire:

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

Avertissement: il existe certaines différences dans la priorité des opérateurs lorsque vous passez de C à C++ et vous pourriez être surpris par le ou les bogues subtils qui en découlent.

8
dirkgently

Le fait que l'opérateur ternaire soit une expression et non une instruction lui permet d'être utilisé dans les développements de macros pour les macros de type fonction utilisées dans le cadre d'une expression. Const ne fait peut-être pas partie du C d'origine, mais le préprocesseur de macro est très ancien.

Je l’ai vu utilisé dans un paquet de tableau qui utilisait des macros pour les accès à un tableau soumis à vérification. La syntaxe pour une référence vérifiée était quelque chose comme aref(arrayname, type, index), où arrayname était en fait un pointeur sur une structure incluant les limites du tableau et un tableau de caractères non signé pour les données, type était le type réel des données et index était l'index. L’expansion de celui-ci était assez poilue (et je ne vais pas le faire de mémoire), mais elle utilisait des opérateurs ternaires pour effectuer la vérification des liens.

Vous ne pouvez pas faire cela en tant qu'appel de fonction en C à cause du besoin de polymorphisme de l'objet renvoyé. Il fallait donc une macro pour transtyper le texte dans l'expression . En C++, vous pouviez le faire comme un appel de fonction surchargé basé sur un modèle (probablement pour l'opérateur []), mais C ne possède pas de telles fonctionnalités.

Edit: Voici l’exemple dont je parlais, tiré du paquet de tableau Berkeley CAD (édition glu 1.4). La documentation de l'utilisation de array_fetch est la suivante:

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

Récupère un élément d'un tableau. UNE erreur d'exécution se produit lors d'une tentative de référence en dehors des limites du fichier tableau. Il n'y a pas de vérification de type que la valeur à la position donnée est en fait du type utilisé quand déréférencer le tableau.

et voici la définition de macro de array_fetch (notez l'utilisation de l'opérateur ternaire et de l'opérateur de séquencement des virgules pour exécuter toutes les sous-expressions avec les bonnes valeurs dans le bon ordre dans le cadre d'une seule expression):

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

L’extension de array_insert (qui agrandit le tableau si nécessaire, comme un vecteur C++) est encore plus poilue, impliquant plusieurs opérateurs ternaires imbriqués. 

8
dewtell

C'est un sucre syntaxique et un raccourci pratique pour les blocs brefs si/sinon qui ne contiennent qu'une déclaration. Sur le plan fonctionnel, les deux constructions doivent fonctionner de manière identique.

4
Dana the Sane

L'opérateur ternaire peut être plus performant qu'une clause normale if sinon, cela peut être critique dans les applications intégrées, mais l'optimisation du compilateur peut aussi réduire cette différence.

mSDN blog Comportement non classique du processeur: comment faire quelque chose peut être plus rapide que de ne pas le faire donne un exemple qui indique clairement la différence entre ternaire ( conditionnel) et l'opérateur if/else.

donnez le code suivant:

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

le coût pour la frontière différente sont très différents et bizarres (voir le matériel original). alors que si changement:

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

à

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

Le temps d'exécution est maintenant indépendant de la valeur limite, puisque:

l'optimiseur a pu supprimer la branche de l'expression ternaire.

mais sur mon ordinateur de bureau Intel i5 cpu/windows 10/vs2015, le résultat de mon test est assez différent avec MSDN blog.

en mode débogage, si/else coût:

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

et coût ternaire opérateur:

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 

en mode release, si/else coûte:

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

et coût ternaire opérateur:

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

l'opérateur ternaire est plus lent que si/sinon énoncé sur ma machine!

ainsi, selon différentes techniques d'optimisation du compilateur, l'opérateur ternal et si/else peuvent se comporter de manière très différente.

1
Brent81

ternaire = forme simple de if-else. Il est principalement disponible pour la lisibilité.

0
Alphaneo
  • Certains des opérateurs les plus obscurs de C existent uniquement parce qu’ils permettent l’implémentation de différentes macros de type fonction en tant qu’expression unique qui renvoie un résultat. Je dirais que c’est la raison principale pour laquelle les opérateurs ?: et , sont autorisés à exister, même si leur fonctionnalité est par ailleurs redondante.

    Disons que nous souhaitons implémenter une macro de type fonction qui renvoie le plus grand des deux paramètres. Ce serait alors appelé comme par exemple:

    int x = LARGEST(1,2);
    

    La seule façon de mettre en œuvre cela en tant que macro de type fonction serait

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

    Cela ne serait pas possible avec une instruction if ... else, car elle ne renvoie pas de valeur de résultat. Remarque)

  • L’autre objectif de ?: est qu’il améliore dans certains cas la lisibilité. Le plus souvent, if...else est plus lisible, mais pas toujours. Prenons, par exemple, les instructions de commutation répétitives longues:

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

    Cela peut être remplacé par le beaucoup plus lisible

    switch(something)
    {
      case A: array[i] = (x == A) ? x : y; break;
      case B: array[i] = (x == B) ? x : y; break;
      ...
    }
    
  • Veuillez noter que ?: ne jamais donne un code plus rapide que if-else. C'est un mythe étrange créé par des débutants confus. En cas de code optimisé, ?: donne des performances identiques à if-else dans la grande majorité des cas. 

    Le cas échéant, ?: peut être plus lent que if-else, car il est fourni avec des promotions de type implicite obligatoires, même de l'opérande qui ne sera pas utilisé. Mais ?: ne peut jamais être plus rapide que if-else.


Remarque) Maintenant, bien sûr, quelqu'un va se disputer et se demander pourquoi ne pas utiliser une fonction. En effet, si vous pouvez utiliser une fonction, il est préférable de choisir always plutôt qu'une macro de type fonction. Mais parfois, vous ne pouvez pas utiliser de fonctions. Supposons par exemple que x dans l'exemple ci-dessus soit déclaré à la portée du fichier. L'initialiseur doit alors être une expression constante, il ne peut donc pas contenir d'appel de fonction. D'autres exemples pratiques d'utilisation de macros de type fonction impliquent une programmation de type sécurisé avec _Generic ou "macros X".

0
Lundin