C/C++中使用字符串的switch case语句

31

可能重复:
C/C++:非整数的switch

您好,我需要在switch case语句中使用字符串。 我目前的解决方案是使用自己的哈希函数计算字符串的哈希值。但问题在于我必须手动预先计算所有字符串的哈希值。是否有更好的方法?

h=_myhash (mystring);
switch (h)
{
case 66452:
   .......
case 1342537:
   ........
}

2
你上面所描述的是很重要的知识,当然你的工具箱里也应该有其他工具。通常它与完美哈希和代码生成相结合(即自动维护,类似于使用编译器编译器(如yacc)而不是直接编写底层样板)。 - Fred Nurk
1
这是C/C++:非整数的switch的副本,还有许多类似的副本。 - sbi
@Fields: C++只是没有隐藏switch(int)switch(string)具有非常不同的性能特征。请使用if/else if。 - Ed S.
@EdS。如果需要维护的大型扩展项目,您是否仍然会使用if else if?与非常多的if-else语句的复杂性相比,switch更容易理解。理想情况下,我会建议寻找完全不同的解决方案,但在这两者之间,switch肯定具有更高的可维护性。 - Fields
@Fields: 我认为 A) 使用 switch v if/elseif 实际上根本不是可维护性的问题,B) 如果你的条件链如此之长,并且需要手动维护,那么你已经编写了一些难以维护的代码。 - Ed S.
显示剩余4条评论
10个回答

39

只需使用 if() { } else if () { } 链即可。使用哈希值将是维护的噩梦。switch 旨在成为低级语句,不适合用于字符串比较。


18
你可以使用标准集合将字符串映射到函数指针,当匹配时执行该函数。
编辑:使用我在评论中提供链接的文章中的示例,您可以声明一个函数指针类型:
typedef void (*funcPointer)(int);

并创建多个函数以匹配该签名:

void String1Action(int arg);
void String2Action(int arg);

这个映射将会是从 std::stringfuncPointer 的:

std::map<std::string, funcPointer> stringFunctionMap;

然后添加字符串和函数指针:

stringFunctionMap.add("string1", &String1Action);

我没有测试我刚刚发布的任何代码,这都是我脑海中的想法 :)


1
你能给我一个使用函数指针的例子吗? - Luke
1
以下是谷歌搜索结果的链接:http://www.cprogramming.com/tutorial/function-pointers.html 同时,也可以看看Boost库中的boost::bind(更加复杂但非常灵活)。 - Tony

7
通常情况下,您可以使用哈希表和函数对象来解决此问题,这两个工具在Boost、TR1和C++0x中都有提供。
void func1() {
}
std::unordered_map<std::string, std::function<void()>> hash_map;
hash_map["Value1"] = &func1;
// .... etc
hash_map[mystring]();

这会在运行时增加一些额外的开销,但可维护性提高数千倍。哈希表提供O(1)的插入、查找等操作,这使它们与汇编风格的跳转表具有相同的复杂度。


我很感谢你提供的样例,这是一个非常好的起点。我需要一些高效且易于维护的东西。谢谢。 - Luke
如果mystring不在哈希表中,如何处理错误? - Luke
if(map.find("foo") == map.end()) { /* not found */ } - Axel Gneiting

3
最好的方法是使用源代码生成,这样您就可以使用。
if (hash(str) == HASH("some string") ..

在你的主要源代码中,一个预构建步骤将把HASH(const char*)表达式转换为整数值。

2
你可以创建一个哈希表。键可以是字符串,值可以是整数。将整数设置为常量作为值,然后可以使用 switch 检查它们。

2

我认为Ruslik的建议使用源代码生成是一个很好的想法。但是我不会使用“主”和“生成”的源文件概念。我更愿意有一个代码几乎与你的相同的文件:

h=_myhash (mystring);
switch (h)
{
case 66452: // = hash("Vasia")
   .......
case 1342537: // = hash("Petya")
   ........
}

下一步,我会编写一个简单的脚本。如果您不想使用其他语言,Perl非常适合这种事情,但是您甚至可以用C/C++编写一个简单的程序。这个脚本或程序将获取源文件,逐行读取它,查找所有这些case NUMBERS: // = hash("SOMESTRING")行(在此处使用正则表达式),将NUMBERS替换为实际的哈希值,并将修改后的源代码写入临时文件。最后,它将备份源文件并用临时文件替换它。如果您不想让源文件每次都有新的时间戳,程序可以检查是否实际上发生了更改,如果没有,就跳过文件替换。
最后要做的是将此脚本集成到所使用的构建系统中,这样在构建项目之前不会意外地忘记启动它。

我在其中一个重复问题中发布了类似的解决方案,使用C++11和constantexpr来“内联”字符串的哈希值,基于其他类似问题的答案(https://dev59.com/z2855IYBdhLWcg3wvnKE#45226108)。 其中一个棘手的部分是处理“碰撞”,即switch键“h”不匹配 case字符串但匹配哈希值的情况。 - wawiesel

1

你的问题没有很好的解决方案,所以这里有一个可以的解决方案 ;-)

当断言被禁用时,它可以保持效率;当断言被启用时,如果哈希值错误,它会引发断言错误。

我怀疑 D 编程语言可以在编译时计算哈希值,从而消除了明确编写哈希值的需要。

template <std::size_t h>
struct prehash
{
    const your_string_type str;

    static const std::size_t hash_value = h;

    pre_hash(const your_string_type& s) : str(s)
    {
        assert(_myhash(s) == hash_value);
    }
};

/* ... */

std::size_t h = _myhash(mystring);

static prehash<66452> first_label = "label1";

switch (h) {
case first_label.hash_value:
    // ...
    ;
}

顺便提一下,考虑从声明 myhash() 的初始下划线中删除它(抱歉,但stackoverflow强制我在_和myhash之间插入一个空格)。 C ++实现可以自由地使用以下划线和大写字母开头的宏名称(Herb Sutter的“Exceptional C ++ Style”第36项),因此,如果您习惯于以下划线开头命名事物,则可能会有一天给符号命名以下划线和大写字母开头,而实现已定义具有相同名称的宏。

1

1
您可以使用枚举和映射,将您的字符串转换为映射的键,而枚举值则是该键对应的值。

1

如果您追求性能,并且不想每次都通过所有的if子句(如果有很多)或需要对值进行哈希处理,则可以通过使用enum向函数发送一些额外信息,或者只需将enum类型添加到您的结构中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接