STL字符特征的重点是什么?


83

我注意到在我的SGI STL参考副本中,有一个有关角色特征的页面,但是我看不到如何使用这些特征?它们是否替换了string.h函数?似乎没有使用它们std::string,例如length()on上的方法std::string未使用Character Traitslength()方法。为什么存在性格特征,并且在实践中曾经使用过它们?

Answers:


171

字符特征是流和字符串库中极其重要的组成部分,因为它们使流/字符串类能够将存储哪些字符的逻辑与应对该字符进行哪些操作的逻辑分开

首先,默认字符特征类,char_traits<T>在C ++标准中得到了广泛使用。例如,没有名为的类std::string。而是有一个如下所示的类模板std::basic_string

template <typename charT, typename traits = char_traits<charT> >
    class basic_string;

然后,std::string定义为

typedef basic_string<char> string;

同样,标准流定义为

template <typename charT, typename traits = char_traits<charT> >
    class basic_istream;

typedef basic_istream<char> istream;

那么为什么要按原样构造这些类呢?为什么要使用奇怪的特征类作为模板参数?

原因是在某些情况下,我们可能希望拥有一个类似的字符串std::string,但具有一些稍微不同的属性。一个典型的例子是,如果您要以忽略大小写的方式存储字符串。例如,我可能想创建一个名为CaseInsensitiveString这样的字符串

CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {  // Always true
    cout << "Strings are equal." << endl;
}

也就是说,我可以使用一个字符串,其中两个仅区别大小写的字符串相等。

现在,假设标准库作者在不使用特征的情况下设计了字符串。这意味着我将在标准库中拥有一个非常强大的字符串类,在我的情况下这是完全没有用的。我不能为该字符串类重用许多代码,因为比较总是会与我希望它们的工作方式相违背。但是通过使用特征,实际上可以重用驱动代码std::string来获得不区分大小写的字符串。

如果拉起C ++ ISO标准的副本,并查看字符串比较运算符的工作方式定义,您会发现它们都是根据compare函数定义的。该函数依次通过调用来定义

traits::compare(this->data(), str.data(), rlen)

str您要比较的字符串在哪里,并且rlen是两个字符串长度中的较小者。这实际上非常有趣,因为这意味着compare直接定义使用compare指定为模板参数的traits类型导出的函数!因此,如果我们定义一个新的traits类,然后进行定义,compare以便不区分大小写地比较字符,我们可以构建一个行为类似于std::string,但不区分大小写的字符串类!

这是一个例子。我们继承自std::char_traits<char>以获取所有我们未编写的函数的默认行为:

class CaseInsensitiveTraits: public std::char_traits<char> {
public:
    static bool lt (char one, char two) {
        return std::tolower(one) < std::tolower(two);
    }

    static bool eq (char one, char two) {
        return std::tolower(one) == std::tolower(two);
    }

    static int compare (const char* one, const char* two, size_t length) {
        for (size_t i = 0; i < length; ++i) {
            if (lt(one[i], two[i])) return -1;
            if (lt(two[i], one[i])) return +1;
        }
        return 0;
    }
};

(请注意,我还定义eqlt在这里,这比分别为平等和小于,字符,然后定义compare这个功能而言)。

现在我们有了这个traits类,我们可以CaseInsensitiveString简单地定义为

typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;

瞧!现在,我们有了一个字符串,该字符串不区分大小写地对待所有内容!

当然,除此之外,还有其他使用特征的原因。例如,如果要定义使用固定大小的某些基础字符类型的字符串,则可以专门char_traits处理该类型,然后使用该类型创建字符串。例如,在Windows API中,TCHAR取决于您在预处理期间设置的宏,其类型可以是窄字符还是宽字符。然后,您可以TCHAR通过写出s来制作字符串

typedef basic_string<TCHAR> tstring;

现在,您有一串TCHAR

在所有这些示例中,请注意,我们只是将某些traits类(或使用已存在的traits类)定义为某些模板类型的参数,以便获取该类型的字符串。这样做的全部目的是,basic_string作者只需要指定如何使用特征即可,并且我们可以神奇地使它们使用我们的特征,而不是使用默认特征来获得具有某些细微差别或怪癖的字符串,而不是默认字符串类型的一部分。

希望这可以帮助!

编辑:正如@phooji所指出的那样,此特性概念不仅由STL使用,也不特定于C ++。作为一种完全无耻的自我推广,前一段时间我写了一个三元搜索树这里描述的一种基数树)的实现,该树使用特征存储任何类型的字符串,并使用客户端希望它们存储的任何比较类型。如果您想查看一个实际使用示例,可​​能会很有趣。

编辑:针对您的std::string不使用的要求,traits::length事实证明它在几个地方使用。最值得注意的是,当您std::string使用char*C样式的字符串构造出一个字符串时,该字符串的新长度是通过调用该字符串而得出的traits::length。似乎它traits::length主要用于处理C样式的字符序列,这是C ++中字符串的“最小公分母”,而std::string用于处理任意内容的字符串。


15

14
似乎您已经对用户名做到了合理:)也许也很相关:许多boost库使用概念和类型特征类,因此它不仅仅是标准库。此外,其他语言也使用了类似的技术而不使用模板,请参见深奥的示例:ocaml.janestreet.com/?q=node/11
phooji

2
很好的结构(三元搜索树),但是我要指出的是,可以通过多种方式“尝试”“压缩”:1 /使用字符范围来指向孩子,而不是单个字符(明显),2 /路径压缩(Patricia树)和分支末尾的3 /桶(即,只要使用少于K的字符串,就使用排序后的字符串数组)。将它们组合在一起(我将1和3组合在一起)可以极大地减少内存消耗,而不会对速度性能造成多于一个常数的影响(并且实际上,存储桶会减少跳转次数)。
Matthieu M.

2
@ dan04:尝试获取任何标准的类/算法以使用您的函数。
Xeo 2012年

2
所以...简而言之,特征只是basic_string类使用的一种接口,用于操纵各种类型的字符,而不管它们到底是什么字符,对吗?
Virus721 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.