Как преобразовать std::string в нижний регистр?
Я хочу преобразовать std::string
в нижнем регистре. Я в курсе функции tolower()
Тем не менее, в прошлом у меня были проблемы с этой функцией, и она вряд ли идеальна в любом случае, так как использование с std::string
потребует итерации по каждому символу.
Есть ли альтернатива, которая работает 100% времени?
34 ответа
Из этого:
#include <algorithm>
#include <string>
std::string data = "Abc";
std::transform(data.begin(), data.end(), data.begin(), ::tolower);
Вы действительно не собираетесь обходиться итерацией по каждому персонажу. Там нет никакого способа узнать, является ли символ строчными или прописными в противном случае.
Если ты действительно ненавидишь tolower()
Вот непереносимая альтернатива, которую я не рекомендую вам использовать:
char easytolower(char in) {
if(in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
std::transform(data.begin(), data.end(), data.begin(), easytolower);
Быть в курсе, что ::tolower()
может выполнять подстановку только для одного байта символа, что плохо подходит для многих сценариев, особенно если используется многобайтовое кодирование, например UTF-8.
Для этого есть алгоритм Boost:
#include <boost/algorithm/string.hpp>
std::string str = "HELLO, WORLD!";
boost::algorithm::to_lower(str); // modifies str
Или для не на месте:
#include <boost/algorithm/string.hpp>
const std::string str = "HELLO, WORLD!";
const std::string lower_str = boost::algorithm::to_lower_copy(str);
ТЛ; др
Используйте библиотеку ICU.
Сначала вы должны ответить на вопрос: какова кодировка вашего std::string
? Это ISO-8859-1? Или, возможно, ISO-8859-8? Или кодовая страница Windows 1252? Знает ли это то, что вы используете для преобразования прописных букв в строчные? (Или это с треском проваливается для персонажей более 0x7f
?)
Если вы используете UTF-8 (единственный разумный выбор среди 8-битных кодировок) с std::string
как контейнер, вы уже обманываете себя, полагая, что вы все еще контролируете вещи, потому что вы храните многобайтовую последовательность символов в контейнере, который не знает о многобайтовой концепции. Даже так просто, как .substr()
тикающая бомба замедленного действия. (Поскольку разбиение многобайтовой последовательности приведет к недопустимой (под) строке.)
И как только вы попробуете что-то вроде std::toupper( 'ß' )
В любой кодировке у вас большие проблемы. (Потому что просто невозможно сделать это "правильно" со стандартной библиотекой, которая может доставить только один символ результата, а не "SS"
необходимо здесь.) [1] Другой пример будет std::tolower( 'I' )
, который должен давать разные результаты в зависимости от локали. В Германии, 'i'
было бы правильно; в индейке, 'ı'
(LATIN SMALL LETTER I DOTLESS I) - ожидаемый результат.
Тогда есть смысл, что стандартная библиотека зависит от того, какие локали поддерживаются на машине, на которой работает ваше программное обеспечение... и что вы делаете, если это не так?
Так что вы действительно ищете строковый класс, который способен справиться со всем этим правильно, и это неstd::string
,
(Примечание C++11: std::u16string
а также std::u32string
лучше, но все еще не идеально.)
Хотя Boost выглядит неплохо, с точки зрения API, Boost.Locale по сути является оболочкой для ICU. Если Boost скомпилирован с поддержкой ICU... если нет, Boost.Locale ограничен поддержкой локали, скомпилированной для стандартной библиотеки.
И поверьте мне, заставить Boost компилироваться с ICU иногда бывает очень больно. (Для Windows нет предварительно скомпилированных двоичных файлов, так что вам придется поставлять их вместе с вашим приложением, и это открывает новую банку с червями...)
Поэтому лично я бы порекомендовал получить полную поддержку Unicode прямо изо рта лошади и напрямую использовать библиотеку ICU:
#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/locid.h>
#include <iostream>
int main()
{
char const * someString = "Eidenges\xe4\xdf";
icu::UnicodeString someUString( someString, "ISO-8859-1" );
// Setting the locale explicitly here for completeness.
// Usually you would use the user-specified system locale.
std::cout << someUString.toLower( "de_DE" ) << "\n";
std::cout << someUString.toUpper( "de_DE" ) << "\n";
return 0;
}
Компиляция (с G++ в этом примере):
g++ -Wall example.cpp -licuuc -licuio
Это дает:
eidengesäß
EIDENGESÄSS
[1] В 2017 году Совет по немецкой орфографии постановил, что "ẞ" U+1E9E LATIN CAPITAL LAPTER SHARP S может быть официально использовано, как вариант, помимо традиционной конверсии "SS", чтобы избежать двусмысленности, например, в паспортах (где имена пишутся с большой буквы)). Мой прекрасный пример, устарел по решению комитета...
Используя основанный на диапазоне цикл for C++11, более простой код будет:
#include <iostream> // std::cout
#include <string> // std::string
#include <locale> // std::locale, std::tolower
int main ()
{
std::locale loc;
std::string str="Test String.\n";
for(auto elem : str)
std::cout << std::tolower(elem,loc);
}
Другой подход с использованием диапазона на основе цикла с эталонной переменной
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
cout<<test<<endl;
Если строка содержит символы UTF-8 вне диапазона ASCII, то boost:: attribute:: to_lower не преобразует их. Лучше использовать boost:: locale:: to_lower, когда задействован UTF-8. См. http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/conversions.html
Это продолжение ответа Стефана Май: если вы хотите поместить результат преобразования в другую строку, вам необходимо предварительно выделить место для хранения перед вызовом std::transform
, Поскольку STL хранит преобразованные символы в итераторе назначения (увеличивая его на каждой итерации цикла), размер строки назначения не будет автоматически изменяться, и вы рискуете переполнить память.
#include <string>
#include <algorithm>
#include <iostream>
int main (int argc, char* argv[])
{
std::string sourceString = "Abc";
std::string destinationString;
// Allocate the destination space
destinationString.resize(sourceString.size());
// Convert the source string to lower case
// storing the result in destination string
std::transform(sourceString.begin(),
sourceString.end(),
destinationString.begin(),
::tolower);
// Output the result of the conversion
std::cout << sourceString
<< " -> "
<< destinationString
<< std::endl;
}
Самый простой способ преобразовать строку в loweercase, не заботясь о пространстве имен std, заключается в следующем
1: строка с / без пробелов
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
getline(cin,str);
//------------function to convert string into lowercase---------------
transform(str.begin(), str.end(), str.begin(), ::tolower);
//--------------------------------------------------------------------
cout<<str;
return 0;
}
2: строка без пробелов
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
cin>>str;
//------------function to convert string into lowercase---------------
transform(str.begin(), str.end(), str.begin(), ::tolower);
//--------------------------------------------------------------------
cout<<str;
return 0;
}
Насколько я вижу, библиотеки Boost действительно плохо работают с точки зрения производительности. Я проверил их unordered_map в STL, и в среднем он был в 3 раза медленнее (лучший случай 2, худший - 10 раз). Также этот алгоритм выглядит слишком низким.
Разница настолько велика, что я уверен, что любое дополнение, которое вам нужно сделать, чтобы tolower
сделать его равным ускорению "для ваших нужд" будет намного быстрее, чем ускорение.
Я провел эти тесты на Amazon EC2, поэтому производительность менялась во время теста, но вы все еще поняли.
./test
Elapsed time: 12365milliseconds
Elapsed time: 1640milliseconds
./test
Elapsed time: 26978milliseconds
Elapsed time: 1646milliseconds
./test
Elapsed time: 6957milliseconds
Elapsed time: 1634milliseconds
./test
Elapsed time: 23177milliseconds
Elapsed time: 2421milliseconds
./test
Elapsed time: 17342milliseconds
Elapsed time: 14132milliseconds
./test
Elapsed time: 7355milliseconds
Elapsed time: 1645milliseconds
-O2
сделал это так:
./test
Elapsed time: 3769milliseconds
Elapsed time: 565milliseconds
./test
Elapsed time: 3815milliseconds
Elapsed time: 565milliseconds
./test
Elapsed time: 3643milliseconds
Elapsed time: 566milliseconds
./test
Elapsed time: 22018milliseconds
Elapsed time: 566milliseconds
./test
Elapsed time: 3845milliseconds
Elapsed time: 569milliseconds
Источник:
string str;
bench.start();
for(long long i=0;i<1000000;i++)
{
str="DSFZKMdskfdsjfsdfJDASFNSDJFXCKVdnjsafnjsdfjdnjasnJDNASFDJDSFSDNJjdsanjfsdnfjJNFSDJFSD";
boost::algorithm::to_lower(str);
}
bench.end();
bench.start();
for(long long i=0;i<1000000;i++)
{
str="DSFZKMdskfdsjfsdfJDASFNSDJFXCKVdnjsafnjsdfjdnjasnJDNASFDJDSFSDNJjdsanjfsdnfjJNFSDJFSD";
for(unsigned short loop=0;loop < str.size();loop++)
{
str[loop]=tolower(str[loop]);
}
}
bench.end();
Я предполагаю, что мне следует пройти тестирование на выделенной машине, но я буду использовать этот EC2, поэтому мне не нужно тестировать его на своей машине.
Мой собственный шаблон функций, который выполняет верхний / нижний регистр.
#include <string>
#include <algorithm>
//
// Lowercases string
//
template <typename T>
std::basic_string<T> lowercase(const std::basic_string<T>& s)
{
std::basic_string<T> s2 = s;
std::transform(s2.begin(), s2.end(), s2.begin(), tolower);
return std::move(s2);
}
//
// Uppercases string
//
template <typename T>
std::basic_string<T> uppercase(const std::basic_string<T>& s)
{
std::basic_string<T> s2 = s;
std::transform(s2.begin(), s2.end(), s2.begin(), toupper);
return std::move(s2);
}
Я написал эту простую вспомогательную функцию:
#include <locale> // tolower
string to_lower(string s) {
for(char &c : s)
c = tolower(c);
return s;
}
Применение:
string s = "TEST";
cout << to_lower("HELLO WORLD"); // output: "hello word"
cout << to_lower(s); // won't change the original variable.
Альтернативой Boost является POCO (pocoproject.org).
POCO предлагает два варианта:
- Первый вариант делает копию без изменения оригинальной строки.
- Второй вариант изменяет исходную строку на месте.
Версии "на месте" всегда имеют в названии "In Place".
Обе версии демонстрируются ниже:
#include "Poco/String.h"
using namespace Poco;
std::string hello("Stack Overflow!");
// Copies "STACK OVERFLOW!" into 'newString' without altering 'hello.'
std::string newString(toUpper(hello));
// Changes newString in-place to read "stack overflow!"
toLowerInPlace(newString);
std::ctype::tolower()
из стандартной библиотеки локализации C++ правильно сделает это за вас. Вот пример, извлеченный из справочной страницы Tolower
#include <locale>
#include <iostream>
int main () {
std::locale::global(std::locale("en_US.utf8"));
std::wcout.imbue(std::locale());
std::wcout << "In US English UTF-8 locale:\n";
auto& f = std::use_facet<std::ctype<wchar_t>>(std::locale());
std::wstring str = L"HELLo, wORLD!";
std::wcout << "Lowercase form of the string '" << str << "' is ";
f.tolower(&str[0], &str[0] + str.size());
std::wcout << "'" << str << "'\n";
}
Поскольку ни в одном из ответов не упоминается о предстоящей библиотеке Ranges, которая доступна в стандартной библиотеке начиная с C++20 и в настоящее время отдельно доступна на GitHub как range-v3
Я хотел бы добавить способ выполнить это преобразование, используя его.
Чтобы изменить строку на месте:
str |= action::transform([](unsigned char c){ return std::tolower(c); });
Чтобы сгенерировать новую строку:
auto new_string = original_string
| view::transform([](unsigned char c){ return std::tolower(c); });
(Не забудьте #include <cctype>
и требуемые заголовки диапазонов.)
Примечание: использование unsigned char
поскольку аргумент к лямбде вдохновлен cppreference, который заявляет:
Как и все остальные функции из
<cctype>
, поведениеstd::tolower
не определено, если значение аргумента не может быть представлено какunsigned char
ни равноEOF
, Чтобы использовать эти функции безопасно с простымchar
с (илиsigned char
s) аргумент должен быть сначала преобразован вunsigned char
:char my_tolower(char ch) { return static_cast<char>(std::tolower(static_cast<unsigned char>(ch))); }
Точно так же они не должны напрямую использоваться со стандартными алгоритмами, когда тип значения итератора
char
или жеsigned char
, Вместо этого преобразуйте значение вunsigned char
первый:std::string str_tolower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), // static_cast<int(*)(int)>(std::tolower) // wrong // [](int c){ return std::tolower(c); } // wrong // [](char c){ return std::tolower(c); } // wrong [](unsigned char c){ return std::tolower(c); } // correct ); return s; }
На платформах Microsoft вы можете использовать strlwr
семейство функций: http://msdn.microsoft.com/en-us/library/hkxwh33z.aspx
// crt_strlwr.c
// compile with: /W3
// This program uses _strlwr and _strupr to create
// uppercase and lowercase copies of a mixed-case string.
#include <string.h>
#include <stdio.h>
int main( void )
{
char string[100] = "The String to End All Strings!";
char * copy1 = _strdup( string ); // make two copies
char * copy2 = _strdup( string );
_strlwr( copy1 ); // C4996
_strupr( copy2 ); // C4996
printf( "Mixed: %s\n", string );
printf( "Lower: %s\n", copy1 );
printf( "Upper: %s\n", copy2 );
free( copy1 );
free( copy2 );
}
Есть способ преобразовать верхний регистр в нижний БЕЗ выполнения тестов, и это довольно просто. Функция / макрос isupper() использует clocale.h для решения проблем, связанных с вашим местоположением, но если нет, вы всегда можете настроить UtoL[] на свое усмотрение.
Учитывая, что символы C на самом деле являются просто 8-битными целыми числами (игнорируя широкие наборы символов на данный момент), вы можете создать 256-байтовый массив, содержащий альтернативный набор символов, и в функции преобразования использовать символы в вашей строке в качестве индексов в конверсионный массив.
Вместо сопоставления 1-в-1 задайте для членов массива в верхнем регистре значения BYTE int для символов в нижнем регистре. Вы можете найти полезными islower() и isupper() здесь.
Код выглядит так...
#include <clocale>
static char UtoL[256];
// ----------------------------------------------------------------------------
void InitUtoLMap() {
for (int i = 0; i < sizeof(UtoL); i++) {
if (isupper(i)) {
UtoL[i] = (char)(i + 32);
} else {
UtoL[i] = i;
}
}
}
// ----------------------------------------------------------------------------
char *LowerStr(char *szMyStr) {
char *p = szMyStr;
// do conversion in-place so as not to require a destination buffer
while (*p) { // szMyStr must be null-terminated
*p = UtoL[*p];
p++;
}
return szMyStr;
}
// ----------------------------------------------------------------------------
int main() {
time_t start;
char *Lowered, Upper[128];
InitUtoLMap();
strcpy(Upper, "Every GOOD boy does FINE!");
Lowered = LowerStr(Upper);
return 0;
}
Этот подход, в то же время, позволит вам переназначить любые другие символы, которые вы хотите изменить.
Этот подход имеет одно огромное преимущество при работе на современных процессорах: нет необходимости делать предсказание ветвления, поскольку нет тестов, содержащих ветвление. Это сохраняет логику предсказания ветвления ЦП для других циклов и предотвращает задержки конвейера.
Некоторые здесь могут признать этот подход тем же, который использовался для преобразования EBCDIC в ASCII.
Попробуйте эту функцию:)
string toLowerCase(string str) {
int str_len = str.length();
string final_str = "";
for(int i=0; i<str_len; i++) {
char character = str[i];
if(character>=65 && character<=92) {
final_str += (character+32);
} else {
final_str += character;
}
}
return final_str;
}
Вот метод макроса, если вы хотите что-то простое:
#define STRTOLOWER(x) std::transform (x.begin(), x.end(), x.begin(), ::tolower)
#define STRTOUPPER(x) std::transform (x.begin(), x.end(), x.begin(), ::toupper)
#define STRTOUCFIRST(x) std::transform (x.begin(), x.begin()+1, x.begin(), ::toupper); std::transform (x.begin()+1, x.end(), x.begin()+1,::tolower)
Однако обратите внимание, что комментарий @AndreasSpindler к этому ответу по- прежнему является важным соображением, однако, если вы работаете над чем-то, что не является просто символами ASCII.
Есть ли альтернатива, которая работает 100% времени?
нет
Есть несколько вопросов, которые вы должны задать себе, прежде чем выбрать метод в нижнем регистре.
- Как строка закодирована? простой ASCII? UTF-8? какая-то форма расширенного унаследованного кодирования ASCII?
- Что вы подразумеваете под строчными? Правила отображения дел варьируются в зависимости от языка! Вы хотите что-то, что локализовано в локали пользователей? Вы хотите что-то, что ведет себя согласованно на всех системах, на которых работает ваше программное обеспечение? Вы просто хотите использовать символы ASCII в нижнем регистре и проходить через все остальное?
- Какие библиотеки доступны?
Получив ответы на эти вопросы, вы можете начать искать решение, соответствующее вашим потребностям. Не существует одного размера, который подходит всем, кто работает везде!
C++ не имеет методов tolower или toupper, реализованных для строки, но он доступен для char. Можно легко прочитать каждый символ строки, преобразовать его в нужный регистр и вернуть обратно в строку. Пример кода без использования сторонней библиотеки:
#include<iostream>
int main(){
std::string str = std::string("How IS The Josh");
for(char &ch : str){
ch = std::tolower(ch);
}
std::cout<<str<<std::endl;
return 0;
}
Для символьной операции над строкой: для каждого символа в строке
// tolower example (C++)
#include <iostream> // std::cout
#include <string> // std::string
#include <locale> // std::locale, std::tolower
int main ()
{
std::locale loc;
std::string str="Test String.\n";
for (std::string::size_type i=0; i<str.length(); ++i)
std::cout << std::tolower(str[i],loc);
return 0;
}
Для получения дополнительной информации: http://www.cplusplus.com/reference/locale/tolower/
Взгляните на отличный c++17 cpp-unicodelib (GitHub). Это один файл и только заголовок.
#include <exception>
#include <iostream>
#include <codecvt>
// cpp-unicodelib, downloaded from GitHub
#include "unicodelib.h"
#include "unicodelib_encodings.h"
using namespace std;
using namespace unicode;
// converter that allows displaying a Unicode32 string
wstring_convert<codecvt_utf8<char32_t>, char32_t> converter;
std::u32string in = U"Je suis là!";
cout << converter.to_bytes(in) << endl;
std::u32string lc = to_lowercase(in);
cout << converter.to_bytes(lc) << endl;
Выход
Je suis là!
je suis là!
Скопируйте, потому что было запрещено улучшать ответ. Спасибо ТАК
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
Объяснение:
for(auto& c : test)
основанный на диапазоне для цикла вида for (
range_declaration
:
range_expression
)
loop_statement
:
range_declaration
:auto& c
Здесь автоматический спецификатор используется для автоматического вывода типа. Таким образом, тип вычитается из инициализатора переменных.range_expression
:test
Диапазон в этом случае - символы строкиtest
,
Символы строки test
доступны в качестве ссылки внутри цикла для идентификатора c
,
Используйте fplus:: to_lower_case ().
(fplus: https://github.com/Dobiasd/FunctionalPlus.
Поиск 'to_lower_case' в http://www.editgym.com/fplus-api-search/)
fplus::to_lower_case(std::string("ABC")) == std::string("abc");
С другой стороны, существует очень распространенный вариант использования, который заключается в выполнении свертывания регистра без учета локали в строках Unicode. В этом случае можно получить хорошую производительность свертывания регистра, если осознать, что набор сворачиваемых символов конечен и относительно невелик (< 2000 кодовых точек Юникода). Это работает очень хорошо: сгенерированный идеальный хэш (гарантированное отсутствие коллизий) можно использовать для преобразования каждого входного символа в его эквивалент в нижнем регистре.
При использовании UTF-8 вам нужно внимательно относиться к многобайтовым символам и выполнять соответствующие итерации. Однако UTF-8 имеет довольно простые правила кодирования, которые делают эту операцию эффективной.
Более подробную информацию, включая ссылки на соответствующие части стандарта Unicode и идеальный генератор хэшей, см. в моем ответе здесь на вопрос Как добиться независимого от Unicode сравнения без учета регистра в C++.
Поскольку вы используете std::string, вы используете С++. Если вы используете С++ 11 или выше, для этого не нужно ничего особенного. Еслиwords
являетсяvector<string>
, затем:
for (auto & str : words) {
for(auto & ch : str)
ch = tolower(ch);
}
Не имеет странных исключений. Возможно, вы захотите использовать w_char, но в остальном это должно делать все на месте.
Google
absl
библиотека имеет
absl::AsciiStrToLower
/
absl::AsciiStrToUpper
используйте этот код, чтобы изменить регистр строки в С++.
#include<bits/stdc++.h>
using namespace std;
int main(){
string a = "sssAAAAAAaaaaDas";
transform(a.begin(),a.end(),a.begin(),::tolower);
cout<<a;
}
Добавьте несколько дополнительных библиотек для строки ASCII to_lower, обе из которых являются производственным уровнем и с микрооптимизациями, что, как ожидается, будет быстрее, чем существующие ответы здесь (TODO: добавить результат теста).
Безумие Facebook :
void toLowerAscii(char* str, size_t length)
Abseil Google :
void AsciiStrToLower(std::string* s);
Я написал шаблонную версию, которая работает с любой строкой:
#include <type_traits> // std::decay
#include <ctype.h> // std::toupper & std::tolower
template <class T = void> struct farg_t { using type = T; };
template <template<typename ...> class T1,
class T2> struct farg_t <T1<T2>> { using type = T2*; };
//---------------
template<class T, class T2 =
typename std::decay< typename farg_t<T>::type >::type>
void ToUpper(T& str) { T2 t = &str[0];
for (; *t; ++t) *t = std::toupper(*t); }
template<class T, class T2 = typename std::decay< typename
farg_t<T>::type >::type>
void Tolower(T& str) { T2 t = &str[0];
for (; *t; ++t) *t = std::tolower(*t); }
Протестировано компилятором gcc:
#include <iostream>
#include "upove_code.h"
int main()
{
std::string str1 = "hEllo ";
char str2 [] = "wOrld";
ToUpper(str1);
ToUpper(str2);
std::cout << str1 << str2 << '\n';
Tolower(str1);
Tolower(str2);
std::cout << str1 << str2 << '\n';
return 0;
}
выход:
>HELLO WORLD
>
>hello world