什么是STL字符特性的要点? [英] What is the point of STL Character Traits?

查看:136
本文介绍了什么是STL字符特性的要点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

解决方案

字符特征是流和字符串中非常重要的组成部分因为它们允许流/字符串类将正在存储的是什么字符的逻辑与对这些字符执行什么操作的逻辑分开。



首先,在C ++标准中广泛使用默认字符traits类 char_traits< T> 。例如,没有类 std :: string 。相反,有一个类模板 std :: basic_string ,如下所示:

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

然后,定义 std :: string as

  typedef basic_string< char>串; 

同样,标准流定义为

 模板< typename charT,typename traits = char_traits< charT> > 
class basic_istream;

typedef basic_istream< char> istream;

那么为什么这些类会按原样结构化?为什么我们应该使用一个奇怪的traits类作为模板参数?



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

  CaseInsensitiveString c1 =HI!,c2 =hi!; 
if(c1 == c2){//总是真的
cout<< 字符串是平等的。 << endl;
}

也就是说,我可以有一个字符串,敏感性被比较。



现在,假设标准库作者设计的字符串没有使用traits。这意味着我将在标准库中有一个非常强大的字符串类,在我的情况下是完全无用的。我不能重用这个字符串类的大部分代码,因为比较将总是工作反对我想要他们的工作。但是通过使用traits,实际上可以重用驱动 std :: string 的代码来获得不区分大小写的字符串。



如果你拉一个C ++ ISO标准的副本,并看看字符串的比较运算符的工作原理的定义,你会看到它们都定义在 compare 函数。这个函数又通过调用

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

其中 str 你正在比较, rlen 是两个字符串长度中较小的一个。这实际上很有趣,因为它意味着 compare 的定义直接使用由traits类型导出的 compare 指定为模板参数!因此,如果我们定义一个新的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 if(lt(one [i],two [i]))return -1;
if(lt [two] [i],one [i]))return +1;
}
返回0;
}
};

(注意我也定义了 eq lt 这里,分别比较字符的相等和小于,然后定义比较函数)。



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

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

我们现在有一个字符串,对待一切情况不敏感!



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

  typedef basic_string< code>来创建 TCHAR  ; TCHAR> tstring; 

现在你有一个字符串 TCHAR 在所有这些例子中,注意我们刚刚定义了一些traits类(或者使用已经存在的)作为一些模板类型的参数,以便获得该类型的字符串。这一点的要点是, basic_string 作者只需要指定如何使用traits,我们神奇地可以使他们使用我们的traits而不是默认情况下获得字符串一些细微差别或怪异不是默认字符串类型的一部分。



希望这有助于!



:正如@phooji指出的,traits的这个概念不仅仅是由STL使用,也不是特定于C ++。作为一个完全无耻的自我推广,一段时间后,我写了一个三元搜索树的实现(此处描述的基数树类型 ),它使用traits来存储任何类型的字符串,并使用任何比较类型客户希望他们存储。



EDIT :为了回应您的声明,您可能会发现一个有趣的例子。 std :: string 不使用 traits :: length ,结果证明它在几个地方。最值得注意的是,当从 char * C样式字符串中构造 std :: string 的字符串是通过调用 traits :: length 得到的。看起来 traits :: length 主要用于处理C样式的字符序列,这是C ++中字符串的最小公分母,而 std :: string 用于处理任意内容的字符串。


I notice that in my copy of the SGI STL reference, there is a page about Character Traits but I can't see how these are used? Do they replace the string.h functions? They don't seem to be used by std::string, e.g. the length() method on std::string doesn't make use of the Character Traits length() method. Why do Character Traits exist and are they ever used in practice?

解决方案

Character traits are an extremely important component of the streams and strings libraries because they allow the stream/string classes to separate out the logic of what characters are being stored from the logic of what manipulations should be performed on those characters.

To begin with, the default character traits class, char_traits<T>, is used extensively in the C++ standard. For example, there is no class called std::string. Rather, there's a class template std::basic_string that looks like this:

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

Then, std::string is defined as

typedef basic_string<char> string;

Similarly, the standard streams are defined as

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

typedef basic_istream<char> istream;

So why are these classes structured as they are? Why should we be using a weird traits class as a template argument?

The reason is that in some cases we might want to have a string just like std::string, but with some slightly different properties. One classic example of this is if you want to store strings in a way that ignores case. For example, I might want to make a string called CaseInsensitiveString such that I can have

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

That is, I can have a string where two strings differing only in their case sensitivity are compared equal.

Now, suppose that the standard library authors designed strings without using traits. This would mean that I'd have in the standard library an immensely powerful string class that was entirely useless in my situation. I couldn't reuse much of the code for this string class, since comparisons would always work against how I wanted them to work. But by using traits, it's actually possible to reuse the code that drives std::string to get a case-insensitive string.

If you pull up a copy of the C++ ISO standard and look at the definition of how the string's comparison operators work, you'll see that they're all defined in terms of the compare function. This function is in turn defined by calling

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

where str is the string you're comparing to and rlen is the smaller of the two string lengths. This is actually quite interesting, because it means that the definition of compare directly uses the compare function exported by the traits type specified as a template parameter! Consequently, if we define a new traits class, then define compare so that it compares characters case-insensitively, we can build a string class that behaves just like std::string, but treats things case-insensitively!

Here's an example. We inherit from std::char_traits<char> to get the default behavior for all the functions we don't write:

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;
    }
};

(Notice I've also defined eq and lt here, which compare characters for equality and less-than, respectively, and then defined compare in terms of this function).

Now that we have this traits class, we can define CaseInsensitiveString trivially as

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

And voila! We now have a string that treats everything case-insensitively!

Of course, there are other reasons besides this for using traits. For example, if you want to define a string that uses some underlying character type of a fixed-size, then you can specialize char_traits on that type and then make strings from that type. In the Windows API, for example, there's a type TCHAR that is either a narrow or wide character depending on what macros you set during preprocessing. You can then make strings out of TCHARs by writing

typedef basic_string<TCHAR> tstring;

And now you have a string of TCHARs.

In all of these examples, notice that we just defined some traits class (or used one that already existed) as a parameter to some template type in order to get a string for that type. The whole point of this is that the basic_string author just needs to specify how to use the traits and we magically can make them use our traits rather than the default to get strings that have some nuance or quirk not part of the default string type.

Hope this helps!

EDIT: As @phooji pointed out, this notion of traits is not just used by the STL, nor is it specific to C++. As a completely shameless self-promotion, a while back I wrote an implementation of a ternary search tree (a type of radix tree described here) that uses traits to store strings of any type and using whatever comparison type the client wants them to store. It might be an interesting read if you want to see an example of where this is used in practice.

EDIT: In response to your claim that std::string doesn't use traits::length, it turns out that it does in a few places. Most notably, when you construct a std::string out of a char* C-style string, the new length of the string is derived by calling traits::length on that string. It seems that traits::length is used mostly to deal with C-style sequences of characters, which are the "least common denominator" of strings in C++, while std::string is used to work with strings of arbitrary contents.

这篇关于什么是STL字符特性的要点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆