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

372

在不将字符串转换为所有大写或所有小写的情况下,在C++中进行不区分大小写的字符串比较的最佳方法是什么?

请指出这些方法是否支持Unicode,并且它们的可移植性如何。


@Adam:虽然这种变体在可用性方面很好,但在性能方面很差,因为它创建了不必要的副本。我可能会忽略某些东西,但我认为最好(非Unicode)的方法是使用std::stricmp。否则,请阅读Herb的观点 - Konrad Rudolph
在 C 语言中,通常需要强制将整个字符串转换为大写字母,然后进行比较 - 或者自己编写比较函数 :P - Michael Dorgan
一个稍后的问题有一个更简单的答案:strcasecmp(至少适用于BSD和POSIX编译器)https://dev59.com/mWDVa4cB1Zd3GeqPailc - Móż
@Mσᶎ 这个问题也有答案,但需要注意的是 strcasecmp 不是标准库的一部分,且至少有一个常用编译器中缺失。 - Mark Ransom
30个回答

4

对于大小写敏感的比较,可以使用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先出现


2
因为这种做法几乎不是 C++ 的方式,所以给它点踩。 - Thomas Daugaard
这是我大学的C++约定,但我会记住在这里发布时。 - reubenjohn
4
据我所知,stricmp是微软的扩展功能。而BSD似乎有一个名为strcasecmp()的替代函数。 - uliwitness

4
我有使用过Unicode国际组件库的良好经验——它们非常强大,并提供了转换、区域设置支持、日期和时间渲染、大小写映射(您似乎不需要)以及排序等方法,包括大小写和重音不敏感的比较(以及更多)。我只使用过C++版本的库,但它们似乎也有Java版本。
存在方法来执行所谓的规范化比较,如@Coincoin所提到的,并且甚至可以考虑区域设置——例如(这是一个排序示例,不是严格相等),在传统的西班牙语(在西班牙),字母组合“ll”在“l”和“m”之间排序,因此“lz”<“ll”<“ma”。

3
在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

1
除了这个答案,阅读 https://dev59.com/Z2ct5IYBdhLWcg3wAo-H#12414441 也是值得的,因为它是一个 C 函数,而且据说不可移植。 - Claus Jørgensen
我们需要使用哪个 #include 才能使这个程序正常工作? - ekkis
1
要使用_stricmp,您必须包含<string.h>。您可以在此处阅读:https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/stricmp-wcsicmp-mbsicmp-stricmp-l-wcsicmp-l-mbsicmp-l?view=vs-2019 - DAme
1
微软,你很努力! - AdrianTut

2

看起来以上的解决方案没有使用比较方法并重新实现了总数,所以这是我的解决方案,希望它对你有用(它运行良好)。

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

2
无论您最终选择哪种方法,请注意,如果该方法包括使用一些答案建议的strcmpstrcmp通常无法处理Unicode数据。通常,它甚至不能处理基于字节的Unicode编码,如utf-8,因为strcmp仅进行逐字节比较,而使用utf-8编码的Unicode代码点可能需要超过1个字节。唯一正确处理的特定Unicode情况是,当使用基于字节的编码的字符串仅包含U+00FF以下的代码点时,逐字节比较就足够了。

1
如果您需要经常将源字符串与其他字符串进行比较,一种优雅的解决方案是使用正则表达式。
std::wstring first = L"Test";
std::wstring second = L"TEST";

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

尝试了这个,但编译出错:错误:请求将'const char[5]'转换为非标量类型'std::wstring {aka std :: basic_string<wchar_t>}' - Deqing
坏主意。这是最糟糕的解决方案。 - Behrouz.M
这不是一个好的解决方案,但即使你想使用它,你也需要在宽字符串常量前加上 L,例如 L"TEST"。 - celticminstrel
有人能解释一下为什么这是最糟糕的解决方案吗?因为性能问题吗?创建正则表达式很耗费时间,但之后比较应该非常快。 - smibe
它是可用和便携的,主要问题是第一个字符串不能包含正则表达式使用的任何字符。因此,它不能用作一般字符串比较。它也会更慢,有一个标志可以使其按照smibe所说的方式工作,但仍然不能用作一般函数。 - Ben

1
截至2013年初,由IBM维护的ICU项目是一个相当不错的答案。

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

ICU是一个“完整的、便携式的Unicode库,紧密跟踪行业标准”。对于字符串比较的具体问题,Collation对象可以满足您的需求。
Mozilla项目在2012年中期采用了ICU用于Firefox的国际化。您可以在此跟踪工程讨论,包括构建系统和数据文件大小等问题:

0
如果您不想使用Boost库,那么这里有一个解决方案,只需使用C++标准io头文件即可。
#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;
}

我相信 std::toupper 在 #include <cctype> 中,你可能需要包含它。 - David Ledger
如果您使用的是全局版本,例如::toupper,那么您可能不需要包含<ctype>,因为有两个版本c版本和c++版本,带有locale,我猜。所以最好使用全局版本"::toupper()"。 - Haseeb Mir
当其中一个字符串为空时,这个解决方案会失败:"" -- 在这种情况下它返回true,而应该返回false。 - ekkis
@ekkis 现在我已经更新了它,并添加了单元测试,你现在可以检查一下。 - undefined

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

这个程序可能可以更高效,但是这里提供了一个庞大的版本,包含所有的细节。

虽然不是很便携,但是在我的电脑上运行良好(我对文字不太熟悉,只懂图片)。


这不是问题所问的Unicode支持。 - Behrouz.M
不支持非英语字符集。 - Robert Andrzejuk

-4
一个简单的比较只有大小写字符不同的字符串的方法是进行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
    }
}

3
根据这个,"++j" 会被认为等于 "KKJ",而 "1234" 会被认为等于 "QRST"。我怀疑这不是任何人想要的。 - celticminstrel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接