284

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

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

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

4

22 回答 22

229

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

为了生成 switch 语句的代码,编译器必须理解两个值相等的含义。对于整数和枚举之类的项目,这是一个微不足道的比较。但是编译器应该如何比较两个字符串值呢?区分大小写、不区分大小写、文化意识等……如果不完全了解字符串,就无法准确回答。

此外,C/C++ switch 语句通常生成为分支表。为字符串样式开关生成分支表并不容易。

于 2009-03-16T12:30:36.803 回答
67

如前所述,编译器喜欢构建查找表,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 语句所做的事情......这很有趣。

于 2009-03-16T12:52:08.530 回答
36

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
}

更新:

上面的例子是 C++11。constexpr函数必须带有单个语句。这在下一个 C++ 版本中得到了放松。

在 C++14 和 C++17 中,您可以使用以下哈希函数:

constexpr uint32_t hash(const char* data, size_t const size) noexcept{
    uint32_t hash = 5381;

    for(const char *c = data; c < data + size; ++c)
        hash = ((hash << 5) + hash) + (unsigned char) *c;

    return hash;
}

C++17 也有std::string_view,所以你可以用它代替const char *.

在 C++20 中,您可以尝试使用consteval.

于 2017-10-12T14:07:05.373 回答
19

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

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

通过编译器对初始化列表的支持,可以在 codeguru 代码中使用静态,这意味着 VS 2013 plus。gcc 4.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]);
}
于 2014-12-10T00:07:27.413 回答
13

问题是,出于优化的原因,C++ 中的 switch 语句除了基本类型外,不能用于任何东西,您只能将它们与编译时常量进行比较。

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

要解决这个问题,恐怕您将不得不诉诸 if 语句。

于 2009-03-16T12:24:53.667 回答
13

std::map+ 没有枚举的 C++11 lambdas 模式

unordered_map对于潜在的摊销O(1)在 C++ 中使用 HashMap 的最佳方法是什么?

#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)每次都需要付费从头开始构建它。

在这里,我们可以摆脱方法变量的{}初始化:类方法中的静态变量,但我们也可以使用描述的方法:C++ 中的静态构造函数?我需要初始化私有静态对象static

有必要将 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;
    }
}
于 2017-02-25T22:51:11.893 回答
10

要使用最简单的容器添加变体(不需要有序的地图)......我不会为枚举而烦恼 - 只需将容器定义放在开关之前,这样就很容易看到哪个数字代表这种情况下。

这会在 中进行散列查找,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}
};
// in C++20, you can replace .count with .contains
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

}
于 2016-07-03T05:02:16.823 回答
6

在 C++ 和 C 中,开关仅适用于整数类型。请改用 if else 阶梯。C++ 显然可以为字符串实现某种 swich 语句——我想没有人认为这是值得的,我同意他们的观点。

于 2009-03-16T12:17:44.730 回答
6

为什么不?您可以使用具有等效语法和相同语义的switch 实现。C语言根本没有对象和字符串对象,但字符串中C的字符串是指针引用的以空结尾的字符串。该C++语言有可能为对象比较或检查对象相等性创建重载函数。因为足够灵活,可以对语言字符串C和 支持比较或检查语言相等性的任何类型的对象进行这种切换。而现代允许这个开关实现足够有效。C++CC++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或任何支持相等操作(或快速模式的组合)的结构或类。

特征

  • 支持比较或检查相等性的任何类型的数据
  • 构建级联嵌套 switch 语句的可能性。
  • 通过案例陈述打破或失败的可能性
  • 使用非常量 case 表达式的可能性
  • 可以通过树搜索启用快速静态/动态模式(用于 C++11)

与语言切换的 Sintax 差异是

  • 大写关键字
  • CASE 语句需要括号
  • 分号';' 不允许在语句末尾
  • CASE 语句中的冒号 ':' 是不允许的
  • 在 CASE 语句的末尾需要 BREAK 或 FALL 关键字之一

对于C++97使用线性搜索的语言。对于C++11更现代的可能使用quick模式 wuth 树搜索,其中不允许 CASE中的return语句。C语言实现存在于使用char*类型和以零结尾的字符串比较的地方。

阅读有关此开关实现的更多信息。

于 2016-10-11T11:09:42.203 回答
4

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

switch (char[]) { // ...
switch (int[]) { // ...
于 2009-03-16T12:31:01.437 回答
3

在 c++ 中,字符串不是一等公民。字符串操作是通过标准库完成的。我想,这就是原因。此外,C++ 使用分支表优化来优化 switch case 语句。看看链接。

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

于 2009-03-16T13:28:29.693 回答
2

在 C++ 中,您只能在 int 和 char 上使用 switch 语句

于 2009-03-16T12:15:25.920 回答
2

聚会迟到了,这是我前段时间想出的一个解决方案,它完全遵守所要求的语法。

#include <uberswitch/uberswitch.hpp>

int main()
{
    uswitch (std::string("raj"))
    {
        ucase ("sda"): /* ... */ break;  //notice the parenthesis around the value.
    }
}

这是代码:https ://github.com/falemagn/uberswitch

于 2020-08-29T15:58:29.357 回答
1

您可以将字符串放在一个数组中,并constexpr在编译时使用 a 将它们转换为索引。

constexpr const char* arr[] = { "bar", "foo" };
constexpr int index(const char* str) { /*...*/ }

do_something(std::string str)
{
    switch(quick_index(str))
    {
        case index("bar"):
            // ...
            break;

        case index("foo"):
            // ...
            break;

        case -1:
        default:
            // ...
            break;
    }

对于quick_index,不一定是constexpr,您可以例如在运行时使用 anunordered_map来完成 O(1) 。(或者对数组进行排序并使用二进制搜索,请参见此处的示例。)

这是 C++11 的完整示例,带有一个简单的自定义constexpr字符串比较器。将在编译时检测到重复的情况和不在数组中的情况(index给定)。-1显然没有发现失踪案件。后来的 C++ 版本有更灵活constexpr的表达式,允许更简单的代码。

#include <iostream>
#include <algorithm>
#include <unordered_map>

constexpr const char* arr[] = { "bar", "foo", "foobar" };

constexpr int cmp(const char* str1, const char* str2)
{
    return *str1 == *str2 && (!*str1 || cmp(str1+1, str2+1));
}

constexpr int index(const char* str, int pos=0)
{
    return pos == sizeof(arr)/sizeof(arr[0]) ? -1 : cmp(str, arr[pos]) ? pos : index(str,pos+1);
}

int main()
{
    // initialize hash table once
    std::unordered_map<std::string,int> lookup;
    int i = 0;
    for(auto s : arr) lookup[s] = i++;
    auto quick_index = [&](std::string& s)
        { auto it = lookup.find(s); return it == lookup.end() ? -1 : it->second; };
    
    // usage in code
    std::string str = "bar";
    
    switch(quick_index(str))
    {
        case index("bar"):
            std::cout << "bartender" << std::endl;
            break;

        case index("foo"):
            std::cout << "fighter" << std::endl;
            break;

        case index("foobar"):
            std::cout << "fighter bartender" << std::endl;
            break;
            
        case -1:
        default:
            std::cout << "moo" << std::endl;
            break;
    }
}
于 2021-05-16T18:20:43.310 回答
0
    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;

    }
于 2016-03-08T16:58:26.920 回答
0

开关问题的更多功能解决方法:

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)
    {
        // ...
    }
}
于 2018-11-18T09:39:03.603 回答
0

您可以在字符串上使用开关。你需要的是字符串表,检查每个字符串

char** strings[4] = {"Banana", "Watermelon", "Apple", "Orange"};

unsigned get_case_string(char* str, char** _strings, unsigned n)
{
    while(n)
    {
        n--
        if(strcmp(str, _strings[n]) == 0) return n;
    }
    return 0;
}

unsigned index = get_case_string("Banana", strings, 4);

switch(index)
{
    case 1: break;/*Found string `Banana`*/
    default: /*No string*/
}
于 2020-08-05T08:19:28.653 回答
0

野兔对尼克的解决方案的评论真的很酷。这里是完整的代码示例(在 C++11 中):

constexpr uint32_t hash(const std::string& s) noexcept
{
    uint32_t hash = 5381;
    for (const auto& c : s)
        hash = ((hash << 5) + hash) + (unsigned char)c;
    return hash;
}

constexpr inline uint32_t operator"" _(char const* p, size_t) { return hash(p); }

std::string s = "raj";
switch (hash(s)) {
case "sda"_:
    // do_something();
    break;
default:
    break;
}
于 2021-10-26T09:37:30.823 回答
-1

您不能在 switch case 中使用字符串。只允许使用 int 和 char。相反,您可以尝试使用 enum 来表示字符串并在 switch case 块中使用它,例如

enum MyString(raj,taj,aaj);

在 swich case 语句中使用它。

于 2009-03-16T12:28:34.593 回答
-1

开关仅适用于整数类型(int、char、bool 等)。为什么不使用映射将字符串与数字配对,然后将该数字与开关一起使用?

于 2012-08-11T04:21:19.680 回答
-1

在许多情况下,您可以通过从字符串中拉出第一个字符并打开它来获得额外的工作。如果您的案例以相同的值开头,则可能最终不得不在 charat(1) 上进行嵌套切换。任何阅读您的代码的人都会感谢您的提示,因为大多数人会怀疑 if-else-if

于 2016-09-23T03:11:15.703 回答
-2

那是因为 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

于 2016-03-24T19:46:31.043 回答