在不将字符串转换为所有大写或所有小写的情况下,在C++中进行不区分大小写的字符串比较的最佳方法是什么?
请指出这些方法是否支持Unicode,并且它们的可移植性如何。
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
}
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 <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);
});
}
std::ranges
的 C++20 版本:#include <ranges>
#include <algorithm>
#include <string_view>
bool iequals(std::string_view lhs, std::string_view rhs) {
auto to_lower{ std::ranges::views::transform(static_cast<int(*)(int)>(std::tolower)) };
return std::ranges::equal(lhs | to_lower, rhs | to_lower);
}
std::tolower
函数会出现问题,需要对char
进行static_cast
强制转换为unsigned char
类型后再调用。 - Evg利用标准的char_traits
。记住,std::string
实际上是std::basic_string<char>
的typedef,或者更明确地说,是std::basic_string<char,std::char_traits<char> >
。 char_traits
类型描述了字符比较、复制、转换等操作。您所需做的就是用自定义的char_traits
为basic_string
typedef出一个新的字符串,并提供该字符串以不区分大小写方式进行比较。
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
。 - Andreas Spindler你是在谈论一个简单的不区分大小写的比较还是一个完整的规范化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
与分音符相结合或直接使用字母ä
、ö
、ü
来创建 - 但是两个点的距离(略微)不同(直接字符更窄)... - Aconcagua我对非 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;
}
if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2))
可以吗? - Shadow2531在字符串的情况下,boost::iequals不支持UTF-8编码。
您可以使用boost::locale。
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
您可以在Unix上使用strcasecmp
或在Windows上使用stricmp
。
到目前为止,还有一件事情没有提到,如果您正在使用stl字符串与这些方法,最好先比较两个字符串的长度,因为在字符串类中已经提供了这个信息。如果您要比较的两个字符串首先长度就不相同,则这样做可以避免进行昂贵的字符串比较操作。
我正在尝试从所有帖子中汇编一个好的答案,因此请帮忙编辑:
这是一种实现方法,虽然它会改变字符串,不支持Unicode,但它是可移植的,这是一个优点:
我正在尝试从所有帖子中汇编一个好的答案,因此请帮忙编辑:
这是一种实现方法,虽然它会改变字符串,不支持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()实际上不是标准库的一部分,而是由大多数编译器供应商实现的。
要获得真正的Unicode友好实现,似乎必须离开标准库。一个好的第三方库是IBM ICU(国际Unicode组件)
此外,boost::iequals提供了一个相当不错的实用程序来进行此类比较。
std::tolower
不应直接在 char
上调用,需要使用 static_cast
转换为 unsigned char
。 - Evgstd::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
只适用于字符是 ASCII 编码的情况。对于 std::string
则没有这样的保证 - 所以很容易出现未定义行为。 - plasmacelstd::string
而不是std::lexicographical_compare
上。 - Brian Rodriguezstd::string
而不是std::lexicographical_compare
。 - undefined
std::stricmp
。否则,请阅读Herb的观点。 - Konrad Rudolphstrcasecmp
不是标准库的一部分,且至少有一个常用编译器中缺失。 - Mark Ransom