¿Por qué la instrucción switch no se puede aplicar en cadenas?


Compilando el siguiente código y obtuvo el error de type illegal.

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

No se puede usar cadena en switch o case. ¿Por qué? ¿Hay alguna solución que funcione bien para soportar la lógica similar a switch on strings?

Author: TangKe, 2009-03-16

18 answers

El motivo tiene que ver con el tipo system. C / C++ realmente no soporta cadenas como tipo. Apoya la idea de una matriz de caracteres constante, pero no entiende completamente la noción de una cadena.

Para generar el código para una instrucción switch el compilador debe entender lo que significa que dos valores sean iguales. Para elementos como ints y enums, esta es una comparación de bits trivial. Pero, ¿cómo debería comparar el compilador 2 valores de cadena? Minúsculas, insensible, consciente de la cultura, etc... Sin una conciencia completa de una cadena esto no puede ser contestado con precisión.

Además, las sentencias switch C/C++ se generan típicamente como tablas de ramas. No es tan fácil generar una tabla de ramas para un interruptor de estilo de cadena.

 156
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:30:36

Como se mencionó anteriormente, a los compiladores les gusta construir tablas de búsqueda que optimizan las sentencias switch para que se acerquen 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.

Voy a ofrecer una alternativa que usted puede ser que desee considerar, lo he utilizado en el pasado con buen efecto. En lugar de cambiar la cadena en sí, cambie el resultado de una función hash que usa 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 compilador de C haría con una sentencia switch... es curioso cómo sucede.

 48
Author: D.Shawley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:52:08

Solo puede usar switch en primitivos como int, char y enum. La solución más fácil para hacerlo como quieras, es usar una enumeración.

#include <map>
#include <string>
#include <iostream.h>

// 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<std::string, StringValue> 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.

 27
Author: MarmouCorp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 22:51:03

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 constantes de tiempo de compilación.

Presumiblemente la razón de la restricción es que el compilador es capaz de aplicar alguna forma de optimización compilando el código hasta 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. Desde ramificación y y bucles no juegan muy bien con CPU modernas, esto puede ser una optimización importante.

Para ir alrededor de esto, me temo que tendrá que recurrir a declaraciones if.

 12
Author: tomjen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:24:53

C++ 11 actualización de aparentemente no @ MarmouCorp anterior, 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 enum de la clase (mejor que enum simple porque sus valores son de ámbito dentro de él, y búsqueda inversa para mensajes de error agradables).

El uso de estática en el código codeguru es posible con el soporte del compilador para listas inicializadoras, lo que significa VS 2013 plus. gcc 4.8.1 estaba bien con él, no estoy seguro de cuánto más atrás sería compatible.

/// <summary>
/// Enum for String values we want to switch on
/// </summary>
enum class TestType
{
    SetType,
    GetType
};

/// <summary>
/// Map from strings to enum values
/// </summary>
std::map<std::string, TestType> MnCTest::s_mapStringToTestType =
{
    { "setType", TestType::SetType },
    { "getType", TestType::GetType }
};

/// <summary>
/// Map from enum values to strings
/// </summary>
std::map<TestType, std::string> 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]);
}
 11
Author: Dirk Bester,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-23 01:49:39

std::map + C++11 lambdas pattern without enums

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

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main() {
    int result;
    const std::unordered_map<std::string,std::function<void()>> m{
        {"one",   [&](){ result = 1; }},
        {"two",   [&](){ result = 2; }},
        {"three", [&](){ result = 3; }},
    };
    const auto end = m.end();
    std::vector<std::string> 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 usar este patrón eficientemente dentro de las clases, inicialice el mapa lambda estáticamente, o de lo contrario pagará O(n) cada vez para construirlo desde cero.

Aquí podemos salirnos con la suya con la inicialización {} de un static variable de método: 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 el contexto lambda capture [&] en un argumento, o eso habría sido indefinido: const static auto lambda usado con capture by reference

Ejemplo que produce la misma salida que la anterior:

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class RangeSwitch {
public:
    void method(std::string key, int &result) {
        static const std::unordered_map<std::string,std::function<void(int&)>> 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<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        rangeSwitch.method(s, result);
        std::cout << s << " " << result << std::endl;
    }
}
 8
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-21 13:30:45

En C++ y C los conmutadores solo funcionan en tipos enteros. Use una escalera if else en su lugar. Obviamente, C++ podría haber implementado algún tipo de declaración swich para cadenas-supongo que nadie pensó que valiera la pena, y estoy de acuerdo con ellos.

 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:31:27

¿Por qué no? Puede usar la implementación de switch con sintaxis equivalente y la misma semántica. El lenguaje C no tiene objetos y strings objetos en absoluto, pero las cadenas en C son cadenas terminadas en null a las que hace referencia el puntero. El lenguaje C++ tiene la posibilidad de hacer funciones de sobrecarga para comparación de objetos o comprobación de igualdades de objetos. Como C como C++ es lo suficientemente flexible para tener tal interruptor para cadenas para C lenguaje y para objetos de cualquier tipo que soporten comparación o comprobación igualdad para el lenguaje C++. Y moderno C++11 permiten tener este interruptor implementación lo suficientemente efectiva.

Su código será así: {[18]]}

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 comarisiones para el modo quick).

Características

  • cualquier tipo de datos que soporten comparaciones o comprobación de igualdad
  • posibilidad de construir estado de conmutador anidado en cascada.
  • posibilidad de romper o caer a través de las declaraciones de caso
  • posibilidad de usar expresiones de caso no constantes
  • posible habilitar el modo estático/dinámico rápido con búsqueda en árbol (para C++11)

Las diferencias Sintax con el interruptor de idioma es

  • palabras clave en mayúsculas
  • necesita paréntesis para la sentencia CASE
  • punto y coma ';' al final de las declaraciones no está permitido
  • dos puntos': 'en EL CASO no se permite la declaración
  • necesita una palabra clave BREAK or FALL al final de la sentencia CASE

Para C++97 el lenguaje utilizado es la búsqueda lineal. Para C++11 y más moderno es posible usar quick modo de búsqueda en árbol wuth donde devuelve instrucción en CASO de que no se permita. La implementación del lenguaje C existe donde se usan comparaciones de tipo y cadena con terminación cero.

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

 5
Author: oklas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-11 11:09:42

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

 4
Author: CodeMonkey1313,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:15:25

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[]) { // ...
 4
Author: grilix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:31:01

C++

Función 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
}
 4
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-12 14:07:05

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 tablas de ramas para optimizar las instrucciones de mayúsculas y minúsculas de switch. Echa un vistazo al enlace.

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

 3
Author: chappar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 13:34:19

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 put simplemente ponga la definición del contenedor inmediatamente antes del interruptor para que sea fácil ver qué número representa qué caso.

Esto hace una búsqueda hash en el unordered_map y utiliza el int asociado para conducir la instrucción switch. Debería ser bastante rápido. Tenga en cuenta que at se utiliza en lugar de [], ya que he hecho que contiene const. Usar [] puede ser peligroso if 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. Por lo tanto, es posible que desee probar primero usando count().

const static std::unordered_map<std::string,int> 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<std::string,int> 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

}
 2
Author: rsjaffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-03 17:25:51

No se puede usar cadena en caso de interruptor.Solo se permiten int y char. En su lugar, puede probar enum para representar la cadena y usarla en el bloque switch case como

enum MyString(raj,taj,aaj);

Utilícelo en la sentencia swich case.

 0
Author: anil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-16 12:44:17

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

 0
Author: derpface,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-11 04:21:19
    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;

    }
 0
Author: Juan Llanes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-08 16:58:26

En muchos casos se puede avid trabajo extra tirando del primer carácter de la cadena y la activación de que. puede terminar teniendo que hacer un interruptor anidado en charat (1) si sus casos comienzan con el mismo valor. cualquiera que lea su código apreciaría una pista, sin embargo, porque la mayoría prob solo si-else-if

 0
Author: Marshall Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-23 03:11:15

Esto se debe a que 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. Dado que 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)

 -2
Author: Jean-Luc Nacif Coelho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-24 20:06:07