我注意到在我的SGI STL参考副本中,有一个有关角色特征的页面,但是我看不到如何使用这些特征?它们是否替换了string.h函数?似乎没有使用它们std::string
,例如length()
on上的方法std::string
未使用Character Traitslength()
方法。为什么存在性格特征,并且在实践中曾经使用过它们?
Answers:
字符特征是流和字符串库中极其重要的组成部分,因为它们使流/字符串类能够将存储哪些字符的逻辑与应对该字符进行哪些操作的逻辑分开。
首先,默认字符特征类,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;
}
};
(请注意,我还定义eq
和lt
在这里,这比分别为平等和小于,字符,然后定义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
用于处理任意内容的字符串。