2011-11-16 21 views
5

Encontré hoy un código de cambio de caja y me sorprendió un poco ver cómo funcionaba. El código fue:¿Funciona el conmutador de casos así?

switch (blah) 
{ 
case a: 
    break; 
case b: 
    break; 
case c: 
case d: 
case e: 
    { 
    /* code here */ 
    } 
    break; 
default : 
    return; 
} 

Para mi sorpresa en el escenario en el que la variable fue c, el camino se fueron dentro del segmento "código aquí". Estoy de acuerdo en que no hay interrupción al final de la parte c del interruptor de caja, pero me hubiera imaginado que pasaría por default en su lugar. Cuando aterriza en una línea case blah:, ¿no comprueba si su valor actual coincide con el caso particular y solo entonces le permiten ingresar al segmento específico? De lo contrario, ¿cuál es el sentido de tener un caso?

Respuesta

13

Esto se llama caso de caída a través de, y es un comportamiento deseable. Le permite compartir código entre casos.

Un ejemplo de cómo utilizar el caso de paso al siguiente comportamiento:

switch(blah) 
{ 
case a: 
    function1(); 
case b: 
    function2(); 
case c: 
    function3(); 
    break; 
default: 
    break; 
} 

Si introduce el interruptor cuando blah == a, entonces se ejecutará function1(), function2() y function3().

Si no quiere tener este comportamiento, puede optar por salir de ella mediante la inclusión de break declaraciones.

switch(blah) 
{ 
case a: 
    function1(); 
    break; 
case b: 
    function2(); 
    break; 
case c: 
    function3(); 
    break; 
default: 
    break; 
} 

La forma en que una sentencia switch funciona es que será (más o menos) ejecutar un goto para saltar a la etiqueta de su caso, y mantener en funcionamiento a partir de ese punto. Cuando la ejecución llega a break, abandona el bloque de conmutadores.

+0

: -¡Gracias por la elaborada respuesta! Entiendo cómo funciona. Es solo que es contra lo que el sentido común me dicta. Todavía creo que el comportamiento es contrario a la intuición. Hasta ahora he pensado que esos "casos (bla)" eran como un cheque para asegurar si tu variable realmente es (bla) ... ¡al parecer no funciona así! – Manish

+2

@Manish: Switch a menudo se implementa con un "[branch table] (http://en.wikipedia.org/wiki/Branch_table)", por lo que también podría ayudar a su comprensión. El sentido común no existe en la programación, por cierto. Necesita "sentido del programador" en su lugar :) –

+2

@Manish Ambos comportamientos son deseables, uno puede tener un incumplimiento predeterminado y explícitamente 'break out ', o un break-out explícito y' continue' explícitamente al próximo caso si así lo desea. Desde una perspectiva de sentido común, esta última puede parecer más natural, pero con la implementación en mente, la caída predeterminada es más natural, por lo que se eligió. –

10

Ese es el comportamiento correcto, y se conoce como "caída". Esto le permite tener múltiples casos manejados por el mismo código. En situaciones avanzadas, es posible que desee realizar algún código en un caso, luego pasar a otro caso.

ejemplo Contrived:

switch(command) 
{ 
    case CMD_SAVEAS: 
    { 
     this->PromptForFilename(); 
    } // DO NOT BREAK, we still want to save 
    case CMD_SAVE: 
    { 
     this->Save(); 
    } break; 


    case CMD_CLOSE: 
    { 
     this->Close(); 
    } break; 

    default: 
     break; 
} 
+1

El famoso ejemplo de fall through es [dispositivo de Duff] (http://en.wikipedia.org/wiki/Duff%27s_device). –

+0

Es un uso bastante bueno, pero imagino que es algo que el compilador usualmente hará por ti cuando optimices la velocidad. No me gustaría verlo escrito en C a menos que haya una razón REALMENTE buena y un bloque de comentarios apropiado que explique por qué es necesario y cómo mantenerlo. – shenles

2

Por suerte para nosotros, C++ no depende de su imaginación :-)

pienso en las etiquetas de los interruptores como etiquetas "Ir a", y el switch(blah) simplemente "va a" la etiqueta correspondiente, y luego el código solo fluye desde allí.

2

En realidad, la declaración del interruptor funciona de la manera que usted observó. Está diseñado para que pueda combinar varios casos juntos hasta que se encuentre un descanso y actúa como un colador.

Aquí está un ejemplo del mundo real de uno de mis proyectos:

struct keystore_entry *new_keystore(p_rsd_t rsd, enum keystore_entry_type type, const void *value, size_t size) { 
     struct keystore_entry *e; 
     e = rsd_malloc(rsd, sizeof(struct keystore_entry)); 
     if (!e) 
       return NULL; 
     e->type = type; 
     switch (e->type) { 
     case KE_DOUBLE: 
       memcpy(&e->dblval, value, sizeof(double)); 
       break; 
     case KE_INTEGER: 
       memcpy(&e->intval, value, sizeof(int)); 
       break; 

     /* NOTICE HERE */ 

     case KE_STRING: 
       if (size == 0) { 
         /* calculate the size if it's zero */ 
         size = strlen((const char *)value); 
       } 
     case KE_VOIDPTR: 
       e->ptr = rsd_malloc(rsd, size); 
       e->size = size; 
       memcpy(e->ptr, value, size); 
       break; 

     /* TO HERE */ 
     default: 
       return NULL; 
     } 
     return e; 
} 

El código para KE_STRING y KE_VOIDPTR casos es idéntico excepto para el cálculo del tamaño en caso de cadena.

4

Esto se denomina caída.

Se está haciendo exactamente lo que está viendo: varios casos se van a ejecutar misma pieza de código.

También es conveniente en hacer un procesamiento adicional para ciertos casos, y algo de lógica compartida:

// psuedo code: 
void stopServer() { 
    switch (serverStatus) 
    case STARTING: 
    { 
     extraCleanUpForStartingServer(); 
     // fall-thru 
    } 
    case STARTED: 
    { 
     deallocateResources(); 
     serverStatus = STOPPED; 
     break; 
    } 
    case STOPPING: 
    case STOPPED: 
    default: 
     // ignored 
     break; 
} 

Este es un uso típico de caída a través de switch de los casos. En caso de INICIAR y COMENZAR, tenemos que desasignar Recursos y cambiar el estado a DETENIDO, pero INICIAR necesita una limpieza adicional. De la manera anterior, puede presentar claramente la 'lógica común' más lógica adicional en INICIAR.

STOPPED, STOPPING y el valor predeterminado son similares, todos caen a la lógica predeterminada (que está ignorando).

No siempre es una buena manera de codificar de esta manera, pero si se usa bien, puede presentar mejor la lógica.

Cuestiones relacionadas