Pourquoi devons-nous utiliser l'interrupteur de rodage?

74

Qui a décidé (et sur quels concepts) que la switchconstruction (dans de nombreuses langues) doit utiliser breakdans chaque énoncé?

Pourquoi devons-nous écrire quelque chose comme ceci:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(remarqué cela en PHP et JS; il y a probablement beaucoup d'autres langages qui l'utilisent)

Si switchest une alternative de if, pourquoi nous ne pouvons pas utiliser la même construction que pour if? C'est à dire:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

On dit que cela breakempêche l'exécution du bloc suivant celui en cours. Mais, est-ce que quelqu'un se heurte réellement à la situation, où il était nécessaire d'exécuter le bloc actuel et les suivants? Je n'ai pas Pour moi, breakc'est toujours là. Dans chaque bloc. Dans chaque code.

trejder
la source
1
Comme la plupart des réponses l'ont fait remarquer, cela est lié à une "chute directe", dans laquelle plusieurs conditions sont acheminées via les mêmes blocs "alors". Cependant, cela dépend de la langue - RPG, par exemple, traite une CASEinstruction de manière équivalente à un bloc géant if / elseif.
Clockwork-Muse
34
C-était une mauvaise décision prise par les concepteurs C (comme beaucoup d’autres décisions, il a été décidé de faciliter la transition entre assemblage -> C et la traduction, plutôt que pour faciliter la programmation) , ce qui a malheureusement été hérité dans d'autres langues basées sur C. Utilisation de la règle simple "Toujours faire la casse commune par défaut !!" , nous pouvons voir que le comportement par défaut aurait dû être rompu, avec un mot clé séparé pour le non-respect, car il s’agit de loin du cas le plus courant.
BlueRaja - Danny Pflughoeft
4
J'ai utilisé fall-through à plusieurs reprises, bien que la déclaration de changement dans la langue de mon choix à l'époque (C) ait sans doute été conçue pour l'éviter; par exemple j'ai fait case 'a': case 'A': case 'b': case 'B'mais surtout parce que je ne peux pas faire case in [ 'a', 'A', 'b', 'B' ]. Une question légèrement meilleure est, dans mon langage préféré actuel (C #), la pause est obligatoire et il n’ya pas de chute implicite; l'oubli breakest une erreur de syntaxe ...: \
KutuluMike
1
Les retombées avec le code réel se retrouvent souvent dans les implémentations de l'analyseur. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
Stop Harming Monica
1
@ BlueRaja-DannyPflughoeft: C a également été conçu pour être facile à compiler. «Émettre un saut si breakest présent n'importe où» est une règle beaucoup plus simple à appliquer que «Ne pas émettre de saut si fallthroughest présent dans un switch».
Jon Purdy

Réponses:

95

C a été l’une des premières langues à avoir l’ switchénoncé sous cette forme, et toutes les autres langues principales en ont hérité, choisissant pour la plupart de conserver la sémantique C par défaut. les moins importants que de garder le comportement auquel tout le monde était habitué.

Quant à la raison pour laquelle C a été conçu de cette manière, il découle probablement du concept de C en tant qu '"assemblage portable". L' switchinstruction est fondamentalement une abstraction d'une table de branche , et une table de branche a également un impact implicite et nécessite une instruction de saut supplémentaire pour l'éviter.

Donc, fondamentalement, les concepteurs de C ont également choisi de conserver la sémantique de l’assembleur par défaut.

Michael Borgwardt
la source
3
VB.NET est un exemple de langage qui l'a changé. Il n'y a pas de danger que cela rentre dans la clause relative à la cause.
Poke
1
Tcl est une autre langue qui ne tombe jamais dans la clause suivante. Pratiquement jamais voulu le faire non plus (contrairement au C où il est utile lors de l'écriture de certains types de programmes).
Donal Fellows
4
Les langues ancêtres de C, B et l'ancienne BCPL , ont toutes deux (eu?) Des instructions switch; BCPL l'a épelé switchon.
Keith Thompson
Les déclarations de cas de Ruby ne passent pas à travers; vous pouvez obtenir un comportement similaire en ayant deux valeurs de test dans une seule when.
Nathan Long
86

Parce que switchn'est pas une alternative de if ... elsedéclarations dans ces langues.

En utilisant switch, nous pouvons associer plus d'une condition à la fois, ce qui est très apprécié dans certains cas.

Exemple:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}
Satish Pandey
la source
15
more than one block ... unwanted in most casesJe ne suis pas d'accord avec toi.
Satish Pandey
2
@DanielB Cela dépend de la langue; Les instructions de commutateur C # ne permettent pas cette possibilité.
Andy
5
@Andy Parlons-nous encore de la chute? Les instructions de commutateur C # le permettent vraiment (voir le 3ème exemple), d’où la nécessité de break. Mais oui, autant que je sache, le périphérique de Duff ne fonctionne pas en C #.
Daniel B
8
@ DanielB Oui, nous le sommes, mais le compilateur n'autorisera pas une condition, du code, puis une autre condition sans interruption. Vous pouvez avoir plusieurs conditions empilées sans code entre ou vous avez besoin d'une pause. De votre lien C# does not support an implicit fall through from one case label to another. Vous pouvez tomber, mais vous ne pouvez pas oublier une pause par accident.
Andy
3
@Matsemann .. Je peux maintenant utiliser tout le matériel, if..elsemais dans certaines situations, il switch..caseserait préférable. L'exemple ci-dessus serait un peu complexe si nous utilisons if-else.
Satish Pandey
15

Cela a été demandé pour Stack Overflow dans le contexte de C: Pourquoi l'instruction switch a-t-elle été conçue pour nécessiter une pause?

Pour résumer la réponse acceptée, c'était probablement une erreur. La plupart des autres langues ont probablement juste suivi C. Cependant, certaines langues telles que C # semblent avoir résolu ce problème en autorisant les retombées - mais uniquement lorsque le programmeur le dit explicitement (source: le lien ci-dessus, je ne parle pas C # moi-même) .

Tapio
la source
Google Go fait la même chose que IIRC (autorisant le passage à l’écran si spécifié explicitement).
Leo
PHP le fait aussi. Et plus vous regardez le code obtenu, plus vous vous sentez mal à l'aise. J'aime le /* FALLTHRU */commentaire dans la réponse liée par @Tapio ci-dessus, pour prouver que vous le pensiez bien.
DaveP
1
Dire C # a goto case, comme le lien, ne montre pas pourquoi cela fonctionne si bien. Vous pouvez toujours empiler plusieurs cas comme ci-dessus, mais dès que vous insérez du code, vous devez disposer d'un explicite goto case whateverpour passer au cas suivant ou vous obtenez un compilateur. Si vous me demandez, des deux, la possibilité d'empiler des cas sans se soucier de la négligence par inadvertance est de loin une fonctionnalité meilleure que de permettre une chute explicite avec goto case.
Jesper
9

Je vais répondre avec un exemple. Si vous voulez lister le nombre de jours pour chaque mois de l’année, il est évident que certains mois en ont 31, 30 et 28/29. Cela ressemblerait à ceci,

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

Ceci est un exemple où plusieurs observations ont le même effet et sont toutes regroupées. Il y avait évidemment une raison pour le choix du mot clé break et non pas le if ... sinon si construit.

La principale chose à prendre en compte ici est qu'une instruction switch avec beaucoup de cas similaires n'est pas un if ... sinon si ... sinon si ... sinon pour chacun des cas, mais plutôt si (1, 2, 3) ... sinon si (4,5,6) sinon ...

Awemo
la source
2
Votre exemple a l'air plus bavard qu'un ifsans aucun avantage. Peut-être que si votre exemple donnait un nom ou effectuait un travail spécifique à un mois en plus des retombées, l'utilité des retombées serait plus évidente? (sans dire si on DEVRAIT ou non en premier lieu)
horatio
1
C'est vrai. Cependant, j'essayais simplement de montrer des cas dans lesquels une chute pourrait être utilisée. Il serait évidemment bien mieux que chacun des mois nécessite une action individuelle.
Awemo
8

Il y a deux situations où «passer à travers» d'un cas à l'autre peut arriver - le cas vide:

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

et le cas non vide

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Nonobstant la référence au périphérique de Duff , les instances légitimes pour le second cas sont rares et généralement interdites par les normes de codage et signalées lors d'une analyse statique. Et là où il se trouve, il est le plus souvent dû à l’omission de a break.

Le premier est parfaitement sensé et commun.

Pour être honnête, je ne vois aucune raison d’avoir besoin de l’ breakanalyseur de langage et de l’analyseur de langage sachant qu’un corps de cas vide est un échec, alors qu’un cas non vide est autonome.

Il est dommage que le panel ISO C semble plus préoccupé par l'ajout de nouvelles fonctionnalités (non désirées) mal définies au langage, plutôt que par la correction des fonctionnalités non définies, non spécifiées ou définies par la mise en œuvre, sans parler de l'illogique.

Andrew
la source
2
Heureusement que l'ISO C n'est pas en train d'introduire des changements radicaux dans la langue!
Jack Aidley
4

Ne pas forcer le breakpermet un certain nombre de choses qui pourraient autrement être difficiles à faire. D'autres ont noté des cas de regroupement, pour lesquels il existe un certain nombre de cas non triviaux.

Un cas où il est impératif de breakne pas l'utiliser est le périphérique de Duff . Ceci est utilisé pour " dérouler " les boucles où il peut accélérer les opérations en limitant le nombre de comparaisons requises. Je pense que l’utilisation initiale permettait une fonctionnalité qui était auparavant trop lente avec une boucle entièrement enroulée. Il négocie la taille du code pour la vitesse dans certains cas.

Il est judicieux de remplacer le breakavec un commentaire approprié, si le cas contient du code. Sinon, quelqu'un corrigera les manquants breaket introduira un bogue.

BillThor
la source
Merci pour l' exemple de Duff's Device (+1). Je n'étais pas au courant de ça.
Trejder
3

En C, où l’origine semble être, le bloc de code de l’ switchinstruction n’est pas une construction spéciale. C'est un bloc de code normal, tout comme un bloc sous une ifinstruction.

switch ()
{
}

if ()
{
}

caseet defaultsont des étiquettes de saut à l'intérieur de ce bloc, spécifiquement liées à switch. Ils sont traités comme des étiquettes de saut normales goto. Il y a une règle spécifique qui est importante ici: les étiquettes de saut peuvent être presque partout dans le code, sans interrompre le flux de code.

En tant que bloc de code normal, il n'est pas nécessaire que ce soit une instruction composée. Les étiquettes sont également facultatives. Ce sont des switchdéclarations valables en C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

La norme C elle-même donne ceci à titre d'exemple (6.8.4.2):

switch (expr) 
{ 
    int i = 4; 
    f(i); 
  case 0: 
    i=17; 
    /*falls through into default code */ 
  default: 
    printf("%d\n", i); 
} 

Dans le fragment de programme artificiel, l'objet dont l'identificateur est i existe avec une durée de stockage automatique (dans le bloc) mais n'est jamais initialisé. Ainsi, si l'expression de contrôle a une valeur différente de zéro, l'appel de la fonction printf accédera à une valeur indéterminée. De même, l'appel de la fonction f est inaccessible.

En outre, defaultest une étiquette de saut aussi, et peut donc être n'importe où, sans avoir à être le dernier cas.

Cela explique également le périphérique de Duff:

switch (count % 8) {
    case 0: do {  *to = *from++;
    case 7:       *to = *from++;
    case 6:       *to = *from++;
    case 5:       *to = *from++;
    case 4:       *to = *from++;
    case 3:       *to = *from++;
    case 2:       *to = *from++;
    case 1:       *to = *from++;
            } while(--n > 0);
}

Pourquoi la chute? Parce que dans le flux de code normal dans un bloc de code normal, l'instruction suivante est attendue , tout comme vous le feriez dans un ifbloc de code.

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

Je suppose que la raison en était la facilité de mise en œuvre. Vous n'avez pas besoin de code spécial pour analyser et compiler un switchbloc, en prenant soin de règles spéciales. Vous venez de l'analyser comme n'importe quel autre code et vous ne devez vous soucier que des étiquettes et de la sélection de saut.

Une question intéressante de suivi de tout cela est si les déclarations imbriquées suivantes affichent "Terminé". ou pas.

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

La norme C s'en occupe (6.8.4.2.4):

Un cas ou une étiquette par défaut est accessible uniquement dans l'instruction de commutateur englobante la plus proche.

Sécurise
la source
1

Plusieurs personnes ont déjà évoqué la notion d'association de plusieurs conditions, ce qui est très précieux de temps en temps. Cependant, la possibilité de faire correspondre plusieurs conditions ne nécessite pas nécessairement que vous fassiez exactement la même chose pour chaque condition correspondante. Considérer ce qui suit:

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

Il existe deux manières différentes de faire correspondre des ensembles de conditions multiples. Avec les conditions 1 et 2, ils tombent simplement dans la même parcelle de code et font exactement la même chose. Aux conditions 3 et 4, toutefois, bien que les deux appels aboutissent doSomething3And4(), seuls 3 appels sont nécessaires doSomething3().

Panzercrisis
la source
En retard pour le parti, mais ce n'est absolument pas "1 et 2" comme le suggère le nom de la fonction: il y a trois états différents qui peuvent tous conduire au code lors du case 2déclenchement, donc si nous voulons être corrects (et nous le faisons), le réel le nom de la fonction devrait être doSomethingBecause_1_OR_2_OR_1AND2()ou quelque chose du genre (bien que le code légitime dans lequel une immuable corresponde à deux cas différents tout en restant bien écrit, le code est pratiquement inexistant, il devrait donc au moins le rester doSomethingBecause1or2())
Mike 'Pomax' Kamermans
En d' autres termes, doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). Cela ne fait pas référence à la logique et / ou.
Panzercrisis
Je sais que ce n’est pas le cas, mais étant donné la confusion qui règne autour de ce type de code, il devrait expliquer de manière absolue ce que «et» est, car s’appuyer sur une personne pour deviner «naturelle et» contre «fin logique» dans une réponse qui ne fonctionne que quand précisément expliqué, nécessite plus d'explications dans la réponse elle-même, ou une réécriture du nom de la fonction pour supprimer cette ambiguïté =)
Mike 'Pomax' Kamermans
1

Pour répondre à deux de vos questions.

Pourquoi C a-t-il besoin de pauses?

Cela revient aux racines de Cs en tant qu '"assembleur portable". Où psudo code comme celui-ci était commun: -

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

l'instruction switch a été conçue pour fournir des fonctionnalités similaires à un niveau supérieur.

Avons-nous des interrupteurs sans pause?

Oui, c'est assez courant et il y a quelques cas d'utilisation;

Premièrement, vous souhaiterez peut-être prendre la même mesure dans plusieurs cas. Nous faisons cela en empilant les cas les uns sur les autres:

case 6:
case 9:
    // six or nine code

Un autre cas d'utilisation courant dans les machines d'état est qu'après le traitement d'un état, nous souhaitons immédiatement entrer et traiter un autre état:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;
James Anderson
la source
-2

L'instruction Break est une instruction sautante qui permet à l'utilisateur de quitter le commutateur englobant le plus proche (pour votre cas), pendant que vous faites, pour ou pour chaque. c'est aussi simple que cela.

jaspe
la source
-3

Je pense qu'avec tout ce qui est écrit ci-dessus - le principal résultat de ce fil est que si vous voulez concevoir un nouveau langage - la valeur par défaut devrait être que vous n'avez pas besoin d'ajouter une breakdéclaration et que le compilateur la traitera comme si vous l'aviez fait.

Si vous souhaitez que le cas rare où vous souhaitez passer à la prochaine affaire - indiquez-le simplement avec une continuedéclaration.

Cela peut être amélioré de manière à ce que cela ne se produise que si vous utilisez des accolades à l'intérieur du cas, de sorte que l'exemple avec les mois précédents de plusieurs cas exécutant le même code exact - fonctionne toujours comme prévu sans avoir besoin de continue.

ethans
la source
2
Je ne suis pas sûr de comprendre votre suggestion concernant la déclaration continue. Continuer a une signification très différente en C et C ++ et si un nouveau langage ressemblait à C, mais utilisait parfois le mot-clé comme avec C et parfois de cette autre manière, je pense que la confusion régnerait. Bien qu'il puisse y avoir des problèmes avec la chute d'un bloc marqué dans un autre, j'aime bien utiliser plusieurs cas avec le même traitement avec un bloc de code partagé.
DeveloperDon
C utilise depuis longtemps les mêmes mots-clés à des fins multiples. Pensez *, &, staticetc.
idrougge