Как лучше всего выполнять сравнение строк без учета регистра в C++ без преобразования строки в верхний или нижний регистр?
Укажите, совместимы ли эти методы с Unicode и насколько они переносимы.
@ [Adam] (# 11679): Хотя этот вариант хорош с точки зрения удобства использования, он плох с точки зрения производительности, поскольку создает ненужные копии. Я могу кое-что пропустить, но я считаю, что лучший (не-Unicode) способ - использовать std::stricmp. В противном случае почитайте, что Herb должен сказать.
у более позднего вопроса есть более простой ответ: strcasecmp (по крайней мере, для компиляторов BSD и POSIX) stackoverflow.com/questions/9182912/…
@ Mσᶎ этот вопрос также имеет такой ответ с важной оговоркой, что strcasecmp не является частью стандарта и отсутствует по крайней мере в одном общем компиляторе.





Предполагая, что вы ищете метод, а не магическую функцию, которая уже существует, честно говоря, лучшего способа нет. Мы все могли бы писать фрагменты кода с умными приемами для ограниченного набора символов, но, в конце концов, в какой-то момент вам придется преобразовать символы.
Лучше всего выполнить это преобразование до сравнения. Это дает вам большую гибкость, когда дело доходит до схем кодирования, о которых ваш фактический оператор сравнения не должен знать.
Вы, конечно, можете «спрятать» это преобразование за своей собственной строковой функцией или классом, но вам все равно нужно преобразовать строки перед сравнением.
Строковые функции Visual C++ с поддержкой юникода: http://msdn.microsoft.com/en-us/library/cc194799.aspx
тот, который вы, вероятно, ищете, - _wcsnicmp
По иронии судьбы, «широкие символьные коды» Microsoft НЕ являются чистыми для Юникода, потому что они не обрабатывают нормализацию Юникода.
FYI, strcmp() и stricmp() уязвимы для переполнения буфера, поскольку они просто обрабатываются, пока не достигнут нулевого терминатора. Безопаснее использовать _strncmp() и _strnicmp().
Верно, хотя перезапись буфера значительно менее опасна, чем перезапись буфера.
stricmp() и strnicmp() не являются частью стандарта POSIX :-( Однако вы можете найти strcasecmp(), strcasecmp_l(), strncasecmp() и strncasecmp_l() в заголовке POSIX strings.h :-) см. opengroup.org
@AdamRosenfield «хуже» зависит от контекста. С точки зрения безопасности, иногда весь смысл перезаписи заключается в том, чтобы перечитать.
У меня был хороший опыт использования Международные компоненты для библиотек Unicode - они чрезвычайно мощные и предоставляют методы для преобразования, поддержки локали, рендеринга даты и времени, сопоставления регистра (которое вам, похоже, не нужно) и сопоставление, которое включает case- сравнение без акцента (и многое другое). Я использовал только версию библиотек для C++, но, похоже, у них также есть версия для Java.
Существуют методы для выполнения нормализованных сравнений, на которые ссылается @Coincoin, и они могут даже учитывать локаль - например (и это пример сортировки, а не строгое равенство), традиционно в испанском (в Испании) буквенная комбинация "ll" сортирует между "л" и "м", поэтому "лз"
Вы говорите о глупом сравнении без учета регистра или о полном нормализованном сравнении Unicode?
Тупое сравнение не найдет строк, которые могут быть одинаковыми, но не равны двоично.
Пример:
U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).
Все они эквивалентны, но имеют разные двоичные представления.
Тем не менее, Юникод нормализация должен быть обязательным для чтения, особенно если вы планируете поддерживать хангыль, тайский и другие азиатские языки.
Кроме того, IBM в значительной степени запатентовала наиболее оптимизированные алгоритмы Unicode и сделала их общедоступными. Они также поддерживают реализацию: IBM ICU
Возможно, вы захотите отредактировать эту ссылку ICU на site.icu-project.org
Я пытаюсь сколотить хороший ответ из всех сообщений, так что помогите мне отредактировать это:
Вот способ сделать это, хотя он и преобразует строки и не поддерживает 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 () фактически не является частью библиотеки std, а реализуется только большинством поставщиков компиляторов.
Чтобы получить действительно дружественную к Unicode реализацию, похоже, вы должны выйти за пределы библиотеки std. Одна хорошая сторонняя библиотека - IBM ICU (Международные компоненты для Unicode)
Также boost :: iequals предоставляет довольно хорошую утилиту для подобного сравнения.
Подскажите, пожалуйста, что означает :: tolower, почему вы можете использовать tolower вместо tolower () и что раньше было '::'? Благодарность
Это не очень эффективное решение - вы делаете копии обеих строк и преобразуете их все, даже если первый символ отличается.
Если вы все равно собираетесь сделать копию, почему бы не передать ее по значению, а не по ссылке?
Думаю, это простой совет без наддува. :)
вопрос явно задает не transform всю строку перед сравнением
std::tolower должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.
Если вы работаете в системе POSIX, вы можете использовать strcasecmp. Однако эта функция не является частью стандартного языка C и недоступна в Windows. Это будет выполнять сравнение без учета регистра для 8-битных символов, если локаль - POSIX. Если языковой стандарт не POSIX, результаты не определены (поэтому может быть выполнено локализованное сравнение, а может и нет). Эквивалент для широких символов недоступен.
В противном случае большое количество исторических реализаций библиотеки C имеют функции stricmp () и strnicmp (). Visual C++ в Windows переименовал все это, поставив перед ними знак подчеркивания, потому что они не являются частью стандарта ANSI, поэтому в этой системе они называются _stricmp или _strnicmp. Некоторые библиотеки также могут иметь функции, эквивалентные расширенным символам или многобайтовым файлам (обычно называемые, например, wcsicmp, mbcsicmp и т. д.).
C и C++ в значительной степени игнорируют проблемы интернационализации, поэтому нет хорошего решения этой проблемы, кроме использования сторонней библиотеки. Ознакомьтесь с IBM ICU (Международные компоненты для Unicode), если вам нужна надежная библиотека для C / C++. ICU предназначен как для Windows, так и для Unix-систем.
Моей первой мыслью о версии, отличной от 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;
}
std::tolower должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.
@Evg, так if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2)) подойдет?
Да, это должно быть правильным способом.
@Evg, исправлено. Спасибо.
Я написал версию char_traits без учета регистра для использования с std :: basic_string, чтобы сгенерировать std :: string, которая не чувствительна к регистру при выполнении сравнений, поиска и т. д. С использованием встроенных функций-членов std :: basic_string.
Другими словами, я хотел сделать что-то подобное.
std::string a = "Hello, World!";
std::string b = "hello, world!";
assert( a == b );
... который std :: string не может обработать. Вот как используются мои новые char_traits:
std::istring a = "Hello, World!";
std::istring b = "hello, world!";
assert( a == b );
... а вот реализация:
/* ---
Case-Insensitive char_traits for std::string's
Use:
To declare a std::string which preserves case but ignores case in comparisons & search,
use the following syntax:
std::basic_string<char, char_traits_nocase<char> > noCaseString;
A typedef is declared below which simplifies this use for chars:
typedef std::basic_string<char, char_traits_nocase<char> > istring;
--- */
template<class C>
struct char_traits_nocase : public std::char_traits<C>
{
static bool eq( const C& c1, const C& c2 )
{
return ::toupper(c1) == ::toupper(c2);
}
static bool lt( const C& c1, const C& c2 )
{
return ::toupper(c1) < ::toupper(c2);
}
static int compare( const C* s1, const C* s2, size_t N )
{
return _strnicmp(s1, s2, N);
}
static const char* find( const C* s, size_t N, const C& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if ( ::toupper(s[i]) == ::toupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::toupper(c1) == ::toupper(c2) ;
}
};
template<>
struct char_traits_nocase<wchar_t> : public std::char_traits<wchar_t>
{
static bool eq( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) == ::towupper(c2);
}
static bool lt( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) < ::towupper(c2);
}
static int compare( const wchar_t* s1, const wchar_t* s2, size_t N )
{
return _wcsnicmp(s1, s2, N);
}
static const wchar_t* find( const wchar_t* s, size_t N, const wchar_t& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if ( ::towupper(s[i]) == ::towupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::towupper(c1) == ::towupper(c2) ;
}
};
typedef std::basic_string<char, char_traits_nocase<char> > istring;
typedef std::basic_string<wchar_t, char_traits_nocase<wchar_t> > iwstring;
Это работает для обычных символов, но не будет работать для всего Юникода, так как капитализация не обязательно двунаправленная (есть хороший пример на греческом языке с использованием сигмы, который я не могу вспомнить прямо сейчас; что-то вроде этого имеет два нижних и один верхний регистр , и в любом случае вы не сможете получить правильное сравнение)
Это действительно неправильный путь. Чувствительность к регистру не должна быть свойством самих строк. Что происходит, когда один и тот же строковый объект требует сравнения как с учетом регистра, так и без учета регистра?
Если чувствительность к регистру не подходит для того, чтобы быть «частью» строки, то и функция find () вообще не подходит. Что для вас может быть правдой, и это нормально. ИМО, самое лучшее в C++ - это то, что он не навязывает программисту конкретную парадигму. Это то, что вы хотите / вам нужно.
На самом деле, я думаю, что большинство гуру C++ (например, из комитета по стандартам) согласны с тем, что было ошибкой помещать find () в std :: basic_string вместе с множеством других вещей, которые с таким же успехом можно было бы поместить в бесплатные функции. . Кроме того, есть некоторые проблемы с помещением его в шрифт.
Как отмечали другие, в этом решении есть два основных недостатка (по иронии судьбы, один - это интерфейс, а другой - реализация ;-)).
… Но поскольку Херб Саттер совершил ту же ошибку, и я, по-видимому, даже связал его статью (я этого не помню!), Я не могу жаловаться.
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
}
Это дружелюбно к UTF-8? Думаю, нет.
Нет, потому что UTF-8 позволяет кодировать идентичные строки с помощью разных двоичных кодов из-за акцентов, комбинаций, проблем с двунаправленным текстом и т. д.
@ vy32 Это совершенно неверно! Комбинации UTF-8 исключают друг друга. Он всегда должен использовать кратчайшее возможное представление, в противном случае это искаженная последовательность UTF-8 или кодовая точка, с которой необходимо обращаться с осторожностью.
@Wiz, вы игнорируете проблему нормализации строки Unicode. ñ может быть представлено как объединение ˜, за которым следует n, или как символ ñ. Перед выполнением сравнения необходимо использовать нормализацию строки Unicode. Пожалуйста, ознакомьтесь с техническим отчетом Unicode № 15, unicode.org/reports/tr15
@ vy32 (я никогда не отвечал на этот комментарий), но это еще не значит, что он не поддерживает UTF-8. Сравнение должно подвергаться нормализации либо до полного разложения, либо до полного составления, чтобы устранить такие проблемы эквивалентности. Тем не менее, ничто не мешает функции iequals делать это.
На самом деле я не уверен, подходит ли std :: basic_string для кодирования переменной длины, такого как UTF8. Конечно, это что-то сделает. Во многих случаях (в частности, в подмножестве ASCII) это будет работать правильно. Но я думаю, что std :: basic_string (и, следовательно, все его использования) могут предполагать кодирование фиксированной длины. Но, возможно, это аргумент шаблона Traits, который должен иметь дело с этим.
Необходимо рассмотреть две отдельные проблемы: преобразование между UTF-32 и UTF-8 (фиксированное и детерминированное) и состав строки из кодовых точек UTF-32 (ни фиксированных, ни детерминированных).
В общем случае для Unicode это не сработает. «ß» и «SS» должны сравниваться как равные, но алгоритмы Boost String не справляются с этим.
@dalle Почему «ß» и «SS» должны сравниваться как равные? В каких сценариях использования? Большинство не хотят "ß" в Швейцарии, например.
@wonkorealtime: потому что "ß", преобразованное в верхний регистр, равно "SS": fileformat.info/info/unicode/char/df/index.htm
Небольшая проблема, "#include
Также обратите внимание, что регистр также может зависеть от языка, даже в Unicode. то есть в турецком языке верхний регистр U + 0069 (нижний регистр i) равен U + 0130 (верхний регистр I с точкой), а не U + 0049 (верхний регистр I). Их немного, но unicode.org/Public/UNIDATA/SpecialCasing.txt
Определенно нет Unicode-friendly. Другой пример - греческая заглавная буква «Σ», которая преобразуется в строчную букву «σ» или «ς» в зависимости от позиции слова. boost::iequals подчиняется std::locale(), который не может справиться с этими вещами. Все, что не ОИТ, на данный момент лежит сквозь зубы.
OP не спрашивал, как это сделать с помощью Boost.
Просто отметьте, какой метод вы в конечном итоге выберете, если этот метод включает использование strcmp, что предлагают некоторые ответы:
strcmp не работает с данными Unicode в целом. В общем, он даже не работает с байтовыми кодировками Unicode, такими как utf-8, поскольку strcmp выполняет только побайтовые сравнения, а кодовые точки Unicode, закодированные в utf-8, могут занимать более 1 байта. Единственный конкретный случай Unicode, который strcmp правильно обрабатывает, - это когда строка, закодированная с помощью байтовой кодировки, содержит только кодовые точки ниже U + 00FF - тогда достаточно побайтного сравнения.
Вы можете использовать strcasecmp в Unix или stricmp в Windows.
Одна вещь, о которой до сих пор не упоминалось, заключается в том, что если вы используете строки stl с этими методами, полезно сначала сравнить длину двух строк, поскольку эта информация уже доступна вам в классе строк. Это может помешать проведению дорогостоящего сравнения строк, если две сравниваемые строки изначально даже не имеют одинаковой длины.
Поскольку определение длины строки состоит из перебора каждого символа в строке и сравнения его с 0, действительно ли существует такая большая разница между этим и простым сравнением строк сразу? Я предполагаю, что вы получите лучшую локальность памяти в случае, когда обе строки не совпадают, но, вероятно, почти вдвое время выполнения в случае совпадения.
C++ 11 указывает, что сложность std :: string :: length должна быть постоянной: cplusplus.com/reference/string/string/length
Это забавный факт, но он не имеет здесь большого значения. strcasecmp () и stricmp () принимают строки C без декорирования, поэтому std :: string не используется.
Эти методы вернут -1, если вы сравните «a» с «ab». Длина разная, но перед ab стоит «а». Таким образом, простое сравнение длин невозможно, если вызывающий абонент заботится о порядке.
В библиотеке Boost.String есть множество алгоритмов для сравнения без учета регистра и так далее.
Вы можете реализовать свое собственное, но зачем беспокоиться, когда это уже сделано?
Нет встроенного способа с std :: string?
Нет, нет.
Воспользуйтесь преимуществами стандарта char_traits. Напомним, что std::string на самом деле является typedef для std::basic_string<char> или, точнее, std::basic_string<char, std::char_traits<char> >. Тип char_traits описывает, как сравниваются символы, как они копируются, как они приводятся и т. д. Все, что вам нужно сделать, это ввести новую строку вместо basic_string и предоставить ей свой собственный 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.
Насколько я знаю из моих собственных экспериментов, это делает ваш новый строковый тип несовместимым с std :: string.
Конечно, есть - для его же блага. Строка без учета регистра - это нечто другое: typedef std::basic_string<char, ci_char_traits<char> > istring, а не typedef std::basic_string<char, std::char_traits<char> > string.
Я знаю, что это скопировано непосредственно из GotW29, и я предполагаю, что что-то из этого широко цитируемого было правильным, но для меня (в Visual Studio 2005) функция поиска здесь не работает. Это приводит к тому, что basic_string :: find переполняет буфер и аварийно завершает работу. Мне пришлось изменить "return s;" на «return (n> = 0? s: NULL);».
"Все, что тебе нужно сделать ..."
Метод compare () дважды вызывает toupper () для каждого символа. Вероятно, следует буферизовать результат toupper (), чтобы уменьшить нагрузку на процессор.
@Nathan, вероятно, использует компилятор, который может выполнять базовую CSE для кода ...
Любая языковая конструкция, которая вызывает такое безумие в этом тривиальном случае, должна и может быть отброшена без сожаления.
@ErikAronesty, а вы бы порекомендовали ...?
@DaveKennedy Я думаю, Эрик советует отказаться от человеческих языков, поскольку те - это языковые конструкции, которые вызывают это безумие. :-)
Хочу отметить, что вторым параметром find должен быть std::size_t, а не int. К сожалению, я не могу редактировать, потому что вопрос заблокирован. Также возможно реализовать find и compare с точки зрения eq, lt и ne.
Хотя это делает нечувствительную к регистру строку несовместимой с std::string, преобразование между ними с помощью конструктора диапазона тривиально.
Дополнительное наблюдение: на первый взгляд, этот метод несовместим с std::unordered_map. (Или, по крайней мере, реализация строкового хэша в стандартной библиотеке MSVC, похоже, не использует черты char для чего-либо.) Так что при использовании этого с std::unordered_map, вероятно, также потребуется специализация std::hash.
std::toupper должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.
В C++ 17 мы можем использовать string_view для использования ci_char_traits
std::string s1{ "Ignore my CASE" }; std::string s2{ "ignore my case" }; std::basic_string_view<std::string::value_type, ci_char_traits> ci_view{ s1.c_str() }; std::cout << std::boolalpha << "\"" << s1 << "\" equals \"" << s2 << "\": " << (s1.compare(s2) == 0) << std::endl; std::cout << "\"" << s1 << "\" equals \"" << s2 << "\" (ignore casing): " << (ci_view.compare(s2.c_str()) == 0) << std::endl;
Проблема с ускорением заключается в том, что вы должны зависеть от него и зависеть от него. В некоторых случаях непросто (например, Android).
А использование char_traits означает, что все ваши сравнения нечувствительны к регистру, что обычно не то, что вам нужно.
Этого должно хватить. Он должен быть достаточно эффективным. Не обрабатывает юникод или что-то еще.
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);
});
}
Фактически, библиотека строк boost - это библиотека только для заголовков, поэтому нет необходимости на что-либо ссылаться. Кроме того, вы можете использовать утилиту boost 'bcp' для копирования только заголовков строк в ваше исходное дерево, поэтому вам не нужно требовать полную библиотеку boost.
Ах, я не знал о bcp, он действительно полезен. Спасибо за информацию!
Полезно знать простую версию, не зависящую от ускорения.
@Anna Необходимо создать и связать текстовую библиотеку ускорения. Он использует IBM ICU.
Также доступно с C++ 11
std::equal недоступен в C++ 11.
std::tolower должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.
@Evg Это безумие. К счастью, это, похоже, одно из тех «технически неопределенных действий, но все в любом случае поступают разумно», так что де-факто, вероятно, все в порядке. Я проверил GCC, Clang и ICC с помощью -O3, и все они работают нормально. Я полагаю, если бы вы сделали компилятор, который этого не делал, он не смог бы скомпилировать много существующего кода.
boost :: iequals несовместим с utf-8 в случае строки. Вы можете использовать boost :: locale.
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
По состоянию на начало 2013 года проект ICU, поддерживаемый IBM, является довольно хорошим ответом на это.
ICU - это «полная портативная библиотека Unicode, которая точно соответствует отраслевым стандартам». Для конкретной проблемы сравнения строк объект Collation делает то, что вы хотите.
Проект Mozilla принял ICU для интернационализации в Firefox в середине 2012 года; вы можете отслеживать технические обсуждения, включая вопросы систем сборки и размера файлов данных, здесь:
Для моих основных нужд сравнения строк без учета регистра я предпочитаю не использовать внешнюю библиотеку и не хочу иметь отдельный строковый класс с нечувствительными к регистру признаками, несовместимыми со всеми другими моими строками.
Итак, я придумал следующее:
bool icasecmp(const string& l, const string& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](string::value_type l1, string::value_type r1)
{ return toupper(l1) == toupper(r1); });
}
bool icasecmp(const wstring& l, const wstring& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](wstring::value_type l1, wstring::value_type r1)
{ return towupper(l1) == towupper(r1); });
}
Простая функция с одной перегрузкой для char и другой для whar_t. Не использует ничего нестандартного, поэтому подойдет на любой платформе.
При сравнении равенства не будут учитываться такие проблемы, как кодирование переменной длины и нормализация Unicode, но basic_string не поддерживает это, о чем я все равно знаю, и обычно это не проблема.
В случаях, когда требуются более сложные лексикографические манипуляции с текстом, вам просто нужно использовать стороннюю библиотеку, такую как Boost, чего и следовало ожидать.
Вероятно, вы могли бы сделать эту одну функцию, если бы вы сделали ее шаблоном и использовали basic_string
Как бы единый шаблон функции мог вызывать toupper или towupper, не прибегая к использованию специализации или макросов, перегрузка функции кажется более простой и подходящей реализацией, чем любая другая.
Просто используйте 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 одинаковы
А стоит перед б, поэтому яблоко стоит перед мячом
Голосуйте против, потому что это вряд ли способ делать что-то в C++.
Это соглашение о C++ в моем университете, но я буду помнить об этом при публикации здесь
stricmp - это расширение Microsoft AFAIK. Вместо этого в BSD есть strcasecmp ().
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, о чем был задан вопрос.
Это не поддерживает неанглийские наборы символов.
Если вам нужно чаще сравнивать исходную строку с другими строками, одним из элегантных решений является использование регулярного выражения.
std::wstring first = L"Test";
std::wstring second = L"TEST";
std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);
Пробовал, но ошибка компиляции: error: conversion from 'const char [5]' to non-scalar type 'std::wstring {aka std::basic_string<wchar_t>}' requested
плохая идея. Это худшее решение.
Это не очень хорошее решение, но даже если вы захотите его использовать, вам понадобится L перед константами широкой строки, например L "TEST"
Было бы неплохо, если бы кто-нибудь мог объяснить, почему это худшее решение. Из-за проблем с производительностью? Создание регулярного выражения стоит дорого, но после этого сравнение должно быть очень быстрым.
его можно использовать и переносить, основная проблема в том, что first не может содержать символы, которые использует регулярное выражение. Из-за этого его нельзя использовать как обычное сравнение строк. Он также будет медленнее, есть флаг, чтобы он работал так, как говорит smibe, но все равно не может использоваться в качестве общей функции.
Простой способ сравнить строки, которые отличаются только строчными и прописными буквами, - это выполнить сравнение ascii. Все заглавные и строчные буквы отличаются на 32 бита в таблице ascii, используя эту информацию, мы имеем следующее ...
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
}
}
В соответствии с этим «++ j» будет найден равным «KKJ», а «1234» будет найден равным «QRST». Я сомневаюсь, что это кому-то нужно.
См. std::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 () {
char foo[] = "Apple";
char 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, foo + 5, bar, bar + 9);
std::cout << '\n';
std::cout << "Using mycomp as comparison object: ";
std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9, mycomp);
std::cout << '\n';
return 0;
}
Этот метод потенциально небезопасен и не переносится. std::tolower работает, только если символ закодирован в ASCII. Для std::string такой гарантии нет, поэтому его поведение легко может быть неопределенным.
@plasmacel Затем используйте функцию, которая работает с / другими кодировками.
Поздно к вечеринке, но вот вариант, который использует std::locale и, следовательно, правильно обрабатывает турецкий язык:
auto tolower = std::bind1st(
std::mem_fun(
&std::ctype<char>::tolower),
&std::use_facet<std::ctype<char> >(
std::locale()));
дает вам функтор, который использует активный языковой стандарт для преобразования символов в нижний регистр, который затем можно использовать через std::transform для генерации строк в нижнем регистре:
std::string left = "fOo";
transform(left.begin(), left.end(), left.begin(), tolower);
Это также работает для строк на основе wchar_t.
Сделать это без использования Boost можно, получив указатель строки C с помощью c_str() и используя strcasecmp:
std::string str1 = "aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
//case insensitive equal
}
Коротко и красиво. Никаких других зависимостей, кроме расширенный std C lib.
strcasecmp(str1.c_str(), str2.c_str()) == 0
возвращает истинный, если str1 и str2 равны.
strcasecmp может не существовать, могут быть аналоги stricmp, strcmpi и т. д.
Пример кода:
#include <iostream>
#include <string>
#include <string.h> //For strcasecmp(). Also could be found in <mem.h>
using namespace std;
/// Simple wrapper
inline bool str_ignoreCase_cmp(std::string const& s1, std::string const& s2) {
if (s1.length() != s2.length())
return false; // optimization since std::string holds length in variable.
return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}
/// Function object - comparator
struct StringCaseInsensetiveCompare {
bool operator()(std::string const& s1, std::string const& s2) {
if (s1.length() != s2.length())
return false; // optimization since std::string holds length in variable.
return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator()(const char *s1, const char * s2){
return strcasecmp(s1,s2)==0;
}
};
/// Convert bool to string
inline char const* bool2str(bool b){ return b?"true":"false"; }
int main()
{
cout<< bool2str(strcasecmp("asd","AsD")==0) <<endl;
cout<< bool2str(strcasecmp(string{"aasd"}.c_str(),string{"AasD"}.c_str())==0) <<endl;
StringCaseInsensetiveCompare cmp;
cout<< bool2str(cmp("A","a")) <<endl;
cout<< bool2str(cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
cout<< bool2str(str_ignoreCase_cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
return 0;
}
Выход:
true
true
true
true
true
странно, что в C++ std :: string нет метода сравнения без учета регистра ..
«strcasecmp не является частью стандарта» - Марк Рэнсом 01 дек.
да, но в большинстве современных компиляторов он есть или его другой названный аналог. stricmp, strcmpi, strcasecmp и др. Спасибо. сообщение отредактировано.
TODO: используйте cout << boolalpha, а не мой bool2str, потому что он неявно преобразовывает bool в символы для потока.
Это в
str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})
Вы можете использовать приведенный выше код в C++ 14, если не можете использовать boost. Вы должны использовать std::towlower для широких символов.
Я думаю, вам нужно добавить str1.size() == str2.size() && на передний план, чтобы он не выходил за пределы, когда str2 является префиксом str1.
Похоже, что вышеупомянутые решения не используют метод сравнения и не реализуют total снова, поэтому вот мое решение и надеюсь, что оно сработает для вас (оно работает нормально).
#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;
}
Простой способ сравнить две строки в 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
В дополнение к этому ответу стоит прочитать stackoverflow.com/a/12414441/95309, так как это а) функция C и б) предположительно не переносимая.
какой #include нам нужен, чтобы это работало?
@ekkis, чтобы использовать _stricmp, вы должны включить
Если вы не хотите использовать Библиотека Boost, то вот решение, использующее только стандартный заголовок io C++.
#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
Если вы будете использовать глобальную версию, подобную this :: toupper, вам может не понадобиться включать
это решение не работает, когда одна из строк пуста: "" - оно возвращает истину в том случае, когда должно возвращать ложь
В c обычно заставляли перебирать всю струну, а затем сравнивать таким образом - или бросать свое собственное сравнение: P