¿Por qué tenemos que usar break in switch?

74

¿Quién decidió (y con base en qué conceptos) que la switchconstrucción (en muchos idiomas) tiene que usar breaken cada enunciado?

¿Por qué tenemos que escribir algo como esto?

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

(Noté esto en PHP y JS; probablemente hay muchos otros lenguajes que usan esto)

Si switches una alternativa de if, ¿por qué no podemos usar la misma construcción que para if? Es decir:

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

Se dice que breakimpide la ejecución del bloque que sigue al actual. Pero, ¿alguien realmente se encuentra con la situación, donde había alguna necesidad de ejecutar el bloque actual y los siguientes? No lo hice Para mí, breaksiempre está ahí. En cada cuadra. En cada codigo.

trejder
fuente
1
Como la mayoría de las respuestas han señalado, esto está relacionado con el "fracaso", donde múltiples condiciones se enrutan a través de los mismos bloques "entonces". Sin embargo, esto depende del lenguaje: RPG, por ejemplo, trata una CASEdeclaración de manera equivalente a un bloque gigante if / elseif.
Clockwork-Muse
34
Fue una mala decisión tomada por los diseñadores de C (como muchas decisiones, fue hecha para hacer la transición del ensamblaje -> C, y la traducción de regreso, más fácil, en lugar de facilitar la programación) , que desafortunadamente fue heredada en otros lenguajes basados ​​en C. Usando la regla simple "¡Siempre haga que el caso común sea el predeterminado!" , podemos ver que el comportamiento predeterminado debería haber sido romper, con una palabra clave separada para fallar, ya que ese es, con mucho, el caso más común.
BlueRaja - Danny Pflughoeft
44
He usado fall-through en varias ocasiones, aunque podría decirse que la declaración de cambio en mi idioma preferido en ese momento (C) podría haber sido diseñada para evitarlo; Por ejemplo, lo he hecho case 'a': case 'A': case 'b': case 'B'pero principalmente porque no puedo hacerlo case in [ 'a', 'A', 'b', 'B' ]. Una pregunta un poco mejor es, en mi lenguaje preferido actual (C #), el descanso es obligatorio , y no hay una caída implícita; olvidar breakes un error de sintaxis ...: \
KutuluMike
1
La falla con el código real a menudo se encuentra en implementaciones de analizador. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
Deja de dañar a Mónica el
1
@ BlueRaja-DannyPflughoeft: C también fue diseñado para ser fácil de compilar. "Emitir un salto si breakestá presente en alguna parte" es una regla significativamente más simple de implementar que "No emitir un salto si fallthroughestá presente en un switch".
Jon Purdy

Respuestas:

95

C fue uno de los primeros lenguajes en tener la switchdeclaración en esta forma, y ​​todos los demás lenguajes principales la heredaron de allí, en su mayoría eligieron mantener la semántica de C por defecto; o no pensaron en las ventajas de cambiarla o juzgaron menos importantes que mantener el comportamiento al que todos estaban acostumbrados.

En cuanto a por qué C fue diseñado de esa manera, probablemente se deriva del concepto de C como "ensamblaje portátil". La switchdeclaración es básicamente una abstracción de una tabla de ramificación , y una tabla de ramificación también tiene una caída implícita y requiere una instrucción de salto adicional para evitarla.

Básicamente, los diseñadores de C también optaron por mantener la semántica del ensamblador por defecto.

Michael Borgwardt
fuente
3
VB.NET es un ejemplo de un lenguaje que lo cambió. No hay peligro de que fluya dentro de la cláusula del caso.
meter
1
Tcl es otro idioma que nunca pasa a la siguiente cláusula. Prácticamente nunca quise hacerlo (a diferencia de C, donde es útil al escribir ciertos tipos de programas).
Donal Fellows
44
Los lenguajes ancestrales de C, B y el BCPL anterior , ambos tienen (¿tenían?) Declaraciones de cambio; BCPL lo deletreó switchon.
Keith Thompson
Las declaraciones de caso de Ruby no se caen; puede obtener un comportamiento similar al tener dos valores de prueba en uno solo when.
Nathan Long el
86

Porque switchno es una alternativa de if ... elsedeclaraciones en esos idiomas.

Al usarlo switch, podemos igualar más de una condición a la vez, lo cual es muy apreciado en algunos casos.

Ejemplo:

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
fuente
15
more than one block ... unwanted in most casesNo estoy de acuerdo contigo.
Satish Pandey
2
@DanielB Sin embargo, depende del idioma; Las declaraciones de cambio de C # no permiten esa posibilidad.
Andy
55
@Andy ¿Todavía estamos hablando de fall-through? Las declaraciones de cambio de C # definitivamente lo permiten (verifique el tercer ejemplo), de ahí la necesidad de hacerlobreak . Pero sí, que yo sepa, el dispositivo de Duff no funciona en C #.
Daniel B
8
@DanielB Sí, lo estamos, pero el compilador no permitirá una condición, código y luego otra condición sin interrupción. Puede tener varias condiciones apiladas sin código entre ellas, o necesita un descanso. Desde tu enlace C# does not support an implicit fall through from one case label to another. Puede fallar, pero no hay posibilidad de olvidar accidentalmente un descanso.
Andy
3
@ Matsemann ... Ahora puedo hacer todo lo que usemos, if..elsepero en algunas situaciones switch..casesería preferible. El ejemplo anterior sería un poco complejo si utilizamos if-else.
Satish Pandey
15

Esto se ha preguntado en Stack Overflow en el contexto de C: ¿Por qué se diseñó la instrucción switch para necesitar un descanso?

Para resumir la respuesta aceptada, probablemente fue un error. La mayoría de los otros lenguajes probablemente solo han seguido a C. Sin embargo, algunos lenguajes como C # parecen haber solucionado esto al permitir la caída, pero solo cuando el programador lo dice explícitamente (fuente: el enlace de arriba, no hablo C #) .

Tapio
fuente
Google Go hace lo mismo IIRC (permitiendo errores si se especifica explícitamente).
Leo
PHP también lo hace. Y cuanto más miras el código resultante, más incómodo te hace sentir. Me gusta el /* FALLTHRU */comentario en la respuesta vinculada por @Tapio arriba, para demostrar que lo dices en serio .
DaveP
1
Decir que C # tiene goto case, como lo hace el enlace, no ilustra por qué eso funciona tan bien. Todavía puede apilar varios casos como el anterior, pero tan pronto como inserte el código, debe tener un mensaje explícito goto case whateverpara pasar al siguiente caso u obtener un error del compilador. Si me pregunta, de los dos, la capacidad de apilar casos sin preocuparse por una caída accidental por negligencia es, con mucho, una mejor característica que la habilitación de la caída explícita goto case.
Jesper
9

Contestaré con un ejemplo. Si desea enumerar la cantidad de días para cada mes del año, es obvio que algunos meses tienen 31, algunos 30 y 1 28/29. Se vería así

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

Este es un ejemplo en el que varios casos tienen el mismo efecto y están todos agrupados. Obviamente, había una razón para elegir la palabra clave break y no la construcción if ... else if .

Lo principal a tener en cuenta aquí es que una declaración de cambio con muchos casos similares no es un if ... else if ... else if ... else para cada uno de los casos, sino más bien if (1, 2, 3) ... si if (4,5,6) más ...

Awemo
fuente
2
Su ejemplo se ve más detallado que un ifsin ningún beneficio. ¿Quizás si su ejemplo asignara un nombre o hiciera un trabajo específico para un mes además de la falla, la utilidad de la falla sería más evidente? (sin comentar si uno DEBE en primer lugar)
horatio
1
Eso es verdad. Sin embargo, solo estaba tratando de mostrar casos en los que se podía utilizar la falla. Obviamente, sería mucho mejor si cada uno de los meses necesitara algo hecho individualmente.
Awemo
8

Hay dos situaciones en las que puede ocurrir un "fracaso" de un caso a otro: el caso vacío:

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

y el caso no vacío

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

A pesar de la referencia al dispositivo de Duff , las instancias legítimas para el segundo caso son pocas y distantes, y generalmente están prohibidas por los estándares de codificación y marcadas durante el análisis estático. Y donde se encuentra, la mayoría de las veces se debe a la omisión de a break.

El primero es perfectamente sensible y común.

Para ser honesto, no puedo ver ninguna razón para necesitar el breakanalizador de lenguaje y saber que un cuerpo de caso vacío es una falla, mientras que un caso no vacío es independiente.

Es una pena que el panel ISO C parezca más preocupado por agregar características nuevas (no deseadas) y mal definidas al lenguaje, en lugar de corregir las características indefinidas, no especificadas o definidas por la implementación, sin mencionar lo ilógico.

Andrés
fuente
2
¡Gracias a Dios que el ISO C no está en introducir cambios importantes en el lenguaje!
Jack Aidley
4

No forzar el breakpermite una serie de cosas que de otro modo podrían ser difíciles de hacer. Otros han señalado casos de agrupación, para los cuales hay una serie de casos no triviales.

Un caso en el que es imperativo que breakno se use es el dispositivo de Duff . Esto se utiliza para " desenrollar " bucles donde puede acelerar las operaciones limitando el número de comparaciones requeridas. Creo que el uso inicial permitió una funcionalidad que anteriormente había sido demasiado lenta con un ciclo completamente enrollado. Se cambia el tamaño del código por velocidad en algunos casos.

Es una buena práctica reemplazar el breakcon un comentario apropiado, si el caso tiene algún código. De lo contrario, alguien reparará lo que falta breake introducirá un error.

BillThor
fuente
Gracias por el ejemplo del dispositivo Duff (+1). No estaba al tanto de eso.
trejder
3

En C, donde parece estar el origen, el bloque de código de la switchdeclaración no es una construcción especial. Es un bloque de código normal, al igual que un bloque debajo de una ifinstrucción.

switch ()
{
}

if ()
{
}

casey defaultson etiquetas de salto dentro de este bloque, específicamente relacionadas con switch. Se manejan como las etiquetas de salto normales goto. Aquí hay una regla específica que es importante: las etiquetas de salto pueden estar en casi todas partes del código, sin interrumpir el flujo del código.

Como un bloque de código normal, no necesita ser una declaración compuesta. Las etiquetas también son opcionales. Estas son switchdeclaraciones válidas en C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

El estándar C en sí mismo da esto como un ejemplo (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); 
} 

En el fragmento de programa artificial, el objeto cuyo identificador es i existe con una duración de almacenamiento automática (dentro del bloque) pero nunca se inicializa y, por lo tanto, si la expresión de control tiene un valor distinto de cero, la llamada a la función printf accederá a un valor indeterminado. Del mismo modo, no se puede acceder a la llamada a la función f.

Además, también defaultes una etiqueta de salto, y por lo tanto puede estar en cualquier lugar, sin la necesidad de ser el último caso.

Esto también explica el dispositivo 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);
}

¿Por qué la caída? Debido a que en el flujo de código normal en un bloque de código normal, se espera pasar a la siguiente instrucción , tal como lo esperaría en un ifbloque de código.

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

Supongo que la razón de esto fue la facilidad de implementación. No necesita un código especial para analizar y compilar un switchbloque, teniendo en cuenta las reglas especiales. Simplemente lo analiza como cualquier otro código y solo tiene que cuidar las etiquetas y la selección de salto.

Una pregunta de seguimiento interesante de todo esto es si las siguientes declaraciones anidadas imprimen "Listo". o no.

int a = 10;

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

El estándar C se preocupa por esto (6.8.4.2.4):

Solo se puede acceder a una etiqueta de caso o predeterminada dentro de la declaración de interruptor adjunta más cercana.

Seguro
fuente
1

Varias personas ya han mencionado la noción de emparejar múltiples condiciones, lo cual es muy valioso de vez en cuando. Sin embargo, la capacidad de hacer coincidir múltiples condiciones no necesariamente requiere que haga exactamente lo mismo con cada condición que coincida. Considera lo siguiente:

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

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

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

Aquí hay dos formas diferentes de hacer coincidir conjuntos de condiciones múltiples. Con las condiciones 1 y 2, simplemente caen exactamente en el mismo diagrama de código y hacen exactamente lo mismo. Sin embargo, con las condiciones 3 y 4, aunque ambas terminan llamando doSomething3And4(), solo 3 llamadas doSomething3().

Panzercrisis
fuente
Tarde a la fiesta, pero esto no es absolutamente "1 y 2" como sugiere el nombre de la función: hay tres estados diferentes que pueden conducir al código en la case 2activación, por lo que si queremos ser correctos (y lo hacemos) lo real el nombre de la función tendría que ser doSomethingBecause_1_OR_2_OR_1AND2()o algo (aunque el código legítimo en el que un inmutable coincide con dos casos diferentes sin dejar de ser un código bien escrito es prácticamente inexistente, por lo menos esto debería ser doSomethingBecause1or2())
Mike 'Pomax' Kamermans
En otras palabras, doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). No se refiere a lógica y / o.
Panzercrisis
Sé que no lo es, pero dado que la gente se confunde con este tipo de código, debería explicar absolutamente qué es "y", porque confiar en alguien para adivinar "final natural" y "versus" lógico "en una respuesta que solo funciona cuando se explica con precisión, necesita más explicación en la respuesta en sí, o una reescritura del nombre de la función para eliminar esa ambigüedad =)
Mike 'Pomax' Kamermans
1

Para responder dos de tus preguntas.

¿Por qué C necesita descansos?

Se reduce a las raíces Cs como un "ensamblador portátil". Donde el código psudo como este era común:

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

La declaración de cambio se diseñó para proporcionar una funcionalidad similar en un nivel superior.

¿Alguna vez tenemos interruptores sin interrupciones?

Sí, esto es bastante común y hay algunos casos de uso;

En primer lugar, es posible que desee realizar la misma acción para varios casos. Hacemos esto apilando los casos uno encima del otro:

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

Otro caso de uso común en las máquinas de estado es que después de procesar un estado, inmediatamente queremos ingresar y procesar otro estado:

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

La declaración de interrupción es una declaración de salto que permite al usuario salir del interruptor de cierre más cercano (para su caso), mientras, hacer, para o para cada uno. Es tan fácil como eso.

jaspe
fuente
-3

Creo que con todo lo escrito anteriormente, el resultado principal de este hilo es que si va a diseñar un nuevo lenguaje, lo predeterminado debería ser que no necesita agregar una breakdeclaración y el compilador lo tratará como si lo hiciera.

Si desea ese caso raro en el que desea continuar con el siguiente caso, simplemente indíquelo con una continuedeclaración.

Esto se puede mejorar para que solo si usa llaves dentro de la caja, no continúe, de modo que el ejemplo con los meses anteriores de varias cajas que realizan el mismo código exacto, siempre funcionaría como se esperaba sin la necesidad de continue.

etanos
fuente
2
No estoy seguro de entender su sugerencia en relación con la declaración de continuación. Continuar tiene un significado muy diferente en C y C ++ y si un nuevo lenguaje fuera como C, pero usara la palabra clave a veces como con C y a veces de esta manera alternativa, creo que reinaría la confusión. Si bien puede haber algunos problemas al caer de un bloque etiquetado a otro, me gusta el uso de múltiples casos con el mismo manejo con un bloque de código compartido.
DesarrolladorDon
C tiene una larga tradición de usar las mismas palabras clave para múltiples propósitos. Piense *, &, staticetc.
idrougge