87

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

4

1 回答 1

178

字符特征是流和字符串库的一个极其重要的组成部分,因为它们允许流/字符串类将存储哪些字符的逻辑与应该对这些字符执行哪些操作的逻辑分开。

首先,默认字符特征类 ,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类型导出的函数!因此,如果我们定义一个新的特征类,然后定义compare它以不区分大小写的方式比较字符,我们可以构建一个行为类似于 的字符串类std::string,但不区分大小写!

这是一个例子。我们继承 fromstd::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根据这个函数定义)。

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

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

瞧!我们现在有了一个不区分大小写处理所有内容的字符串!

当然,除此之外,使用特征还有其他原因。例如,如果你想定义一个使用固定大小的底层字符类型的字符串,那么你可以专注char_traits于该类型,然后从该类型创建字符串。例如,在 Windows API 中,有一种类型TCHAR是窄字符还是宽字符,具体取决于您在预处理期间设置的宏。然后,您可以TCHAR通过编写 s 来制作字符串

typedef basic_string<TCHAR> tstring;

现在你有一串TCHARs。

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

希望这可以帮助!

编辑:正如@phooji 所指出的,这种特征概念不仅被 STL 使用,也不是特定于 C++ 的。作为一个完全无耻的自我推销,不久前我写了一个三元搜索树(这里描述的一种基数树)的实现,它使用特征来存储任何类型的字符串,并使用客户希望它们存储的任何比较类型。如果您想查看在实践中使用它的示例,这可能是一本有趣的书。

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

于 2011-03-16T00:59:07.693 回答