我们不需要字符串类型
字符串是应该作为内置类型还是仅仅作为字符数组的一个别名呢?考虑到实现细节的可选性,我并不认为需要对字符串进行类型特化。在C++看来,字符串 和“vector”容器基本上是一样的,除了某些特殊操作,例如:大小写转换,需要依赖容器元素“char”类型(而不是作用于容器本身)。
什么是字符串?
字符串除了是一系列的字符组成,没有什么特别的。更确切地说,程序中的字符串是特定字符集的字符集合。这里的字符并不总是图象字符,它可以包含可打印字符,连接字符,或者控制字符,那么这又有什么不同吗?
考虑计算字符串的长度,它应该返回的是字符串中字形字符、连接符号的总数还是应该返回字符串中字符占用空间的长度?两个字面等价但是内部存储不同的 的字符串应该返回相同的长度吗?考虑字符串规范化的复杂程度和应用相同规则来计算‘length’长度听起来很荒唐可笑。不同场景下字符串的长度很难统一 计算,而且这还依赖于字符渲染引擎。唯一有意义的是返回字符串存储空间大小——而这和计算字符数组的长度是一致的。
我们可以通过对字符串进行索引和取下标操作。那么我们应该使用字符字面索引还是字符存储索引?另外,考虑到组合Unicode字符串,没有统一的标 准来衡量哪些字符是字面显示字符,哪些是控制字符。字符的组合种类很多(不受限制),因此没有固定的字符类型来定义一个“逻辑”字符。因此字符串的操作应 该针对存储字符地址——这又和字符数组没有区别了。
C++中的不同
C++中的string和vector只有一个明显的区别是:string是以null结尾的。并且string提供c_str方法返回内部字符串存储指针。(C++11定义了string来表明这是存储字符串的有效方式)。
对C++来说,如果string不提供c_str方法,那么string类就基本没有存在的必要(相对于vector)。然而,这是也不是一个必须 的特性,提供出来只是为了方便将string进行转换,从而方便调用早期C风格的字符串指针API。怪异的是,C++标准库也使用了类似的接 口,ofstream的构造函数需要传递的是一个‘const char*’指针而不是string类型。(在C++11中修复了该问题)
使用null作为字符串结束符也是一个糟糕的选择,导致在C函数库中,一些函数如:strcat, strcpy并不安全。使用C风格的字符串是一件令人生畏且容易出错的差事。现代风格的API接口已经很少依赖使用null作为字符串结束,这些函数通常 都要求提供字符串的长度作为一个参数。
导致C++中的string和vector有所不同,这是因为历史包袱,而大多数程序员都不需要关心它的存在。
字符代理和变长字符编码
前述的讨论基于这样一个假设:字符串存储中一个存储元素编码一个字符。而通常采用这种编码方式是效率低下的,使用变长字符编码可以解决这个问题:使 用不同数量的字节来表示一个字符。例如:在UTF-16中,一个字符可以由2个字节或者4个字节进行存储。而不存储字符的单元作为存储序列的一部分,被叫 做字符代理。
字符代理和连接字符不同。字符代理在字符集中没有意义:它只是用来填充编码占位。对字符的到操作依赖以实际存储元素位置。将编码字符串当作一系列的 字符来操作,通常很麻烦而且容易导致未知语义。编码字符串的length应该返回什么值呢?是编码字符的个数还是存储元素的个数?
目前的方法是,将编码字符串作为一种特殊字符类型,并提供一定程度的抽象,你可要存储各种类型的字符,也可以将其作为一个字符序列操作。length返回字符的个数,而与底层编码方式无关(或者通过其他方法返回)。
设计这个类的挑战在于效率。基本操作如索引字符变成了一个线性复杂度操作。需要先对字符串进行解码,从开始扫描,重新组织字符代理,并计算真实的字 符个数。即便是简单的前向扫描也依赖以循环和下一个字符状态的解析。而这种操作负载在所有的基本操作都会被累积,比如拆分,翻译和正则表达式匹配。
目前(内置字符串类型)的语言并没有按这种方式操作字符串,考虑效率问题,这使得采用这种方式变得没有吸引力。而在字符域,加载字符,解码字符和对 字符串进行处理则简单得多。这种方式会消耗更多内存,但是我认为这对于世界上现有的字符集来说并不是一个主要问题——尽管存在大量的字符集,但是相较其他 集合则小得多。
函数库的支持
字符串有许多相对于简单数组的特殊操作:规范化、字符转换、正则表达式运算、字符解析、格式化、裁剪、编码等等。相对地,任何类型的“vector”都有一些特殊操作:数字可以累加,求平均,计算中位数。向量可以做变换,简化和栅格化。
值得探讨的是,一些集合运算是应该作为一个成员函数还是独立函数。如果上述操作作为一个成员函数,那么需要特地提供一个”string”类型。而上 述操作提供为独立函数的话,使用原始”array”数组类型就可以工作了。显然,上述操作都可以写成独立函数,没有那个函数需要特别的处理,只需要提供 array接口就可以了。
不过有一个语法上的特例,如果我把”str.toUpperCase()”提取为独立函数显得有点怪异。D语言则完全统一了函数调用语法。我预计C++也会跟随这一趋势,许多操作函数已经被当作独立函数提供而不是采用成员函数。似乎发展也倾向于独立函数。
如果独立函数可行,那么就没有必要提取一个string类。字符串操作可以写成作用于字符数组的独立函数。
字符串代表什么?
如果你的字符串不仅仅使用ASCI字符,通常可以考虑使用Unicode字符集,但是,也有可能在你的代码中,只使用了ascii码或者是 latin-1编码。不过大多数字符串都不会仅限于此。有些语言,比如PHP,允许你在全局范围设定编码方式。使用string做标记,通常都不会做太多 变化(译:我猜测作者的意思应该是大多数语言已经内置了string的编码方式,而且不允许调整)。
我们假定字符集使用ascii编码,以ascii编码的字符串使用一个模版类string。为了标识不同于其他字符集,我们把这个字符串类型标识为 “char ascii”。一旦我们做了这样的设定,我们就不希望再感受到string的类型了,它工作起来就和数组很相似了。
回到刚才谈到的变长编码:如果你使用UTF-16编码字符,并且需要使用字符代理。现在string变得含义模糊了,string应该被当做 unicode字符组合还是真实的utf16编码值?(沿用刚才的实现)使用一个类型别名标识比较合适,我们假定为“type utf16:binary 16bit”,并作为一个数组。现在歧义消除了,字符串是编码值的集合,而不是字符。
仅仅是一个typedef?
现在我觉得不需要定义一个特殊的string类型,如果需要string类型,可以仅仅使用一个数组的别名。但是string作为基础类型被大量使 用,这也导致了许多问题。一些情况下需要着重考虑字符编码,使用特定类型的数组就会比较安全。同时也需要一个富字符串处理函数库,但是不应该作为一个字符 串类型(string)提供。
你能举例有什么情况下需要专门的string类型,或者这样做会更有效吗?