Как преобразовать std::string в нижний регистр?
Я хочу преобразовать std::string
в нижний регистр. Я знаю о функции tolower()
, однако в прошлом у меня были проблемы с этой функцией, и это вряд ли идеально, так как использование с std::string
потребует итерации по каждому символу.
Есть ли альтернатива, которая работает в 100% случаев?
Ответы
Ответ 1
Адаптировано из не так часто задаваемых вопросов:
#include <algorithm>
#include <cctype>
#include <string>
std::string data = "Abc";
std::transform(data.begin(), data.end(), data.begin(),
[](unsigned char c){ return std::tolower(c); });
Вы действительно не собираетесь уходить без итерации каждого персонажа. Там нет никакого способа узнать, является ли символ строчными или прописными в противном случае.
Если вы действительно ненавидите tolower()
, вот специальная альтернатива только для ASCII, которую я не рекомендую вам использовать:
char asciitolower(char in) {
if (in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
std::transform(data.begin(), data.end(), data.begin(), asciitolower);
Имейте в tolower()
что tolower()
может выполнять подстановку только для одного байта символа, что плохо подходит для многих сценариев, особенно при использовании многобайтовой кодировки, такой как UTF-8.
Ответ 2
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);
Ответ 3
ТЛ; др
Используйте библиотеку ICU.
Сначала вы должны ответить на вопрос: какова кодировка вашей std::string
? Это ISO-8859-1? Или, возможно, ISO-8859-8? Или кодовая страница Windows 1252? Знает ли это то, что вы используете для преобразования прописных букв в строчные? (Или он с треском проваливается для персонажей более 0x7f
?)
Если вы используете UTF-8 (единственный разумный выбор среди 8-битных кодировок) с std::string
качестве контейнера, вы уже обманываете себя, полагая, что вы все еще контролируете вещи, потому что вы храните многобайтовый символ последовательность в контейнере, который не знает о многобайтовой концепции. Даже .substr()
простая .substr()
как .substr()
- это бомба замедленного действия. (Поскольку разбиение многобайтовой последовательности приведет к недопустимой (sub-) строке.)
И как только вы попробуете что-то вроде std::toupper( 'ß' )
, в любой кодировке у вас будут большие проблемы. (Поскольку это просто невозможно сделать "правильно" со стандартной библиотекой, которая может доставить только один символ результата, а не "SS"
необходимый здесь.) [1] Другим примером будет std::tolower( 'I' )
, который должен давать разные результаты в зависимости от локали. В Германии 'i'
будет правильным; в Турции 'ı'
(LATIN SMALL LETTER DOTLESS I) - это ожидаемый результат (который, опять же, больше чем один байт в кодировке UTF-8).
Тогда есть смысл, что стандартная библиотека зависит от того, какие локали поддерживаются на машине, на которой работает ваше программное обеспечение... и что вы делаете, если это не так?
Итак, что вы действительно ищете, так это строковый класс, который способен правильно std::basic_string<>
все это, и это не какой-либо из вариантов std::basic_string<>
.
(С++ 11 примечание: std::u16string
и std::u32string
лучше, но все же не идеально. С++ 20 привел std::u8string
, но все это делает указание кодировки. Во многих других отношениях они все еще остаются неосведомленный о механике Unicode, как нормализация, сопоставление,...)
Хотя 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", чтобы избежать двусмысленности, например, в паспортах (где имена пишутся с большой буквы)). Мой прекрасный пример, устарел по решению комитета...
Ответ 4
Если строка содержит символы UTF-8 вне диапазона ASCII, то boost:: algorithm:: to_lower не преобразует их. Лучше использовать boost:: locale:: to_lower, когда задействован UTF-8. См. http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/conversions.html
Ответ 5
Используя диапазон, основанный на цикле С++ 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);
}
Ответ 6
Это продолжение ответа Stefan Mai: если вы хотите поместить результат преобразования в другую строку, вам нужно предварительно выделить его пространство для хранения до вызова 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;
}
Ответ 7
Другой подход, использующий диапазон, основанный на цикле с ссылочной переменной
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
cout<<test<<endl;
Ответ 8
Насколько я понимаю, библиотеки Boost очень плохи по производительности. Я протестировал их unordered_map в STL, и он был в среднем в 3 раза медленнее (лучший случай 2, худший был 10 раз). Также этот алгоритм выглядит слишком низким.
Разница настолько велика, что я уверен, что любое дополнение, которое вам нужно сделать для tolower
, чтобы сделать его равным усилению "для ваших нужд", будет быстрее, чем boost.
Я проверил эти тесты на 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, поэтому мне не нужно тестировать его на моей машине.
Ответ 9
Самый простой способ преобразовать строку в 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;
}
Ответ 10
std::ctype::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";
}
Ответ 11
Альтернативой Boost является POCO (pocoproject.org).
POCO предоставляет два варианта:
- Первый вариант делает копию без изменения исходной строки.
- Второй вариант изменяет исходную строку на месте.
В версиях "In Place" всегда есть имя "InPlace".
Обе версии демонстрируются ниже:
#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);
Ответ 12
Есть способ конвертировать верхний регистр в нижний БЕЗ выполнения, если тесты, и это довольно прямолинейно. Функция isupper()/macro использования clocale.h должна заботиться о проблемах, связанных с вашим местоположением, но если нет, вы всегда можете настроить UtoL [] на содержание вашего сердца.
Учитывая, что символы C на самом деле являются всего лишь 8-битными int (игнорируя широкие наборы символов на данный момент), вы можете создать массив из 256 байтов, содержащий альтернативный набор символов, а в функции преобразования использовать символы в вашей строке как индексов в матрицу преобразования.
Вместо сопоставления 1-for-1, дайте элементам массива верхнего регистра значения BYTE int для символов нижнего регистра. Здесь вы можете найти islower() и isupper().
![enter image description here]()
Код выглядит так:
#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.
Ответ 13
Вот макро техника, если вы хотите что-то простое:
#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.
Ответ 14
// 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/
Ответ 15
Есть ли альтернатива, которая работает 100% времени?
нет
Есть несколько вопросов, которые вы должны задать себе, прежде чем выбрать метод в нижнем регистре.
- Как строка закодирована? простой ASCII? UTF-8? какая-то форма расширенного унаследованного кодирования ASCII?
- Что вы подразумеваете под строчными? Правила отображения дел варьируются в зависимости от языка! Хотите ли вы что-то локализованное для локали пользователей? Вы хотите что-то, что ведет себя согласованно во всех системах, на которых работает ваше программное обеспечение? Вы просто хотите использовать символы ASCII в нижнем регистре и проходить через все остальное?
- Какие библиотеки доступны?
Получив ответы на эти вопросы, вы можете начать искать решение, соответствующее вашим потребностям. Нет единого размера, который подходит всем, кто работает везде!
Ответ 16
Поскольку ни в одном из ответов не упоминалась будущая библиотека Ranges, которая доступна в стандартной библиотеке начиная с С++ 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>
и необходимые заголовки Ranges.)
Примечание: использование unsigned char
в качестве аргумента для лямбды основано на cppreference, который гласит:
Как и все другие функции из <cctype>
, поведение std::tolower
не определено, если значение аргумента не может быть представлено как unsigned char
и равно EOF
. Чтобы безопасно использовать эти функции с обычным char
(или signed char
), аргумент должен быть сначала преобразован в 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;
}
Ответ 17
На платформах 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 );
}
Ответ 18
Фрагмент кода
#include<bits/stdc++.h>
using namespace std;
int main ()
{
ios::sync_with_stdio(false);
string str="String Convert\n";
for(int i=0; i<str.size(); i++)
{
str[i] = tolower(str[i]);
}
cout<<str<<endl;
return 0;
}
Ответ 19
Используйте 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");
Ответ 20
Я делаю что-то вроде этого...
void toLower(string &str)
{
for(int i=0;i<strlen(str.c_str());i++)
{
str[i]= tolower(str[i]);
}
}
Ответ 21
Скопируйте, потому что было отказано в улучшении ответа. Спасибо, SO
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
доступны в качестве ссылки внутри цикла for через идентификатор c
.
Ответ 22
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;
}
Для символьной операции над строкой: для каждого символа в строке
Ответ 23
Мой собственный шаблон функций, который выполняет верхний/нижний регистр.
#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);
}
Ответ 24
Это может быть еще одна простая версия для преобразования верхнего и нижнего регистров и наоборот. Я использовал версию сообщества VS2017 для компиляции этого исходного кода.
#include <iostream>
#include <string>
using namespace std;
int main()
{
std::string _input = "lowercasetouppercase";
#if 0
// My idea is to use the ascii value to convert
char upperA = 'A';
char lowerA = 'a';
cout << (int)upperA << endl; // ASCII value of 'A' -> 65
cout << (int)lowerA << endl; // ASCII value of 'a' -> 97
// 97-65 = 32; // Difference of ASCII value of upper and lower a
#endif // 0
cout << "Input String = " << _input.c_str() << endl;
for (int i = 0; i < _input.length(); ++i)
{
_input[i] -= 32; // To convert lower to upper
#if 0
_input[i] += 32; // To convert upper to lower
#endif // 0
}
cout << "Output String = " << _input.c_str() << endl;
return 0;
}
Примечание: если есть специальные символы, тогда их нужно обрабатывать с помощью проверки условий.
Ответ 25
Я пробовал std:: transform, все, что я получаю, является отвратительной stl-критической ошибкой компиляции, которую могут понять только друиды от 200 лет назад (не могу преобразовать из flibidi flabidi flu)
это прекрасно работает и может быть легко изменено
string LowerCase(string s)
{
int dif='a'-'A';
for(int i=0;i<s.length();i++)
{
if((s[i]>='A')&&(s[i]<='Z'))
s[i]+=dif;
}
return s;
}
string UpperCase(string s)
{
int dif='a'-'A';
for(int i=0;i<s.length();i++)
{
if((s[i]>='a')&&(s[i]<='z'))
s[i]-=dif;
}
return s;
}
Ответ 26
//You can really just write one on the fly whenever you need one.
#include <string>
void _lower_case(std::string& s){
for(unsigned short l = s.size();l;s[--l]|=(1<<5));
}
//Here is an example.
//http://ideone.com/mw2eDK