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

Posted

技术标签:

【中文标题】不区分大小写的 std::string.find()【英文标题】:Case insensitive std::string.find() 【发布时间】:2010-06-30 18:28:27 【问题描述】:

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

我找到了各种答案,大多数人建议使用Boost,这在我的情况下不是一个选项。另外,我需要支持std::wstring/wchar_t。有什么想法吗?

【问题讨论】:

有一个关于这个主题的Gotw:gotw.ca/gotw/029.htm stristr 不存在,但“char *strcasestr(const char *haystack, const char *needle);”有没有。这样不行吗? @Nasir, strcasestr 在 Windows 下不可用。 【参考方案1】:

您可以将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;

【讨论】:

你为什么在这里使用模板? @rstackhouse,这里的模板是为了支持不同的字符类型(char & wchar_t)。 谢谢,基里尔。对于像我这样一无所知的人,请在迭代器声明之后插入 std::advance( it, offset ); 以从偏移量开始搜索。 对于那些(像我一样)不熟悉模板的人,能否也发布一个没有模板,没有语言环境的标准版本?只为wstring 例如@KirillV.Lyadvinsky? std::toupper 的调用是否真的适用于宽字符?你不需要打电话给std::towupper吗?【参考方案2】:

新的 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(),
    [](char ch1, char ch2)  return std::toupper(ch1) == std::toupper(ch2); 
  );
  return (it != strHaystack.end() );

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

【讨论】:

如果我想使用相同的函数在字符串str 中找到一个字符c 怎么办。使用findStringIC(str, (string)c) 调用它不起作用 这种类型的字符到字符串转换不起作用,您必须实际创建字符串对象,如std::string(1, 'x') 请参阅coliru.stacked-crooked.com/a/af4051dd1d15972e 如果您经常这样做,可能值得创建一个特定的函数不需要每次都创建一个新对象。 在大多数情况下,最好在进行不区分大小写的搜索时使用tolower()。甚至 Ada 都将其更改为小写! Unicode.org 可能在某处解释了一些原因,但我不知道具体原因。 大写更好msdn.microsoft.com/en-us/library/bb386042.aspx 但当然不完美。如果你需要土耳其语,那会很难***.com/questions/234591/upper-vs-lower-case 和haacked.com/archive/2012/07/05/… 在这种情况下不需要模板。对于 C++17,你可能想看看 string_view 而不是 std::string skebanga.github.io/string-view【参考方案3】:

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

tolower

注意:

长字符串效率低。 小心internationalization issues。

【讨论】:

因为对于较大的字符串来说效率非常低。 如果您的软件需要本地化,这也不是一个好主意。见土耳其测试:haacked.com/archive/2012/07/05/… 在 C++ 中对未编码为 ANSI 的任何内容执行基本的大写和小写操作时,您将发现这些参数会让您不知所措 xD 简而言之,从 C++ 开始,标准库的处理并非易事17.【参考方案4】:

为什么不使用 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;

【讨论】:

通常情况下,除非 C++ 问题被标记为 Boost,否则假定 Boost 不是一个选项。【参考方案5】:

由于您正在进行子字符串搜索(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。有什么想法吗?

语言环境中的 tolower/toupper 也适用于宽字符串,因此上述解决方案应该同样适用(简单地将 std::string 更改为 std::wstring)。

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

【讨论】:

【参考方案6】:

如果您想根据 Unicode 和区域设置规则进行“真实”比较,请使用 ICU’s Collator class。

【讨论】:

【参考方案7】:

提供 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 library

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

在此示例中,您应该注意您的搜索词没有任何正则表达式特殊字符。

【讨论】:

"...我找到了各种答案,大多数人建议使用 Boost,这在我的情况下不是一个选项"【参考方案8】:
#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;

有点脏,但又短又快。

【讨论】:

【参考方案9】:

我喜欢Kiril V. Lyadvinsky 和CC 的回答。但我的问题比不区分大小写更具体一些;我需要一个惰性 Unicode 支持的命令行参数解析器,它可以在处理字母数字字符串搜索时消除误报/否定,这些搜索可能在用于格式化我正在搜索的字母数字关键字的基本字符串中包含特殊字符,例如 Wolfjäger不应该匹配 jäger 但应该匹配 &lt;jäger&gt;

这基本上只是 Kiril/CC 的答案,对字母数字精确长度匹配进行了额外处理。

/* 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());

【讨论】:

最后3行代码应该是return (extracted_match.length() == subString.length()); “应该”的措辞可能有点强,但我同意这是一种改进! :) Ty & 更新 ^_^【参考方案10】:

wxWidgets 有非常丰富的字符串 API wxString

可以通过(使用大小写转换方式)完成

int Contains(const wxString& SpecProgramName, const wxString& str)

  wxString SpecProgramName_ = SpecProgramName.Upper();
  wxString str_ = str.Upper();
  int found = SpecProgramName.Find(str_);
  if (wxNOT_FOUND == found)
  
    return 0;
  
  return 1;

【讨论】:

以上是关于不区分大小写的 std::string.find()的主要内容,如果未能解决你的问题,请参考以下文章

自己的 std::string::find 实现(蛮力搜索)

std::string::find 总是返回 string::npos 甚至

std::string::find 返回值问题

C++ std::string::find()函数(在字符串中查找内容)

C++中std::string::find_last_of用法

C++ std::string::find_first_of()函数(在字符串中搜索与其参数中指定的任何字符匹配的第一个字符)