编译以下代码并得到type illegal
的错误。
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
您无法在switch
或case
中使用字符串。为什么?是否有任何解决方案可以很好地支持类似于切换字符串的逻辑?
答案 0 :(得分:166)
之所以与类型系统有关。 C / C ++并不真正支持字符串作为一种类型。它确实支持常量char数组的想法,但它并不完全理解字符串的概念。
为了生成switch语句的代码,编译器必须理解两个值相等的含义。对于像int和enums这样的项目,这是一个微不足道的比较。但是编译器应该如何比较2个字符串值呢?区分大小写,不敏感,文化意识等...如果没有完全意识到字符串,则无法准确回答。
此外,C / C ++ switch语句通常生成为branch tables。为字符串样式开关生成分支表并不容易。
答案 1 :(得分:53)
如前所述,编译器喜欢构建查找表,尽可能将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语句的处理方式......有趣的是如何发生。
答案 2 :(得分:29)
您只能使用switch,例如int,char和enum。最简单的解决方案就是使用枚举。
#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日。
答案 3 :(得分:13)
C ++ 11更新显然不是@MarmouCorp,而是http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm
使用两个映射在字符串和类枚举之间进行转换(优于普通枚举,因为它的值在其中作用域,反向查找好的错误消息)。
在codeguru代码中使用static是可能的,因为编译器支持初始化列表,这意味着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]);
}
答案 4 :(得分:11)
问题在于,出于优化的原因,C ++中的switch语句不能用于任何原始类型,并且只能将它们与编译时常量进行比较。
据推测,限制的原因是编译器能够应用某种形式的优化,将代码编译为一条cmp指令,以及根据运行时参数的值计算地址的goto。由于分支和循环不能很好地与现代CPU配合使用,因此这可能是一项重要的优化。
要解决这个问题,我恐怕你不得不求助于if语句。
答案 5 :(得分:9)
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
}
答案 6 :(得分:7)
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映射,否则每次都要从{0}开始构建它。
在这里,我们可以使用O(n)
方法变量的{}
初始化:Static variables in class methods,但我们也可以使用以下所述的方法:static constructors in C++? I need to initialize private static objects
有必要将lambda context capture static
转换为参数,或者未定义的参数:const static auto lambda used with capture by reference
产生与上面相同的输出的示例:
[&]
答案 7 :(得分:6)
在C ++和C中,交换机仅适用于整数类型。请改用if else梯形图。 C ++显然可以为字符串实现某种swich语句 - 我想没有人认为它是值得的,我同意它们。
答案 8 :(得分:5)
为什么不呢?您可以使用具有等效语法和相同语义的switch implementation。
C
语言根本没有对象和字符串对象,但是
C
中的字符串是由指针引用的空终止字符串。
C++
语言有可能为其创建重载函数
对象比较或检查对象的等式。
由于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
或支持相等操作的任何结构或类(或快速模式的同义词)。
Sintax与语言切换的差异是
对于使用C++97
语言的线性搜索。
对于C++11
和更现代的可能使用quick
模式wuth树搜索,其中返回语句在CASE中变得不被允许。
存在C
语言实现,其中使用char*
类型和零终止字符串比较。
阅读more about此切换实施。
答案 9 :(得分:4)
我认为原因在于C字符串不是原始类型,正如tomjen所说的那样,在字符串中将其视为char数组,因此您无法执行以下操作:
switch (char[]) { // ...
switch (int[]) { // ...
答案 10 :(得分:4)
使用最简单的容器添加变体(不需要有序的地图)......我不打算使用枚举 - 只需将容器定义放在开关之前,这样就很容易看到哪个数字代表哪种情况。
这会在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
}
答案 11 :(得分:3)
在c ++中,字符串不是一等公民。字符串操作通过标准库完成。我想,这就是原因。此外,C ++使用分支表优化来优化switch case语句。看看链接。
答案 12 :(得分:3)
在C ++中,您只能在int和char上使用switch语句
答案 13 :(得分:2)
晚些时候,这是我前一段时间想出的一个解决方案,它完全遵守所要求的语法。
#include <uberswitch/uberswitch.hpp>
int main()
{
uberswitch (std::string("raj"))
{
case ("sda"): /* ... */ break; //notice the parenthesis around the value.
}
}
答案 14 :(得分:1)
您可以使用打开字符串。 您需要的是字符串表,检查每个字符串
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*/
}
答案 15 :(得分: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)
{
// ...
}
}
答案 16 :(得分:0)
您可以将字符串放入数组中,并在编译时使用 constexpr
将它们转换为索引。
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
,您可以例如在运行时使用 unordered_map
来完成 O(1)。 (或对数组进行排序并使用二分查找,see here for an example。)
这是 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;
}
}
答案 17 :(得分:0)
在许多情况下,您可以通过从字符串中拉出第一个字符并打开它来进行额外的工作。如果您的案例以相同的值开头,可能最终必须对charat(1)进行嵌套切换。任何阅读你的代码的人都会欣赏一个暗示,因为大多数人只会知道if-else-if
答案 18 :(得分: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;
}
答案 19 :(得分:0)
切换仅适用于整数类型(int,char,bool等)。为什么不使用地图将字符串与数字配对,然后将该数字与交换机一起使用?
答案 20 :(得分:0)
你不能在switch case中使用string.Only int&amp; char是允许的。相反,您可以尝试使用枚举来表示字符串,并在开关案例块中使用它,如
enum MyString(raj,taj,aaj);
在swich case语句中使用它。
答案 21 :(得分:-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)