sentencia - ¿Hay algún caso útil usando una instrucción switch sin llaves?
switch case (5)
Aquí hay un ejemplo escrito por Dennis Ritchie en 1972 durante su trabajo en el primer compilador de C. El módulo c02.c, vinculado al pie de la página que acabo de vincular, incluye
easystmt()
{
extern peeksym, peekc, cval;
if((peeksym=symbol())==20) /* name */
return(peekc!='':''); /* not label */
if (peeksym==19) { /* keyword */
switch(cval)
case 10: /* goto */
case 11: /* return */
case 17: /* break */
case 18: /* continue */
return(1);
return(0);
}
return(peeksym!=2); /* { */
}
Después de leer su código de 1972, está claro que Dennis era un fanático de las declaraciones de cambio, las usó bastante. No es tan sorprendente dado que casi todo fue codificado como un int en parte por la falta de otras posibilidades de tipo de datos. Su implementación de compilador no usó estructuras en esa etapa porque estaba a punto de agregarlas al lenguaje. El envío dinámico, los vtables y el polimorfismo estaban muy lejos. Intenté y no pude encontrar una referencia para esto, pero si recuerdo correctamente, Dennis "inventó" declaraciones de cambio o al menos aportó ideas que llevan a la forma que toman en C y las consideró una de sus mejores o más orgullosas adiciones al idioma .
La posibilidad de omitir las llaves hace que las declaraciones modificadas sean formalmente similares a las declaraciones if
, for
, do
y while
, lo que ayuda a simplificar y unificar la gramática. Ver la declaración de selección y las producciones de declaración de iteración en la gramática C (por ejemplo, en el Apéndice A13 de Kernighan y Ritchie, páginas 236-237 en mi copia) donde se definen estas cosas.
Obviamente, uno siempre puede agregar llaves, pero tal vez eso parezca pesado para ejemplos tan simples como este. Este ejemplo podría codificarse como una declaración disyuntiva if, pero creo que una de las ideas que Dennis tenía para el cambio fue que al compilador se le está ofreciendo más claramente la oportunidad de optimizar la implementación de la lógica de bifurcación en función de las constantes particulares involucradas.
En H & S5 encontré la instrucción switch "más extraña" (8.7.1, p.287) que no usa llaves.
Aquí está la muestra:
switch (x)
default:
if (prime(x))
case 2: case 3: case 5: case 7:
process_prime(x);
else
case 4: case 6: case 8: case 9: case 10:
process_composite(x);
La idea parece ser evitar la sobrecarga de prime(x)
para los números pequeños más comunes.
Cuando vi esa afirmación, estaba confundido acerca de los paréntesis faltantes, pero al revisar la gramática oficial ( C1X pre-estándar , 6.8.4, p.147), la sintaxis era correcta: una instrucción switch simplemente tiene una declaración después de la expresión del interruptor y el paréntesis de cierre.
Pero en mi práctica de programación, nunca más volví a encontrar una declaración de cambio tan curiosa (y no quisiera ver ninguna en el código por el que deba responsabilizarme), pero empecé a preguntarme:
¿Alguno de ustedes conoce esa expresión de cambio, sin usar refuerzos, pero que aún tiene sentido? No solo switch (i);
(¿Qué es legal, pero NOP), pero utilizando al menos dos etiquetas de casos con algún tipo de propósito útil?
En la práctica, los interruptores se utilizan con llaves (incluso en el dispositivo de Duff) por razones de legibilidad. Y agregar llaves no hace daño.
He pensado en otro caso.
Supongamos que tengo un contador de tipo unsigned char que indica el número de iteraciones de un ciclo, pero si el contador es igual a cero, necesita pasar el ciclo 256 veces. Si mi forma de pensar es correcta, podría codificar esto de la siguiente manera:
uint8_t counter;
/* counter will get its value here somewhere */
switch (counter)
default:
while (0 < counter)
{
case 0:
/* Perform action */
counter--;
}
Por supuesto, esto supone que un subdesbordamiento de 0x00 da como resultado 0xFF para un char sin signo. Pero lo hace para todos mis entornos, aunque PC Lint se quejará ... Y sí, contiene llaves, pero solo por el while
, no para el switch
. Si sabes algo mejor, ¡déjame escucharlo!
¿Podría programar así? ¡Nunca! ... bueno, en un pequeño procesador de 8 bits ¡incluso podría! :-)
Sección 6.8.4.2 La instrucción switch dice:
Una instrucción switch hace que el control salte hacia, dentro o más allá de la instrucción que es el cuerpo del interruptor, dependiendo del valor de una expresión de control, y de la presencia de una etiqueta predeterminada y los valores de las etiquetas de cada caso en el interruptor cuerpo. Solo se puede acceder a una etiqueta de caso o predeterminada dentro de la declaración de interruptor de cierre más cercana.
Los términos cuerpo de interruptor y enunciado de enclavamiento más cercano parecen no requerir llaves. Así que tienes razón, parece extraño, pero es legal. (Nunca lo vi antes)
Si utiliza estructuras de control en macros, un switch
lugar de if
es útil, ya que no tiene ningún else
problema.
#define DEBUG_PRINT(...) switch (!debug_mode) case 0: fprintf(__VA_ARGS__)
Con eso no tienes sorpresas si un usuario de esa macro pone esto en una condición adicional
if (unclear) DEBUG_PRINT(stderr, "This is really %unclear/n", unclear);
else {
// do something reasonable here
}
Tal macro de depuración tiene la ventaja de estar siempre compilada (y luego optimizada). Por lo tanto, el código de depuración debe permanecer válido durante todo el tiempo en vivo del programa.
También observe aquí que es importante que el switch
no use {}
, de lo contrario, el ejemplo if/else
tampoco funcionaría. Todo esto podría lograrse por otros medios ( if/else
, (void)0
y do/while
tricks) pero este es el más conveniente que conozco.
Y no me malinterpreten, no digo que todos deberían usar estructuras de control dentro de las macros, sin duda deberían saber lo que están haciendo. Pero hay situaciones en las que está justificado.