关于c ++:为什么switch语句不能应用于字符串?

Why the switch statement cannot be applied on strings?

编译以下代码,得到type illegal的错误。

1
2
3
4
5
6
7
8
int main()
{
    // Compilation error - switch expression of type illegal
    switch(std::string("raj"))
    {
    case"sda":
    }
}

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


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

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

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


如前所述,编译器喜欢构建查找表,以便尽可能地将switch语句优化到接近o(1)的时间。将此与C++语言没有字符串类型——EDCOX1,1 }相结合,这是标准库的一部分,标准库本身不是语言的一部分。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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语句的处理方式…真有趣。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#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 by Stefan Ruck on July 25th, 2001.


显然不是@ @ MaMouCuP以上的更新,但是HTTP://www. CODEGURU.COM/CPP/CPP/CPPPYMFC/ TUNELY.PHP/C4067/Swit-on Strugs-in -C.HTM

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <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
<div class="
suo-content">[collapse title=""]<ul><li>我应该注意到,后来我发现了一个解决方案,需要字符串文字和编译时计算(C++ 14或17,我想),在那里你可以在编译时散列字符串,并在运行时散列开关字符串。对于非常长的交换机来说,这可能是值得的,但如果这很重要的话,甚至更不值得向后兼容。</li></ul>[/collapse]</div><hr><P>问题是,由于优化的原因,C++中的转换语句对原始类型的任何东西都不起作用,只能将它们与编译时常数进行比较。</P><P>可能造成这种限制的原因是编译器能够应用某种形式的优化,将代码编译为一条cmp指令和一个goto,其中地址是根据运行时参数的值计算的。由于分支和循环不能很好地与现代CPU配合使用,因此这可能是一个重要的优化。</P><P>为了解决这个问题,恐怕你不得不求助于国际单项体育联合会的声明。</P><div class="suo-content">[collapse title=""]<ul><li>不是所有的基元类型都能工作。只有整型。</li><li>可以使用字符串的switch语句的优化版本绝对是可能的。它们不能重复使用原始类型所使用的相同代码路径,这并不意味着它们不能使<wyn>std::string</wyn>和其他语言中的第一公民,并用有效的算法在switch语句中支持它们。</li></ul>[/collapse]</div><hr><P>C++</P><P>constexpr哈希函数:</P>[cc lang="cpp"]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
}


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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;
    }
}

输出:

1
2
3
4
one 1
two 2
three 3
foobar -1

static一起使用内部方法

为了在类内有效地使用这个模式,可以静态初始化lambda映射,否则每次都要支付O(n)来从头构建它。

在这里,我们可以使用EDCOX1的19初始化EDCOX1×17的方法变量:类方法中的静态变量,但是我们也可以使用C++中的静态构造函数所描述的方法。我需要初始化私有静态对象

有必要将lambda上下文捕获[&]转换为一个参数,否则将无法定义:const static auto lambda与capture by reference一起使用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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;
    }
}


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


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

您的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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模式的wuth树搜索,在这种情况下不允许返回语句。存在C语言实现,其中使用char*类型和以零结尾的字符串比较。

阅读有关此交换机实现的更多信息。


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

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


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


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

http://en.wikipedia.org/wiki/switch_声明


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

这将在unordered_map中执行哈希查找,并使用关联的int来驱动switch语句。应该很快。注意,使用at代替[],正如我所说的包含const。使用[]可能很危险——如果字符串不在映射中,您将创建一个新的映射,最终可能会得到未定义的结果或不断增长的映射。

注意,如果字符串不在映射中,at()函数将抛出异常。因此,您可能希望首先使用count()进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
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;


}

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

1
2
3
4
5
6
7
8
9
10
11
12
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

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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)
    {
        // ...
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    cout <<"
Enter word to select your choice
"
;
    cout <<"ex to exit program (0)
"
;    
    cout <<"m     to set month(1)
"
;
    cout <<"y     to set year(2)
"
;
    cout <<"rm     to return the month(4)
"
;
    cout <<"ry     to return year(5)
"
;
    cout <<"pc     to print the calendar for a month(6)
"
;
    cout <<"fdc      to print the first day of the month(1)
"
;
    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
"
;
            cin >> c;
            cout << endl;
            myCalendar.setMonth(c);
            break;
        }
        case 2:
            cout <<"Enter year(yyyy)
"
;
            cin >> y;
            cout << endl;
            myCalendar.setYear(y);
            break;
        case 3:
             myCalendar.getMonth();
            break;
        case 4:
            myCalendar.getYear();
        case 5:
            cout <<"Enter month and year
"
;
            cin >> c >> y;
            cout << endl;
            myCalendar.almanaq(c,y);
            break;
        case 6:
            break;

    }


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

1
enum MyString(raj,taj,aaj);

在swich case语句中使用它。


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


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)