¿Por qué la sentencia switch no se puede aplicar en cadenas?

int main() { switch(std::string("raj")) //Comstacktion error - switch expression of type illegal { case"sda": } } 

La razón por la cual tiene que ver con el sistema de tipo. C / C ++ realmente no admite cadenas como un tipo. Soporta la idea de una matriz de caracteres constante, pero realmente no comprende completamente la noción de una cadena.

Para generar el código de una instrucción switch, el comstackdor debe comprender lo que significa que dos valores sean iguales. Para elementos como enteros y enumeraciones, esta es una comparación de bits triviales. Pero, ¿cómo debería el comstackdor comparar 2 valores de cadena? Sensible a mayúsculas, insensible, consciente de cultura, etc. … Sin una conciencia completa de una cadena, no se puede responder con precisión.

Además, las sentencias de cambio C / C ++ se suelen generar como tablas de bifurcación . No es tan fácil generar una tabla de twigs para un interruptor de estilo de cuerda.

Como se mencionó anteriormente, a los comstackdores les gusta crear tablas de búsqueda que optimicen las sentencias de switch para aproximarse al tiempo O (1) siempre que sea posible. Combine esto con el hecho de que el lenguaje C ++ no tiene un tipo de cadena – std::string es parte de la biblioteca estándar que no es parte del lenguaje per se.

Ofreceré una alternativa que quizás quiera considerar, la he usado en el pasado con buenos resultados. En lugar de cambiar la cadena en sí, cambie el resultado de una función hash que utiliza la cadena como entrada. Su código será casi tan claro como cambiar la cadena si está utilizando un conjunto predeterminado de cadenas:

 enum string_code { eFred, eBarney, eWilma, eBetty, ... }; string_code hashit (std::string const& inString) { if (inString == "Fred") return eFred; if (inString == "Barney") return eBarney; ... } void foo() { switch (hashit(stringValue)) { case eFred: ... case eBarney: ... } } 

Hay un montón de optimizaciones obvias que más o menos siguen lo que el comstackdor de C haría con una statement de cambio … gracioso cómo sucede eso.

Solo puede usar la primitiva de activación como int, char y enum. La solución más fácil para hacerlo como lo desea, es usar una enumeración.

 #include  #include  #include  // Value-Defintions of the different String values static enum StringValue { evNotDefined, evStringValue1, evStringValue2, evStringValue3, evEnd }; // Map to associate the strings with the enum values static std::map s_mapStringValues; // User input static char szInput[_MAX_PATH]; // Intialization static void Initialize(); int main(int argc, char* argv[]) { // Init the string map Initialize(); // Loop until the user stops the program while(1) { // Get the user's input cout << "Please enter a string (end to terminate): "; cout.flush(); cin.getline(szInput, _MAX_PATH); // Switch on the value switch(s_mapStringValues[szInput]) { case evStringValue1: cout << "Detected the first valid string." << endl; break; case evStringValue2: cout << "Detected the second valid string." << endl; break; case evStringValue3: cout << "Detected the third valid string." << endl; break; case evEnd: cout << "Detected program end command. " << "Programm will be stopped." << endl; return(0); default: cout << "'" << szInput << "' is an invalid string. s_mapStringValues now contains " << s_mapStringValues.size() << " entries." << endl; break; } } return 0; } void Initialize() { s_mapStringValues["First Value"] = evStringValue1; s_mapStringValues["Second Value"] = evStringValue2; s_mapStringValues["Third Value"] = evStringValue3; s_mapStringValues["end"] = evEnd; cout << "s_mapStringValues contains " << s_mapStringValues.size() << " entries." << endl; } 

Código escrito por Stefan Ruck el 25 de julio de 2001.

El problema es que, por razones de optimización, la instrucción switch en C ++ no funciona en nada más que en tipos primitivos, y solo puede compararlos con las constantes de tiempo de comstackción.

Presumiblemente, el motivo de la restricción es que el comstackdor puede aplicar alguna forma de optimización comstackndo el código en una instrucción cmp y un goto donde la dirección se calcula en función del valor del argumento en tiempo de ejecución. Como los bucles de bifurcación y y no funcionan muy bien con CPU modernas, esto puede ser una optimización importante.

Para evitar esto, me temo que tendrá que recurrir a las declaraciones de if.

C ++ 11 actualización de aparentemente no @MarmouCorp arriba, pero http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Utiliza dos mapas para convertir entre las cadenas y la clase enum (mejor que la enumeración simple porque sus valores tienen un ámbito dentro de ella, y búsqueda inversa de buenos mensajes de error).

El uso de estática en el código codeguru es posible con la compatibilidad del comstackdor para las listas de inicializadores, lo que significa VS 2013 plus. gcc 4.8.1 estaba bien con eso, no estoy seguro de cuánto más atrás sería compatible.

 ///  /// Enum for String values we want to switch on ///  enum class TestType { SetType, GetType }; ///  /// Map from strings to enum values ///  std::map MnCTest::s_mapStringToTestType = { { "setType", TestType::SetType }, { "getType", TestType::GetType } }; ///  /// Map from enum values to strings ///  std::map MnCTest::s_mapTestTypeToString { {TestType::SetType, "setType"}, {TestType::GetType, "getType"}, }; 

 std::string someString = "setType"; TestType testType = s_mapStringToTestType[someString]; switch (testType) { case TestType::SetType: break; case TestType::GetType: break; default: LogError("Unknown TestType ", s_mapTestTypeToString[testType]); } 

std::map + C ++ 11 patrón lambdas sin enumeraciones

unordered_map para el potencial O(1) amortizado O(1) : ¿Cuál es la mejor manera de usar un HashMap en C ++?

 #include  #include  #include  #include  #include  int main() { int result; const std::unordered_map> m{ {"one", [&](){ result = 1; }}, {"two", [&](){ result = 2; }}, {"three", [&](){ result = 3; }}, }; const auto end = m.end(); std::vector strings{"one", "two", "three", "foobar"}; for (const auto& s : strings) { auto it = m.find(s); if (it != end) { it->second(); } else { result = -1; } std::cout << s << " " << result << std::endl; } } 

Salida:

 one 1 two 2 three 3 foobar -1 

Uso dentro de métodos con static

Para utilizar este patrón de manera eficiente dentro de las clases, inicialice el mapa lambda estáticamente, o bien pague O(n) cada vez para construirlo desde cero.

Aquí podemos saltear con la {} inicialización de una variable de método static : variables estáticas en métodos de clase , pero también podríamos usar los métodos descritos en: constructores estáticos en C ++? Necesito inicializar objetos estáticos privados

Era necesario transformar la captura de contexto lambda [&] en un argumento, o que hubiera sido indefinido: const static auto lambda utilizado con captura por referencia

Ejemplo que produce el mismo resultado que el anterior:

 #include  #include  #include  #include  #include  class RangeSwitch { public: void method(std::string key, int &result) { static const std::unordered_map> m{ {"one", [](int& result){ result = 1; }}, {"two", [](int& result){ result = 2; }}, {"three", [](int& result){ result = 3; }}, }; static const auto end = m.end(); auto it = m.find(key); if (it != end) { it->second(result); } else { result = -1; } } }; int main() { RangeSwitch rangeSwitch; int result; std::vector strings{"one", "two", "three", "foobar"}; for (const auto& s : strings) { rangeSwitch.method(s, result); std::cout << s << " " << result << std::endl; } } 

En C ++ y C, los switches solo funcionan en tipos enteros. Use una escalera if else en su lugar. C ++ obviamente podría haber implementado algún tipo de statement swich para cadenas – Creo que nadie pensó que valía la pena, y estoy de acuerdo con ellos.

Por qué no? Puede usar la implementación de switch con syntax equivalente y la misma semántica. El lenguaje C no tiene objetos y cadenas de objetos en absoluto, pero las cadenas en C son cadenas terminadas nulas a las que hace referencia el puntero. El C++ tiene la posibilidad de realizar funciones de sobrecarga para comparar objetos o verificar la igualdad de objetos. Como C como C++ es lo suficientemente flexible como para tener dicho interruptor para cadenas para el lenguaje C y para objetos de cualquier tipo que admitan la comparación o verifiquen la igualdad para el C++ . Y el moderno C++11 permite que esta implementación de conmutadores sea lo suficientemente efectiva.

Tu código será así:

 std::string name = "Alice"; std::string gender = "boy"; std::string role; SWITCH(name) CASE("Alice") FALL CASE("Carol") gender = "girl"; FALL CASE("Bob") FALL CASE("Dave") role = "participant"; BREAK CASE("Mallory") FALL CASE("Trudy") role = "attacker"; BREAK CASE("Peggy") gender = "girl"; FALL CASE("Victor") role = "verifier"; BREAK DEFAULT role = "other"; END // the role will be: "participant" // the gender will be: "girl" 

Es posible usar tipos más complicados, por ejemplo std::pairs o cualquier estructura o clase que soporte operaciones de igualdad (o comarisions para modo rápido ).

Caracteristicas

  • cualquier tipo de datos que apoyen las comparaciones o verifiquen la igualdad
  • posibilidad de construir estados de conmutación nesteds en cascada.
  • posibilidad de romper o caer a través de declaraciones de casos
  • posibilidad de usar expresiones de casos no constantes
  • Posibilidad de habilitar el modo estático / dynamic rápido con la búsqueda de árbol (para C ++ 11)

Las diferencias de Sintax con el cambio de idioma son

  • palabras clave mayúsculas
  • necesita paréntesis para la statement CASE
  • punto y coma ‘;’ al final de las declaraciones no está permitido
  • colon ‘:’ en la instrucción CASE no está permitido
  • necesita una palabra clave BREAK o FALL al final de la sentencia CASE

Para el C++97 utilizado la búsqueda lineal. Para C++11 y más moderno es posible usar quick modo quick búsqueda de árbol wuth donde la statement de retorno en CASE no está permitida. La implementación del lenguaje C existe donde se usan las comparaciones de cadenas de tipo char* y terminadas en cero.

Lea más sobre esta implementación de conmutadores.

En C ++ solo puede usar una instrucción switch en int y char

Creo que la razón es que en C las cadenas no son tipos primitivos, como dijo tomjen, piense en una cadena como una matriz de caracteres, por lo que no puede hacer cosas como:

 switch (char[]) { // ... switch (int[]) { // ... 

C ++

función de hash constexpr:

 constexpr unsigned int hash(const char *s, int off = 0) { return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off]; } switch( hash(str) ){ case hash("one") : // do something case hash("two") : // do something } 

En C ++ las cadenas no son ciudadanos de primera clase. Las operaciones de cadena se realizan a través de la biblioteca estándar. Creo que esa es la razón. Además, C ++ utiliza la optimización de la tabla de twigs para optimizar las sentencias de cambio de mayúsculas y minúsculas. Eche un vistazo al enlace.

http://en.wikipedia.org/wiki/Switch_statement

Para agregar una variación usando el contenedor más simple posible (sin necesidad de un mapa ordenado) … No me molestaría con una enumeración: simplemente coloque la definición del contenedor inmediatamente antes del cambio para que sea fácil ver qué número representa cuyo caso.

Esto realiza una búsqueda hash en el unordered_map y utiliza la int asociada para conducir la instrucción switch. Debería ser bastante rápido. Tenga at cuenta que se usa en lugar de [] , ya que he hecho que contiene const . Usar [] puede ser peligroso: si la cadena no está en el mapa, creará una nueva asignación y puede terminar con resultados indefinidos o un mapa en continuo crecimiento.

Tenga en cuenta que la función at() lanzará una excepción si la cadena no está en el mapa. Así que es posible que desee probar primero usando count() .

 const static std::unordered_map string_to_case{ {"raj",1}, {"ben",2} }; switch(string_to_case.at("raj")) { case 1: // this is the "raj" case break; case 2: // this is the "ben" case break; } 

La versión con una prueba para una cadena indefinida sigue:

 const static std::unordered_map string_to_case{ {"raj",1}, {"ben",2} }; switch(string_to_case.count("raj") ? string_to_case.at("raj") : 0) { case 1: // this is the "raj" case break; case 2: // this is the "ben" case break; case 0: //this is for the undefined case } 

No puede usar una cuerda en la caja del interruptor. Solo se permiten int y char. En su lugar, puede probar enum para representar la cadena y usarla en el bloque de la caja del interruptor como

 enum MyString(raj,taj,aaj); 

Úselo en la statement de caso Swich.

Los conmutadores solo funcionan con tipos integrales (int, char, bool, etc.). ¿Por qué no usar un mapa para vincular una cadena con un número y luego usar ese número con el interruptor?

  cout << "\nEnter word to select your choice\n"; cout << "ex to exit program (0)\n"; cout << "m to set month(1)\n"; cout << "y to set year(2)\n"; cout << "rm to return the month(4)\n"; cout << "ry to return year(5)\n"; cout << "pc to print the calendar for a month(6)\n"; cout << "fdc to print the first day of the month(1)\n"; cin >> c; cout << endl; a = c.compare("ex") ?c.compare("m") ?c.compare("y") ? c.compare("rm")?c.compare("ry") ? c.compare("pc") ? c.compare("fdc") ? 7 : 6 : 5 : 4 : 3 : 2 : 1 : 0; switch (a) { case 0: return 1; case 1: ///m { cout << "enter month\n"; cin >> c; cout << endl; myCalendar.setMonth(c); break; } case 2: cout << "Enter year(yyyy)\n"; cin >> y; cout << endl; myCalendar.setYear(y); break; case 3: myCalendar.getMonth(); break; case 4: myCalendar.getYear(); case 5: cout << "Enter month and year\n"; cin >> c >> y; cout << endl; myCalendar.almanaq(c,y); break; case 6: break; } 

en muchos casos puede hacer un trabajo adicional extra sacando el primer carácter de la cuerda y encendiéndolo. puede terminar teniendo que hacer un cambio nested en charat (1) si sus casos comienzan con el mismo valor. cualquiera que lea su código apreciaría una pista, porque la mayoría lo haría solo si-else-if

Eso es porque C ++ convierte los interruptores en tablas de salto. Realiza una operación trivial en los datos de entrada y salta a la dirección correcta sin comparar. Como una cadena no es un número, sino una matriz de números, C ++ no puede crear una tabla de salto a partir de ella.

 movf INDEX,W ; move the index value into the W (working) register from memory addwf PCL,F ; add it to the program counter. each PIC instruction is one byte ; so there is no need to perform any multiplication. ; Most architectures will transform the index in some way before ; adding it to the program counter table ; the branch table begins here with this label goto index_zero ; each of these goto instructions is an unconditional branch goto index_one ; of code goto index_two goto index_three index_zero ; code is added here to perform whatever action is required when INDEX = zero return index_one ... 

(código de wikipedia https://en.wikipedia.org/wiki/Branch_table )