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

请指出这些方法是否对unicode友好,以及它们的可移植性如何。


当前回答

无论你最终选择什么方法,如果该方法碰巧包含一些答案建议的strcmp的使用,请注意:

strcmp一般不处理Unicode数据。一般来说,它甚至不能使用基于字节的Unicode编码,比如utf-8,因为strcmp只进行逐字节的比较,而用utf-8编码的Unicode代码点可以占用超过1个字节。strcmp正确处理的唯一特定的Unicode情况是,使用基于字节的编码方式编码的字符串只包含低于U+00FF的代码点,那么每个字节的比较就足够了。

其他回答

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
}

你说的是一个愚蠢的不区分大小写的比较还是一个完全标准化的Unicode比较?

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

例子:

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

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

也就是说,Unicode标准化应该是必读的,特别是如果您计划支持韩文,Thaï和其他亚洲语言。

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

利用标准char_traits。回想一下,std::string实际上是std::basic_string<char>的类型定义,或者更明确地说,std::basic_string<char, std::char_traits<char> >。char_traits类型描述了字符如何比较,如何复制,如何转换等。您所需要做的就是在basic_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期《本周大师》

只需使用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之前,所以苹果在球之前

对于非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;
}