代码之家  ›  专栏  ›  技术社区  ›  yesraaj

为什么switch语句不能应用于字符串?

  •  180
  • yesraaj  · 技术社区  · 15 年前

    编译以下代码并得到错误 type illegal .

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

    也不能在中使用字符串 switch case . 为什么?是否有任何解决方案可以很好地支持类似于打开字符串的逻辑?

    19 回复  |  直到 6 年前
        1
  •  159
  •   JaredPar    15 年前

    原因与类型系统有关。C/C++不支持字符串作为一种类型。它确实支持常量char数组的概念,但它并不完全理解字符串的概念。

    为了生成switch语句的代码,编译器必须理解两个值相等意味着什么。对于像int和enum这样的项,这是一个微不足道的位比较。但是编译器应该如何比较2个字符串值呢?区分大小写、不敏感、有文化意识等…如果没有对字符串的完全了解,就无法准确地回答这个问题。

    另外,C/C++转换语句通常生成为 branch tables . 为字符串样式的开关生成分支表几乎没有那么容易。

        2
  •  53
  •   D.Shawley    15 年前

    如前所述,编译器喜欢构建优化的查找表 switch 尽可能接近O(1)时间。将此与C++语言没有字符串类型的事实相结合—— std::string 是标准库的一部分,本身不是语言的一部分。

    我会提供一个你可能想考虑的替代方案,我过去已经用过了,效果很好。而不是切换字符串本身,而是切换使用字符串作为输入的哈希函数的结果。如果您使用的是一组预先确定的字符串,那么您的代码几乎与切换字符串一样清晰:

    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:
            ...
        }
    }
    

    有一系列明显的优化,几乎都遵循C编译器对switch语句的处理方式…真有趣。

        3
  •  29
  •   Matthew D. Scholefield    9 年前

    只能在int、char和enum等基元上使用switch。最简单的解决方案是使用枚举。

    #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;
    }
    

    Code written 作者:Stefan Ruck,2001年7月25日。

        4
  •  12
  •   Dirk Bester    10 年前

    显然不是@ MouthCoup上面的C++ 11更新 http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

    使用两个映射在字符串和类枚举之间进行转换(比普通枚举更好,因为它的值在其中有作用域,并且反向查找好的错误消息)。

    编译器支持初始值设定项列表,这意味着vs 2013 plus可以在codeguru代码中使用static。GCC4.8.1可以接受,不确定它在多大程度上是兼容的。

    /// <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]);
    }
    
        5
  •  11
  •   tomjen    15 年前

    问题是,由于优化的原因,C++中的转换语句对原始类型的任何东西都不起作用,只能将它们与编译时常数进行比较。

    可能造成这种限制的原因是编译器能够应用某种形式的优化,将代码编译为一条cmp指令和一个goto,其中地址是根据运行时参数的值计算的。由于分支和循环不能很好地与现代CPU配合使用,因此这可能是一个重要的优化。

    为了解决这个问题,恐怕你不得不求助于国际单项体育联合会的声明。

        6
  •  7
  •   Ciro Santilli OurBigBook.com    6 年前

    std::map +C++ 11不带枚举的LAMBDAS模式

    unordered_map 潜在摊销 O(1) : What is the best way to use a HashMap in 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;
        }
    }
    

    输出:

    one 1
    two 2
    three 3
    foobar -1
    

    在方法内部使用 static

    要在类内有效地使用此模式,请静态初始化lambda映射,否则您需要支付 O(n) 每次从头开始建造。

    在这里我们可以摆脱 {} 初始化 静止的 方法变量: Static variables in class methods ,但我们也可以使用以下方法: static constructors in C++? I need to initialize private static objects

    必须转换lambda上下文捕获 [&] 变为一个论点,或者说是未定义的: const static auto lambda used with capture by reference

    产生与上述相同输出的示例:

    #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;
        }
    }
    
        7
  •  6
  •   anon    15 年前

    在C++和C开关中只对整数类型进行工作。使用if-else阶梯代替。C++显然可以为字符串实现某种SWICH语句——我想没有人认为它值得,我同意。

        8
  •  6
  •   Nick    7 年前

    C++

    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
    }
    
        9
  •  5
  •   oklas    8 年前

    为什么不?你可以使用 switch implementation 具有相同的语法和语义。 这个 C 语言根本没有对象和字符串对象,但是 字符串 C 是指针引用的以空结尾的字符串。 这个 C++ 语言有可能为 对象比较或检查对象的相等性。 AS C 作为 C++ 具有足够的灵活性,可以为 C 语言和支持比较或检查的任何类型的对象 平等 C++ 语言。现代 C++11 允许使用此开关 实施足够有效。

    您的代码如下:

    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"
    

    例如,可以使用更复杂的类型 std::pairs 或支持相等操作(或 快的 模式)。

    特征

    • 支持比较或检查相等性的任何类型的数据
    • 可以构建级联嵌套开关状态。
    • 有可能打破或破坏案例陈述
    • 使用非常量case表达式的可能性
    • 可以使用树形搜索(C/C++ 11)实现快速静态/动态模式

    与语言切换的sintax差异是

    • 大写关键字
    • case语句需要括号
    • 语句末尾不允许使用分号“;”
    • 不允许在case语句中使用冒号“:”
    • 在case语句末尾需要break或fall关键字之一

    为了 C++97 语言使用线性搜索。 为了 C++ 11 更现代的可能使用 quick 模式五树搜索 返回 不允许使用语句。 这个 C 语言实现存在于 char* 使用类型和以零结尾的字符串比较。

    more about 此开关实现。

        10
  •  4
  •   grilix    15 年前

    我认为原因是,在C字符串中不是原始类型,正如Tomjen所说,将字符串看作char数组,因此不能执行以下操作:

    switch (char[]) { // ...
    switch (int[]) { // ...
    
        11
  •  3
  •   CodeMonkey1313    15 年前

    在C++中,只能在int和char上使用开关语句。

        12
  •  3
  •   chappar    15 年前

    在C++中,字符串不是第一类公民。字符串操作通过标准库完成。我想,这就是原因。此外,C++使用分支表优化来优化交换机实例语句。看看这个链接。

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

        13
  •  3
  •   rsjaffe    8 年前

    要使用尽可能简单的容器添加变体(不需要已排序的映射)…我不必担心枚举——只需将容器定义放在开关前面,这样就可以很容易地看到哪个数字代表哪种情况。

    这将在 unordered_map 并使用 int 驱动switch语句。应该很快。注意 at 使用而不是 [] 就像我说的那样 const . 使用 [] 可能很危险——如果字符串不在映射中,您将创建一个新的映射,最终可能会得到未定义的结果或不断增长的映射。

    请注意 at() 如果字符串不在映射中,函数将引发异常。所以你可能想先用 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;
    
    
    }
    

    测试未定义字符串的版本如下:

    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
    
    }
    
        14
  •  0
  •   strager    15 年前

    不能在开关大小写中使用字符串。只允许使用in t&char。相反,您可以尝试使用枚举来表示字符串,并在开关盒块中使用它,如

    enum MyString(raj,taj,aaj);
    

    在swich case语句中使用它。

        15
  •  0
  •   derpface    12 年前

    开关只能与整型(int、char、bool等)一起使用。为什么不使用map将字符串与数字配对,然后将该数字与开关一起使用呢?

        16
  •  0
  •   Juan Llanes    8 年前
        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;
    
        }
    
        17
  •  0
  •   Marshall Taylor    8 年前

    在许多情况下,您可以通过从字符串中提取第一个字符并打开它来完成额外的工作。如果您的案例以相同的值开始,那么可能最终不得不在charat(1)上执行嵌套切换。但是,任何阅读您的代码的人都会感激您的提示,因为大多数人会在

        18
  •  0
  •   FelikZ    6 年前

    针对交换机问题的更多功能解决方案:

    class APIHandlerImpl
    {
    
    // define map of "cases"
    std::map<string, std::function<void(server*, websocketpp::connection_hdl, string)>> in_events;
    
    public:
        APIHandlerImpl()
        {
            // bind handler method in constructor
            in_events["/hello"] = std::bind(&APIHandlerImpl::handleHello, this, _1, _2, _3);
            in_events["/bye"] = std::bind(&APIHandlerImpl::handleBye, this, _1, _2, _3);
        }
    
        void onEvent(string event = "/hello", string data = "{}")
        {
            // execute event based on incomming event
            in_events[event](s, hdl, data);
        }
    
        void APIHandlerImpl::handleHello(server* s, websocketpp::connection_hdl hdl, string data)
        {
            // ...
        }
    
        void APIHandlerImpl::handleBye(server* s, websocketpp::connection_hdl hdl, string data)
        {
            // ...
        }
    }
    
        19
  •  -2
  •   Jean-Luc Nacif Coelho    8 年前

    这是因为C++将开关转换为跳转表。它对输入数据执行一个简单的操作,并在不进行比较的情况下跳到正确的地址。因为字符串不是一个数字,而是一个数字数组,所以C++不能从它创建一个跳转表。

    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
    ...
    

    (来自维基百科的代码 https://en.wikipedia.org/wiki/Branch_table )