C++中,由于字符串一开始并非内置的类型,于是,史前时代,江湖上出现了种种的字符串,各有各的优点,但自然,也各有各的缺陷,群雄割据,天下大乱,民不聊生。大伙儿盼星星,盼月亮,盼着真正的字符串能出来一统江湖,好不容易,等到1998的C++标准出来了,官方的字符串string终于露面了,自然,它并不是语言内置支持的,而是在STL库中。当然,字符串这东西,就算要在C++编译器的层面上给予支持,其实也很不容易。可是,这个官方的string,单纯它复杂的模板定义和那一大陀函数成员,就足以吓退众多意志不坚定之人。好了,好不容易,鼓起勇气,再仔细瞅瞅string中的东西,发现它也不是很美妙,既不强大,部分函数的功能存在重复又或者STL的算法中已有提供,更要命的是,效率也不怎么高。总之,不能说string的设计不如史前的各种stringS,但也强不到那里去。当然,官方的总比非官方的更具权威,但每次使用string时,想到它背地里除了正常字符串操作之外,还可能做了各种的低效的内存分配释放的操作,又或者线程安全,又或者引用计数,内心就一直惴惴不安。于是,宁愿一再小心翼翼地用着字符数组。但是,用得多了,也很郁闷,字符数组自然高效、灵活,但总是要千编一律地一再写着容易出错的代码,我脆弱的心灵终于头晕眼花了。于是,我决定按照自己的意愿写一个字符串,不敢欲与群雄争锋,只是,为了能够在自己的代码中,替换字符数组。我对它的要求是,字符数组能做到的事情,它也要做到,并且,效率上,绝不妥协。
俗话说,过早的优化是万恶之源。但在此,在设计本家字符串时,一开始,就要考虑到效率的细节上去了。首先,它要支持堆栈变量的形式,不要它进行内存的分配释放操作,就好像堆栈上的字符数组那样。咦,好像很神奇,其实,只要想到TR1中那个经典的array,通过使用神奇的模板技术,就有办法做到了。所以,此字符串的使用好比这样子,CStackString <MAX_PATH> sFile,暂时假定这个字符串的名字叫CStackString。
但是,使用模板之后,字符串的字符数组的长度只要不一样,它们就都属于不同类型变量,并且之间还都不兼容呢,虽然它们都是字符串。此外,还会编译器还将生产出一堆重复的代码。这无论如何,都不能忍受。于是,自然而然,就想到了继承。CStackString模板类继承于非模板的mybasestring,mybasestring中实现了CStackString的各种各样的操作,而CStackString只要仿照array那样子,定义好自己的数据成员即可。嗯,还是看看代码,马上就明白到底是怎么一回事了。
class CMyBaseString
{
public:
typedef size_t size_type;
typedef char *pointer;
typedef const char *const_pointer;
typedef CMyBaseString _Myt;
public:
char operator[](size_type nPos)const
{
assert(nPos < m_nLen);
return m_str[nPos];
}
const_pointer c_str()const { return m_str; }
const_pointer right(size_type nLen)const
{
assert(nLen < m_nLen);
return m_str+m_nLen-nLen;
}
int compare(const_pointer str)const
{
return strcmp(m_str, str);
}
_Myt& assign(const char* str)
{
m_nLen = strlen(str);
assert(m_nLen < m_nBuffSize);
strcpy(m_str, str);
return *this;
}
_Myt& append(const_pointer str)
{
size_type nLen = strlen(str);
assert(m_nLen + nLen < m_nBuffSize);
strcpy(m_str+m_nLen, str);
m_nLen += nLen;
return *this;
}
_Myt& format(const_pointer sFormat, )
{
va_list argList;
va_start( argList, sFormat );
m_nLen = vsprintf(m_str, sFormat, argList);
va_end( argList );
assert(m_nLen < m_nBuffSize);
return *this;
}
//.
protected:
CMyBaseString(pointer sBuf, size_type nBuffSize)
{
m_nBuffSize = nBuffSize;
m_nLen = 0;
m_str = sBuf;
m_str[0] = 0;
}
private:
size_type m_nBuffSize;
size_type m_nLen;
pointer m_str;
};
template<size_t _size>
class CStackString : public CMyBaseString
{
public:
CStackString(const char* str) : CMyBaseString(m_sMine, _size) { assign(str);}
CStackString() : CMyBaseString(m_sMine, _size) {}
private:
char m_sMine[_size];
};
int main()
{
CStackString<20> sTest("hello");
cout << sTest.c_str() << endl;
cout << sTest.right(3) << endl;
return 0;
}
于是通过基类mybasestring,各种不同类型的template CStackString就又联系在一块了。Mybasestring可看成定义了数据接口的基类,其子类的头部数据必须与它保持一致,嗯,很好。然后,在mybasestring中实现的各种功能,都可以用在mystring身上了,而CStackString中无须实现任何功能,它只负责在堆栈上分配内存。所以,mybasestring不仅可以用在堆栈上,还能用于堆上,只要再继续定义一个能在堆上分配内存的mybasestring的子类即可,然后都能相容于堆栈上的CStackString字符串。
……。 经过一番努力,这个字符串类几乎包含了字符数组的一切基本功能,基本上可代替字符数组了。为了能够用到STL中的各种算法,再在其上增加一些返回迭代器的函数,好比begin,end,rbegin,rend,它们都很容易实现。还有,为了使用起来更加友好方便更具效率,貌似应该再实现一些全局操作符的重载运算;……;好了,打住。如果打算将这个字符串很好地融入到STL中,需要做出更多的努力。原本只打算代替字符数组而已。代码在VC2005以上版本编译时,会出现一些警告,可以用#pregma的指令将其disable掉,或者使用其中的所谓的安全字符串操作函数。
好不容易,终于就实现了一个基于堆栈上的字符串,它是窄字符的。别忘了,还有宽字符的字符串呢。这也没什么,只须将mybasestring重命名为CMyBaseStringA,CStackString改为CStackStringA。然后再分别实现与CMyBaseStringA与CStackStringA同样接口的CMyBaseStringW和CStackStringW。然后,再针对UNICODE,Typedef或defined一对CMyBaseStringT和CStackStringT。在这里,并不想模仿STL中的string或MFC中的CString那样子,template一个basic_string(simple_string),然后分别进行模板特化,近来越看越觉得这种模板特化的方式相当恶心,只是将代码搞得更加复杂,却没带来多大的好处。
相比于其他的字符串实现,这个mybasestring不过是将内存分配拱手让人罢了。这样一来,就带来一些新的问题。首先,它要假设其子类给它分配了足够的内存,不过,在C++传统,经常假设用户分配了足够的内存;然后,因为脱离了内存管理,有一些功能自然也就无法实现出来了,C的字符串函数也还不是这样,当缓冲溢出时,该崩溃就还得崩溃。
再次向C++的无所不能顶礼膜拜。C++,你是电,你是光, 你是唯一的神话, 我只爱你,You are my Super Star!
再次声明,本字符串只为取代字符数组,至于其它的种种无理要求,均不在本座的考虑范围之内。