2011-03-03 10 views
11

Estoy leyendo un texto en C language. El texto dice que switch{} case solo puede aceptar el tipo entero.Acerca del interruptor {} caso en C?

Tengo curiosidad acerca de por qué switch{} case no acepta otros tipos como flotador o cuerda. ¿Hay alguna razón detrás de esto?

Muchas gracias.

+0

En mi humilde opinión, supongo que es debido al tamaño fijo y los valores constantes de número entero. También puedes usar char, cuando se expresa como int: http://stackoverflow.com/questions/650162/why-switch-statement-cannot-be-applied-on-strings – vaugham

+2

Amigo, solo para tu información, las carrozas no son fácilmente comparables (piense en precisión en el lado derecho del punto decimal.) Piénselo. –

Respuesta

18

La razón clásica es probablemente que para las "expresiones de decisión" con valores enteros, es posible hacer optimizaciones muy buenas.

Básicamente, puede asignar la lista de declaraciones de casos a una tabla que contiene direcciones, y luego saltar directamente en función del valor. Obviamente, para flotadores y cuerdas que no funciona.

En GCC, usted puede hacer esto a mano utilizando some extensions así:

const char * digit_name(int d) 
{ 
    const void * handlers[] = { &&zero, &&one, &&two, &&three, &&four, 
           &&five, &&six, &&seven, &&eight, &&nine }; 
    goto *handlers[d]; /* Assumes d is in range 0..9. */ 

zero: return "zero"; 
one: return "one"; 
two: return "two"; 
three: return "three"; 
four: return "four"; 
five: return "five"; 
six: return "six"; 
seven: return "seven"; 
eight: return "eight"; 
nine: return "nine"; 
return NULL; 
} 

Esto es, en general, llamado un "goto calculado", y debe quedar claro cómo un switch básicamente se puede compilar hasta algo muy similar. La definición estricta de la expresión activada ayuda, como el uso de un enum.

Además, C realmente no tiene mucho concepto de cadenas de texto.

+1

Para un conjunto escaso de valores, algunos compiladores también están utilizando la búsqueda binaria; algunos incluso deberían poder comenzar con una búsqueda binaria y luego cambiar a una tabla de salto para una región densa. Otra cosa que he visto es utilizar la retroalimentación del perfil para sesgar la búsqueda binaria con el fin de favorecer casos comunes. – AProgrammer

+0

Sin embargo, si solo hay un puñado de cajas de cambio, un simple lineal es (x == case1) {...}; if (x == case1) {...}; ... puede tomar menos instrucciones que una búsqueda binaria completa. Siempre depende del problema real. – datenwolf

0

Bueno, una comparación de los valores de coma flotante no es fiable debido a errores de redondeo, y la comparación de cadenas no se admite de forma predeterminada por C (sólo por la función strcmp por ejemplo)

-> no hay manera de determinar automáticamente el métodos de comparación por el compilador.

0

La respuesta corta es que los tipos enteros son fáciles de comparar y la comparación es muy rápida. Los tipos de puntos flotantes tal como están en C no se pueden comparar de manera confiable. No creo que C tenga tipos de cadenas, pero las cadenas son lentas para comparar ... tendrás que comparar matrices de caracteres ... lentos. Estoy seguro de que alguien te dará una respuesta más completa y más científica.

1

valores de coma flotante no suelen ser directamente comparables

x = 1/3.0; 
switch (x) { 
    case 0.3333: /* ... */; break; 
    case 0.333333333875634875634: /* ... */; break; 
    case 0.333333333784532452321: /* ... */; break; 
    case 0.333333333847632874632: /* ... */; break; 
    default: break; 
} 

mismo con cuerdas (sin strcpy(buff, "foobar"); if (buff == "foobar") /* ... */;)

3

La filosofía del lenguaje de C es lo que ves es lo que obtienes. No hay mecanismos ocultos. Esta es en realidad una de las grandes fortalezas del lenguaje.

Encender entero implica una bifurcación como se esperaba, mientras que la comparación en flotante y cadena tendría un costo oculto.

3

Yo respondería con una pregunta: ¿Por qué está usando una declaración de cambio y no si ... si es así?

Sorprendentemente, muchos programadores no hacen esa pregunta, pero lo que se refiere switch de algo fundamental que debe estar allí en el idioma. ¡Eso no es verdad! Puede escribir cualquier clase de programa C sin usar switch. Estrictamente hablando, switch es una característica redundante.

¿Por qué usarlo?

La legibilidad es no la razón por la cual. switch en realidad tiene una sintaxis mucho peor y menos intuitiva que if-else. La necesidad de declaraciones de interrupción dentro del conmutador, las reglas de sintaxis de cambio extrañas que permiten declarar un caso dentro de un ámbito local de otro caso, la ubicación arbitraria del valor predeterminado.

switch no solo es menos legible, sino que también es mucho más propenso a errores que if-else. La ruptura olvidada es el peligro más obvio, que ha llevado a millones y millones de errores difíciles de encontrar en el software.

Otro argumento más evidente en contra switch ser más legible es el código "hueso desnudo":

if (A) 
{ 
} 

else if (B) 
{ 
} 

else if (C) 
{ 
} 

else 
{ 
} 


switch(something) 
{ 
    case A: 
    { 
    break; 
    } 

    case B: 
    { 
    break; 
    } 

    case C: 
    { 
    break; 
    } 

    default: 
    { 
    break; 
    } 
} 

El Si y el interruptor de arriba son equivalentes en la sintaxis y la función, y debe ser compilado hasta el mismísimo codigo de maquina. Aquí están las estadísticas de este ejemplo interruptor

  if-else switch 
Symbols 33  65  // Not counting the expressions 
Lines  12  19  // Not counting empty lines 

requiere más código para el mismo resultado, por lo tanto se debe ser considerado como menos fácil de leer que if-else.

Antes de que empieces a discutir sobre cambiar el aspecto más parecido a una tabla que if-else, todo se trata de formatear el código, e irrelevante. Nada en la norma que impide escribir código como este:

if  (A) {} 
else if (B) {} 
else if (C) {} 
else  {} 

switch(something) 
{ 
    case A: { break; } 
    case B: { break; } 
    case C: { break; } 
    default: { break; } 
} 

yo consideraría cualquier forma que sea legible, si lo desea algún tipo de sintaxis mínima a modo de mesa.

Por supuesto, puede haber razones estéticas, supersticiosas o religiosas por las cuales switch se deben utilizar para facilitar la lectura, pero prefiero dejar esas discusiones fuera del tema a sitios que no están relacionados con la programación.


Así que el cambio es menos seguro y menos legible que if-else. Lo que queda que puede atraer a un programador es la eficiencia. Un programador con n casos que deben ser probados por el programa seguramente desea tener algo que hace que la búsqueda de la carcasa correcta sea lo más rápida posible. Es una mala idea examinar todos los casos de forma lineal.

Como ya sabrán, es posible optimizar una mucho if-else o switch, al implementarlo como una matriz de punteros de función:

typedef void (*Func_t)(void); 

const Func_t cases [N] = { ... }; 
cases[i](); 

Esta optimización drástica a menudo es exactamente lo que un compilador lo hace cuando se encuentra con una declaración de cambio. Sin embargo, esta optimización solo se puede realizar si todos los casos son enteros adyacentes. Si no son adyacentes, entonces la adyacencia se puede crear a través de una tabla de búsqueda const.

Pero si los casos no son de tipo entero, la optimización anterior no se puede hacer. Si fueron flotadores, cadenas u otra cosa, no hay una forma sensata de optimizar el código.

Al menos en mi libro, switch solo existe para este propósito: hace que sea más fácil para el compilador crear código más efectivo que if-else. Por lo tanto, entra dentro de la misma categoría que las palabras clave en línea, el registro, etc., que también hacen que sea más fácil para el compilador optimizar su código.

0

Tienes que pensar en "¿cómo se puede convertir este código C en ensamblaje?".

El interruptor conditionnal es solo un tipo de instrucción JMP complicada, que por cierto necesita que los casos sean ordenados antes de la compilación (supongo que el compilador clasificará sus casos), pero no estoy tan seguro.

En php, por ejemplo, puedes cambiar {} con una cadena, que puede usar algún tipo de búsqueda dicotómica (busca primero los primeros caracteres, etc.), al igual que los mapas.

Debe comprender que los lenguajes compilados son una forma de generar código de ensamblaje "bastante bueno", pero eso no significa que será mejor que si acabara de ensamblar su programa.

Con un lenguaje como C o C++, creará un programa rápidamente y su compilador podrá realizar optimizaciones triviales, pero, por el amor de Dios, sea amable al usar un lenguaje de programación, piense en la imagen completa de su programa antes, y no olvides que los idiomas son solo herramientas, no son mágicos: si te olvidas del comportamiento básico de bajo nivel, te equivocaste.

+0

No, no, los casos de 'cambio' no se ordenarán en general, por la simple razón de que pueden solaparse si no terminan con un descanso. De manera más general, las etiquetas de "casos" son solo etiquetas en un bloque de instrucciones anidado arbitrariamente. –

0

Un interruptor es probablemente la mejor opción cuando hay un número discreto de opciones. El compilador puede advertirle sobre casos duplicados y si usa enumeraciones, un buen compilador advertirá sobre los valores que no se manejan.

Como una buena práctica, los flotantes/dobles no deben probarse para la igualdad, "si (f = 3.141516)" es una invitación para dolores de cabeza, "const float kEpsilon = 1e-5;" y luego use "if (fabs (f - 3.141516) < kEpsilon)" Elija un valor épsilon que sea relevante para su problema. Una función o macro en línea puede ayudar a escribir esto de una manera más legible.

0

No podemos usar el flotador en la caja del interruptor. Es porque las carrozas son imprecisas. Nunca se sabe cuál será ese número en realidad.

Cuestiones relacionadas