介绍
这篇文章描述了一种在C++中对短字符串(长度为4以内)进行switch-case操作的尝试,如同整型值那样,以此避免因字符串匹配带来的开销,稍微提高运行效率。
背景
如大家所知,C++并不支持对字符串(单字符还是支持的)做switch case的分支操作,因为switch的表达式只支持整型值或可转化为整型值的类型。既然switch case不能用了,那可选的方案就只剩下if-else、map、hash(或还有其他,望告知),先分别对这三种方案做个简要说明。
if-else,如果字符串的可能值只有三种或三种以下,那么这也是个不错的选择,代码简单,易读,至于效率嘛,顶多也就做3次的字符串比较;可要是字符串的可能值不止3种,比如数10种,那么这种方案就不太胜任了,最糟的情况下,字符串比较可能要贯穿整个if判断,这种代码常见于新手。
map(或者叫Dictionary),得益于boost和c++11的function库,通过key-value的方式,将“字符串-functor”进行一一匹对,分支选择依赖于map查找的返回项(functor),相对于if-else 而言,平均效率提高了,而且代码看起来也好一些,添加新的字符串时,只需在map初始化时加入即可,为此付出的代价就只是map存储的内容开销了,这也算是空间换时间的一种方式吧。
hash,应该是这三种当中最高效的了,几乎就相当于整型值的switch-case操作了,先对字符串hash,然后每个case分支都是匹配字符串的hash值。由于switch-case的特性,case表达式必须是个constant-expression,即不能依赖于运行时计算原字符串的hash值,编译器不答应,所以只能对每个可能字符串先计算好hash值,这种代码必须写上原字符串作为注释,这样的代码看起来很怪异,就像是这样的:
int hash_val = hash_func(str); switch(hash_val) { case hash_option1: break; // "abcd" case hash_option2: break; // "abdc" case hash_option3: break; // "acbd" case hash_option4: break; // "acdb" }
那么,克服hash这个方案所存在的问题,尽量使它达到switch-case的“功效”,是这个实验的目的。
实现
从上面的代码片段可看出,只要有办法能够让字符串“abcd”在编译期自动计算出hash值,那么这个方案就可行了。那如何将字符串自动转换为整型值呢?宏定义???与字符串相关的宏定义有“#”这么一个符号,或许它能帮上忙,google之,果然,除了“##”、“#”、之外,还有“#@”这么一种组合,将标记转换为字符,如
#define makechar(x) #@x char a; a = makechar(b); bool is_equal = (a == 'b'); // is_equal 值为true
尝试一下多个字符makechar(abc),编译、调试后发现abc被按字节“或”到整型值,那接下来只需将待匹配的字符串也按同样的方式“转化”为整型值就行了,直接贴代码了:
template<typename T> T copy_str_to(T& dest, const char* str) { int len = (int)strlen(str); for (int i = len - 1; i >= 0; --i) *(((char*)&dest) + (len - i - 1)) = str[i]; return dest; } template<typename T> T copy_wstr_to(T& dest, const wchar_t* str) { int len = (int)wcslen(str); for (int i = len - 1; i >= 0; --i) *(((char*)&dest) + (len - i - 1)) = (char)str[i]; return dest; } ////////////////////////////////////////////////////////////////////////// template<typename T> struct cat_str_to { template<typename char_type> static T go(const char_type* psz) { T dest; return copy_str_to(dest, psz); } template<> static T go<wchar_t>(const wchar_t* psz) { T dest; return copy_wstr_to(dest, psz); } }; ////////////////////////////////////////////////////////////////////////// template<short cvt> struct _case_cvtr_2{static const short value = cvt;}; template<int cvt> struct _case_cvtr_4{static const int value = cvt;}; //--------------------------usage--------------------------// #define str_switch_2(str) switch(cat_str_to<short>::go(str)) #define str_case_2(a) case _case_cvtr_2<#@a>::value #define str_switch_4(str) switch(cat_str_to<int>::go(str)) #define str_case_4(a) case _case_cvtr_4<#@a>::value //--------------------------usage--------------------------//
使用
#define SWITCH str_switch_4 #define CASE str_case_4 SWITCH (str) { CASE(abc) break; CASE(abcd) break; CASE(db) break; default: break; }