编译以下代码,得到的错误type illegal
。
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
您不能在switch
或中使用字符串case
。为什么?是否有任何解决方案可以很好地支持类似于打开字符串的逻辑?
QMetaEnum
编译以下代码,得到的错误type illegal
。
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
您不能在switch
或中使用字符串case
。为什么?是否有任何解决方案可以很好地支持类似于打开字符串的逻辑?
QMetaEnum
Answers:
之所以与类型系统有关。C / C ++并不真正支持将字符串作为一种类型。它确实支持常量char数组的概念,但是它并没有真正完全理解字符串的概念。
为了生成switch语句的代码,编译器必须了解两个值相等的含义。对于像整数和枚举这样的项目,这是一个微不足道的比较。但是编译器应该如何比较2个字符串值?区分大小写,不区分大小写,文化意识等。。。如果不完全了解字符串,则无法准确回答。
此外,C / C ++ switch语句通常作为分支表生成。为字符串样式开关生成分支表并不是那么容易。
std::string
添加文字时。这主要是历史性的。但是想到的一个问题是,按照switch
当前的工作方式,必须在编译时检测到重复case
的;但是,对于字符串来说,这可能并不容易(考虑运行时语言环境选择等)。我想这样的事情将需要案例,或者添加未指定的行为(从来都不是我们想要做的事情)。constexpr
std::string
值甚至std::string
一个const char数组(即使用operator ==),都有明确的定义,没有技术上的原因可以阻止编译器为提供该运算符的任何类型生成switch语句。它将引发一些问题,例如标签的寿命,但所有这些主要是语言设计的决定,而不是技术上的困难。
如前所述,编译器喜欢建立查找表,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 ++
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更新显然不是上面的@MarmouCorp,而是http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm
使用两个映射在字符串和类枚举之间进行转换(比普通枚举更好,因为其值在其内部范围内,并且反向查找以获得不错的错误消息)。
编译器支持初始化程序列表,这意味着VS 2013 plus,可以在codeguru代码中使用static。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]);
}
问题是,出于优化的原因,C ++中的switch语句仅对原始类型无效,并且只能将它们与编译时间常数进行比较。
限制的原因大概是编译器能够应用某种形式的优化将代码编译为一个cmp指令和一个goto,其中运行时根据参数的值来计算地址。由于分支和和循环在现代CPU中不能很好地发挥作用,因此这可能是重要的优化。
要解决此问题,恐怕您将不得不诉诸if语句。
std::string
与其他人成为该语言的先民,并使用有效的算法在switch语句中支持它们。
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上下文捕获[&]
转换为参数,否则将无法定义该参数:const static auto 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;
}
}
switch
声明之间有区别。switch
语句中的case值重复是编译时失败。使用std::unordered_map
静默接受重复值。
在C ++和C中,开关仅适用于整数类型。改用if else梯子。C ++显然可以为字符串实现某种形式的swich语句-我猜没有人认为这值得,我同意他们的观点。
为什么不?您可以将开关实现与等效语法和相同语义一起使用。该C
语言根本没有对象和字符串对象,但是in C
中的字符串是指针引用的以null终止的字符串。该C++
语言可以为对象比较提供重载功能或检查对象的相等性。由于C
作为C++
具有足够的灵活性,以对字符串这样的开关C
语言和任何类型的对象支持comparaison或支票平等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 中的return语句。C
存在使用char*
类型和零终止字符串比较的语言实现。
要使用最简单的容器添加变体(不需要有序的地图)...我不会为枚举而烦恼-只需将容器定义放在开关之前,这样就很容易看出哪个数字代表这种情况下。
这会在中进行散列查找,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
}
我认为原因是正如tomjen所说,在C字符串中不是原始类型,所以在字符串中将其视为char数组,因此您不能执行以下操作:
switch (char[]) { // ...
switch (int[]) { // ...
在c ++中,字符串不是一等公民。字符串操作通过标准库完成。我认为,这就是原因。同样,C ++使用分支表优化来优化开关案例语句。看一下链接。
在C ++中,您只能在int和char上使用switch语句
long
和long long
,而不会变成int
。那里没有被截断的风险。
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;
}
解决开关问题的更多功能性变通办法:
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)
{
// ...
}
}
您不能在切换大小写的情况下使用字符串。仅允许使用int和char。相反,您可以尝试用enum表示字符串,并在switch case块中使用它,例如
enum MyString(raj,taj,aaj);
在scase case语句中使用它。
这是因为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
...
(来自Wikipedia https://en.wikipedia.org/wiki/Branch_table的代码)
cmp
/ jcc
实现可能同样有效。