Сравнение строк без учета регистра в C++

Как лучше всего выполнять сравнение строк без учета регистра в C++ без преобразования строки в верхний или нижний регистр?

Укажите, совместимы ли эти методы с Unicode и насколько они переносимы.

В c обычно заставляли перебирать всю струну, а затем сравнивать таким образом - или бросать свое собственное сравнение: P

Michael Dorgan 22.05.2010 05:10

@ [Adam] (# 11679): Хотя этот вариант хорош с точки зрения удобства использования, он плох с точки зрения производительности, поскольку создает ненужные копии. Я могу кое-что пропустить, но я считаю, что лучший (не-Unicode) способ - использовать std::stricmp. В противном случае почитайте, что Herb должен сказать.

Konrad Rudolph 26.08.2008 16:17

у более позднего вопроса есть более простой ответ: strcasecmp (по крайней мере, для компиляторов BSD и POSIX) stackoverflow.com/questions/9182912/…

Móż 06.11.2013 01:39

@ Mσᶎ этот вопрос также имеет такой ответ с важной оговоркой, что strcasecmp не является частью стандарта и отсутствует по крайней мере в одном общем компиляторе.

Mark Ransom 01.12.2014 22:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
372
4
509 379
30
Перейти к ответу Данный вопрос помечен как решенный

Ответы 30

Предполагая, что вы ищете метод, а не магическую функцию, которая уже существует, честно говоря, лучшего способа нет. Мы все могли бы писать фрагменты кода с умными приемами для ограниченного набора символов, но, в конце концов, в какой-то момент вам придется преобразовать символы.

Лучше всего выполнить это преобразование до сравнения. Это дает вам большую гибкость, когда дело доходит до схем кодирования, о которых ваш фактический оператор сравнения не должен знать.

Вы, конечно, можете «спрятать» это преобразование за своей собственной строковой функцией или классом, но вам все равно нужно преобразовать строки перед сравнением.

Строковые функции Visual C++ с поддержкой юникода: http://msdn.microsoft.com/en-us/library/cc194799.aspx

тот, который вы, вероятно, ищете, - _wcsnicmp

По иронии судьбы, «широкие символьные коды» Microsoft НЕ являются чистыми для Юникода, потому что они не обрабатывают нормализацию Юникода.

vy32 19.06.2011 03:36

FYI, strcmp() и stricmp() уязвимы для переполнения буфера, поскольку они просто обрабатываются, пока не достигнут нулевого терминатора. Безопаснее использовать _strncmp() и _strnicmp().

Верно, хотя перезапись буфера значительно менее опасна, чем перезапись буфера.

Adam Rosenfield 17.11.2008 23:47

stricmp() и strnicmp() не являются частью стандарта POSIX :-( Однако вы можете найти strcasecmp(), strcasecmp_l(), strncasecmp() и strncasecmp_l() в заголовке POSIX strings.h :-) см. opengroup.org

oHo 11.04.2013 16:27

@AdamRosenfield «хуже» зависит от контекста. С точки зрения безопасности, иногда весь смысл перезаписи заключается в том, чтобы перечитать.

karmakaze 21.03.2015 19:22

У меня был хороший опыт использования Международные компоненты для библиотек 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

DevSolar 13.12.2017 16:15

Я пытаюсь сколотить хороший ответ из всех сообщений, так что помогите мне отредактировать это:

Вот способ сделать это, хотя он и преобразует строки и не поддерживает 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 () и что раньше было '::'? Благодарность

VextoR 11.03.2011 11:40

Это не очень эффективное решение - вы делаете копии обеих строк и преобразуете их все, даже если первый символ отличается.

Timmmm 13.03.2011 21:14

Если вы все равно собираетесь сделать копию, почему бы не передать ее по значению, а не по ссылке?

celticminstrel 21.06.2015 05:17

Думаю, это простой совет без наддува. :)

cmcromance 08.11.2016 11:33

вопрос явно задает не transform всю строку перед сравнением

Sandburg 06.06.2019 18:43

std::tolower должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.

Evg 26.09.2020 12:53

Если вы работаете в системе 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 26.09.2020 12:50

@Evg, так if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2)) подойдет?

Shadow2531 27.09.2020 13:51

Да, это должно быть правильным способом.

Evg 27.09.2020 14:54

@Evg, исправлено. Спасибо.

Shadow2531 27.09.2020 16:31

Я написал версию 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;

Это работает для обычных символов, но не будет работать для всего Юникода, так как капитализация не обязательно двунаправленная (есть хороший пример на греческом языке с использованием сигмы, который я не могу вспомнить прямо сейчас; что-то вроде этого имеет два нижних и один верхний регистр , и в любом случае вы не сможете получить правильное сравнение)

coppro 25.11.2008 00:02

Это действительно неправильный путь. Чувствительность к регистру не должна быть свойством самих строк. Что происходит, когда один и тот же строковый объект требует сравнения как с учетом регистра, так и без учета регистра?

Ferruccio 25.11.2008 00:07

Если чувствительность к регистру не подходит для того, чтобы быть «частью» строки, то и функция find () вообще не подходит. Что для вас может быть правдой, и это нормально. ИМО, самое лучшее в C++ - это то, что он не навязывает программисту конкретную парадигму. Это то, что вы хотите / вам нужно.

John Dibling 25.11.2008 09:20

На самом деле, я думаю, что большинство гуру C++ (например, из комитета по стандартам) согласны с тем, что было ошибкой помещать find () в std :: basic_string вместе с множеством других вещей, которые с таким же успехом можно было бы поместить в бесплатные функции. . Кроме того, есть некоторые проблемы с помещением его в шрифт.

Andreas Magnusson 25.11.2008 10:50

Как отмечали другие, в этом решении есть два основных недостатка (по иронии судьбы, один - это интерфейс, а другой - реализация ;-)).

Konrad Rudolph 25.11.2008 11:15

… Но поскольку Херб Саттер совершил ту же ошибку, и я, по-видимому, даже связал его статью (я этого не помню!), Я не могу жаловаться.

Konrad Rudolph 25.11.2008 11:18
Ответ принят как подходящий

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? Думаю, нет.

vladr 30.10.2010 04:23

Нет, потому что UTF-8 позволяет кодировать идентичные строки с помощью разных двоичных кодов из-за акцентов, комбинаций, проблем с двунаправленным текстом и т. д.

vy32 19.06.2011 03:35

@ vy32 Это совершенно неверно! Комбинации UTF-8 исключают друг друга. Он всегда должен использовать кратчайшее возможное представление, в противном случае это искаженная последовательность UTF-8 или кодовая точка, с которой необходимо обращаться с осторожностью.

Wiz 11.11.2011 03:44

@Wiz, вы игнорируете проблему нормализации строки Unicode. ñ может быть представлено как объединение ˜, за которым следует n, или как символ ñ. Перед выполнением сравнения необходимо использовать нормализацию строки Unicode. Пожалуйста, ознакомьтесь с техническим отчетом Unicode № 15, unicode.org/reports/tr15

vy32 11.11.2011 07:21

@ vy32 (я никогда не отвечал на этот комментарий), но это еще не значит, что он не поддерживает UTF-8. Сравнение должно подвергаться нормализации либо до полного разложения, либо до полного составления, чтобы устранить такие проблемы эквивалентности. Тем не менее, ничто не мешает функции iequals делать это.

Wiz 23.01.2012 02:36

На самом деле я не уверен, подходит ли std :: basic_string для кодирования переменной длины, такого как UTF8. Конечно, это что-то сделает. Во многих случаях (в частности, в подмножестве ASCII) это будет работать правильно. Но я думаю, что std :: basic_string (и, следовательно, все его использования) могут предполагать кодирование фиксированной длины. Но, возможно, это аргумент шаблона Traits, который должен иметь дело с этим.

Adam Badura 10.06.2012 03:42

Необходимо рассмотреть две отдельные проблемы: преобразование между UTF-32 и UTF-8 (фиксированное и детерминированное) и состав строки из кодовых точек UTF-32 (ни фиксированных, ни детерминированных).

Jerry Coffin 21.03.2013 18:59

В общем случае для Unicode это не сработает. «ß» и «SS» должны сравниваться как равные, но алгоритмы Boost String не справляются с этим.

dalle 05.06.2013 15:43

@dalle Почему «ß» и «SS» должны сравниваться как равные? В каких сценариях использования? Большинство не хотят "ß" в Швейцарии, например.

wonko realtime 10.12.2013 20:15

@wonkorealtime: потому что "ß", преобразованное в верхний регистр, равно "SS": fileformat.info/info/unicode/char/df/index.htm

Mooing Duck 30.05.2014 03:11

Небольшая проблема, "#include "не сработало, потому что не удалось разрешить предикат, в то время как версия без комментариев сработала. Как правильно использовать версию с минимальной зависимостью заголовков?

Andrew Hundt 30.06.2015 21:57

Также обратите внимание, что регистр также может зависеть от языка, даже в Unicode. то есть в турецком языке верхний регистр U + 0069 (нижний регистр i) равен U + 0130 (верхний регистр I с точкой), а не U + 0049 (верхний регистр I). Их немного, но unicode.org/Public/UNIDATA/SpecialCasing.txt

Rahly 12.06.2017 13:18

Определенно нет Unicode-friendly. Другой пример - греческая заглавная буква «Σ», которая преобразуется в строчную букву «σ» или «ς» в зависимости от позиции слова. boost::iequals подчиняется std::locale(), который не может справиться с этими вещами. Все, что не ОИТ, на данный момент лежит сквозь зубы.

DevSolar 07.02.2020 15:57

OP не спрашивал, как это сделать с помощью Boost.

Craig B 24.11.2020 02:41

Просто отметьте, какой метод вы в конечном итоге выберете, если этот метод включает использование strcmp, что предлагают некоторые ответы:

strcmp не работает с данными Unicode в целом. В общем, он даже не работает с байтовыми кодировками Unicode, такими как utf-8, поскольку strcmp выполняет только побайтовые сравнения, а кодовые точки Unicode, закодированные в utf-8, могут занимать более 1 байта. Единственный конкретный случай Unicode, который strcmp правильно обрабатывает, - это когда строка, закодированная с помощью байтовой кодировки, содержит только кодовые точки ниже U + 00FF - тогда достаточно побайтного сравнения.

Вы можете использовать strcasecmp в Unix или stricmp в Windows.

Одна вещь, о которой до сих пор не упоминалось, заключается в том, что если вы используете строки stl с этими методами, полезно сначала сравнить длину двух строк, поскольку эта информация уже доступна вам в классе строк. Это может помешать проведению дорогостоящего сравнения строк, если две сравниваемые строки изначально даже не имеют одинаковой длины.

Поскольку определение длины строки состоит из перебора каждого символа в строке и сравнения его с 0, действительно ли существует такая большая разница между этим и простым сравнением строк сразу? Я предполагаю, что вы получите лучшую локальность памяти в случае, когда обе строки не совпадают, но, вероятно, почти вдвое время выполнения в случае совпадения.

uliwitness 22.01.2014 17:28

C++ 11 указывает, что сложность std :: string :: length должна быть постоянной: cplusplus.com/reference/string/string/length

bradtgmurray 05.02.2014 01:37

Это забавный факт, но он не имеет здесь большого значения. strcasecmp () и stricmp () принимают строки C без декорирования, поэтому std :: string не используется.

uliwitness 05.02.2014 21:39

Эти методы вернут -1, если вы сравните «a» с «ab». Длина разная, но перед ab стоит «а». Таким образом, простое сравнение длин невозможно, если вызывающий абонент заботится о порядке.

Nathan 13.02.2014 03:33

В библиотеке Boost.String есть множество алгоритмов для сравнения без учета регистра и так далее.

Вы можете реализовать свое собственное, но зачем беспокоиться, когда это уже сделано?

Нет встроенного способа с std :: string?

WilliamKF 22.05.2010 04:58

Нет, нет.

Dean Harding 22.05.2010 05:06
"... зачем беспокоиться, когда это уже сделано?" - what if you are not using Boost? The OP did not have the tag with the question.
jww 08.02.2016 01:07

Воспользуйтесь преимуществами стандарта 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.

Zan Lynx 27.09.2012 01:25

Конечно, есть - для его же блага. Строка без учета регистра - это нечто другое: typedef std::basic_string<char, ci_char_traits<char> > istring, а не typedef std::basic_string<char, std::char_traits<char> > string.

Andreas Spindler 09.10.2012 13:24

Я знаю, что это скопировано непосредственно из GotW29, и я предполагаю, что что-то из этого широко цитируемого было правильным, но для меня (в Visual Studio 2005) функция поиска здесь не работает. Это приводит к тому, что basic_string :: find переполняет буфер и аварийно завершает работу. Мне пришлось изменить "return s;" на «return (n> = 0? s: NULL);».

njplumridge 28.03.2013 15:47

"Все, что тебе нужно сделать ..."

Tim MB 19.04.2013 14:03

Метод compare () дважды вызывает toupper () для каждого символа. Вероятно, следует буферизовать результат toupper (), чтобы уменьшить нагрузку на процессор.

Nathan 13.02.2014 03:30

@Nathan, вероятно, использует компилятор, который может выполнять базовую CSE для кода ...

The Paramagnetic Croissant 12.10.2014 11:41

Любая языковая конструкция, которая вызывает такое безумие в этом тривиальном случае, должна и может быть отброшена без сожаления.

Erik Aronesty 14.11.2014 17:17

@ErikAronesty, а вы бы порекомендовали ...?

Big McLargeHuge 15.11.2015 06:34

@DaveKennedy Я думаю, Эрик советует отказаться от человеческих языков, поскольку те - это языковые конструкции, которые вызывают это безумие. :-)

srm 21.03.2018 19:35

Хочу отметить, что вторым параметром find должен быть std::size_t, а не int. К сожалению, я не могу редактировать, потому что вопрос заблокирован. Также возможно реализовать find и compare с точки зрения eq, lt и ne.

Pharap 16.07.2018 17:31

Хотя это делает нечувствительную к регистру строку несовместимой с std::string, преобразование между ними с помощью конструктора диапазона тривиально.

celticminstrel 11.10.2019 21:37

Дополнительное наблюдение: на первый взгляд, этот метод несовместим с std::unordered_map. (Или, по крайней мере, реализация строкового хэша в стандартной библиотеке MSVC, похоже, не использует черты char для чего-либо.) Так что при использовании этого с std::unordered_map, вероятно, также потребуется специализация std::hash.

celticminstrel 19.03.2020 22:11

std::toupper должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.

Evg 26.09.2020 12:49

В C++ 17 мы можем использовать string_view для использования ci_char_traits

dodjango 02.10.2020 23:58

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;

dodjango 03.10.2020 00:06

Проблема с ускорением заключается в том, что вы должны зависеть от него и зависеть от него. В некоторых случаях непросто (например, 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.

Gretchen 10.03.2011 00:47

Ах, я не знал о bcp, он действительно полезен. Спасибо за информацию!

Timmmm 13.03.2011 21:15

Полезно знать простую версию, не зависящую от ускорения.

Deqing 17.05.2014 07:31

@Anna Необходимо создать и связать текстовую библиотеку ускорения. Он использует IBM ICU.

Behrouz.M 01.06.2015 09:46

Также доступно с C++ 11

martian 21.06.2018 21:06

std::equal недоступен в C++ 11.

Timmmm 22.06.2018 21:47

std::tolower должен вызывать нет непосредственно на char, требуется от static_cast до unsigned char.

Evg 26.09.2020 12:50

@Evg Это безумие. К счастью, это, похоже, одно из тех «технически неопределенных действий, но все в любом случае поступают разумно», так что де-факто, вероятно, все в порядке. Я проверил GCC, Clang и ICC с помощью -O3, и все они работают нормально. Я полагаю, если бы вы сделали компилятор, который этого не делал, он не смог бы скомпилировать много существующего кода.

Timmmm 02.03.2021 16:54

boost :: iequals несовместим с utf-8 в случае строки. Вы можете использовать boost :: locale.

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Основной - игнорировать диакритические знаки и регистр символов, сравнивая только базовые буквы. Например, «фасад» и «фасад» - это одно и то же.
  • Вторичный - игнорировать регистр символов, но учитывать акценты. «фасад» и «фасад» разные, но «фасад» и «фасад» одинаковы.
  • Третичный - учитывайте и корпус, и акценты: «фасад» и «фасад» - разные вещи. Не обращайте внимания на знаки препинания.
  • Четвертичный - учитывайте регистр, акценты и знаки препинания. Слова должны быть идентичными с точки зрения представления Unicode.
  • Идентично - как четвертичный, но также сравните кодовые точки.

По состоянию на начало 2013 года проект ICU, поддерживаемый IBM, является довольно хорошим ответом на это.

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

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вместо отдельных версий строки / строки?

uliwitness 22.01.2014 17:31

Как бы единый шаблон функции мог вызывать toupper или towupper, не прибегая к использованию специализации или макросов, перегрузка функции кажется более простой и подходящей реализацией, чем любая другая.

Neutrino 28.06.2015 18:39

Просто используйте 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++.

Thomas Daugaard 30.07.2013 13:09

Это соглашение о C++ в моем университете, но я буду помнить об этом при публикации здесь

reubenjohn 13.08.2013 22:04

stricmp - это расширение Microsoft AFAIK. Вместо этого в BSD есть strcasecmp ().

uliwitness 22.01.2014 17:30

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 01.06.2015 16:06

Это не поддерживает неанглийские наборы символов.

Robert Andrzejuk 29.04.2018 18:02

Если вам нужно чаще сравнивать исходную строку с другими строками, одним из элегантных решений является использование регулярного выражения.

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

Deqing 15.05.2015 08:18

плохая идея. Это худшее решение.

Behrouz.M 01.06.2015 16:05

Это не очень хорошее решение, но даже если вы захотите его использовать, вам понадобится L перед константами широкой строки, например L "TEST"

celticminstrel 21.06.2015 05:27

Было бы неплохо, если бы кто-нибудь мог объяснить, почему это худшее решение. Из-за проблем с производительностью? Создание регулярного выражения стоит дорого, но после этого сравнение должно быть очень быстрым.

smibe 30.09.2015 15:49

его можно использовать и переносить, основная проблема в том, что first не может содержать символы, которые использует регулярное выражение. Из-за этого его нельзя использовать как обычное сравнение строк. Он также будет медленнее, есть флаг, чтобы он работал так, как говорит smibe, но все равно не может использоваться в качестве общей функции.

Ben 17.08.2016 00:37

Простой способ сравнить строки, которые отличаются только строчными и прописными буквами, - это выполнить сравнение 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». Я сомневаюсь, что это кому-то нужно.

celticminstrel 21.06.2015 05:24

См. 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 27.03.2018 17:27

@plasmacel Затем используйте функцию, которая работает с / другими кодировками.

Brian Rodriguez 06.04.2018 18:05

Поздно к вечеринке, но вот вариант, который использует 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 нет метода сравнения без учета регистра ..

kyb 30.09.2016 18:52

«strcasecmp не является частью стандарта» - Марк Рэнсом 01 дек.

Liviu 21.10.2016 17:21

да, но в большинстве современных компиляторов он есть или его другой названный аналог. stricmp, strcmpi, strcasecmp и др. Спасибо. сообщение отредактировано.

kyb 21.10.2016 22:01

TODO: используйте cout << boolalpha, а не мой bool2str, потому что он неявно преобразовывает bool в символы для потока.

kyb 01.06.2017 15:15

Это в в библиотеках gcc.

Owl 08.08.2017 00:09

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.

ɲeuroburɳ 01.08.2017 17:06

Похоже, что вышеупомянутые решения не используют метод сравнения и не реализуют 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 и б) предположительно не переносимая.

Claus Jørgensen 23.08.2018 15:18

какой #include нам нужен, чтобы это работало?

ekkis 23.06.2019 00:37

@ekkis, чтобы использовать _stricmp, вы должны включить как вы можете прочитать здесь: docs.microsoft.com/en-us/cpp/c-runtime-library/reference/…

DAme 01.07.2019 10:47

Если вы не хотите использовать Библиотека 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 , возможно, вам потребуется включить его.

David Ledger 02.12.2018 15:00

Если вы будете использовать глобальную версию, подобную this :: toupper, вам может не понадобиться включать потому что есть две версии c версия и версия C++ с локалью, я думаю. Так что лучше использовать глобальную версию ":: toupper ()"

HaseeB Mir 02.12.2018 16:52

это решение не работает, когда одна из строк пуста: "" - оно возвращает истину в том случае, когда должно возвращать ложь

ekkis 23.06.2019 00:35

Другие вопросы по теме