Chcę przekonwertować std::string
na małe litery. Zdaję sobie sprawę z tej funkcji tolower()
, jednak w przeszłości miałem problemy z tą funkcją i nie jest ona idealna, ponieważ używa się jej zstd::string
wymagałoby iteracji nad każdą postacią.
Czy istnieje alternatywa, która działa w 100% przypadków?
c++
string
c++-standard-library
tolower
Konrad
źródło
źródło
Odpowiedzi:
Na podstawie niezbyt często zadawanych pytań :
Naprawdę nie uciekniesz bez iteracji po każdej postaci. Nie ma sposobu, aby dowiedzieć się, czy postać jest pisana małymi lub dużymi literami.
Jeśli naprawdę nie cierpisz
tolower()
, oto specjalistyczna alternatywa tylko dla ASCII, której nie polecam używać:Należy pamiętać, że
tolower()
może to zrobić tylko substytucja na jeden bajt, co jest niewłaściwe dla wielu skryptów, zwłaszcza jeśli używa się kodowania wielobajtowego, takiego jak UTF-8.źródło
char
do::tolower(int)
.) Musisz upewnić się, że nie przejdziesz wartości ujemnej.::tolower
może równie dobrze spowodować awarię, jest to UB dla danych innych niż ASCII.Zwiększenie zapewnia algorytm łańcuchowy do tego :
Lub w przypadku braku miejsca :
źródło
to_lower_copy
tl; dr
Użyj biblioteki ICU . Jeśli tego nie zrobisz, procedura konwersji po cichu załamie się na przypadkach, o których prawdopodobnie nawet nie wiesz.
Najpierw musisz odpowiedzieć na pytanie: jakie jest twoje kodowanie
std::string
? Czy to jest ISO-8859-1? A może ISO-8859-8? Lub Windows Codepage 1252? Czy to, czego używasz do konwersji wielkich i małych liter, wie o tym? (A może źle to kończy się w przypadku postaci0x7f
?)Jeśli używasz UTF-8 (jedyny rozsądny wybór wśród kodowań 8-bitowych) z
std::string
jako kontenerem, już oszukujesz siebie, aby uwierzyć, że nadal kontrolujesz rzeczy, ponieważ przechowujesz wielobajtową sekwencję znaków w kontenerze który nie zna koncepcji wielobajtowej. Nawet coś tak prostego jak.substr()
tykająca kula czasowa. (Ponieważ podział sekwencji wielobajtowej spowoduje niepoprawny (pod-) ciąg znaków.)I gdy tylko spróbujesz czegoś takiego
std::toupper( 'ß' )
, w jakimkolwiek kodowaniu, będziesz miał poważne kłopoty. (Ponieważ po prostu nie jest możliwe zrobienie tego „dobrze” ze standardową biblioteką, która może dostarczyć tylko jeden znak wyniku, a nie"SS"
tutaj potrzebny.) [1] Innym przykładem byłbystd::tolower( 'I' )
inny wynik, w zależności od ustawień regionalnych . W Niemczech'i'
byłoby poprawne; w Turcji'ı'
(LATIN SMALL LETTER DOTLESS I) to oczekiwany wynik (który w kodowaniu UTF-8 to więcej niż jeden bajt). Jeszcze innym przykładem jest język grecki Sigma , wielkie litery'∑'
, małe litery'σ'
... z wyjątkiem końca słowa, gdzie jest'ς'
.Więc, każda konwersja przypadku, która działa na znak na raz lub, co gorsza, bajt na raz, jest zepsuta przez projekt.
To jest sens, że standardowa biblioteka, po co to , zależnie od obsługiwanych ustawień narodowych, zależy od jest w stanie zrobić na komputerze, na którym działa twoje oprogramowanie ... i co robisz, jeśli nie jest?
Tak więc naprawdę szukasz klasy ciągów, która jest w stanie poradzić sobie z tym wszystkim poprawnie i nie jest to żaden z
std::basic_string<>
wariantów .(Uwaga C ++ 11:
std::u16string
istd::u32string
są lepsze , ale wciąż nie są idealne. Przyniesiono C ++ 20std::u8string
, ale wszystko to określa kodowanie. Pod wieloma innymi względami nadal nie znają mechaniki Unicode, takiej jak normalizacja, zestawianie, .. .)Podczas gdy Boost wygląda ładnie, pod względem API, Boost.Locale jest zasadniczo otoczeniem ICU .Jeśli Boost jest skompilowany z obsługą ICU ... jeśli nie jest, Boost.Locale jest ograniczony do obsługi ustawień regionalnych skompilowanej dla standardowej biblioteki.
I uwierz mi że kompilacja Boosta na OIOM-ie może czasem być prawdziwym bólem. (Nie ma wstępnie skompilowanych plików binarnych dla systemu Windows, więc musisz je dostarczyć razem z aplikacją, a to otwiera zupełnie nową puszkę robaków ...)
Więc osobiście poleciłbym uzyskanie pełnego wsparcia Unicode prosto z pyska konia i korzystanie z OIOM biblioteki :
Skompiluj (z G ++ w tym przykładzie):
To daje:
Zauważ, że konwersja Σ <-> σ w środku słowa, a konwersja Σ <-> ς na końcu słowa. Nie
<algorithm>
rozwiązanie nie może tego zapewnić.[1] W 2017 r. Rada Ortografii Niemieckiej orzekła, że „ẞ” U + 1E9E LATIN CAPITAL LETTER SHARP S może być oficjalnie używany jako opcja obok tradycyjnej konwersji „SS”, aby uniknąć dwuznaczności, np. W paszportach (gdzie nazwy są pisane wielkimi literami ). Mój piękny przykład, który stał się nieaktualny decyzją komisji ...
źródło
toupper
itolower
nadal działają na pojedyncze postacie. Klasa strun wciąż nie ma pojęcia normalizacji (np. Czy „ü” jest kodowane jako „u z diaeresisą” lub „u + diaeresis łączący”) lub gdzie łańcuch może być lub nie może być oddzielony. I tak dalej. łańcuch u8 jest (podobnie jak inne standardowe klasy łańcuchowe) odpowiedni do „przechodzenia”. Ale jeśli chcesz przetwarzać Unicode, potrzebujesz ICU.Korzystając z pętli C ++ 11 opartej na zakresie, prostszym kodem byłoby:
źródło
Jeśli ciąg zawiera znaki UTF-8 spoza zakresu ASCII, wówczas boost :: algorytm :: to_lower nie przekształci ich. Lepiej użyj boost :: locale :: to_lower, gdy zaangażowany jest UTF-8. Zobacz http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/conversions.html
źródło
Jest to kontynuacja odpowiedzi Stefana Mai: jeśli chcesz umieścić wynik konwersji w innym ciągu, musisz wcześniej przydzielić miejsce do przechowywania przed wywołaniem
std::transform
. Ponieważ STL przechowuje przekształcone znaki w docelowym iteratorze (zwiększając go przy każdej iteracji pętli), ciąg docelowy nie zostanie automatycznie zmieniony, a Ty ryzykujesz tupanie pamięci.źródło
Inne podejście wykorzystujące zakres oparty na pętli ze zmienną odniesienia
źródło
O ile mi wiadomo, biblioteki Boost są naprawdę kiepskie pod względem wydajności. Przetestowałem ich nieuporządkowaną mapę do STL i była średnio 3 razy wolniejsza (najlepszy przypadek 2, najgorszy był 10 razy). Również ten algorytm wygląda zbyt nisko.
Różnica jest tak duża, że jestem pewien, że jakikolwiek dodatek, który musisz zrobić,
tolower
aby zrównoważyć zwiększenie „na twoje potrzeby”, będzie znacznie szybszy niż doładowanie.Zrobiłem te testy na Amazon EC2, dlatego wydajność była różna podczas testu, ale nadal masz pomysł.
-O2
zrobiło to tak:Źródło:
Chyba powinienem przejść testy na dedykowanym komputerze, ale będę używać tego EC2, więc tak naprawdę nie muszę go testować na moim komputerze.
źródło
Najprostszym sposobem na konwersję łańcucha znaków na małą literę bez zawracania sobie głowy standardową przestrzenią nazw jest następująca
1: ciąg znaków z / bez spacji
2: ciąg bez spacji
źródło
std::ctype::tolower()
ze standardowej biblioteki lokalizacji C ++ zrobi to za Ciebie poprawnie. Oto przykład wyodrębniony ze strony referencyjnej tolowerźródło
const
? Wydaje się, że sprawia to, że jest nieco bardziej niechlujny (np. Nie wygląda na to, że można go użyćf.tolower()
), ponieważ musisz umieścić znaki w nowym ciągu. Czy użyłbyśtransform()
czegoś podobnegostd::bind1st( std::mem_fun() )
do operatora?tolower
zlocale
parametrem niejawne wywołanie funkcjiuse_facet
wydaje się być wąskim gardłem wydajności. Jeden z moich współpracowników osiągnął kilkukrotny wzrost prędkości poprzez zastąpienieboost::iequals
(która ma ten problem) wersją, którause_facet
jest wywoływana tylko raz poza pętlą.Alternatywą dla Boost jest POCO (pocoproject.org).
POCO oferuje dwa warianty:
Wersje „In Place” zawsze mają w nazwie nazwę „InPlace”.
Obie wersje pokazano poniżej:
źródło
Istnieje sposób na konwersję wielkich liter na małe BEZ wykonywania testów , i jest to dość proste. Korzystanie z funkcji clocale.h przez funkcję isupper () / makro powinno zająć się problemami związanymi z twoją lokalizacją, ale jeśli nie, zawsze możesz dostosować UtoL [] do treści twojego serca.
Biorąc pod uwagę, że znaki C są w rzeczywistości 8-bitowymi liczbami całkowitymi (w tej chwili ignorując szerokie zestawy znaków), możesz utworzyć 256-bajtową tablicę zawierającą alternatywny zestaw znaków, aw funkcji konwersji użyj znaków w łańcuchu jako indeksów dolnych do tablica konwersji.
Zamiast mapowania 1 do 1, należy podać elementom tablicy wielkimi literami wartości BYTE int dla małych liter. Przydatne mogą być tutaj islower () i isupper () .
Kod wygląda następująco ...
Takie podejście pozwoli jednocześnie na ponowne mapowanie dowolnych znaków, które chcesz zmienić.
Podejście to ma jedną ogromną zaletę w przypadku uruchamiania na nowoczesnych procesorach - nie ma potrzeby przewidywania rozgałęzień, ponieważ nie ma testów obejmujących rozgałęzienia. To oszczędza logikę przewidywania gałęzi CPU dla innych pętli i ma tendencję do zapobiegania utknięciu rurociągu.
Niektórzy tutaj mogą uznać to podejście za takie samo, jak w przypadku konwersji EBCDIC na ASCII.
źródło
Ponieważ żadna z odpowiedzi nie wspomniała o nadchodzącej bibliotece Ranges, która jest dostępna w bibliotece standardowej od C ++ 20 i obecnie jest osobno dostępna w GitHub as
range-v3
, chciałbym dodać sposób przeprowadzenia tej konwersji przy użyciu tej biblioteki .Aby zmodyfikować ciąg w miejscu:
Aby wygenerować nowy ciąg:
(Nie zapomnij o
#include <cctype>
wymaganych nagłówkach zakresów.)Uwaga: użycie
unsigned char
argumentu do lambda jest zainspirowane cppreferencją , która stwierdza:źródło
Moje własne funkcje szablonu, które wykonują wielkie / małe litery.
źródło
towlower
szerokich znaków, które obsługują UTF-16.Oto technika makro, jeśli chcesz czegoś prostego:
Należy jednak pamiętać, że komentarz @ AndreasSpindler do tej odpowiedzi nadal jest ważnym czynnikiem, jeśli pracujesz nad czymś, co nie jest tylko znakami ASCII.
źródło
void strtoupper(std::string& x) { std::transform (x.begin(), x.end(), x.begin(), ::toupper); }
x
może być prawidłowym wyrażeniem, które po prostu kompiluje się poprawnie, ale daje całkowicie fałszywe wyniki z powodu makr.Aby uzyskać więcej informacji: http://www.cplusplus.com/reference/locale/tolower/
źródło
Nie
Jest kilka pytań, które musisz sobie zadać przed wybraniem metody o małej obudowie.
Po uzyskaniu odpowiedzi na te pytania możesz zacząć szukać rozwiązania, które odpowiada Twoim potrzebom. Nie ma jednego uniwersalnego rozmiaru, który pasowałby do wszystkich na całym świecie!
źródło
Wypróbuj tę funkcję :)
źródło
Na platformach Microsoft można korzystać z
strlwr
rodziny funkcji: http://msdn.microsoft.com/en-us/library/hkxwh33z.aspxźródło
Fragment kodu
źródło
Użyj fplus :: to_lower_case ().
(fplus: https://github.com/Dobiasd/FunctionalPlus .
Wyszukaj „to_lower_case” w http://www.editgym.com/fplus-api-search/ )
źródło
Skopiuj, ponieważ zabroniono poprawiania odpowiedzi. Dziękuję
Wyjaśnienie:
for(auto& c : test)
jest tego rodzaju opartą na zakresie pętlą :for (
range_declaration
:
range_expression
)
loop_statement
range_declaration
: Wauto& c
tym przypadku automatyczny specyfikator służy do automatycznego odliczania typu. Zatem typ jest odejmowany od inicjalizatora zmiennych.
range_expression
:test
Zakres w tym przypadku to znaki ciągu
test
.Znaki ciągu
test
są dostępne jako odniesienie wewnątrz pętli for poprzez identyfikatorc
.źródło
C ++ nie ma zaimplementowanych dla łańcucha znaków metod tolower ani toupper, ale jest dostępny dla char. Można łatwo odczytać każdy znak ciągu, przekształcić go w wymaganą wielkość liter i umieścić z powrotem w ciągu. Przykładowy kod bez użycia biblioteki innej firmy:
Dla operacji na łańcuchach opartych na znakach : Dla każdego znaku w łańcuchu
źródło
Może to być kolejna prosta wersja do konwersji wielkich liter na małe i odwrotnie. Użyłem wersji społeczności VS2017 do skompilowania tego kodu źródłowego.
Uwaga: jeśli są znaki specjalne, należy je traktować za pomocą funkcji sprawdzania warunków.
źródło
Próbowałem std :: transform, wszystko, co dostaję, to obrzydliwy błąd kompilacji stl striptiz, który mogą zrozumieć tylko druidzi sprzed 200 lat (nie można przekonwertować z grypy na flibidi flabidi)
działa to dobrze i można je łatwo dostosować
źródło