Guru of the Week 条款29:不区分大小写的string

类别:VC语言 点击:0 评论:0 推荐:
GotW#29 不区分大小写的string (Case-Insensitive Strings)

难度:7/10

你期望一个不分大小写的字符串类型吗?你的使命是,应该选个现成的并接受它,还是自己写一个。

问题

写一个不分大小写的字符串类型,它其它方面都与标准库中的“string”类相同,只是在大小写区分上和(非标的,但被广泛使用的)C函数stricmp():

    ci_string s( "AbCdE" );

    // case insensitive

    assert( s == "abcde" );

    assert( s == "ABCDE" );

    // still case-preserving, of course

    assert( strcmp( s.c_str(), "AbCdE" ) == 0 );

    assert( strcmp( s.c_str(), "abcde" ) != 0 );

解决方案

写一个不分大小写的字符串类型,它其它方面都与标准库中的“string”类相同,只是在大小写区分上和(非标的,但被广泛使用的)C函数stricmp():

“怎么实现一个不分大小写的字符串类型”这个问题是如此常见,以致于它需要一份专有的FAQ--所以才在GotW中讨论它。

注意1:stricmp()这个不区分大小写的字符串比较函数不是C标准的一部分,但它为很多C编译器扩展提供。

注意2:“不区分大小写”的实际含义完全取决于你的程序和国家语言。例如,很多语言根本就没有大小写;但即使如此,你仍然需要决策重读和非重读字符是否等价,诸如此类。

下面是我们期望达到的目标:本GotW指导了如何为标准string类实现“不区分大小写”,无论你处在什么语境下。

    ci_string s( "AbCdE" );

    // case insensitive

    assert( s == "abcde" );

    assert( s == "ABCDE" );

    // still case-preserving, of course

    assert( strcmp( s.c_str(), "AbCdE" ) == 0 );

    assert( strcmp( s.c_str(), "abcde" ) != 0 );

关键点是领会“string类”在标准C++中到底是什么。如果你看一下string的头文件,你将看到如下的东西:

  typedef basic_string<char> string;

所以,string并不是一个真正的类,它是一个模板的(特化的)typedef。再向下,basic_string<>模板申明如下,这是其全貌:

  template<class charT,

           class traits = char_traits<charT>,

           class Allocator = allocator<charT> >

      class basic_string;

所以,“string”实际上是“basic_string<char, char_traits<char>, allocator<char> >”。我们不必操心分配器(allocator)部分,关键点是char_traits部分,它决定了字符的相互作用和比较运算(!运算)。

basic_string提供了常用的比较函数以比较两个string对象是否相等,或一个小于另一个,等等。这些string类的比较函数是建立在char_traits模板提供的字符比较函数基础上的。具体一点,char_traits模板提供了如下的字符比较函数:eq()(相等)、ne()(不等)、lt()(小于)、compare()(比较字符序列)、find()(搜索字符序列)。

如果你希望在(string的)这些操作上有不同的行为,我们所要做的只是提供一个不同的char_traits模板。这是最容易的方法:

  struct ci_char_traits : public char_traits<char>

                // 继承为了得到我们不必过载的函数

  {

    static bool eq( char c1, char c2 )

      { return toupper(c1) == toupper(c2); }

    static bool ne( char c1, char c2 )

      { return toupper(c1) != toupper(c2); }

    static bool lt( char c1, char c2 )

      { return toupper(c1) <  toupper(c2); }

    static int compare( const char* s1,

                        const char* s2,

                        size_t n ) {

      return memicmp( s1, s2, n );

             // 如果你的编译器提供了它,

             //  不然你就得自己实现一个。

    }

    static const char*

    find( const char* s, int n, char a ) {

      while( n-- > 0 && toupper(*s) != toupper(a) ) {

          ++s;

      }

      return s;

    }

  };

最后将它们合在一起:

  typedef basic_string<char, ci_char_traits> ci_string;

我们重定义了一个“ci_string”,它的操作非常象标准的“string”,只是它用ci_char_traits代替了char_traits<char>以使用特别的比较规则。我们只不过将ci_char_traits的规则实现为“不区分大小写”,就使得ci_string大动手脚就表现为“不区分大小写”了--也就是说,我们根本没有碰basic_string就有了一个“不区分大小写”的string!

这次的GotW揭示了basic_string模板的工作原理以及实现使用上的灵活性。如果你期望不用上面的memicmp()和toupper()实现的这些比较函数,只需要用你自己的代码替换这5个函数,怎么满足你自己的程序的需求,就怎么实现它们。

 

习题

1.这样从char_traits<char>继承出ci_char_traits安全吗?为什么安全或为什么不安全?

2. 为什么下面的代码编译不通过?

(WQ注,由于C++的改进,此代码已经可以编译通过,并正常运行!)

    ci_string s = "abc";

    cout << s << endl;

提示1:参见GotW #19。

提示2:摘自21.3.7.9 [lib.string.io],basic_string的operator<<操作申明如下(是个偏特化):

    template<class charT, class traits, class Allocator>

    basic_ostream<charT, traits>&

    operator<<(basic_ostream<charT, traits>& os,

               const basic_string<charT,traits,Allocator>& str);

(WQ注,C++标准库中,现在已将“basic_ostream<charT, traits>& os”改为“ostream&”,所以没问题了。)

ANSWER: Notice first that cout is actually a basic_ostream<char, char_traits<char> >. Then we see the problem: operator<< for basic_string is templated and all, but it's only specified for insertion into a basic_ostream with the same 'char type' and 'traits type' as the string. That is, the standard operator<< will let you output a ci_string to a basic_ostream<char, ci_char_traits>, which isn't what cout is even though ci_char_traits inherits from char_traits<char> in the above solution.

(由于已错误,不译。)

有两个解决办法:定义ci_strings自己的流入/流出函数,或使用“.c_str()”:

        cout << s.c_str() << endl;

3. 当在标准string对象和ci_string对象间使用其它操作(如+、+=、=)时,发生什么?例如:

        string    a = "aaa";

        ci_string b = "bbb";

        string    c = a + b;

答案:还是,定义ci_string自己的这些操作,或使用“.c_str()”:

        string    c = a + b.c_str();

本文地址:http://com.8s8s.com/it/it2351.htm