提问人:Adam 提问时间:8/15/2008 最后编辑:Toby SpeightAdam 更新时间:9/14/2023 访问量:629488
C++ 中不区分大小写的字符串比较 [已关闭]
Case-insensitive string comparison in C++ [closed]
问:
在 C++ 中进行不区分大小写的字符串比较而不将字符串转换为全大写或全小写的最佳方法是什么?
请说明这些方法是否对Unicode友好,以及它们的可移植性如何。
答:
假设您正在寻找一种方法而不是已经存在的魔术函数,坦率地说,没有更好的方法。我们都可以为有限的字符集编写带有巧妙技巧的代码片段,但在一天结束时,您必须在某个时候转换字符。
此转换的最佳方法是在比较之前进行。这为您在编码方案方面提供了很大的灵活性,而实际的比较运算符应该对此一无所知。
当然,您可以将此转换“隐藏”在自己的字符串函数或类后面,但是在比较之前仍然需要转换字符串。
支持 unicode 的可视C++字符串函数:http://msdn.microsoft.com/en-us/library/cc194799.aspx
您可能正在寻找的是_wcsnicmp
评论
仅供参考,并且容易受到缓冲区溢出的影响,因为它们只是处理,直到它们遇到 null 终止符。使用和_strnicmp()
更安全。strcmp()
stricmp()
_strncmp()
评论
stricmp()
并且不是 POSIX 标准的一部分:-(但是,您可以在 POSIX 标头中找到 、 和 :-)查看 opengroup.orgstrnicmp()
strcasecmp()
strcasecmp_l()
strncasecmp()
strncasecmp_l()
strings.h
我在使用 International Components for Unicode 库方面有很好的经验 - 它们非常强大,并且提供了转换、区域设置支持、日期和时间呈现、大小写映射(您似乎不想要)和排序规则的方法,其中包括不区分大小写和重音的比较(等等)。我只使用了 C++ 版本的库,但它们似乎也有 Java 版本。
存在执行@Coincoin所指的规范化比较的方法,甚至可以解释语言环境 - 例如(这是一个排序示例,不是严格意义上的相等),传统上在西班牙语(在西班牙)中,字母组合“ll”在“l”和“m”之间排序,因此“lz”<“ll”<“马”。
您说的是不区分大小写的哑比较还是完全规范化的 Unicode 比较?
愚蠢的比较不会找到可能相同但不二进制相等的字符串。
例:
U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).
都是等价的,但它们也具有不同的二进制表示形式。
也就是说,Unicode 规范化应该是一本必读的书,特别是如果您打算支持韩文、泰语和其他亚洲语言。
此外,IBM 几乎为最优化的 Unicode 算法申请了专利,并将它们公开发布。他们还维护一个实现:IBM ICU
评论
a
o
u
ä
ö
ü
我试图从所有帖子中拼凑出一个好的答案,所以帮我编辑这个:
这里有一种方法,虽然它确实转换了字符串,并且对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 为进行此类比较提供了一个相当不错的实用程序。
评论
transform
如果你使用的是 POSIX 系统,则可以使用 strcasecmp。但是,此函数不是标准 C 的一部分,在 Windows 上也不可用。这将对 8 位字符执行不区分大小写的比较,只要区域设置为 POSIX。如果区域设置不是 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(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2))) {
return false;
}
}
return true;
}
我编写了一个不区分大小写的 char_traits 版本,用于 std::basic_string,以便在使用内置的 std::basic_string 成员函数进行比较、搜索等时生成一个不区分大小写的 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;
评论
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,因为只能进行逐字节比较,而以 utf-8 编码的 Unicode 码位可以占用超过 1 个字节。正确处理的唯一特定 Unicode 大小写是,当使用基于字节的编码编码的字符串仅包含低于 U+00FF 的码位时,则逐字节比较就足够了。strcmp
strcmp
您可以在 Unix 或 Windows 上使用。strcasecmp
stricmp
到目前为止没有提到的一件事是,如果您在这些方法中使用 stl 字符串,则首先比较两个字符串的长度很有用,因为此信息已在字符串类中可用。如果您要比较的两个字符串一开始的长度甚至不同,这可能会阻止进行成本高昂的字符串比较。
评论
Boost.String 库有很多算法用于进行不区分大小写的比较等。
你可以实现你自己的,但是当它已经完成时,为什么还要麻烦呢?
评论
利用标准 .回想一下,a 实际上是 的 typedef,或者更明确地说,是 。该类型描述了角色如何比较、如何复制、如何投射等。您需要做的就是 typedef 一个新字符串,并为其提供您自己的自定义,这些自定义值不区分大小写。char_traits
std::string
std::basic_string<char>
std::basic_string<char, std::char_traits<char> >
char_traits
basic_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期。
评论
typedef std::basic_string<char, ci_char_traits<char> > istring
typedef std::basic_string<char, std::char_traits<char> > string
boost 的问题在于您必须链接并依赖 boost。在某些情况下并不容易(例如android)。
使用 char_traits 意味着您的所有比较都不区分大小写,这通常不是您想要的。
这应该足够了。它应该是相当有效的。不处理 unicode 或任何东西。
#include <cctype> // std::tolower
#include <algorithm> // std::equal
bool ichar_equals(char a, char b)
{
return std::tolower(static_cast<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
}
bool iequals(const std::string& a, const std::string& b)
{
return a.size() == b.size() &&
std::equal(a.begin(), a.end(), b.begin(), ichar_equals);
}
C++14 版本
#include <cctype> // std::tolower
#include <algorithm> // std::equal
bool iequals(const std::string& a, const std::string& b)
{
return std::equal(a.begin(), a.end(), b.begin(), b.end(), ichar_equals);
}
C++20 版本使用std::ranges
#include <cctype> // std::tolower
#include <algorithm> // std::equal
#include <string_view> // std::string_view
bool iequals(std::string_view lhs, std::string_view rhs)
{
return std::ranges::equal(lhs, rhs, ichar_equals);
}
评论
[](unsigned char a, unsigned char b)
static_cast
Boost::Iequals 在字符串的情况下与 UTF-8 不兼容。 您可以使用 boost::locale。
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
- 主要 -- 忽略重音和字符大小写,仅比较基本字母。例如,“facade”和“Facade”是相同的。
- 次要 -- 忽略字符大小写,但考虑重音。“门面”和“门面”是不同的,但“门面”和“门面”是相同的。
- 第三级 -- 考虑大小写和重音:“Façade”和“façade”是不同的。忽略标点符号。
- 四元 -- 考虑所有大小写、重音符号和标点符号。这些单词在 Unicode 表示形式方面必须相同。
- 相同 -- 作为四元,但也比较代码点。
截至 2013 年初,由 IBM 维护的 ICU 项目是一个很好的答案。
ICU 是一个“完整、可移植的 Unicode 库,密切跟踪行业标准”。对于字符串比较的特定问题,Collation 对象可以执行所需的操作。
Mozilla 项目在 2012 年年中采用了 ICU 在 Firefox 中实现国际化;您可以在此处跟踪工程讨论,包括构建系统和数据文件大小的问题:
- https://groups.google.com/forum/#!topic/mozilla.dev.platform/sVVpS2sKODw
- https://bugzilla.mozilla.org/show_bug.cgi?id=724529(跟踪器)
- https://bugzilla.mozilla.org/show_bug.cgi?id=724531(构建系统)
对于我的基本不区分大小写的字符串比较需求,我宁愿不必使用外部库,也不想要一个具有不区分大小写特征的单独字符串类,该类与我的所有其他字符串不兼容。
所以我想出的是这样的:
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,另一个重载用于 whar_t。不使用任何非标准的东西,所以在任何平台上都应该没问题。
相等性比较不会考虑可变长度编码和 Unicode 规范化等问题,但无论如何,basic_string对此没有任何支持,而且通常不是问题。
如果需要对文本进行更复杂的词典操作,那么您只需使用像 Boost 这样的第三方库,这是意料之中的。
评论
仅用于区分大小写和/或不区分大小写的比较。它们都在头文件中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 和 ApPlE 是一样的
A 在 B 之前,所以 Apple 在 Ball 之前
评论
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;
}
}
}
这可能会更有效率,但这是一个笨重的版本,所有部分都裸露在外。
不是那么便携,但适用于我电脑上的任何内容(不知道,我是图片而不是文字)
评论
如果您必须更频繁地将源字符串与其他字符串进行比较,一个优雅的解决方案是使用正则表达式。
std::wstring first = L"Test";
std::wstring second = L"TEST";
std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);
评论
error: conversion from 'const char [5]' to non-scalar type 'std::wstring {aka std::basic_string<wchar_t>}' requested
比较仅因小写和大写字符而不同的字符串的一种简单方法是进行 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
}
}
评论
请参阅 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() {
std::string foo = "Apple";
std::string 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.begin(), foo.end(), bar.begin(), bar.end());
std::cout << '\n';
std::cout << "Using custom comparison (mycomp): ";
std::cout << std::lexicographical_compare(foo.begin(), foo.end(), bar.begin(), bar.end(), mycomp);
std::cout << '\n';
return 0;
}
评论
std::tolower
std::string
std::string
std::lexicographical_compare
迟到了,但这里有一个使用 的变体,因此可以正确处理土耳其语:std::locale
auto tolower = std::bind1st(
std::mem_fun(
&std::ctype<char>::tolower),
&std::use_facet<std::ctype<char> >(
std::locale()));
为您提供一个函子,该函子使用活动区域设置将字符转换为小写,然后您可以使用该 via 生成小写字符串:std::transform
std::string left = "fOo";
transform(left.begin(), left.end(), left.begin(), tolower);
这也适用于基于字符串的字符串。wchar_t
评论
在不使用 Boost 的情况下执行此操作可以通过获取 C 字符串指针并使用:c_str()
strcasecmp
std::string str1 ="aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
//case insensitive equal
}
短小精悍。除了扩展的 std C 库外,没有其他依赖项。
strcasecmp(str1.c_str(), str2.c_str()) == 0
如果 和 相等,则返回 true。 可能不存在,可能有类似物等。str1
str2
strcasecmp
stricmp
strcmpi
示例代码:
#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
评论
stricmp
strcmpi
strcasecmp
cout << boolalpha
bool2str
str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})
如果您无法使用 boost,则可以在 C++14 中使用上述代码。您必须用于宽字符。std::towlower
评论
str1.size() == str2.size() &&
看起来上面的解决方案没有使用比较方法并再次实现总计,所以这是我的解决方案,希望它对你有用(它工作正常)。
#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;
}
在 c++ 中比较两个字符串(针对 Windows 测试)的一种简单方法是使用 _stricmp
// Case insensitive (could use equivalent _stricmp)
result = _stricmp( string1, string2 );
如果您希望与 std::string 一起使用,请举个例子:
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
评论
如果您不想使用 Boost 库,那么这里是仅使用 C++ 标准 io 标头的解决方案。
#include <iostream>
#include <cctype>
#include <algorithm>
#include <stdexcept>
#include <cassert>
struct iequal
{
bool operator()(int c1, int c2) const
{
return std::toupper(c1) == std::toupper(c2);
}
};
bool iequals(const std::string& str1, const std::string& str2)
{
if (str1.empty() || str2.empty())
{
return str1.empty() && str2.empty();
}
return std::equal(str1.begin(), str1.end(), str2.begin(), iequal());
}
void runTests()
{
assert(iequals("HELLO", "hello") == true);
assert(iequals("HELLO", "") == false);
assert(iequals("", "hello") == false);
assert(iequals("", "") == true);
std::cout << "All tests passed!" << std::endl;
}
int main(void)
{
try
{
runTests();
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
评论
std::stricmp
strcasecmp