分享

为C++添加短字符串的switch

 笔录收藏 2016-04-29

介绍

这篇文章描述了一种在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;
}


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多