Porównanie ciągów znaków bez rozróżniania wielkości liter w C ++ [zamknięte]

373

Jaki jest najlepszy sposób na porównywanie ciągów znaków bez rozróżniania wielkości liter w C ++ bez przekształcania łańcucha na wszystkie wielkie lub małe litery?

Wskaż, czy metody są przyjazne dla Unicode i jak są przenośne.

Adam
źródło
@ [Adam] (# 11679): Chociaż ten wariant jest dobry pod względem użyteczności, jest zły pod względem wydajności, ponieważ tworzy niepotrzebne kopie. Mogę coś przeoczyć, ale uważam, że najlepszym (innym niż Unicode) sposobem jest użycie std::stricmp. W przeciwnym razie przeczytaj, co ma do powiedzenia Herb .
Konrad Rudolph
W c zwykle jeden zmuszony był dotknąć całego struny, a następnie porównać w ten sposób - lub rzucić własne porównanie: P
Michael Dorgan
późniejsze pytanie ma prostszą odpowiedź: strcasecmp (przynajmniej dla kompilatorów BSD i POSIX) stackoverflow.com/questions/9182912/…
Móż
@ Mσᶎ to pytanie również ma tę odpowiedź, z ważnym zastrzeżeniem, które strcasecmpnie jest częścią standardu i brakuje go w co najmniej jednym wspólnym kompilatorze.
Mark Ransom,

Odpowiedzi:

317

Boost zawiera przydatny algorytm do tego:

#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
}
Obrabować
źródło
14
Czy to jest przyjazne dla UTF-8? Myślę, że nie.
vladr
18
Nie, ponieważ UTF-8 pozwala na kodowanie identycznych ciągów za pomocą różnych kodów binarnych, ze względu na akcenty,
kombinacje
10
@ vy32 To absolutnie niepoprawne! Kombinacje UTF-8 wzajemnie się wykluczają. Musi zawsze używać możliwie najkrótszej reprezentacji, jeśli nie, jest to zniekształcona sekwencja UTF-8 lub punkt kodowy, który należy traktować ostrożnie.
Wiz
48
@Wiz, ignorujesz problem normalizacji ciągów Unicode. ñ może być reprezentowane jako połączenie ˜, po którym następuje n lub znak ñ. Przed porównaniem należy użyć normalizacji ciągów Unicode. Zapoznaj się z raportem technicznym Unicode nr 15, unicode.org/reports/tr15
vy32 11.11.11
12
@wonkorealtime: ponieważ „ß” zamienione na wielkie litery to „SS”: fileformat.info/info/unicode/char/df/index.htm
Kaczka Mooing
118

Skorzystaj ze standardu char_traits. Przypomnijmy, że std::stringw rzeczywistości jest typedef dla std::basic_string<char>lub bardziej jawnie std::basic_string<char, std::char_traits<char> >. Ten char_traitstyp opisuje, w jaki sposób znaki się porównują, jak kopiują, jak rzutują itp. Wystarczy, że napiszesz nowy ciąg znaków basic_stringi nadasz mu własny zwyczaj, char_traitsktóry porównuje wielkość liter bez rozróżnienia.

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;

Szczegóły znajdują się na Guru tygodnia nr 29 .

wilhelmtell
źródło
10
O ile wiem z własnych eksperymentów, powoduje to, że twój nowy typ łańcucha jest niezgodny ze std :: string.
Zan Lynx,
8
Oczywiście, że tak - dla własnego dobra. Ciąg bez rozróżniania wielkości liter to coś innego: typedef std::basic_string<char, ci_char_traits<char> > istringnie typedef std::basic_string<char, std::char_traits<char> > string.
Andreas Spindler
232
„Wszystko, co musisz zrobić ...”
Tim MB
3
@Nathan prawdopodobnie używa kompilatora, który jest w stanie wykonać podstawowe CSE na kodzie ...
The Paramagnetic Croissant
17
Wszelkie konstrukcje językowe, które wymuszają takie szaleństwo w tym trywialnym przypadku, powinny i mogą zostać porzucone bez żalu.
Erik Aronesty
86

Problem z doładowaniem polega na tym, że musisz się połączyć i polegać na doładowaniu. W niektórych przypadkach nie jest to łatwe (np. Android).

A używanie char_traits oznacza wszystko porównania nie uwzględniają wielkości liter, co zwykle nie jest tym, czego chcesz.

To powinno wystarczyć. Powinien być dość wydajny. Nie obsługuje jednak Unicode ani nic takiego.

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

Aktualizacja: Bonusowa wersja 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);
                      });
}
Timmmm
źródło
27
W rzeczywistości biblioteka znaków doładowania jest biblioteką zawierającą tylko nagłówki, więc nie ma potrzeby łączenia z niczym. Możesz także użyć narzędzia „bcp” programu boost, aby skopiować tylko nagłówki ciągów do drzewa źródłowego, więc nie musisz wymagać pełnej biblioteki boost.
Gretchen
Ach, nie wiedziałem o bcp, wygląda to naprawdę przydatne. Dzięki za informację!
Timmmm
9
Dobrze wiedzieć, że jest to prosta i nie zależna od wersji wersja.
Deqing
2
@Anna biblioteka tekstowa doładowania musi zostać zbudowana i połączona. Korzysta z IBM ICU.
Behrouz.M
Dostępne również z C ++ 11
martian
58

Jeśli korzystasz z systemu POSIX, możesz użyć strcasecmp . Ta funkcja nie jest jednak częścią standardowego C i nie jest dostępna w systemie Windows. Spowoduje to porównanie bez rozróżniania wielkości liter na 8-bitowych znakach, o ile ustawieniem narodowym jest POSIX. Jeśli ustawieniami narodowymi nie jest POSIX, wyniki są niezdefiniowane (więc może wykonać zlokalizowane porównanie lub nie). Odpowiednik szerokich znaków nie jest dostępny.

W przeciwnym razie duża liczba historycznych implementacji biblioteki C ma funkcje stricmp () i strnicmp (). Visual C ++ w systemie Windows przemianował je wszystkie, poprzedzając je znakiem podkreślenia, ponieważ nie są one częścią standardu ANSI, więc w tym systemie nazywane są _stricmp lub _strnicmp . Niektóre biblioteki mogą mieć także funkcje równoważne szerokim znakom lub wielobajtom (zwykle nazywane np. Wcsicmp, mbcsicmp i tak dalej).

Zarówno C, jak i C ++ są w dużej mierze nieświadomi problemów związanych z internacjonalizacją, więc nie ma dobrego rozwiązania tego problemu, z wyjątkiem korzystania z biblioteki innej firmy. Sprawdź IBM ICU (International Components for Unicode), jeśli potrzebujesz solidnej biblioteki dla C / C ++. ICU jest przeznaczony zarówno dla systemów Windows, jak i Unix.

Derek Park
źródło
53

Czy mówisz o głupim porównaniu bez rozróżniania wielkości liter lub o pełnym znormalizowanym porównaniu Unicode?

Głupie porównanie nie znajdzie ciągów, które mogą być takie same, ale nie są binarne.

Przykład:

U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).

Wszystkie są równoważne, ale mają również różne reprezentacje binarne.

To powiedziawszy, Normalizacja Unicode powinna być obowiązkową lekturą, szczególnie jeśli planujesz wspierać Hangul, Thai i inne języki azjatyckie.

Ponadto IBM opatentował najbardziej zoptymalizowane algorytmy Unicode i udostępnił je publicznie. Utrzymują także implementację: IBM ICU

Moneta
źródło
2
Możesz zechcieć edytować ten link OIOM do strony.icu-project.org
DevSolar
31

boost :: iequals nie jest kompatybilny z utf-8 w przypadku napisów. Możesz użyć boost :: locale .

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Podstawowy - ignoruj ​​akcenty i wielkość liter, porównując tylko litery podstawowe. Na przykład „fasada” i „fasada” są takie same.
  • Wtórne - ignoruj ​​wielkość liter, ale rozważ akcenty. „fasada” i „fasada” są różne, ale „fasada” i „fasada” są takie same.
  • Trzeci - rozważ zarówno przypadek, jak i akcent: „Fasada” i „fasada” są różne. Zignoruj ​​interpunkcję.
  • Czwarty - rozważ wszystkie przypadki, akcenty i znaki interpunkcyjne. Słowa muszą być identyczne pod względem reprezentacji Unicode.
  • Identyczne - jak czwartorzędowe, ale również porównaj punkty kodowe.
Igor Milyakov
źródło
30

Moją pierwszą myślą o wersji innej niż Unicode było zrobienie czegoś takiego:


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(*c1) != tolower(*c2)) {
            return false;
        }
    }
    return true;
}
Shadow2531
źródło
20

Możesz używać w systemie strcasecmpUnix lub stricmpWindows.

Jedną z rzeczy, o których nie wspomniano do tej pory, jest to, że jeśli używasz ciągów stl z tymi metodami, warto najpierw porównać długość tych dwóch ciągów, ponieważ te informacje są już dostępne w klasie ciągów. Może to uniemożliwić wykonanie kosztownego porównania ciągów, jeśli dwa porównywane ciągi nie będą w ogóle nawet tej samej długości.

bradtgmurray
źródło
Skoro określenie długości łańcucha polega na iteracji nad każdym znakiem w łańcuchu i porównaniu go z wartością 0, to czy naprawdę jest tak duża różnica między tym a samym porównywaniem łańcuchów od razu? Wydaje mi się, że masz lepszą lokalizację pamięci w przypadku, gdy oba ciągi się nie zgadzają, ale prawdopodobnie prawie 2-krotnie czas działania w przypadku dopasowania.
uliwitness
3
C ++ 11 określa, że złożoność std :: string :: długości musi być stała: cplusplus.com/reference/string/string/length
bradtgmurray
1
To zabawny fakt, ale nie ma tu większego znaczenia. Zarówno strcasecmp (), jak i stricmp () pobierają nieekorowane ciągi C, więc nie jest zaangażowany ciąg std :: string.
wszechstronność
3
Te metody zwrócą -1, jeśli porównasz „a” vs „ab”. Długości są różne, ale „a” występuje przed „ab”. Tak więc zwykłe porównanie długości nie jest możliwe, jeśli rozmówcy zależy na zamówieniu.
Nathan
13

Próbuję zebrać dobrą odpowiedź ze wszystkich postów, więc pomóż mi edytować to:

Oto metoda na zrobienie tego, chociaż transformuje ciągi i nie jest przyjazna dla Unicode, powinna być przenośna, co jest plusem:

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

Z tego, co przeczytałem, jest on bardziej przenośny niż stricmp (), ponieważ stricmp () nie jest tak naprawdę częścią biblioteki std, ale jest implementowany tylko przez większość dostawców kompilatorów.

Aby uzyskać naprawdę przyjazną dla Unicode implementację, musisz wyjść poza bibliotekę std. Jedną dobrą biblioteką innej firmy jest IBM ICU (International Components for Unicode)

Również boost :: iequals zapewnia dość dobrą użyteczność do wykonywania tego rodzaju porównań.

Adam
źródło
czy możesz powiedzieć, co oznacza :: tolower, dlaczego możesz używać tolower zamiast tolower () i czym jest wcześniej „::”? dzięki
VextoR
17
To nie jest bardzo wydajne rozwiązanie - wykonujesz kopie obu ciągów i transformujesz je wszystkie, nawet jeśli pierwszy znak jest inny.
Timmmm,
2
Jeśli i tak zamierzasz wykonać kopię, dlaczego nie przekazać wartości zamiast referencji?
celticminstrel
Myślę, że to prosta wskazówka bez wzmocnienia. :)
cmcromance
1
pytanie wyraźnie pyta, aby nie transformcały ciąg przed porównaniem
Sandburg
12
str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})

Możesz użyć powyższego kodu w C ++ 14, jeśli nie masz możliwości użycia boost. Musisz używać std::towlowerdo szerokich znaków.

winorośl
źródło
4
Myślę, że musisz dodać a str1.size() == str2.size() &&z przodu, aby nie wykraczało poza granice, gdy str2 jest prefiksem str1.
ɲeuroburɳ
11

Boost.String Biblioteka posiada wiele algorytmów robi case-insenstive porównań i tak dalej.

Możesz wdrożyć własne, ale po co męczyć się, gdy już zostało to zrobione?

Dean Harding
źródło
1
Nie ma wbudowanego sposobu w std :: string?
WilliamKF
6
Nie, nie ma.
Dean Harding
3
„... po co męczyć się, gdy już zostało to zrobione?” - co jeśli nie używasz wzmocnienia? OP nie miał tagu z pytaniem.
jww
11

FYI strcmp()i stricmp()są podatne na przepełnienie bufora, ponieważ przetwarzają tylko, dopóki nie osiągną zerowego terminatora. Jest bezpieczniejszy w użyciu _strncmp()i _strnicmp().

Klin
źródło
6
To prawda, chociaż przeciążenie bufora jest znacznie mniej niebezpieczne niż zastąpienie bufora.
Adam Rosenfield,
4
stricmp()i strnicmp()nie są częścią standardu POSIX :-( Jednak można znaleźć strcasecmp(), strcasecmp_l(), strncasecmp()i strncasecmp_l()POSIX nagłówku strings.h:-) zobaczyć opengroup.org
olibre
2
@AdamRosenfield „gorsze” zależy od kontekstu. W bezpieczeństwie czasami nadrzędnym celem nadpisania jest przemyślenie.
karmakaze
10

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

Próbny

Brian Rodriguez
źródło
Ta metoda jest potencjalnie niebezpieczna i nieprzenośna. std::tolowerdziała tylko wtedy, gdy znak jest zakodowany w ASCII. Nie ma takiej gwarancji na std::string- więc może być łatwo niezdefiniowanym zachowaniem.
plasmacel
@plasmacel Następnie użyj funkcji, która działa z innymi kodowaniami.
Brian Rodriguez,
9

Ze względu na moje podstawowe potrzeby porównywania ciągów bez rozróżniania wielkości liter wolę nie używać biblioteki zewnętrznej, ani też nie chcę osobnej klasy ciągów z cechami bez rozróżniania wielkości liter, która jest niezgodna ze wszystkimi innymi ciągami.

Więc wymyśliłem to:

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

Prosta funkcja z jednym przeciążeniem dla char i innym dla whar_t. Nie używa niczego niestandardowego, więc powinno być dobrze na każdej platformie.

Porównanie równości nie bierze pod uwagę takich kwestii, jak kodowanie o zmiennej długości i normalizacja Unicode, ale basic_string nie obsługuje tego, o czym jestem świadomy, i zwykle nie jest to problem.

W przypadkach, w których wymagana jest bardziej wyrafinowana leksykograficzna manipulacja tekstem, wystarczy użyć biblioteki innej firmy, takiej jak Boost, czego należy się spodziewać.

Neutrino
źródło
2
Prawdopodobnie możesz zrobić tę jedną funkcję, jeśli utworzysz szablon i użyjesz podstawowego łańcucha <T> zamiast osobnych wersji łańcucha / łańcucha?
uliwitness
2
Jak szablon pojedynczej funkcji wywołałby touppera lub towuppera bez uciekania się do specjalizacji lub makr, przeciążenie funkcji wydaje się prostszą i bardziej odpowiednią implementacją niż jedno z nich.
Neutrino
9

Krótko i miło. Żadnych innych zależności, oprócz rozszerzonej biblioteki std C.

strcasecmp(str1.c_str(), str2.c_str()) == 0

zwraca true, jeśli str1i str2są równe. strcasecmpnie może istnieć, nie może być analogi stricmp, strcmpiitp

Przykładowy kod:

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

Wynik:

true
true
true
true
true
kyb
źródło
6
dziwne, że C ++ std :: string nie ma metody porównywania wielkości liter.
kyb 30.09.16
1
„strcasecmp nie jest częścią standardu” - Mark Ransom 1 grudnia 14 o 19:57
Liviu
tak, ale większość współczesnych kompilatorów ma go lub jego analog o innej nazwie. stricmp, strcmpi, strcasecmp, Itd. Dziękuję. edytowana wiadomość.
Kyb
DO ZROBIENIA: użyj cout << boolalphazamiast mojego, bool2strponieważ domyślnie przekształca bool w znaki dla strumienia.
Kyb
Jest w <strings.h> w bibliotekach gcc.
Sowa
7

Wykonanie tego bez użycia wzmocnienia można wykonać, uzyskując wskaźnik łańcucha C za c_str()pomocą strcasecmp:

std::string str1 ="aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
    //case insensitive equal 
}
DavidS
źródło
6

Zakładając, że szukasz metody, a nie magicznej funkcji, która już istnieje, szczerze mówiąc, nie ma lepszego sposobu. Wszyscy moglibyśmy pisać fragmenty kodu za pomocą sprytnych sztuczek dla ograniczonych zestawów znaków, ale pod koniec dnia w którymś momencie musisz przekonwertować znaki.

Najlepszym podejściem do tej konwersji jest zrobienie tego przed porównaniem. Zapewnia to dużą elastyczność, jeśli chodzi o schematy kodowania, których faktyczny operator porównania powinien być nieświadomy.

Możesz oczywiście „ukryć” tę konwersję za własną funkcją lub klasą łańcuchową, ale przed porównaniem wciąż musisz przekonwertować łańcuchy.

Andrew Grant
źródło
6

Napisałem rozróżniającą wielkość liter wersję char_traits do użycia ze std :: basic_string w celu wygenerowania std :: string, który nie rozróżnia wielkości liter podczas wykonywania porównań, wyszukiwania itp. Za pomocą wbudowanych funkcji członkowskich std :: basic_string.

Innymi słowy, chciałem zrobić coś takiego.

std::string a = "Hello, World!";
std::string b = "hello, world!";

assert( a == b );

... z którymi std :: string nie może sobie poradzić. Oto użycie moich nowych char_traits:

std::istring a = "Hello, World!";
std::istring b = "hello, world!";

assert( a == b );

... a oto implementacja:

/*  ---

        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;
John Dibling
źródło
1
Działa to dla zwykłych znaków, ale nie działa dla wszystkich znaków Unicode, ponieważ kapitalizacja niekoniecznie musi być dwukierunkowa (istnieje dobry przykład w języku greckim dotyczący sigmy, którego teraz nie pamiętam; coś takiego ma dwie małe i jedną wielką literę , i tak nie można uzyskać właściwego porównania)
coppro
1
To naprawdę niewłaściwy sposób. Rozróżnianie wielkości liter nie powinno być własnością samych ciągów znaków. Co dzieje się, gdy ten sam ciąg znaków wymaga zarówno rozróżniania wielkości liter, jak i rozróżniania wielkości liter?
Ferruccio
Jeśli rozróżnianie wielkości liter nie jest właściwe, aby być „częścią” ciągu, wówczas funkcja find () w ogóle nie jest. Co dla ciebie może być prawdą i to jest w porządku. IMO największą zaletą C ++ jest to, że nie wymusza on określonego paradygmatu na programatorze. To jest to, czego chcesz / potrzebujesz.
John Dibling,
Właściwie uważam, że większość guru z C ++ (jak ci z komitetu standardów) zgadza się, że błędem było umieszczenie find () w std :: basic_string <> wraz z całą masą innych rzeczy, które równie dobrze można umieścić w darmowe funkcje. Poza tym istnieją pewne problemy z umieszczeniem tego typu.
Andreas Magnusson,
Jak zauważyli inni, w tym rozwiązaniu są dwie główne rzeczy złe (jak na ironię, jedna to interfejs, a druga implementacja ;-)).
Konrad Rudolph,
4

Mam duże doświadczenie w korzystaniu z bibliotek International Components for Unicode - są one niezwykle wydajne i zapewniają metody konwersji, obsługę ustawień regionalnych, renderowanie daty i godziny, mapowanie wielkości liter (których nie chcesz) oraz sortowanie , który obejmuje porównanie bez rozróżniania wielkości liter i akcentów (i więcej). Użyłem tylko bibliotek C ++, ale wydaje się, że mają również wersję Java.

Istnieją metody przeprowadzania znormalizowanych porównań, o których mowa w @Coincoin, i mogą nawet uwzględniać ustawienia regionalne - na przykład (i ten przykład sortowania, nie ściśle równości), tradycyjnie w języku hiszpańskim (w Hiszpanii), kombinacja liter „ll” sortuje między „l” i „m”, więc „lz” <„ll” <„ma”.

Blair Conrad
źródło
4

Wystarczy użyć do rozróżniania wielkości liter strcmp()i / strcmpi()lub bez stricmp()rozróżniania wielkości liter. Które są w pliku nagłówkowym<string.h>

format:

int strcmp(const char*,const char*);    //for case sensitive
int strcmpi(const char*,const char*);   //for case insensitive

Stosowanie:

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;

Wynik

Apple i ApPlE są takie same

a jest przed b, więc jabłko jest przed piłką

reubenjohn
źródło
2
Oddaj głos, ponieważ nie jest to sposób na robienie rzeczy w C ++.
Thomas Daugaard
To konwencja c ++ na moim uniwersytecie, ale będę o tym pamiętać,
pisząc
4
stricmp to rozszerzenie Microsoft AFAIK. BSD wydaje się mieć zamiast tego strcasecmp ().
uliwitness
3

Późno na imprezę, ale tutaj jest wariant, który wykorzystuje std::locale, a zatem poprawnie obsługuje turecki:

auto tolower = std::bind1st(
    std::mem_fun(
        &std::ctype<char>::tolower),
    &std::use_facet<std::ctype<char> >(
        std::locale()));

daje funktor, który wykorzystuje aktywne ustawienia regionalne do konwersji znaków na małe litery, których można następnie użyć std::transformdo wygenerowania ciągów małych liter:

std::string left = "fOo";
transform(left.begin(), left.end(), left.begin(), tolower);

Działa to również w przypadku wchar_tciągów opartych.

Simon Richter
źródło
2

Tylko uwaga na temat jakiejkolwiek metody, którą ostatecznie wybierzesz, jeśli ta metoda obejmuje użycie strcmp , niektóre odpowiedzi sugerują:

strcmpogólnie nie działa z danymi Unicode. Ogólnie rzecz biorąc, nie działa nawet z kodowaniem Unicode opartym na bajtach, takim jak utf-8, ponieważ strcmptylko porównania bajt na bajt i punkty kodu Unicode zakodowane w utf-8 mogą zająć więcej niż 1 bajt. Jedynym konkretnym przypadkiem strcmppoprawnie obsługującym przypadek Unicode jest sytuacja, gdy łańcuch zakodowany za pomocą kodowania bajtowego zawiera tylko punkty kodowe poniżej U + 00FF - wtedy wystarczy porównanie bajtów na bajt.

Johann Gerell
źródło
2

Na początku 2013 r. Projekt ICU, prowadzony przez IBM, jest dość dobrą odpowiedzią na to.

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

ICU to „kompletna, przenośna biblioteka Unicode, która ściśle śledzi standardy branżowe”. W przypadku konkretnego problemu porównywania ciągów obiekt Collation robi to, co chcesz.

Projekt Mozilla przyjął ICU do internacjonalizacji w Firefoksie w połowie 2012 roku; możesz śledzić dyskusję inżynierską, w tym kwestie dotyczące systemów kompilacji i rozmiaru pliku danych, tutaj:

michaelhanson
źródło
2

Wygląda na to, że powyższe rozwiązania nie używają metody porównywania i nie wdrażają ponownie sumy, więc oto moje rozwiązanie i mam nadzieję, że zadziała dla ciebie (działa dobrze).

#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;
}
Jagadeesh Pulamarasetti
źródło
1

Jeśli nie chcesz używać biblioteki Boost, oto rozwiązanie, używając tylko standardowego nagłówka 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;
}
HaSeeB MiR
źródło
Uważam, że std :: toupper jest w #include <cctype>, być może trzeba go dołączyć.
David Ledger
Jeśli użyjesz wersji globalnej takiej jak ta :: toupper, może nie być konieczne dołączenie <typu>, ponieważ są dwie wersje wersja c i wersja c ++ z ustawieniami narodowymi. Lepiej więc użyć wersji globalnej „:: toupper ()”
HaSeeB MiR
to rozwiązanie zawodzi, gdy jeden z ciągów znaków jest pusty: „” - zwraca true w tym przypadku, gdy powinien zwrócić false
ekkis
0

Jeśli musisz częściej porównywać łańcuch źródłowy z innymi łańcuchami, eleganckim rozwiązaniem jest użycie wyrażenia regularnego.

std::wstring first = L"Test";
std::wstring second = L"TEST";

std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);
smibe
źródło
Próbowałem tego, ale błąd kompilacji: error: conversion from 'const char [5]' to non-scalar type 'std::wstring {aka std::basic_string<wchar_t>}' requested
Deqing
kiepski pomysł. To najgorsze rozwiązanie.
Behrouz.M
To nie jest dobre rozwiązanie, ale nawet jeśli chcesz go użyć, potrzebujesz litery L przed swoimi najszerszymi stałymi, np. L „TEST”
celticminstrel
Byłoby miło, gdyby ktoś mógł wyjaśnić, dlaczego jest to najgorsze rozwiązanie. Z powodu problemów z wydajnością? Tworzenie wyrażenia regularnego jest kosztowne, ale potem porównanie powinno być naprawdę szybkie.
smibe
jest użyteczny i przenośny, głównym problemem jest to, że po pierwsze nie może zawierać żadnych znaków, których używa regex. Z tego powodu nie można go używać jako ogólnego porównania ciągów. Będzie również wolniejszy, jest flaga, która sprawia, że ​​działa tak, jak mówi Smibe, ale nadal nie może być używana jako funkcja ogólna.
Ben
0

Prostym sposobem na porównanie dwóch ciągów znaków w c ++ (przetestowanych pod Windows) jest użycie _stricmp

// Case insensitive (could use equivalent _stricmp)  
result = _stricmp( string1, string2 );  

Jeśli chcesz używać ze std :: string, przykład:

std::string s1 = string("Hello");
if ( _stricmp(s1.c_str(), "HELLO") == 0)
   std::cout << "The string are equals.";

Aby uzyskać więcej informacji tutaj: https://msdn.microsoft.com/it-it/library/e0z9k731.aspx

Dama
źródło
Warto przeczytać dodatkowo stackoverflow.com/a/12414441/95309 , ponieważ jest to a) funkcja C ib) podobno nieprzenośna.
Claus Jørgensen
co #include potrzebujemy, aby to zadziałało?
ekkis
1
@ekkis, aby użyć _stricmp, musisz dołączyć <string.h>, jak możesz przeczytać tutaj: docs.microsoft.com/en-us/cpp/c-runtime-library/reference/…
DAme
-1
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;
        }
      }
}

można by to prawdopodobnie uczynić znacznie bardziej wydajnym, ale tutaj jest nieporęczna wersja z wszystkimi odsłoniętymi fragmentami.

nie wszystko jest przenośne, ale działa dobrze z tym, co jest na moim komputerze (nie mam pojęcia, jestem obrazami, a nie słowami)

użytkownik4578093
źródło
To nie jest obsługa Unicode, co jest pytaniem.
Behrouz.M
To nie obsługuje zestawów znaków innych niż angielski.
Robert Andrzejuk
-3

Prostym sposobem na porównanie ciągów znaków, które różnią się jedynie małymi i wielkimi literami, jest porównanie ascii. Wszystkie wielkie i małe litery różnią się o 32 bity w tabeli ascii, korzystając z tych informacji, mamy następujące ...

    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
    }
}
Craig Stoddard
źródło
3
Zgodnie z tym, „++ j” zostanie znalezione jako „KKJ”, a „1234” będzie znalezione jako „QRST”. Wątpię, czy ktoś tego chce.
celticminstrel