Why the switch statement cannot be applied on strings?
编译以下代码,得到
1 2 3 4 5 6 7 8 | int main() { // Compilation error - switch expression of type illegal switch(std::string("raj")) { case"sda": } } |
不能在
原因与类型系统有关。C/C++不支持字符串作为一种类型。它确实支持常量char数组的概念,但它并不完全理解字符串的概念。
为了生成switch语句的代码,编译器必须理解两个值相等意味着什么。对于像int和enum这样的项,这是一个微不足道的位比较。但是编译器应该如何比较2个字符串值呢?区分大小写、不敏感、了解文化等…如果没有对字符串的完全了解,就无法准确地回答这个问题。
另外,C/C++转换语句通常是作为分支表生成的。为字符串样式的开关生成分支表几乎没有那么容易。
如前所述,编译器喜欢构建查找表,以便尽可能地将
我会提供一个你可能想考虑的替代方案,我过去已经用过了,效果很好。而不是切换字符串本身,而是切换使用字符串作为输入的哈希函数的结果。如果您使用的是一组预先确定的字符串,那么您的代码几乎与切换字符串一样清晰:
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 } |
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 |
与
为了在类内有效地使用这个模式,可以静态初始化lambda映射,否则每次都要支付
在这里,我们可以使用EDCOX1的19初始化EDCOX1×17的方法变量:类方法中的静态变量,但是我们也可以使用C++中的静态构造函数所描述的方法。我需要初始化私有静态对象
有必要将lambda上下文捕获
产生与上述相同输出的示例:
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语句——我想没有人认为它值得,我同意。
为什么不?您可以使用具有等效语法和相同语义的开关实现。
您的代码如下:
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" |
可以使用更复杂的类型,例如
- 支持比较或检查相等性的任何类型的数据
- 可以构建层叠嵌套开关状态。
- 有可能打破或破坏案例陈述
- 使用非常量case表达式的可能性
- 可以使用树形搜索(C/C++ 11)实现快速静态/动态模式
与语言切换的sintax差异是
- 大写关键字
- case语句需要括号
- 语句末尾不允许使用分号";"
- 不允许在case语句中使用冒号":"
- 在case语句末尾需要break或fall关键字之一
对于
阅读有关此交换机实现的更多信息。
我认为原因是,在C字符串中不是原始类型,正如Tomjen所说,将字符串看作char数组,因此不能执行以下操作:
1 2 | switch (char[]) { // ... switch (int[]) { // ... |
在C++中,只能在int和char上使用开关语句。
在C++中,字符串不是第一类公民。字符串操作通过标准库完成。我想,这就是原因。此外,C++使用分支表优化来优化交换机实例语句。看看这个链接。
http://en.wikipedia.org/wiki/switch_声明
要使用尽可能简单的容器添加变体(不需要已排序的映射)…我不必担心枚举——只需将容器定义放在开关前面,这样就可以很容易地看到哪个数字代表哪种情况。
这将在
注意,如果字符串不在映射中,
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)