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

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

Case-insensitive string comparison in C++

在C++中,不把字符串转换成大写或小写的最好方法是什么?

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


Boost包含了一个简便的算法:

1
2
3
4
5
6
7
8
9
10
11
#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
}

利用标准char_traits。回想一下,std::string实际上是std::basic_string的typedef,或者更明确地说,是std::basic_string >的typedef。char_traits类型描述了字符的比较方式、复制方式、转换方式等。您只需要在basic_string上为新字符串进行typedef,并为其提供您自己的自定义char_traits来不敏感地比较大小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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联系并依赖于Boost。在某些情况下不容易(如Android)。

使用char_特性意味着所有的比较都不区分大小写,这通常不是你想要的。

这就足够了。它应该是相当有效的。但不处理Unicode或任何内容。

1
2
3
4
5
6
7
8
9
10
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版本(EDOCX1×13):

1
2
3
4
5
6
7
8
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);
                      });
}

你说的是一个不区分大小写的比较还是一个完全规范化的Unicode比较?

哑比较将找不到可能相同但二进制不相等的字符串。

例子:

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

都是等价的,但它们也有不同的二进制表示。

也就是说,Unicode规范化应该是强制性的,特别是如果您计划支持朝鲜文,tha?以及其他亚洲语言。

此外,IBM几乎获得了大多数优化后的Unicode算法的专利,并将它们公开提供。它们还维护一个实现:IBMICU


如果您使用的是POSIX系统,那么可以使用strcasecmp。不过,这个函数不是标准C的一部分,在Windows上也不可用。这将对8位字符执行不区分大小写的比较,只要区域设置是POSIX。如果区域设置不是POSIX,则结果是未定义的(因此它可能会进行本地化比较,也可能不会)。宽字符等价物不可用。

否则,大量的历史C库实现都具有stricmp()和strnicmp()函数。Windows上的VisualC++将所有这些都改写为下划线,因为它们不是ANSI标准的一部分,所以在该系统中,它们被称为StIMPRMP或Y-StRICMP。一些库还可以具有宽字符或多字节等效函数(通常命名为wcsicmp、mbcsicmp等)。

C和C++在很大程度上都不了解国际化问题,所以除了使用第三方库之外,没有解决这个问题的好办法。如果需要一个强大的C/C++库,请查看IBM ICU(Unicode的国际组件)。ICU同时适用于Windows和Unix系统。


boost::iequals在字符串的情况下与utf-8不兼容。您可以使用boost::locale。

1
2
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ?"str1 < str2" :"str1 >= str2") << endl;
  • 主要--忽略重音符号和字符大小写,只比较基本字母。例如"facade"和"fa"?Ade"是一样的。
  • 次要--忽略字符大小写,但考虑重音。""Facade"和"Fa"?ADE"是不同的,但"FA?"阿德"和"FA"?Ade"是一样的。
  • 三级——考虑大小写和重音:"fa?"阿德"和"FA"?"Ade"是不同的。忽略标点符号。
  • 第四纪——考虑所有的大小写、重音和标点符号。单词在Unicode表示方面必须相同。
  • 与第四纪一样,但也要比较代码点。

我对非Unicode版本的第一个想法是这样做:

1
2
3
4
5
6
7
8
9
10
11
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;
}

您可以在Unix上使用strcasecmp,或在Windows上使用stricmp

到目前为止还没有提到的一件事是,如果您在这些方法中使用STL字符串,那么首先比较两个字符串的长度是很有用的,因为这个信息在string类中已经对您可用。如果您要比较的两个字符串的长度一开始都不相同,这可能会阻止进行代价高昂的字符串比较。


支持Unicode的Visual C++字符串函数:HTTP://MSDN.MySo.CON/E-US/Labalay/C1941949ASPX

你可能要找的是_wcsnicmp


我正试图从所有帖子中拼凑出一个好答案,请帮助我编辑:

这是一种实现这一点的方法,尽管它可以转换字符串,而且不支持Unicode,但它应该是可移植的,这是一个优点:

1
2
3
4
5
6
7
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库之外进行。一个好的第三方库是IBMICU(Unicode的国际组件)

此外,iequals为进行这种比较提供了一个相当好的实用程序。


字符串库中有许多算法用于进行案例插入比较等。

你可以实现你自己的,但是为什么在已经完成的时候还要麻烦呢?


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


1
std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})

如果你没有使用Boost的位置,你可以在C++ 14中使用上面的代码。宽字符必须使用std::towlower


对于基本的不区分大小写的字符串比较需求,我不希望使用外部库,也不希望使用具有与所有其他字符串不兼容的不区分大小写特性的单独字符串类。

我想到的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool icasecmp(const string& l, const string& r)
{
    return l.size() == r.size()
        && equal(l.cbegin(), l.cend(), r.cbegin(),
            [](string::value_type l1, string::value_type r1)
                { return toupper(l1) == toupper(r1); });
}

bool icasecmp(const wstring& l, const wstring& r)
{
    return l.size() == r.size()
        && equal(l.cbegin(), l.cend(), r.cbegin(),
            [](wstring::value_type l1, wstring::value_type r1)
                { return towupper(l1) == towupper(r1); });
}

一个简单的函数,其中一个重载char,另一个重载what,不使用任何非标准的函数,所以在任何平台上都可以。

相等比较不会考虑诸如可变长度编码和Unicode规范化之类的问题,但是基本字符串不支持我所知道的问题,而且它通常不是一个问题。

如果需要对文本进行更复杂的词典编纂操作,那么您只需使用第三方库(如Boost),这是可以预料的。


std::lexicographical_compare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// lexicographical_compare example
#include <iostream>  // std::cout, std::boolalpha
#include   // std::lexicographical_compare
#include <cctype>  // std::tolower

// a case-insensitive comparison function:
bool mycomp (char c1, char c2) {
    return std::tolower(c1)<std::tolower(c2);
}

int main () {
    char foo[] ="Apple";
    char bar[] ="apartment";

    std::cout << std::boolalpha;

    std::cout <<"Comparing foo and bar lexicographically (foo < bar):
"
;

    std::cout <<"Using default comparison (operator<):";
    std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9);
    std::cout << '
'
;

    std::cout <<"Using mycomp as comparison object:";
    std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9, mycomp);
    std::cout << '
'
;

    return 0;
}

演示


又短又好。除了扩展的std c lib之外,没有其他依赖项。

1
strcasecmp(str1.c_str(), str2.c_str()) == 0

如果str1str2相等,则返回true。strcasecmp可能不存在,可能有stricmpstrcmpi等类似物。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <string>
#include <string.h> //For strcasecmp(). Also could be found in <mem.h>

using namespace std;

/// Simple wrapper
inline bool str_ignoreCase_cmp(std::string const& s1, std::string const& s2) {
    if(s1.length() != s2.length())
        return false;  // optimization since std::string holds length in variable.
    return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}

/// Function object - comparator
struct StringCaseInsensetiveCompare {
    bool operator()(std::string const& s1, std::string const& s2) {
        if(s1.length() != s2.length())
            return false;  // optimization since std::string holds length in variable.
        return strcasecmp(s1.c_str(), s2.c_str()) == 0;
    }
    bool operator()(const char *s1, const char * s2){
        return strcasecmp(s1,s2)==0;
    }
};


/// Convert bool to string
inline char const* bool2str(bool b){ return b?"true":"false"; }

int main()
{
    cout<< bool2str(strcasecmp("asd","AsD")==0) <<endl;
    cout<< bool2str(strcasecmp(string{"aasd"}.c_str(),string{"AasD"}.c_str())==0) <<endl;
    StringCaseInsensetiveCompare cmp;
    cout<< bool2str(cmp("A","a")) <<endl;
    cout<< bool2str(cmp(string{"Aaaa"],string{"aaaA"})) <<endl;
    cout<< bool2str(str_ignoreCase_cmp(string{"Aaaa"],string{"aaaA"})) <<endl;
    return 0;
}

输出:

1
2
3
4
5
true
true
true
true
true

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

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

1
2
3
4
std::string a ="Hello, World!";
std::string b ="hello, world!";

assert( a == b );

…哪个std::string无法处理。以下是我新特性的用法:

1
2
3
4
std::istring a ="Hello, World!";
std::istring b ="hello, world!";

assert( a == b );

…实现方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*  ---

        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的情况下,可以通过使用c_str()strcasecmp获取C字符串指针来完成此操作:

1
2
3
4
5
6
std::string str1 ="aBcD";
std::string str2 ="AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
    //case insensitive equal
}


我在使用Unicode库的国际组件方面有很好的经验-它们非常强大,并且提供了转换、区域设置支持、日期和时间呈现、大小写映射(您似乎不需要)和排序的方法,其中包括不区分大小写和重音的比较(等等)。我只使用了C++版本的库,但它们似乎也有Java版本。

方法可以执行@coincoin所指的标准化比较,甚至可以解释区域设置-例如(这是一个排序示例,不是严格相等),传统上在西班牙语中(在西班牙),字母组合"l l"在"l"和"m"之间排序,因此"lz"<"l l"<"m a"。


只需将strcmp()用于区分大小写,将strcmpi()stricmp()用于区分大小写的比较。都在头文件

格式:

1
2
int strcmp(const char*,const char*);    //for case sensitive
int strcmpi(const char*,const char*);   //for case insensitive

用途:

1
2
3
4
5
6
string a="apple",b="ApPlE",c="ball";
if(strcmpi(a.c_str(),b.c_str())==0)      //(if it is a match it will return 0)
    cout<<a<<" and"<<b<<" are the same"<<"
"
;
if(strcmpi(a.c_str(),b.c_str()<0)
    cout<<a[0]<<" comes before ball"<<b[0]<<", so"<<a<<" comes before"<<b;

产量

苹果和苹果是一样的

A先于B,所以苹果先于球。


在党的后期,但有一种变体使用了std::locale,从而正确处理土耳其语:

1
2
3
4
5
auto tolower = std::bind1st(
    std::mem_fun(
        &std::ctype<char>::tolower),
    &std::use_facet<std::ctype<char> >(
        std::locale()));

提供一个使用活动区域设置将字符转换为小写的函数,然后可以通过std::transform生成小写字符串:

1
2
std::string left ="fOo";
transform(left.begin(), left.end(), left.begin(), tolower);

这也适用于基于wchar_t的字符串。


截至2013年初,由IBM维护的ICU项目是一个很好的答案。

网址:http://site.icu-project.org/

ICU是一个"完整的、可移植的Unicode库,它密切跟踪行业标准。"对于字符串比较的特定问题,排序规则对象会做您想要做的。

2012年年中,Mozilla项目在Firefox中采用了ICU进行国际化;您可以跟踪工程讨论,包括构建系统和数据文件大小问题,如下所示:

  • https://groups.google.com/forum/!主题/mozilla.dev.platform/svps2skodw
  • https://bugzilla.mozilla.org/show_bug.cgi?ID=724529(跟踪器)
  • https://bugzilla.mozilla.org/show_bug.cgi?ID=724531(构建系统)

看起来上面的解决方案并没有使用比较方法,而是再次实现total,所以这里是我的解决方案,希望它能为您工作(它工作得很好)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
string tolow(string a)
{
    for(unsigned int i=0;i<a.length();i++)
    {
        a[i]=tolower(a[i]);
    }
    return a;
}
int main()
{
    string str1,str2;
    cin>>str1>>str2;
    int temp=tolow(str1).compare(tolow(str2));
    if(temp>0)
        cout<<1;
    else if(temp==0)
        cout<<0;
    else
        cout<<-1;
}


只需注意您最终选择的任何方法,如果该方法恰好包括一些答案建议的strcmp的使用:

一般来说,strcmp不适用于Unicode数据。一般来说,它甚至不适用于基于字节的Unicode编码,如utf-8,因为strcmp只进行字节/字节比较,而用utf-8编码的Unicode码位可以超过1个字节。唯一正确处理的特定unicode大小写strcmp是当使用基于字节的编码编码编码的字符串只包含U+00FF以下的代码点时,那么逐字节比较就足够了。


如果您不想使用Boost库,那么这里只使用C++标准IO报头的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

struct iequal
{
    bool operator()(int c1, int c2) const
    {
        // case insensitive comparison of two characters.
        return std::toupper(c1) == std::toupper(c2);
    }
};

bool iequals(const std::string& str1, const std::string& str2)
{
    // use std::equal() to compare range of characters using the functor above.
    return std::equal(str1.begin(), str1.end(), str2.begin(), iequal());
}

int main(void)
{
    std::string str_1 ="HELLO";
    std::string str_2 ="hello";

    if(iequals(str_1,str_2))
    {
        std::cout<<"String are equal"<<std::endl;  
    }

    else
    {
        std::cout<<"String are not equal"<<std::endl;
    }


    return 0;
}

如果您有一个字符串向量,例如:

1
2
3
4
5
6
7
std::sort(std::begin(myvector), std::end(myvector), [](std::string const &a, std::string const &b)
{
    return std::lexicographical_compare(std::begin(a), std::end(a), std::begin(b), std::end(b), [](std::string::value_type a, std::string::value_type b)
    {
        return std::tolower(a) < std::tolower(b); //case-insensitive
    });
});

http://ideone.com/n6sq6x


如果您需要更频繁地将源字符串与其他字符串进行比较,一个很好的解决方案是使用regex。

1
2
3
4
5
std::wstring first = L"Test";
std::wstring second = L"TEST";

std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);

比较C++中的两个字符串(测试Windows)的简单方法是使用

1
2
// Case insensitive (could use equivalent _stricmp)  
result = _stricmp( string1, string2 );

如果要与std::string一起使用,请举例:

1
2
3
std::string s1 = string("Hello");
if ( _stricmp(s1.c_str(),"HELLO") == 0)
   std::cout <<"The string are equals.";

有关详细信息,请访问:https://msdn.microsoft.com/it-it/library/e0z9k731.aspx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool insensitive_c_compare(char A, char B){
  static char mid_c = ('Z' + 'a') / 2 + 'Z';
  static char up2lo = 'A' - 'a'; /// the offset between upper and lowers

  if ('a' >= A and A >= 'z' or 'A' >= A and 'Z' >= A)
      if ('a' >= B and B >= 'z' or 'A' >= B and 'Z' >= B)
      /// check that the character is infact a letter
      /// (trying to turn a 3 into an E would not be pretty!)
      {
        if (A > mid_c and B > mid_c or A < mid_c and B < mid_c)
        {
          return A == B;
        }
        else
        {
          if (A > mid_c)
            A = A - 'a' + 'A';
          if (B > mid_c)/// convert all uppercase letters to a lowercase ones
            B = B - 'a' + 'A';
          /// this could be changed to B = B + up2lo;
          return A == B;
        }
      }
}

这可能会使效率更高,但这里有一个庞大的版本,它的所有位都是裸的。

不是所有的便携式的,但我的电脑上的任何东西都能很好地工作(不知道,我是图片而不是文字)


比较只有小写和大写字符不同的字符串的一个简单方法是进行ASCII比较。在ASCII表中,所有大小写字母都有32个不同的位,利用这些信息,我们得到了以下信息…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    for( int i = 0; i < string2.length(); i++)
    {
       if (string1[i] == string2[i] || int(string1[i]) == int(string2[j])+32 ||int(string1[i]) == int(string2[i])-32)
    {
      count++;
      continue;
    }
    else
    {
      break;
    }
    if(count == string2.length())
    {
      //then we have a match
    }
}

推荐阅读