Avantage de basculer l'instruction if-else

168

Quelle est la meilleure pratique pour utiliser une switchinstruction par rapport à l'utilisation d'une ifinstruction pour 30 unsignedénumérations où environ 10 ont une action attendue (qui est actuellement la même action). Les performances et l'espace doivent être pris en compte mais ne sont pas critiques. J'ai résumé l'extrait alors ne me détestez pas pour les conventions de dénomination.

switch déclaration:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if déclaration:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}
Zing-
la source
26
Ceci est édité comme «subjectif»? Vraiment? Sûrement «subjectif» est pour des choses qui ne peuvent être prouvées d'une manière ou d'une autre?
Alexandra Franks
Bien sûr, vous pouvez le voir à partir du point duquel génère le code le plus efficace, mais tout compilateur moderne doit être tout aussi efficace. Au final, il s'agit plutôt de la couleur du local à vélos.
jfs
8
Je ne suis pas d'accord, je ne pense pas que ce soit subjectif. Une simple différence ASM compte, vous ne pouvez pas ignorer quelques secondes d'optimisation dans de nombreux cas. Et dans cette question, ce n'est pas une guerre ou un débat religieux, il y a une explication rationnelle de pourquoi on serait plus rapide, il suffit de lire la réponse acceptée.
chakrit
1
Ce qui est plus rapide: stackoverflow.com/questions/6805026/is-switch-faster-than-if
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@RichardFranks offtopic: grats! vous êtes le premier humain à prendre le contrôle de la modération sur SO que j'ai jamais vu
jungle_mole

Réponses:

162

Utilisez le commutateur.

Dans le pire des cas, le compilateur générera le même code qu'une chaîne if-else, donc vous ne perdez rien. En cas de doute, placez les cas les plus courants en premier dans l'instruction switch.

Dans le meilleur des cas, l'optimiseur peut trouver un meilleur moyen de générer le code. Les choses courantes qu'un compilateur fait est de construire un arbre de décision binaire (enregistre les comparaisons et les sauts dans le cas moyen) ou simplement de construire une table de sauts (fonctionne sans comparaison du tout).

Nils Pipenbrinck
la source
2
Techniquement, il y aura toujours une comparaison, pour s'assurer que la valeur de l'énumération se trouve dans la table de saut.
0124816 le
Jep. C'est vrai. L'activation des énumérations et la gestion de tous les cas peuvent cependant éliminer la dernière comparaison.
Nils Pipenbrinck
4
Notez qu'une série de ifs pourrait théoriquement être analysée comme étant la même chose qu'un commutateur par un compilateur, mais pourquoi prendre le risque? En utilisant un commutateur, vous communiquez exactement ce que vous voulez, ce qui facilite la génération de code.
jakobengblom2
5
jakoben: Cela pourrait être fait, mais uniquement pour les chaînes if / else de type switch. En pratique, cela ne se produit pas parce que les programmeurs utilisent switch. J'ai fouillé dans la technologie du compilateur et croyez-moi: trouver de telles constructions "inutiles" prend beaucoup de temps. Pour les compilateurs, une telle optimisation a du sens.
Nils Pipenbrinck
5
@NilsPipenbrinck avec la facilité de construction pseudo-récursive if- elsechaînes dans la programmation méta modèle, et la difficulté de générer des switch casechaînes, que la cartographie peut devenir plus important. (et oui, commentaire ancien, mais le web est pour toujours, ou du moins jusqu'à mardi prochain)
Yakk - Adam Nevraumont
45

Pour le cas particulier que vous avez fourni dans votre exemple, le code le plus clair est probablement:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Évidemment, cela déplace simplement le problème vers une autre zone du code, mais vous avez maintenant la possibilité de réutiliser ce test. Vous avez également plus d'options pour le résoudre. Vous pouvez utiliser std :: set, par exemple:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Je ne suggère pas que c'est la meilleure implémentation de RequiertSpecialEvent, mais simplement que c'est une option. Vous pouvez toujours utiliser un commutateur ou une chaîne if-else, ou une table de recherche, ou une manipulation de bits sur la valeur, peu importe. Plus votre processus de décision devient obscur, plus vous tirerez de la valeur de l'avoir dans une fonction isolée.

Mark Ransom
la source
5
C'est tellement vrai. La lisibilité est tellement meilleure que le commutateur et les instructions if. J'allais en fait répondre moi-même à quelque chose comme ça, mais tu m'as battu. :-)
mlarsen
Si vos valeurs d'énumération sont toutes petites, vous n'avez pas besoin d'un hachage, juste d'une table. Par exemple, const std::bitset<MAXERR> specialerror(initializer); utilisez-le avec if (specialerror[numError]) { fire_special_event(); }. Si vous voulez vérifier les limites, bitset::test(size_t)lèvera une exception sur les valeurs hors limites. ( bitset::operator[]ne vérifie pas la plage). cplusplus.com/reference/bitset/bitset/test . Cela surpassera probablement une implémentation de table de saut générée par le compilateur switch, esp. dans le cas non spécial où il s'agira d'une seule branche non prise.
Peter Cordes
@PeterCordes Je soutiens toujours qu'il vaut mieux placer la table dans sa propre fonction. Comme je l'ai dit, il y a beaucoup d'options qui s'ouvrent lorsque vous faites cela, je n'ai pas essayé de toutes les énumérer.
Mark Ransom
@MarkRansom: Je ne voulais pas être en désaccord avec l'abstrait. Juste depuis que vous avez donné un exemple d'implémentation en utilisant std::set, j'ai pensé souligner que c'est probablement un mauvais choix. Il s'avère que gcc compile déjà le code de l'OP pour tester un bitmap dans un immédiat 32 bits. godbolt: goo.gl/qjjv0e . gcc 5.2 le fera même pour la ifversion. De plus, gcc plus récent utilisera l'instruction de test de bits btau lieu de déplacer pour mettre un 1peu au bon endroit et utiliser test reg, imm32.
Peter Cordes
Ce bitmap à constante immédiate est une grande victoire, car il n'y a pas de cache manquant sur le bitmap. Cela fonctionne si les codes d'erreur "spéciaux" sont tous dans une plage de 64 ou moins. (ou 32 pour le code 32 bits hérité.) Le compilateur soustrait la plus petite valeur de cas, si elle est différente de zéro. Ce qu'il faut retenir, c'est que les compilateurs récents sont suffisamment intelligents pour que vous obteniez probablement du bon code à partir de la logique que vous utilisez, à moins que vous ne lui disiez d'utiliser une structure de données volumineuse.
Peter Cordes
24

Le changement est plus rapide.

Essayez simplement if / else-ing 30 valeurs différentes dans une boucle, et comparez-le au même code en utilisant switch pour voir à quel point le switch est plus rapide.

Maintenant, le commutateur a un vrai problème : le commutateur doit connaître au moment de la compilation les valeurs à l'intérieur de chaque cas. Cela signifie que le code suivant:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

ne compilera pas.

La plupart des gens utiliseront alors définit (Aargh!), Et d'autres déclareront et définiront des variables constantes dans la même unité de compilation. Par exemple:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

Donc, au final, le développeur doit choisir entre "vitesse + clarté" ou "couplage de code".

(Non pas qu'un commutateur ne peut pas être écrit pour être déroutant comme l'enfer ... La plupart des commutateurs que je vois actuellement sont de cette catégorie "déroutante" "... Mais c'est une autre histoire ...)

Modifier le 21/09/2008:

bk1e a ajouté le commentaire suivant: " définition de constantes comme énumérations dans un fichier d'en-tête est une autre façon de gérer cela".

Bien sûr que ça l'est.

Le but d'un type externe était de découpler la valeur de la source. Définir cette valeur comme une macro, comme une simple déclaration const int, ou même comme une énumération a pour effet secondaire d'inligner la valeur. Ainsi, si le define, la valeur enum ou la valeur const int change, une recompilation serait nécessaire. La déclaration extern signifie qu'il n'est pas nécessaire de recompiler en cas de changement de valeur, mais d'un autre côté, il est impossible d'utiliser switch. La conclusion étant l' utilisation du commutateur augmentera le couplage entre le code du commutateur et les variables utilisées comme cas . Quand tout va bien, utilisez le commutateur. Quand ce n'est pas le cas, pas de surprise.

.

Modifier le 15/01/2013:

Vlad Lazarenko a commenté ma réponse, donnant un lien vers son étude approfondie du code d'assemblage généré par un commutateur. Très éclairant: http://lazarenko.me/switch/

Paercebal
la source
La définition de constantes comme énumérations dans un fichier d'en-tête est une autre façon de gérer cela.
bk1e
6
Le changement n'est pas toujours plus rapide .
1
@Vlad Lazarenko: Merci pour le lien! C'était une lecture très intéressante.
paercebal
1
Le lien de @AhmedHussein user404725 est mort. Heureusement, je l'ai trouvé dans WayBack Machine: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . En effet, la WayBack Machine peut être une véritable bénédiction.
Jack Giffin le
Merci beaucoup, c'est très utile
Ahmed Hussein
20

Le compilateur l'optimisera de toute façon - optez pour le commutateur car c'est le plus lisible.

Alexandra Franks
la source
3
Il est fort probable que le compilateur ne touchera pas if-then-else. En fait, gccne le fera pas à coup sûr (il y a une bonne raison à cela). Clang optimisera les deux cas dans une recherche binaire. Par exemple, voyez ceci .
7

Le commutateur, ne serait-ce que pour la lisibilité. Géant si les déclarations sont plus difficiles à maintenir et plus difficiles à lire à mon avis.

ERROR_01 : // échec intentionnel

ou

(ERROR_01 == numError) ||

Ce dernier est plus sujet aux erreurs et nécessite plus de frappe et de formatage que le premier.

scubabbl
la source
6

Code de lisibilité. Si vous voulez savoir ce qui fonctionne le mieux, utilisez un profileur, car les optimisations et les compilateurs varient et les problèmes de performances sont rarement là où les gens pensent qu'ils se trouvent.

Bdoserror
la source
6

Utilisez switch, c'est ce à quoi il sert et ce que les programmeurs attendent.

Je mettrais les étiquettes de cas redondantes cependant - juste pour que les gens se sentent à l'aise, j'essayais de me rappeler quand / quelles sont les règles pour les laisser de côté.
Vous ne voulez pas que le prochain programmeur travaillant dessus ait à réfléchir inutilement aux détails de la langue (ce sera peut-être vous dans quelques mois!)

Martin Beckett
la source
4

Les compilateurs sont vraiment bons pour l'optimisation switch. Gcc récent est également bon pour optimiser un tas de conditions dans unif .

J'ai fait quelques cas de test sur godbolt .

Lorsque les casevaleurs sont regroupées étroitement, gcc, clang et icc sont tous suffisamment intelligents pour utiliser une image bitmap pour vérifier si une valeur est l'une des valeurs spéciales.

par exemple, gcc 5.2 -O3 compile le switchto (et ifquelque chose de très similaire):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Notez que le bitmap est des données immédiates, donc il n'y a pas de manque potentiel de cache de données pour y accéder, ni de table de saut.

gcc 4.9.2 -O3 compile le switchen un bitmap, mais le fait 1U<<errNumberavec mov / shift. Il compile la ifversion en série de branches.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Notez comment il soustrait 1 de errNumber(avec leapour combiner cette opération avec un mouvement). Cela lui permet d'adapter le bitmap dans un 32 bits immédiat, évitant ainsi le 64 bits immédiatmovabsq qui prend plus d'octets d'instructions.

Une séquence plus courte (en code machine) serait:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(L'échec de l'utilisation jc fire_special_eventest omniprésent et est un bogue du compilateur .)

rep retest utilisé dans les cibles de branche, et après les branches conditionnelles, au profit des anciens AMD K8 et K10 (pré-Bulldozer): Que signifie «rep ret»? . Sans cela, la prédiction de branche ne fonctionne pas aussi bien sur ces processeurs obsolètes.

bt(bit test) avec un registre arg est rapide. Il combine le travail de décalage à gauche d'un 1 par errNumberbits et de faire untest , mais c'est toujours une latence de 1 cycle et un seul uop Intel. C'est lent avec un argument mémoire en raison de sa sémantique trop CISC: avec un opérande mémoire pour la "chaîne de bits", l'adresse de l'octet à tester est calculée sur la base de l'autre arg (divisé par 8), et est n'est pas limité au bloc de 1, 2, 4 ou 8 octets pointé par l'opérande mémoire.

D'après les tableaux d'instructions d'Agner Fog , une instruction de décalage à nombre variable est plus lente qu'une instruction btIntel récente (2 uops au lieu de 1, et shift ne fait pas tout le reste).

Peter Cordes
la source
4

Les avantages de switch () sur if else case sont: - 1. Switch est beaucoup plus efficace que if else car chaque cas ne dépend pas du cas précédent contrairement à if else où une déclaration individuelle doit être vérifiée pour une condition vraie ou fausse.

  1. Quand il y a un non. des valeurs pour une seule expression, la casse de commutation est plus flexible que if else car dans if else, le jugement de cas est basé sur seulement deux valeurs, c'est-à-dire vrai ou faux.

  2. Les valeurs dans switch sont définies par l'utilisateur, tandis que les valeurs dans if else case sont basées sur des contraintes.

  3. En cas d'erreur, les instructions dans switch peuvent être facilement vérifiées et corrigées, ce qui est relativement difficile à vérifier en cas de déclaration if else.

  4. Le boîtier de commutation est beaucoup plus compact et facile à lire et à comprendre.

Vineeth Krishna K
la source
2

OMI, c'est un exemple parfait de la raison pour laquelle le basculement des commutateurs a été conçu.

Nescio
la source
en c # c'est le seul cas où la pensée de chute se produit. Bon argument ici.
BCS
2

Si vos cas sont susceptibles de rester groupés à l'avenir - si plus d'un cas correspond à un résultat - le commutateur peut s'avérer plus facile à lire et à maintenir.

TSomKes
la source
2

Ils fonctionnent également bien. Les performances sont à peu près les mêmes avec un compilateur moderne.

Je préfère les instructions if aux instructions case car elles sont plus lisibles et plus flexibles - vous pouvez ajouter d'autres conditions non basées sur l'égalité numérique, comme "|| max <min". Mais pour le cas simple que vous avez publié ici, cela n'a pas vraiment d'importance, faites simplement ce qui vous est le plus lisible.

SquareCog
la source
2

le commutateur est définitivement préféré. Il est plus facile de consulter la liste des cas d'un commutateur et de savoir avec certitude ce qu'il fait que de lire la condition si longue.

La duplication de l' ifétat est dure pour les yeux. Supposons que l'un des a ==été écrit !=; remarqueriez-vous? Ou si une instance de 'numError' a été écrite 'nmuError', qu'est-ce qui vient de se compiler?

Je préfère généralement utiliser le polymorphisme au lieu du commutateur, mais sans plus de détails sur le contexte, c'est difficile à dire.

En ce qui concerne les performances, le mieux est d'utiliser un profileur pour mesurer les performances de votre application dans des conditions similaires à celles que vous attendez dans la nature. Sinon, vous optimisez probablement au mauvais endroit et dans le mauvais sens.

Jay Bazuzi
la source
2

Je suis d'accord avec la compacité de la solution de commutation, mais IMO vous détournez le commutateur ici.
Le but du commutateur est d'avoir une manipulation différente en fonction de la valeur.
Si vous deviez expliquer votre algo en pseudo-code, vous utiliseriez un if parce que, sémantiquement, c'est ce que c'est: si n'importe quelle
erreur fait ça ... Donc à moins que vous n'ayez l'intention un jour de changer votre code pour avoir un code spécifique pour chaque erreur , J'utiliserais si .

Francesca
la source
2
Je ne suis pas d'accord, pour la même raison que je ne suis pas d'accord avec le cas de chute. J'ai lu l'interrupteur comme "Dans les cas 01, 07, 0A, 10, 15, 16 et 20, événement spécial d'incendie." Il n'y a pas de chute à une autre section., Ceci est juste un artefact de la syntaxe C ++ où vous répétez le mot-clé «case» pour chaque valeur.
MSalters
1

Je choisirais l'énoncé if pour des raisons de clarté et de convention, même si je suis sûr que certains seraient en désaccord. Après tout, vous voulez faire quelque chose, ifune condition est vraie! Avoir un interrupteur avec une action semble un peu ... inutile.

William Keller
la source
1

Je dirais utiliser SWITCH. De cette façon, vous n'avez qu'à mettre en œuvre des résultats différents. Vos dix cas identiques peuvent utiliser la valeur par défaut. Si un changement est tout ce dont vous avez besoin pour implémenter explicitement le changement, il n'est pas nécessaire de modifier la valeur par défaut. Il est également beaucoup plus facile d'ajouter ou de supprimer des cas d'un SWITCH que d'éditer IF et ELSEIF.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

Peut-être même tester votre condition (dans ce cas numerror) par rapport à une liste de possibilités, un tableau peut-être pour que votre SWITCH ne soit même pas utilisé à moins qu'il y ait définitivement un résultat.

Lewis
la source
Il y a environ 30 erreurs au total. 10 nécessitent l'action spéciale, j'utilise donc la valeur par défaut pour les ~ 20 erreurs qui ne nécessitent pas d'action ...
Zing-
1

Étant donné que vous n'avez que 30 codes d'erreur, codez votre propre table de saut, puis vous faites tous les choix d'optimisation vous-même (le saut sera toujours le plus rapide), plutôt que d'espérer que le compilateur fera la bonne chose. Cela rend également le code très petit (à part la déclaration statique de la table de saut). Il présente également l'avantage qu'avec un débogueur, vous pouvez modifier le comportement au moment de l'exécution si vous en avez besoin, simplement en poussant directement les données de la table.

Greg Whitfield
la source
Wow, cela semble être un moyen de transformer un problème simple en un problème complexe. Pourquoi se donner tous ces ennuis quand le compilateur fera un excellent travail pour vous. De plus, il s'agit apparemment d'un gestionnaire d'erreurs, donc il n'est probablement pas si critique en termes de vitesse. Un commutateur est de loin la chose la plus facile à lire et à maintenir.
MrZebra le
Une table n'est guère complexe - en fait, elle est probablement plus simple qu'un passage au code. Et la déclaration mentionnait que la performance était un facteur.
Greg Whitfield
Cela ressemble à une optimisation prématurée. Tant que vous gardez vos valeurs d'énumération petites et contiguës, le compilateur devrait le faire pour vous. Le fait de placer le commutateur dans une fonction distincte permet de garder le code qui l'utilise gentil et petit, comme Mark Ransom le suggère dans sa réponse, donne le même avantage de petit code.
Peter Cordes
De plus, si vous souhaitez implémenter quelque chose vous-même, créez un std::bitset<MAXERR> specialerror;, alors if (specialerror[err]) { special_handler(); }. Ce sera plus rapide qu'une table de saut, en particulier. dans le cas non pris.
Peter Cordes
1

Je ne suis pas sûr de la meilleure pratique, mais j'utiliserais le commutateur - puis j'intercepterais les échecs intentionnels via `` par défaut ''

da5id
la source
1

Esthétiquement, j'ai tendance à privilégier cette approche.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

Rendez les données un peu plus intelligentes afin que nous puissions rendre la logique un peu plus stupide.

Je réalise que ça a l'air bizarre. Voici l'inspiration (de la façon dont je le ferais en Python):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()
mbac32768
la source
4
La syntaxe d'une langue n'ont un effet sur la façon dont nous mettons en œuvre une solution ... => Il semble laid C et agréable en Python. :)
rlerallut
Utilisez des bitmaps? Si error_0a est 0x0a etc, vous pouvez les mettre sous forme de bits dans un long long. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Puis utilisez if (special_events & (1LL << numError) fire_special_event ()
paperhorse
1
Beurk. Vous avez transformé une opération du pire des cas O (1) (si des tables de sauts sont générées) en le pire des cas O (N) (où N est le nombre de cas traités), et vous avez utilisé un breakextérieur a case(oui, un mineur péché, mais un péché néanmoins). :)
Mac
Beurk? Il a déclaré que les performances et l'espace ne sont pas essentiels. Je proposais simplement une autre façon de voir le problème. Si nous pouvons représenter un problème d'une manière où les humains parviennent à moins penser, alors je m'en fiche généralement si cela signifie que les ordinateurs doivent penser plus.
mbac32768
1
while (true) != while (loop)

La première est probablement optimisée par le compilateur, ce qui expliquerait pourquoi la deuxième boucle est plus lente lors de l'augmentation du nombre de boucles.

MarioFrost
la source
Cela semble être un commentaire à la réponse de McAnix. Ce n'est que l'un des problèmes avec cette tentative de synchronisation par ifrapport switchà une condition de fin de boucle en Java.
Peter Cordes
1

Quand il s'agit de compiler le programme, je ne sais pas s'il y a une différence. Mais en ce qui concerne le programme lui-même et garder le code aussi simple que possible, je pense personnellement que cela dépend de ce que vous voulez faire. if else if else les déclarations ont leurs avantages, qui je pense sont:

vous permettent de tester une variable par rapport à des plages spécifiques, vous pouvez utiliser des fonctions (Standard Library ou Personal) comme conditionnelles.

(exemple:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

Cependant, les instructions If else peuvent devenir compliquées et compliquées (malgré vos meilleures tentatives) à la hâte. Les instructions de commutation ont tendance à être plus claires, plus propres et plus faciles à lire; mais ne peut être utilisé que pour tester des valeurs spécifiques (exemple:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

Je préfère les déclarations if - else if - else, mais cela dépend vraiment de vous. Si vous souhaitez utiliser des fonctions comme conditions, ou si vous souhaitez tester quelque chose par rapport à une plage, un tableau ou un vecteur et / ou que cela ne vous dérange pas de gérer l'imbrication compliquée, je vous recommande d'utiliser If else if else blocs. Si vous voulez tester des valeurs uniques ou si vous voulez un bloc propre et facile à lire, je vous recommande d'utiliser les blocs de cas switch ().

Jordan Effinger
la source
0

Je ne suis pas la personne pour vous parler de la vitesse et de l'utilisation de la mémoire, mais regarder une déclaration de commutateur est beaucoup plus facile à comprendre qu'une grande déclaration if (en particulier 2-3 mois plus tard)

Ed Brown
la source
0

Je sais que c'est vieux mais

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

Varier le nombre de boucles change beaucoup:

Tandis que if / else: Commutateur 5ms: 1ms Boucles max: 100000

Tandis que if / else: Commutateur 5ms: 3ms Boucles max: 1000000

Tandis que if / else: Commutateur 5ms: 14ms Boucles maximum: 10000000

Tandis que if / else: Commutateur 5ms: 149ms Boucles maximum: 100000000

(ajoutez plus de déclarations si vous le souhaitez)

McAnix
la source
3
Bon point, mais désolé, mec, tu es dans la mauvaise langue. Varier la langue change beaucoup;)
Gabriel Schreiber
2
La if(max) breakboucle s'exécute en temps constant quel que soit le nombre de boucles? On dirait que le compilateur JIT est suffisamment intelligent pour optimiser la boucle counter2=max. Et peut-être que c'est plus lent que de changer si le premier appel à currentTimeMillisa plus de surcharge, parce que tout n'est pas encore compilé JIT? Mettre les boucles dans l'autre ordre donnerait probablement des résultats différents.
Peter Cordes