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

yes*_*aaj 208 c++ string switch-statement

编译以下代码并得到错误type illegal.

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

你不能在任何一个switch或中使用字符串case.为什么?是否有任何解决方案可以很好地支持类似于切换字符串的逻辑?

Jar*_*Par 176

之所以与类型系统有关.C/C++并不真正支持字符串作为一种类型.它确实支持常量char数组的想法,但它并不完全理解字符串的概念.

为了生成switch语句的代码,编译器必须理解两个值相等的含义.对于像int和enums这样的项目,这是一个微不足道的比较.但是编译器应该如何比较2个字符串值呢?区分大小写,不敏感,文化意识等...如果没有完全意识到字符串,则无法准确回答.

此外,C/C++ switch语句通常作为分支表生成.为字符串样式开关生成分支表并不容易.

  • 我拒绝了,因为我不明白编译器如何知道如何比较if语句中的2个字符串值,但忘记了在switch语句中执行相同操作的方法. (94认同)
  • 我不认为前两段是正当理由.尤其是在添加`std :: string`文字时的C++ 14.这主要是历史性的.但是我想到的一个问题是,使用`switch`目前的工作方式,必须在编译时检测到复制的`case`s*; 但是对于字符串来说这可能不那么容易(考虑运行时语言环境选择等).我想这样的事情必须要求`constexpr`案例,或者添加未指明的行为(从来不是我们想做的事情). (13认同)
  • 分支表参数不应该适用 - 这只是编译器作者可用的一种可能方法.对于生产编译器,必须经常使用多种方法,具体取决于交换机的复杂性. (8认同)
  • @plinth,我把它放在那里主要是出于历史原因.许多"为什么C/C++这样做"的问题很容易通过编译器的历史来回答.在他们写作的时候,C得到了美化装配,因此切换真的是一个方便的分支表. (4认同)
  • 对于如何将两个`std :: string`值甚至一个`std :: string`与const char数组(即使用operator ==)进行比较,有一个明确的定义,没有技术上的原因会阻止编译器执行为提供该运算符的任何类型生成一个switch语句。它将引发一些问题,例如标签的寿命,但所有这些主要是语言设计的决定,而不是技术上的困难。 (3认同)
  • 编译器不知道如何比较字符串的原因很差.更好的理由是c ++标准委员会保持沉默,使`std :: string`,`std :: wstring`和其他字符串类型成为语言中的第一个公民. (3认同)
  • @plinth (cont) 是的,他们本可以从那时起更新它,但是 C/C++ 在改变他们做事的方式方面非常缓慢。例如,直到 C99 才将布尔类型转换为 C! (2认同)
  • @MM 我认为特定于语言环境的开关问题是一个红鲱鱼。在 switch 语句的历史中,有没有人在他们的代码中进行过跨语言环境切换?这简直就是一场安全失败的噩梦。switch 是一种编程语言的东西,而不是人类语言的东西。至于 constexpr,您现在可以实际散列传入的字符串值和开关值以获取基于数字的开关。你需要一个 constexpr 哈希函数仍然很丑陋。似乎编译器应该为你做这件事。 (2认同)
  • @ user955249编译器使用`std :: string :: operator ==(std :: string const&rhs)`在if语句中比较两个字符串。因此从技术上讲,编译器本身确实不知道如何比较两个字符串。它依赖于某人编写的代码来进行比较。 (2认同)
  • 没有解决“是否有任何效果很好的解决方案......”部分。 (2认同)

D.S*_*ley 54

如前所述,编译器喜欢构建查找表,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:
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

有一堆显而易见的优化几乎遵循C编译器对switch语句的处理......有趣的是如何发生.

  • 这实在令人失望,因为你实际上并没有散列.使用现代C++,您可以使用constexpr散列函数在编译时实际散列.你的解决方案看起来很干净,但如果不幸的话,梯子会很糟糕.下面的地图解决方案会更好,也可以避免函数调用.此外,通过使用两个映射,您还可以内置文本以进行错误记录. (12认同)
  • 但为什么?您始终可以在开关之上使用 if 语句执行。两者的影响都很小,但是 if-else 查找消除了 switch 的性能优势。仅使用 if-else 应该会稍微快一些,但更重要的是,会显着缩短。 (2认同)

Mar*_*orp 31

您只能使用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;
}
Run Code Online (Sandbox Code Playgroud)

代码由Stefan Ruck于2001年7月25日撰写.

  • 公然抄袭:http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067 (54认同)
  • 更好的是,使用"std :: map <std :: string,some_function_pointer>"将无需切换.它还会使包含开关的功能变得过大. (4认同)
  • "肆无忌惮地抄袭"是一个强有力的表述.stackoverflow的目的是帮助人们解决他们的问题,而不是通过你的声誉"声名鹊起" (4认同)

Dir*_*ter 14

上面显然不是@MarmouCorp的C++ 11更新,但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"}, 
};
Run Code Online (Sandbox Code Playgroud)

...

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]);
}
Run Code Online (Sandbox Code Playgroud)


Nic*_*ick 14

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
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以添加`operator“”使代码更漂亮。`constexpr内联无符号int运算符“” _(char const * p,size_t){return hash(p); }`并像`case“ Peter” _:break;一样使用它[Demo](https://gcc.godbolt.org/z/pfJXRm) (6认同)
  • 我知道,但如果它散列到相同的值,它就不会编译,你会及时注意到它。 (5认同)
  • 您必须确保没有一个案例散列到相同的值。即便如此,您可能会犯一些错误,例如,散列到与 hash("one") 相同值的其他字符串将错误地执行 switch 中的第一个“某事”。 (3认同)
  • 好点 - 但这并不能解决不属于您的交换机的其他字符串的哈希冲突。在某些情况下,这可能无关紧要,但如果这是一个通用的“首选”解决方案,我可以想象它在某个时候是一个安全问题或类似问题。 (2认同)

tom*_*jen 11

问题在于,出于优化的原因,C++中的switch语句不能用于任何原始类型,并且只能将它们与编译时常量进行比较.

据推测,限制的原因是编译器能够应用某种形式的优化,将代码编译为一个cmp指令,以及根据运行时参数的值计算地址的goto.由于分支和循环不能很好地与现代CPU配合使用,因此这可能是一项重要的优化.

为了解决这个问题,我恐怕你不得不求助于if声明.

  • 并非所有原始类型都有效.只有整体类型. (3认同)

rsj*_*ffe 8

要使用最简单的容器添加变体(不需要有序映射)......我不会打扰枚举 - 只需将容器定义放在 switch 之前,这样就很容易看到哪个数字代表这种情况下。

这会在 中进行散列查找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;


}
Run Code Online (Sandbox Code Playgroud)

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

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

}
Run Code Online (Sandbox Code Playgroud)


Cir*_*四事件 8

std::map + C++ 11没有枚举的lambda模式

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

one 1
two 2
three 3
foobar -1
Run Code Online (Sandbox Code Playgroud)

用法里面的方法 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 注意,这种方法和`switch`语句之间是有区别的。在`switch`语句中重复大小写值是编译时失败。使用`std :: unordered_map`静默接受重复值。 (2认同)

小智 6

在C++和C中,交换机仅适用于整数类型.请改用if else梯形图.C++显然可以为字符串实现某种swich语句 - 我想没有人认为它值得,我同意它们.


okl*_*las 5

为什么不?您可以使用具有等效语法和相同语义的switch实现.该C语言根本没有对象和字符串对象,但字符串C是指针引用的空终止字符串.该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"
Run Code Online (Sandbox Code Playgroud)

例如,可以使用更复杂的类型std::pairs或支持相等操作的任何结构或类(或快速模式的同化).

特征

  • 任何类型的数据,支持比较或检查相等性
  • 建立级联嵌套开关状态的可能性.
  • 通过案例陈述打破或跌倒的可能性
  • 使用非常量案例表达式的可能性
  • 可以通过树搜索启用快速静态/动态模式(对于C++ 11)

Sintax与语言切换的区别在于

  • 大写关键字
  • CASE声明需要括号
  • 分号';' 在语句结束时不允许
  • 不允许在CASE语句中使用冒号':'
  • 在CASE语句结束时需要一个BREAK或FALL关键字

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

阅读有关此开关实现的更多信息


归档时间:

查看次数:

332179 次

最近记录:

6 年,8 月 前