不区分大小写的std::string.find()

90

我正在使用std::stringfind()方法来测试一个字符串是否为另一个字符串的子串。现在我需要大小写不敏感的版本。对于字符串比较,我总是可以使用stricmp(),但似乎没有stristr()

我找到了各种答案,大多数建议使用Boost,但这对我来说不是一个选择。此外,我需要支持std::wstring/wchar_t。有什么想法吗?


1
这个主题有一个Gotw:http://www.gotw.ca/gotw/029.htm - Alexandre C.
2
stristr不在,但是"char *strcasestr(const char *haystack, const char *needle);"在。这样不可以吗? - Nasir
@Nasir,strcasestr在Windows下不可用。 - Yuchen
11个回答

89
你可以使用带有自定义断言的 std::search
#include <locale>
#include <iostream>
#include <algorithm>
using namespace std;

// templated version of my_equal so it could work with both char and wchar_t
template<typename charT>
struct my_equal {
    my_equal( const std::locale& loc ) : loc_(loc) {}
    bool operator()(charT ch1, charT ch2) {
        return std::toupper(ch1, loc_) == std::toupper(ch2, loc_);
    }
private:
    const std::locale& loc_;
};

// find substring (case insensitive)
template<typename T>
int ci_find_substr( const T& str1, const T& str2, const std::locale& loc = std::locale() )
{
    typename T::const_iterator it = std::search( str1.begin(), str1.end(), 
        str2.begin(), str2.end(), my_equal<typename T::value_type>(loc) );
    if ( it != str1.end() ) return it - str1.begin();
    else return -1; // not found
}

int main(int arc, char *argv[]) 
{
    // string test
    std::string str1 = "FIRST HELLO";
    std::string str2 = "hello";
    int f1 = ci_find_substr( str1, str2 );

    // wstring test
    std::wstring wstr1 = L"ОПЯТЬ ПРИВЕТ";
    std::wstring wstr2 = L"привет";
    int f2 = ci_find_substr( wstr1, wstr2 );

    return 0;
}

2
你为什么在这里使用模板? - rstackhouse
@rstackhouse,这里的模板支持不同的字符类型(charwchar_t)的支持。 - Kirill V. Lyadvinsky
1
谢谢,Kirill。对于像我一样毫无头绪的人,可以在迭代器声明后插入 std::advance( it, offset ); 以从偏移量开始搜索。 - user1908746
对于那些(像我一样)不熟悉模板的人,您能否也发布一个没有模板、没有本地化的标准版本?例如只针对 wstring 的版本,@KirillV.Lyadvinsky? - Basj
4
调用std::toupper函数是否适用于宽字符?难道不应该调用std::towupper函数吗? - MiloDC
请添加 string.find_first_ofwstring.find_first_of 的实现。 - Nguyen Manh

73

新的C++11风格:

#include <algorithm>
#include <string>
#include <cctype>

/// Try to find in the Haystack the Needle - ignore case
bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
{
  auto it = std::search(
    strHaystack.begin(), strHaystack.end(),
    strNeedle.begin(),   strNeedle.end(),
    [](unsigned char ch1, unsigned char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
  );
  return (it != strHaystack.end() );
}

std::search函数的解释可以在cplusplus.com上找到。


如果我想使用同一个函数在字符串“str”中查找字符“c”,用“findStringIC(str,(string)c)”调用它是不起作用的。 - Enissay
这种 char 到 string 的转换方式不起作用,你必须像 std::string(1, 'x') 这样实际创建字符串对象。请参见 http://coliru.stacked-crooked.com/a/af4051dd1d15972e如果您经常这样做,可能值得创建一个特定的函数,它不需要每次都创建一个新对象。 - CC.
1
在大多数情况下,进行不区分大小写的搜索时最好使用 tolower()。即使 Ada 也将其改为小写!Unicode.org 可能会解释其中的原因,但我不知道确切的原因。 - Alexis Wilke
1
UPPER CASE IS BETTER https://msdn.microsoft.com/en-us/library/bb386042.aspx 但当然不是完美的。如果你需要土耳其语,那会很困难 https://dev59.com/c3VC5IYBdhLWcg3woCnN 和 http://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx/ - CC.
5
这种情况下不需要模板。对于C++17,您可能想看一下string_view而不是std :: string https://skebanga.github.io/string-view/ - CC.
显示剩余3条评论

22

为什么不使用Boost.StringAlgo:

#include <boost/algorithm/string/find.hpp>

bool Foo()
{
   //case insensitive find

   std::string str("Hello");

   boost::iterator_range<std::string::const_iterator> rng;

   rng = boost::ifind_first(str, std::string("EL"));

   return rng;
}

15
通常情况下,如果一个C++问题没有标记Boost,那么就默认不考虑使用Boost。 - kayleeFrye_onDeck

20

为什么不在调用 find() 之前将两个字符串都转换为小写字母呢?

tolower

注意:


18
由于对于更大的字符串来说效率非常低。 - bkausbk
2
如果您的软件需要本地化,这也不是个好主意。参见Turkey test:http://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx/ - Bart
在C++中进行基本的大写和小写操作时,如果针对非ANSI编码的任何内容,你会发现需要处理的参数数量是巨大的,这可能会让你感到不知所措 xD。简单来说,从C++17开始,标准库无法轻松处理这种情况。 - kayleeFrye_onDeck

9

由于您正在进行子字符串搜索(std::string),而不是元素(字符)搜索,因此我不知道任何现有的立即可访问的标准库解决方案可以做到这一点。

尽管如此,这很容易做到:只需将两个字符串转换为大写(或两者都转换为小写 - 在此示例中我选择了大写)。

std::string upper_string(const std::string& str)
{
    string upper;
    transform(str.begin(), str.end(), std::back_inserter(upper), toupper);
    return upper;
}

std::string::size_type find_str_ci(const std::string& str, const std::string& substr)
{
    return upper(str).find(upper(substr) );
}

这不是一个快速的解决方案(边缘处于悲观化领域),但这是我目前所知道的唯一方法。如果您担心效率问题,实现自己的不区分大小写的子字符串查找器也不难。

此外,我需要支持std::wstring/wchar_t。有什么想法吗?

在locale中使用tolower/toupper也适用于宽字符串,因此上面的解决方案应该同样适用(只需将std::string更改为std::wstring即可)。

[编辑] 正如指出的那样,另一种选择是通过指定自己的字符特性从basic_string中调整自己的不区分大小写的字符串类型。如果您可以接受对于给定的字符串类型,所有字符串搜索、比较等都是不区分大小写的,则此方法可行。


我正在寻找的答案,谢谢。 - ycomp

2
如果您想要根据Unicode和语言环境规则进行“真实”的比较,请使用ICU的Collator

1
同时提供Boost版本也是有意义的:这将修改原始字符串。
#include <boost/algorithm/string.hpp>

string str1 = "hello world!!!";
string str2 = "HELLO";
boost::algorithm::to_lower(str1)
boost::algorithm::to_lower(str2)

if (str1.find(str2) != std::string::npos)
{
    // str1 contains str2
}

或者使用完美的boost xpression库

#include <boost/xpressive/xpressive.hpp>
using namespace boost::xpressive;
....
std::string long_string( "very LonG string" );
std::string word("long");
smatch what;
sregex re = sregex::compile(word, boost::xpressive::icase);
if( regex_match( long_string, what, re ) )
{
    cout << word << " found!" << endl;
}

在这个例子中,你需要注意你的搜索词没有任何正则表达式特殊字符。

1
"...我已经找到了各种答案,大多数建议使用 Boost,但在我的情况下这不是一个选择。" - jww

0

我喜欢Kiril V. LyadvinskyCC的答案。但我的问题比仅仅区分大小写更具体;我需要一个懒惰的支持Unicode的命令行参数解析器,可以在处理可能具有特殊字符的字母数字字符串搜索时消除误报与漏报,这些搜索可以针对用于格式化字母数字关键字的基础字符串,例如,Wolfjäger 不应匹配 jäger,但应匹配<jäger>

基本上只是针对字母数字精确长度匹配进行了额外处理。

/* Undefined behavior when a non-alpha-num substring parameter is used. */
bool find_alphanum_string_CI(const std::wstring& baseString, const std::wstring& subString)
{
    /* Fail fast if the base string was smaller than what we're looking for */
    if (subString.length() > baseString.length()) 
        return false;

    auto it = std::search(
        baseString.begin(), baseString.end(), subString.begin(), subString.end(),
        [](char ch1, char ch2)
        {
            return std::toupper(ch1) == std::toupper(ch2);
        }
    );

    if(it == baseString.end())
        return false;

    size_t match_start_offset = it - baseString.begin();

    std::wstring match_start = baseString.substr(match_start_offset, std::wstring::npos);

    /* Typical special characters and whitespace to split the substring up. */
    size_t match_end_pos = match_start.find_first_of(L" ,<.>;:/?\'\"[{]}=+-_)(*&^%$#@!~`");

    /* Pass fast if the remainder of the base string where
       the match started is the same length as the substring. */
    if (match_end_pos == std::wstring::npos && match_start.length() == subString.length()) 
        return true;

    std::wstring extracted_match = match_start.substr(0, match_end_pos);

    return (extracted_match.length() == subString.length());
}

1
代码的最后三行应该是 return (extracted_match.length() == subString.length()); - SJHowe
“应该”可能有点强硬,但我同意这是一个改进!:) 谢谢并更新 ^_^ - kayleeFrye_onDeck

0

最高效的方式

简单而快速。

性能保证是线性的,初始化成本为2 * NEEDLE_LEN比较。(glic)

#include <cstring>
#include <string>
#include <iostream>

int main(void) {

    std::string s1{"abc de fGH"};
    std::string s2{"DE"};

    auto pos = strcasestr(s1.c_str(), s2.c_str());

    if(pos != nullptr)
        std::cout << pos - s1.c_str() << std::endl;

    return 0;
}

strcasestr 似乎是 stdlib 的 GNU 扩展。 - AntonK

0
#include <iostream>
using namespace std;

template <typename charT>
struct ichar {
    operator charT() const { return toupper(x); }
    charT x;
};
template <typename charT>
static basic_string<ichar<charT> > *istring(basic_string<charT> &s) { return (basic_string<ichar<charT> > *)&s; }
template <typename charT>
static ichar<charT> *istring(const charT *s) { return (ichar<charT> *)s; }

int main()
{
    string s = "The STRING";
    wstring ws = L"The WSTRING";
    cout << istring(s)->find(istring("str")) << " " << istring(ws)->find(istring(L"wstr"))  << endl;
}

有点不太干净,但是简短快速。

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