在C ++中不区分大小写的string比较

在C ++中进行不区分大小写的string比较,而不将string转换为全部大写或全部小写的最佳方式是什么?

请说明这些方法是否适用于Unicode,以及它们的便携性。

Boost包含一个方便的algorithm:

 #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<char>的typedef,或者更明确地说是std::basic_string<char, std::char_traits<char> >char_traitstypes描述了字符如何比较,如何复制,如何转换等等。您只需要在basic_string键入一个新的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) { 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 。

你是在说一个愚蠢的大小写不敏感的比较还是一个完整的规范化的Unicode比较?

愚蠢的比较不会find可能是相同的,但不是二进制相等的string。

例:

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

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

也就是说, Unicode规范化应该是一个强制性的阅读,特别是如果你打算支持韩文,泰文和其他亚洲语言。

而且,IBM几乎获得了大部分优化的Unicodealgorithm的专利,并将它们公诸于众。 他们还保持一个实施: IBM ICU

如果你在POSIX系统上,你可以使用strcasecmp 。 这个函数不是标准C的一部分,但是在Windows上也没有。 这将对8位字符进行不区分大小写的比较,只要语言环境是POSIX即可。 如果语言环境不是POSIX,结果是不确定的(所以它可能做一个本地化的比较,或者它可能不会)。 宽字符的等效不可用。

否则,大量的历史C库实现具有stricmp()和strnicmp()的function。 Windows上的Visual C ++通过在下划线前面加上前缀来重新命名所有这些,因为它们不是ANSI标准的一部分,所以在这个系统上它们被称为_stricmp或_strnicmp 。 一些库也可能具有宽字符或多字节等效函数(通常命名为例如wcsicmp,mbcsicmp等)。

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

提升的麻烦是你必须链接并依靠提升。 在某些情况下不容易(例如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; } 

我首先想到的是一个非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; } 

boost :: iequals在string的情况下不兼容utf-8。 你可以使用boost :: locale 。

 comparator<char,collator_base::secondary> cmpr; cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl; 
  • 小学 – 忽略口音和字符大小写,仅比较基本字母。 例如“立面”和“立面”是一样的。
  • 次要的 – 忽略字符的情况下,但考虑口音。 “立面”和“立面”是不同的,但“立面”和“立面”是相同的。
  • 第三方 – 考虑两种情况和口音:“外墙”和“外墙”是不同的。 忽略标点符号。
  • 第四纪 – 考虑所有的情况下,口音和标点符号。 在Unicode表示方面,这些词必须是相同的。
  • 相同 – 作为第四位,但也比较代码点。

支持unicode的Visual C ++string函数: http : //msdn.microsoft.com/en-us/library/cc194799.aspx

你可能正在寻找的是_wcsnicmp

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

到目前为止还没有提到的一件事情是,如果你使用这些方法使用stlstring,首先比较两个string的长度是有用的,因为这个信息已经可以在string类中使用了。 这可以防止进行昂贵的string比较,如果你比较的两个string甚至没有相同的长度在第一位。

我试图凑齐从所有职位的一个很好的答案,所以帮我编辑这个:

这是一个这样做的方法,虽然它转换string,而不是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也提供了一个相当好的工具来进行这种比较。

Boost.String库有很多不区分大小写的比较algorithm等等。

你可以实现你自己的,但为什么打扰已经完成了?

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

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

所以我想到了这个:

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

一个简单的函数,一个用于字符重载,另一个用于whar_t。 不使用任何非标准的东西,所以应该在任何平台上都可以。

平等比较不会考虑可变长度编码和Unicode规范化等问题,但是basic_string不支持那些我知道的,通常也不是问题。

在需要对文本进行更复杂的字典操纵的情况下,您只需使用第三方库(如Boost)即可。

假设你正在寻找一种方法,而不是一个已经存在的魔法函数,坦白地说没有更好的方法。 我们都可以用有限的字符集巧妙的技巧来编写代码片段,但是在某个点的一天结束时,你必须转换字符。

这种转换的最佳方法是在比较之前这样做。 这使您在编码scheme方面有很大的灵活性,您的实际比较运算符应该是无知的。

你当然可以把这个转换隐藏在你自己的string函数或类的后面,但是在比较之前你仍然需要转换string。

我写了一个与std :: basic_string一起使用的char_traits的不区分大小写的版本,以便在使用内置的std :: basic_string成员函数进行比较,search等时生成不区分大小写的std :: 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; 

请参阅std::lexicographical_compare

 // lexicographical_compare example #include <iostream> // std::cout, std::boolalpha #include <algorithm> // 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):\n"; std::cout << "Using default comparison (operator<): "; std::cout << std::lexicographical_compare(foo,foo+5,bar,bar+9); std::cout << '\n'; std::cout << "Using mycomp as comparison object: "; std::cout << std::lexicographical_compare(foo,foo+5,bar,bar+9,mycomp); std::cout << '\n'; return 0; } 

演示

我已经有了使用Unicode库的国际组件的良好经验 – 它们是非常强大的,并提供转换,语言环境支持,date和时间呈现,大小写映射(你似乎并不想要的)和整理 ,其中包括不区分大小写和不区分重音的比较(以及更多)。 我只使用了C ++版本的库,但它们似乎也有一个Java版本。

方法存在执行@Coincoin引用的规范化比较,甚至可以考虑地区 – 例如(这是一个sorting的例子,不是严格的平等),传统上西class牙语(在西class牙),字母组合“ll” “l”和“m”,所以“lz”<“ll”<“ma”。

简短而好看。 没有其他的依赖,比扩展的 std C库。

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

如果str1str2相等则返回truestrcasecmp可能不存在,可能有stricmpstrcmpi等。

示例代码:

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

输出:

 true true true true true 

在不使用Boost的情况下这样做可以通过使用c_str()获取Cstring指针并使用strcasecmp

 std::string str1 ="aBcD"; std::string str2 = "AbCd";; if (strcasecmp(str1.c_str(), str2.c_str()) == 0) { //case insensitive equal } 
 std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);}) 

如果你不能使用boost,你可以在C ++ 14中使用上面的代码。 你必须使用std::towlower来处理宽字符。

只要注意你最终select的任何方法,如果该方法恰好包含使用strcmp ,一些答案build议:

strcmp不能用于一般的Unicode数据。 一般来说,它甚至不能使用基于字节的Unicode编码,比如utf-8,因为strcmp只能进行每字节的字节比较,而使用utf-8编码的Unicode编码点可能需要多于1个字节。 唯一特定的Unicode情况下strcmp正确处理的是,当一个字节编码的string只包含低于U + 00FF的代码点时 – 那么每字节的比较就足够了。

截至2013年初,由IBM维护的ICU项目对此非常好。

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

ICU是一个“完整的,便携式的Unicode库,紧密跟踪行业标准”。 对于string比较的具体问题,Collat​​ion对象做你想要的。

Mozilla项目于2012年中旬在Firefox中通过ICU国际化; 您可以跟踪工程讨论,包括构build系统和数据文件大小的问题,在这里:

只要使用strcmp()区分大小写,使用strcmpi()stricmp()来区分大小写。 哪些在头文件<string.h>

格式:

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

用法:

 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"<<"\n"; if(strcmpi(a.c_str(),b.c_str()<0) cout<<a[0]<<" comes before ball "<<b[0]<<", so "<<a<<" comes before "<<b; 

产量

苹果和ApPlE是一样的

一个在B之前,所以苹果在球之前

如果你有一个string的vector,例如:

 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

晚会之后,但是这是一个使用std::locale的变体,因此可以正确处理土耳其语:

 auto tolower = std::bind1st( std::mem_fun( &std::ctype<char>::tolower), &std::use_facet<std::ctype<char> >( std::locale())); 

给你一个使用活动区域设置将字符转换为小写字母的函子,然后你可以通过std::transform来生成小写string:

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

这也适用于基于wchar_t的string。

看起来像上面的解决scheme是不使用比较方法和再次实施总计,所以这里是我的解决scheme,并希望它适合你(它工作正常)。

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

这可能会变得更有效率,但这是一个庞大的版本,其裸露的所有位。

不是所有的便携式,但与我的电脑上的任何东西(不知道,我的照片不是字)

如果你不得不比较一个源string与其他string,一个优雅的解决scheme是使用正则expression式。

 std::wstring first = L"Test"; std::wstring second = L"TEST"; std::wregex pattern(first, std::wregex::icase); bool isEqual = std::regex_match(second, pattern); 

比较只有小写字母和大写字符的string的一个简单的方法是做一个ascii比较。 所有的大写和小写字母在ASCII表中相差32位,使用这个信息我们有以下…

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