Warum müssen wir Break-In-Schalter verwenden?


74

Wer hat entschieden (und basierend auf welchen Konzepten), dass die switchKonstruktion (in vielen Sprachen) breakin jeder Anweisung verwendet werden muss?

Warum müssen wir so etwas schreiben:

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

(Dies ist in PHP und JS aufgefallen. Es gibt wahrscheinlich viele andere Sprachen, die dies verwenden.)

Wenn switchist eine Alternative von if, warum können wir nicht die gleiche Konstruktion wie für verwenden if? Dh:

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

Es wird gesagt, breakdass die Ausführung des Blocks nach dem aktuellen verhindert wird. Aber ist jemand wirklich in die Situation geraten, in der die Ausführung des aktuellen und der folgenden Blöcke erforderlich war? Habe ich nicht. Für mich breakist immer da. In jedem Block. In jedem Code.


1
Wie die meisten Antworten festgestellt haben, bezieht sich dies auf "Fall-through", bei dem mehrere Bedingungen durch dieselben "then" -Blöcke geleitet werden. Dies ist jedoch sprachabhängig - RPG behandelt beispielsweise eine CASEAnweisung gleichwertig mit einem riesigen if / elseif-Block.
Clockwork-Muse

34
Es war eine schlechte Entscheidung der C-Konstrukteure (wie bei vielen Entscheidungen wurde der Übergang von Baugruppe zu C und die Rückübersetzung erleichtert, anstatt die Programmierung zu vereinfachen) , die dann leider vererbt wurde in anderen C-basierten Sprachen. Verwenden der einfachen Regel "Immer den Standardfall verwenden !!" Wir können sehen, dass das Standardverhalten darin bestehen sollte , mit einem separaten Schlüsselwort zum Durchfallen zu brechen, da dies bei weitem der häufigste Fall ist.
BlueRaja - Danny Pflughoeft

4
Ich habe bei mehreren Gelegenheiten Fall-through verwendet, obwohl die switch-Anweisung in meiner bevorzugten Sprache zu der Zeit (C) möglicherweise so konzipiert wurde, dass sie dies vermeidet. zb habe ich case 'a': case 'A': case 'b': case 'B'aber meistens gemacht weil ich das nicht kann case in [ 'a', 'A', 'b', 'B' ]. Eine etwas bessere Frage ist, dass in meiner derzeit bevorzugten Sprache (C #) die Unterbrechung obligatorisch ist und es keinen impliziten Sturz gibt. Vergessen breakist ein Syntaxfehler ...: \
KutuluMike

1
In Parser-Implementierungen kommt es häufig vor, dass der eigentliche Code verloren geht. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
OrangeDog

1
@ BlueRaja-DannyPflughoeft: C sollte auch einfach zu kompilieren sein. "Einen Sprung ausgeben, wenn er breakirgendwo vorhanden ist" ist eine wesentlich einfachere Regel als "Einen Sprung nicht ausgeben, wenn er fallthroughin a vorhanden ist switch".
Jon Purdy

Antworten:


95

C war eine der ersten Sprachen, die diese switchAussage in dieser Form erhalten hat, und alle anderen wichtigen Sprachen haben sie von dort geerbt. Sie haben sich hauptsächlich dafür entschieden, die C-Semantik standardmäßig beizubehalten - sie haben entweder nicht über die Vorteile einer Änderung nachgedacht oder beurteilt Sie sind weniger wichtig als das Verhalten, an das jeder gewöhnt war.

Warum C so entworfen wurde, ist wahrscheinlich auf das Konzept von C als "tragbare Baugruppe" zurückzuführen. Die switchAnweisung ist im Grunde eine Abstraktion einer Verzweigungstabelle , und eine Verzweigungstabelle hat auch einen impliziten Durchbruch und erfordert eine zusätzliche Sprunganweisung, um diesen zu vermeiden.

Daher haben die Designer von C auch beschlossen, die Assemblersemantik standardmäßig beizubehalten.


3
VB.NET ist ein Beispiel für eine Sprache, die es geändert hat. Es besteht keine Gefahr, dass es in die case-Klausel einfließt.
Stöbern Sie

1
Tcl ist eine andere Sprache, die nie in die nächste Klausel eingeht. Ich wollte es auch so gut wie nie (im Gegensatz zu C, wo es beim Schreiben bestimmter Arten von Programmen nützlich ist).
Donal Fellows

4
Die Vorfahrensprachen von C, B und die ältere BCPL , haben (hatten?) Switch-Anweisungen. BCPL hat es geschrieben switchon.
Keith Thompson

Rubys Argumente fallen nicht durch. Sie können ein ähnliches Verhalten erzielen, indem Sie zwei Testwerte in einem einzigen haben when.
Nathan Long

86

Denn switchist keine Alternative zu if ... elseAussagen in diesen Sprachen.

Durch die Verwendung von switchkönnen wir mehr als eine Bedingung gleichzeitig erfüllen, was in einigen Fällen sehr geschätzt wird.

Beispiel:

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

15
more than one block ... unwanted in most casesIch stimme dir nicht zu.
Satish Pandey

2
@DanielB Es hängt jedoch von der Sprache ab. C # switch-Anweisungen lassen diese Möglichkeit nicht zu.
Andy

5
@Andy Reden wir immer noch von Durchfall? C # switch-Anweisungen erlauben dies definitiv (siehe drittes Beispiel), daher die Notwendigkeit für break. Aber ja, soweit mir bekannt ist, funktioniert Duffs Gerät in C # nicht.
Daniel B

8
@DanielB Ja, aber der Compiler lässt keine Bedingung, keinen Code und dann eine andere Bedingung ohne Unterbrechung zu. Sie können mehrere Bedingungen ohne Code dazwischen stapeln, oder Sie benötigen eine Unterbrechung. Von deinem Link C# does not support an implicit fall through from one case label to another. Sie können durchfallen, aber es gibt keine Chance, versehentlich eine Pause zu vergessen.
Andy

3
@Matsemann .. Ich jetzt können wir alle Sachen mit, if..elseaber in einigen Situationen switch..casewäre vorzuziehen. Das obige Beispiel wäre etwas komplex, wenn wir if-else verwenden würden.
Satish Pandey

15

Dies wurde bei Stack Overflow im Zusammenhang mit C gefragt: Warum sollte die switch-Anweisung eine Unterbrechung benötigen?

Um die akzeptierte Antwort zusammenzufassen, war es wahrscheinlich ein Fehler. Die meisten anderen Sprachen sind wahrscheinlich gerade erst C gefolgt. Einige Sprachen wie C # scheinen dies jedoch durch das Zulassen von Ausfällen behoben zu haben - aber nur, wenn der Programmierer dies ausdrücklich mitteilt (Quelle: der obige Link, ich spreche kein C # selbst). .


Google Go führt dasselbe IIRC-Verfahren aus (sofern dies ausdrücklich angegeben ist, wird ein Fallthrough zugelassen).
Leo

PHP auch. Und je mehr Sie sich den resultierenden Code ansehen, desto unangenehmer fühlen Sie sich. Ich mag den /* FALLTHRU */Kommentar in der Antwort, die von @Tapio oben verlinkt wurde, um zu beweisen, dass Sie es gemeint haben.
DaveP

1
Wenn Sie sagen, dass C # goto casewie der Link funktioniert, lässt sich nicht veranschaulichen, warum dies so gut funktioniert. Sie können immer noch mehrere Fälle wie oben stapeln, aber sobald Sie Code einfügen, müssen Sie einen expliziten Code haben, um goto case whateverzum nächsten Fall überzugehen, oder Sie erhalten einen Compilerfehler. Wenn Sie mich befragen, ist die Möglichkeit, Fälle zu stapeln, ohne sich Gedanken über versehentliches Durchfallen durch Nachlässigkeit zu machen, bei weitem besser, als das explizite Durchfallen mit zu ermöglichen goto case.
Jesper

9

Ich werde mit einem Beispiel antworten. Wenn Sie die Anzahl der Tage für jeden Monat eines Jahres auflisten möchten, ist es offensichtlich, dass einige Monate 31, 30 und 1 28/29 haben. Es würde so aussehen,

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

Dies ist ein Beispiel, in dem mehrere Fälle den gleichen Effekt haben und alle zusammen gruppiert sind. Es gab offensichtlich einen Grund für die Wahl des Schlüsselworts break und nicht das if ... else if- Konstrukt.

Das Wichtigste dabei ist, dass eine switch- Anweisung mit vielen ähnlichen Fällen kein if ... else if ... else if ... else für jeden der Fälle ist, sondern if (1, 2, 3) ... sonst wenn (4,5,6) sonst ...


2
Ihr Beispiel sieht ausführlicher aus als ifohne Nutzen. Wäre es vielleicht sinnvoller, wenn Ihrem Beispiel ein Name zugewiesen würde oder zusätzlich zum Fall-through eine monatenspezifische Arbeit geleistet würde? (ohne zu kommentieren, ob man überhaupt sollte)
Horatio

1
Das ist wahr. Ich habe jedoch nur versucht, Fälle aufzuzeigen, in denen Fall-Through verwendet werden kann. Es wäre natürlich viel besser, wenn in jedem Monat etwas einzeln erledigt werden müsste.
Awemo

8

Es gibt zwei Situationen, in denen von einem Fall zum anderen „durchgefallen“ werden kann - der leere Fall:

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

und der nicht leere Fall

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

Ungeachtet des Verweises auf Duffs Gerät gibt es nur wenige legitime Fälle für den zweiten Fall, die im Allgemeinen durch Kodierungsstandards verboten und während der statischen Analyse gekennzeichnet sind. Und wo es gefunden wird, ist es meistens auf das Weglassen von a zurückzuführen break.

Ersteres ist durchaus sinnvoll und üblich.

Um ehrlich zu sein, sehe ich keinen Grund, den breakund den Sprachparser zu brauchen, der weiß, dass ein leerer Fallkörper ein Durchbruch ist, während ein nicht leerer Fall ein eigenständiger Fall ist.

Es ist schade, dass das ISO-C-Panel mehr damit beschäftigt zu sein scheint, der Sprache neue (unerwünschte) und schlecht definierte Features hinzuzufügen, als die undefinierten, nicht spezifizierten oder implementierungsdefinierten Features zu reparieren, ganz zu schweigen von den unlogischen.


2
Gott sei Dank, die ISO C ist nicht dazu da, bahnbrechende Änderungen in die Sprache einzuführen!
Jack Aidley

4

Nicht erzwingen breakerlaubt eine Reihe von Dingen, die sonst schwierig zu tun sein könnten. Andere haben Gruppierungsfälle festgestellt, für die es eine Reihe nicht trivialer Fälle gibt.

Ein Fall, in dem es unbedingt erforderlich ist, dass das breaknicht verwendet wird, ist Duff's Device . Dies wird zum " Aufrollen " von Schleifen verwendet, um Operationen zu beschleunigen, indem die Anzahl der erforderlichen Vergleiche begrenzt wird. Ich glaube, die anfängliche Verwendung ermöglichte Funktionen, die zuvor mit einer vollständig aufgerollten Schleife zu langsam waren. In einigen Fällen wird die Codegröße gegen die Geschwindigkeit eingetauscht.

Es ist empfehlenswert, das Symbol breakdurch einen entsprechenden Kommentar zu ersetzen , wenn der Fall einen Code enthält. Andernfalls wird jemand das Fehlen beheben breakund einen Fehler einführen.


Danke für das Duff's Device Beispiel (+1). Das war mir nicht bewusst.
Trejder

3

In C, wo der Ursprung zu sein scheint, ist der Codeblock der switchAnweisung kein spezielles Konstrukt. Es ist ein normaler Codeblock, genau wie ein Block unter einer ifAnweisung.

switch ()
{
}

if ()
{
}

caseund defaultsind Sprungmarken in diesem Block, die sich speziell auf beziehen switch. Sie werden genauso behandelt wie normale Sprungmarken für goto. Hierbei ist eine bestimmte Regel wichtig: Sprungmarken können sich fast überall im Code befinden, ohne den Codefluss zu unterbrechen.

Als normaler Codeblock muss es sich nicht um eine zusammengesetzte Anweisung handeln. Die Bezeichnungen sind ebenfalls optional. Dies sind gültige switchAussagen in C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

Der C-Standard selbst gibt dies als Beispiel an (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); 
} 

Im künstlichen Programmfragment existiert das Objekt, dessen Kennung i ist, mit automatischer Speicherdauer (innerhalb des Blocks), wird jedoch niemals initialisiert. Wenn der steuernde Ausdruck einen Wert ungleich Null hat, greift der Aufruf der Funktion printf auf einen unbestimmten Wert zu. Ebenso kann der Aufruf der Funktion f nicht erreicht werden.

Darüber hinaus defaulthandelt es sich auch um eine Sprungmarke und kann somit überall sein, ohne dass der letzte Fall vorliegen muss.

Dies erklärt auch Duffs Gerät:

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

Warum der Durchfall? Da im normalen Codefluss in einem normalen Codeblock ein Durchreichen der nächsten Anweisung erwartet wird , wie Sie es in einem ifCodeblock erwarten würden .

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();
}

Ich vermute, dass der Grund dafür die einfache Implementierung war. Sie benötigen keinen speziellen Code zum Parsen und Kompilieren eines switchBlocks, der spezielle Regeln berücksichtigt . Sie analysieren es einfach wie jeden anderen Code und müssen sich nur um die Bezeichnungen und die Sprungauswahl kümmern.

Eine interessante Folgefrage ist, ob die folgenden verschachtelten Anweisungen "Done" ausgeben. oder nicht.

int a = 10;

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

Dafür sorgt der C-Standard (6.8.4.2.4):

Auf eine Groß- / Kleinschreibung oder eine Standardbezeichnung kann nur mit der nächstliegenden switch-Anweisung zugegriffen werden.


1

Einige Leute haben bereits den Begriff der Übereinstimmung mehrerer Bedingungen erwähnt, der von Zeit zu Zeit sehr wertvoll ist. Die Fähigkeit, mehrere Bedingungen zu erfüllen, erfordert jedoch nicht unbedingt, dass Sie für jede Bedingung, die erfüllt ist, genau dasselbe tun. Folgendes berücksichtigen:

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

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

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

Es gibt zwei verschiedene Arten, wie Gruppen von mehreren Bedingungen hier abgeglichen werden. Mit den Bedingungen 1 und 2 fallen sie einfach auf genau den gleichen Code durch und tun genau das Gleiche. Mit Bedingungen 3 und 4 jedoch, obwohl sie beide Ende durch den Aufruf doSomething3And4(), nur 3 Anrufe doSomething3().


Verspätet zur Party, aber das ist absolut nicht "1 und 2", wie der Funktionsname andeutet: Es gibt drei verschiedene Zustände, die alle zum Code beim case 2Auslösen führen können. Wenn wir also richtig sein wollen (und wir tun es), dann ist das real Funktionsname müsste sein doSomethingBecause_1_OR_2_OR_1AND2()oder so (obwohl legitimer Code, in dem eine unveränderliche Datei zwei verschiedenen Fällen entspricht, während noch gut geschriebener Code praktisch nicht vorhanden ist, sollte dies zumindest sein doSomethingBecause1or2())
Mike 'Pomax' Kamermans

Mit anderen Worten doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). Es bezieht sich nicht auf logische und / oder.
Panzercrisis

Ich weiß, dass dies nicht der Fall ist, aber wenn man bedenkt, warum die Leute über diese Art von Code verwirrt sind, sollte dies unbedingt erklären, was das "und" ist, da man sich darauf verlassen muss, dass jemand "natürlich" und "gegen" logisches Ende "in einer Antwort errät, die nur funktioniert Wenn genau erklärt, braucht mehr Erklärung in der Antwort selbst oder eine Umschreibung des Funktionsnamens, um diese Mehrdeutigkeit zu beseitigen =)
Mike 'Pomax' Kamermans

1

Um zwei Ihrer Fragen zu beantworten.

Warum braucht C Pausen?

Als "portabler Assembler" kommt es auf Cs-Roots an. Wo Psudo-Code wie dieser üblich war: -

    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:
    ......

Die switch-Anweisung wurde entwickelt, um ähnliche Funktionen auf einer höheren Ebene bereitzustellen.

Haben wir jemals Schalter ohne Unterbrechungen?

Ja, das ist ziemlich häufig und es gibt einige Anwendungsfälle.

Zunächst möchten Sie möglicherweise in mehreren Fällen die gleiche Aktion ausführen. Wir tun dies, indem wir die Hüllen aufeinander stapeln:

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

Ein weiterer in Zustandsautomaten üblicher Anwendungsfall ist, dass wir nach der Verarbeitung eines Zustands sofort in einen anderen Zustand eintreten und diesen verarbeiten möchten:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;

-2

Die Anweisung break ist eine Sprunganweisung, mit der der Benutzer den nächstgelegenen umschließenden Schalter (für Ihren Fall) verlassen kann, während, do, for oder foreach ausgeführt wird. es ist genauso einfach.


-3

Ich denke, dass mit allen oben beschriebenen - das Hauptergebnis dieses Threads ist, dass, wenn Sie eine neue Sprache entwerfen - die Standardeinstellung sein sollte, dass Sie keine breakAnweisung hinzufügen müssen und der Compiler sie so behandelt, als ob Sie es getan hätten.

Wenn Sie diesen seltenen Fall möchten, in dem Sie mit dem nächsten Fall fortfahren möchten, geben Sie ihn einfach mit einer continueErklärung an.

Dies kann so verbessert werden, dass nur dann, wenn Sie geschweifte Klammern im Inneren des Gehäuses verwenden, dies nicht weitergeht, sodass das Beispiel mit den obigen Monaten von mehreren Fällen, in denen derselbe exakte Code ausgeführt wird, immer wie erwartet funktioniert, ohne dass der Code benötigt wird continue.


2
Ich bin mir nicht sicher, ob ich Ihren Vorschlag in Bezug auf die continue-Anweisung verstehe. Continue hat in C und C ++ eine ganz andere Bedeutung, und wenn eine neue Sprache wie C wäre, aber das Schlüsselwort manchmal wie C und manchmal auf diese alternative Weise verwendet würde, würde meiner Meinung nach Verwirrung herrschen. Während es einige Probleme geben kann, von einem beschrifteten Block in einen anderen zu fallen, mag ich die Verwendung mehrerer Fälle mit der gleichen Behandlung mit einem gemeinsam genutzten Codeblock.
DeveloperDon

C hat eine lange Tradition darin, dieselben Schlüsselwörter für mehrere Zwecke zu verwenden. Denken Sie an *, &, staticusw.
idrougge
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.