C ++中不区分大小写的字符串比较

时间:2020-03-05 18:39:44  来源:igfitidea点击:

在C ++中进行不区分大小写的字符串比较而不将字符串转换为全部大写或者全部小写的最佳方法是什么?

请指出这些方法是否支持Unicode,以及它们的可移植性。

解决方案

回答

假设我们正在寻找一种方法而不是已经存在的魔术函数,坦率地说,没有更好的方法。我们都可以为有限的字符集编写带有巧妙技巧的代码段,但是到了最后,我们必须转换字符。

进行此转换的最佳方法是在比较之前进行转换。在编码方案方面,这给我们提供了很大的灵活性,实际比较运算符应该忽略这些灵活性。

我们当然可以将这种转换"隐藏"在自己的字符串函数或者类的后面,但是在比较之前,我们仍然需要转换字符串。

回答

支持Unicode的Visual C ++字符串函数:http://msdn.microsoft.com/zh-cn/library/cc194799.aspx

我们可能正在寻找的是" _wcsnicmp"

回答

仅供参考,strcmp()stricmp()容易受到缓冲区溢出的影响,因为它们一直处理直到遇到空终止符为止。使用_strncmp()_strnicmp()更安全。

回答

我使用Unicode库国际组件的功能非常强大,它们具有非常强大的功能,并且提供了转换,语言环境支持,日期和时间渲染,大小写映射(我们似乎不需要)以及排序规则的方法,包括大小写和不区分重音的比较(以及更多)。我只使用了C ++版本的库,但它们似乎也具有Java版本。

存在执行@Coincoin所指的规范化比较的方法,甚至可以解释语言环境(例如,这是一个排序示例,并非严格相等),传统上在西班牙语(在西班牙)中,字母组合" ll"在l"和" m",因此" lz" <" ll" <" ma"。

回答

我们是在说不区分大小写的比较还是完全规范化的Unicode比较?

愚蠢的比较不会找到可能相同但不等于二进制的字符串。

例子:

U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).

都是等效的,但它们也具有不同的二进制表示形式。

就是说,Unicode规范化应该是必读的内容,尤其是如果我们计划支持Hangul,Tha?和其他亚洲语言。

而且,IBM几乎为最优化的Unicode算法申请了专利,并使其公开可用。他们还维护一个实现:IBM ICU

回答

我正在尝试从所有帖子中收集一个好的答案,所以请帮我编辑一下:

这是一种执行此操作的方法,尽管它确实可以转换字符串,并且不支持Unicode,但它应该是可移植的,这是一个加号:

bool caseInsensitiveStringCompare( const std::string& str1, const std::string& str2 ) {
    std::string str1Cpy( str1 );
    std::string str2Cpy( str2 );
    std::transform( str1Cpy.begin(), str1Cpy.end(), str1Cpy.begin(), ::tolower );
    std::transform( str2Cpy.begin(), str2Cpy.end(), str2Cpy.begin(), ::tolower );
    return ( str1Cpy == str2Cpy );
}

根据我的阅读,这比stricmp()更可移植,因为stricmp()实际上不是std库的一部分,而是仅由大多数编译器供应商实现。

为了获得真正的Unicode友好实现,我们似乎必须在std库之外。一个很好的第三方库是IBM ICU(Unicode国际组件)

boost :: iequals还提供了一个相当不错的工具来进行这种比较。

回答

如果我们使用的是POSIX系统,则可以使用strcasecmp。但是,此功能不是标准C的一部分,在Windows上也不可用。只要语言环境为POSIX,这将对8位字符执行不区分大小写的比较。如果语言环境不是POSIX,则结果是不确定的(因此它可能会进行本地化比较,也可能不会)。不能使用宽字符等效项。

失败的是,许多历史悠久的C库实现都具有函数stricmp()和strnicmp()。 Windows上的Visual C ++将所有这些都重命名为下划线,因为它们不是ANSI标准的一部分,因此在该系统上,它们称为_stricmp或者_strnicmp。某些库可能还具有宽字符或者多字节等效功能(通常命名为例如wcsicmp,mbcsicmp等)。

C和C ++都对国际化问题一无所知,因此,除了使用第三方库以外,没有很好的解决方案。如果需要用于C / C ++的强大库,请签出IBM ICU(Unicode的国际组件)。 ICU适用于Windows和Unix系统。

回答

对于非unicode版本,我的第一个想法是执行以下操作:

bool caseInsensitiveStringCompare(const string& str1, const string& str2) {
    if (str1.size() != str2.size()) {
        return false;
    }
    for (string::const_iterator c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) {
        if (tolower(*c1) != tolower(*c2)) {
            return false;
        }
    }
    return true;
}

回答

我写了一个不区分大小写的char_traits版本,用于std :: basic_string,以便生成一个std :: string,该字符串在使用内置std :: basic_string成员函数进行比较,搜索等时不区分大小写。

换句话说,我想做这样的事情。

std::string a = "Hello, World!";
std::string b = "hello, world!";

assert( a == b );

...哪个std :: string无法处理。这是我新的char_traits的用法:

std::istring a = "Hello, World!";
std::istring b = "hello, world!";

assert( a == b );

...这是实现:

/*  ---

        Case-Insensitive char_traits for std::string's

        Use:

            To declare a std::string which preserves case but ignores case in comparisons & search,
            use the following syntax:

                std::basic_string<char, char_traits_nocase<char> > noCaseString;

            A typedef is declared below which simplifies this use for chars:

                typedef std::basic_string<char, char_traits_nocase<char> > istring;

    --- */

    template<class C>
    struct char_traits_nocase : public std::char_traits<C>
    {
        static bool eq( const C& c1, const C& c2 )
        { 
            return ::toupper(c1) == ::toupper(c2); 
        }

        static bool lt( const C& c1, const C& c2 )
        { 
            return ::toupper(c1) < ::toupper(c2);
        }

        static int compare( const C* s1, const C* s2, size_t N )
        {
            return _strnicmp(s1, s2, N);
        }

        static const char* find( const C* s, size_t N, const C& a )
        {
            for( size_t i=0 ; i<N ; ++i )
            {
                if( ::toupper(s[i]) == ::toupper(a) ) 
                    return s+i ;
            }
            return 0 ;
        }

        static bool eq_int_type( const int_type& c1, const int_type& c2 )
        { 
            return ::toupper(c1) == ::toupper(c2) ; 
        }       
    };

    template<>
    struct char_traits_nocase<wchar_t> : public std::char_traits<wchar_t>
    {
        static bool eq( const wchar_t& c1, const wchar_t& c2 )
        { 
            return ::towupper(c1) == ::towupper(c2); 
        }

        static bool lt( const wchar_t& c1, const wchar_t& c2 )
        { 
            return ::towupper(c1) < ::towupper(c2);
        }

        static int compare( const wchar_t* s1, const wchar_t* s2, size_t N )
        {
            return _wcsnicmp(s1, s2, N);
        }

        static const wchar_t* find( const wchar_t* s, size_t N, const wchar_t& a )
        {
            for( size_t i=0 ; i<N ; ++i )
            {
                if( ::towupper(s[i]) == ::towupper(a) ) 
                    return s+i ;
            }
            return 0 ;
        }

        static bool eq_int_type( const int_type& c1, const int_type& c2 )
        { 
            return ::towupper(c1) == ::towupper(c2) ; 
        }       
    };

    typedef std::basic_string<char, char_traits_nocase<char> > istring;
    typedef std::basic_string<wchar_t, char_traits_nocase<wchar_t> > iwstring;

回答

Boost包括一个方便的算法:

#include <boost/algorithm/string.hpp>
// Or, for fewer header dependencies:
//#include <boost/algorithm/string/predicate.hpp>

std::string str1 = "hello, world!";
std::string str2 = "HELLO, WORLD!";

if (boost::iequals(str1, str2))
{
    // Strings are identical
}

回答

如果我们最终选择了任何一种方法,请注意一下,如果该方法恰好包含对strcmp的使用,一些答案表明:

通常,strcmp不适用于Unicode数据。通常,它甚至不能与基于字节的Unicode编码一起使用,例如utf-8,因为strcmp仅进行逐字节比较,而以utf-8编码的Unicode代码点可以占用1个以上的字节。唯一正确的Unicode情况" strcmp"正确处理是,当使用基于字节的编码编码的字符串仅包含U + 00FF以下的代码点时,逐字节比较就足够了。

回答

我们可以在Unix上使用" strcasecmp",在Windows上使用" stricmp"。

到目前为止,还没有提到的一件事是,如果我们在这些方法中使用stl字符串,则首先比较两个字符串的长度很有用,因为该信息已经在字符串类中提供了。如果我们要比较的两个字符串开头的长度甚至不相同,则可以避免进行昂贵的字符串比较。

回答

Boost.String库具有许多用于进行区分大小写的比较的算法,等等。

我们可以实现自己的实现,但是为什么要在完成后再打扰呢?

回答

利用标准的char_traits。回想一下," std :: string"实际上是" std :: basic_string <char>"的类型定义,或者更明确地说,是" std :: basic_string <char,std :: char_traits <char>>"。 char_traits类型描述字符如何比较,如何复制,如何转换等。我们需要做的就是在baseic_string上typedef一个新的字符串,并为其提供自定义的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) {
        while( n-- != 0 ) {
            if( toupper(*s1) < toupper(*s2) ) return -1;
            if( toupper(*s1) > toupper(*s2) ) return 1;
            ++s1; ++s2;
        }
        return 0;
    }
    static const char* find(const char* s, int n, char a) {
        while( n-- > 0 && toupper(*s) != toupper(a) ) {
            ++s;
        }
        return s;
    }
};

typedef std::basic_string<char, ci_char_traits> ci_string;

有关详细信息,请参见《本周大师》第29期。

回答

Boost的问题在于我们必须链接并依赖Boost。在某些情况下(例如android)并不容易。

使用char_traits意味着所有比较都不区分大小写,通常这不是我们想要的。

这样就足够了。它应该是合理有效的。虽然不处理unicode或者其他任何东西。

bool iequals(const string& a, const string& b)
{
    unsigned int sz = a.size();
    if (b.size() != sz)
        return false;
    for (unsigned int i = 0; i < sz; ++i)
        if (tolower(a[i]) != tolower(b[i]))
            return false;
    return true;
}

更新:奖励C ++ 14版本(#include &lt;algorithm>):

bool iequals(const string& a, const string& b)
{
    return std::equal(a.begin(), a.end(),
                      b.begin(), b.end(),
                      [](char a, char b) {
                          return tolower(a) == tolower(b);
                      });
}