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

编译下面的代码会得到错误消息:type illegal

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

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

524815 次浏览

在c++中,你只能在int和char上使用switch语句

在c++和C语言中,开关只适用于整型。使用if else梯子代替。c++显然可以为字符串实现某种switch语句——我猜没有人认为值得这样做,我同意他们的观点。

问题是,由于优化的原因,c++中的switch语句只能用于基本类型,你只能将它们与编译时常量进行比较。

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

要解决这个问题,恐怕你将不得不求助于if语句。

你不能在开关情况下使用字符串。仅int &夏尔是允许的。相反,您可以尝试用enum表示字符串,并在switch case块中使用它

enum MyString(raj,taj,aaj);

在switch case语句中使用它。

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

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

此外,C/ c++ switch语句通常生成为部门表。为字符串样式切换生成分支表远没有那么容易。

我认为原因是在C语言中字符串不是基本类型,就像tomjen说的,把字符串看作一个char数组,所以你不能做这样的事情:

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

如前所述,编译器喜欢构建查找表,尽可能将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语句的处理……真有趣。

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

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

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

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

使用两个映射在字符串和类enum之间进行转换(比普通enum更好,因为它的值是在它内部的范围内,并且反向查找可以获得良好的错误消息)。

在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]);
}
    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;


}

这是因为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)

使用尽可能简单的容器添加一个变体(不需要一个有序的映射)…我不会使用枚举——只是把容器定义放在切换之前,这样就很容易看出哪个数字代表哪个情况。

它在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


}

在许多情况下,您可以通过从字符串中提取第一个字符并打开它来进行额外的工作。如果您的case以相同的值开始,可能最终必须在charat(1)上进行嵌套切换。任何阅读您的代码的人都会喜欢一个提示,因为大多数人会只使用if-else-if

< p >为什么不呢?你可以用相同的语法和语义使用开关实现C语言根本没有对象和字符串对象,但是 C中的字符串是指针引用的空终止字符串。 C++语言有可能为 对象比较或检查对象相等。 由于C As 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++ 11)

汉译语的差异在于语言的转换

  • 大写的字
  • CASE语句需要括号
  • 语句末尾不允许使用分号
  • 冒号':' at CASE语句不被允许
  • 在CASE语句的末尾需要一个BREAK或FALL关键字

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

读取更多关于这个开关实现。

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)来从头构建它。

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

有必要将lambda上下文捕获[&]转换为一个参数,否则将是未定义的:常量静态自动lambda与引用捕获一起使用

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

#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++

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

开关问题的更实用的解决方案:

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)
{
// ...
}
}

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

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*/
}

这是我前段时间提出的一个解决方案,它完全遵守所请求的语法。

#include <uberswitch/uberswitch.hpp>


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

下面是代码:https://github.com/falemagn/uberswitch

你可以把字符串放在一个数组中,并在编译时使用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)。(或者对数组进行排序并使用二进制搜索,请看这里的例子。)

下面是c++ 11的完整示例,其中包含一个简单的自定义constexpr字符串比较器。重复的case和不在数组中的case (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;
}
}

hare对Nick解决方案的评论真的很酷。这里是完整的代码示例(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;
}