Я хочу преобразовать std::string
в нижний регистр. Я знаю о функции tolower()
, однако в прошлом у меня были проблемы с этой функцией, и это вряд ли идеально, так как использование с std::string
потребует итерации по каждому символу.
Есть ли альтернатива, которая работает в 100% случаев?
Из этого:
#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()
что ::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()
простая .substr()
как .substr()
- это бомба замедленного действия. (Поскольку разбиение многобайтовой последовательности приведет к недопустимой (sub-) строке.)
И как только вы попробуете что-то вроде std::toupper( 'ß' )
, в любой кодировке у вас будут большие проблемы. (Поскольку просто невозможно сделать это "правильно" со стандартной библиотекой, которая может доставить только один символ результата, а не "SS"
необходимый здесь.) [1] Другой пример был бы std::tolower( 'I' )
, который должен давать разные результаты в зависимости от локали. В Германии 'i'
будет правильным; в Турции 'ı'
(LATIN SMALL LETTER DOTLESS I) - это ожидаемый результат (который, опять же, больше чем один байт в кодировке UTF-8).
Тогда есть смысл, что стандартная библиотека зависит от того, какие локали поддерживаются на машине, на которой работает ваше программное обеспечение... и что вы делаете, если это не так?
Итак, что вы действительно ищете, так это строковый класс, который способен справиться со всем этим правильно, и это не std::string
.
(Примечание С++ 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", чтобы избежать двусмысленности, например, в паспортах (где имена пишутся с большой буквы)). Мой прекрасный пример, устарел по решению комитета...
locid.h
на Mint, SLES, Aix или Windows. Что не работает на вашей машине, если вы пропустите этот заголовок?
Если строка содержит символы 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
Используя диапазон, основанный на цикле С++ 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);
}
Это продолжение ответа 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;
}
Другой подход, использующий диапазон, основанный на цикле с ссылочной переменной
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
cout<<test<<endl;
Насколько я понимаю, библиотеки 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, поэтому мне не нужно тестировать его на моей машине.
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";
}
const
? Это, кажется, делает его немного более запутанным (например, не похоже, что вы можете использовать f.tolower()
), так как вам нужно поместить символы в новую строку. Вы бы использовали transform()
и что-то вроде std::bind1st( std::mem_fun() )
для оператора?
Самый простой способ преобразовать строку в 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 является POCO (pocoproject.org).
POCO предоставляет два варианта:
Обе версии демонстрируются ниже:
#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);
Вот макро техника, если вы хотите что-то простое:
#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.
Есть способ конвертировать верхний регистр в нижний БЕЗ выполнения, если тесты, и это довольно прямолинейно. Функция isupper()/macro использования clocale.h должна заботиться о проблемах, связанных с вашим местоположением, но если нет, вы всегда можете настроить UtoL [] на содержание вашего сердца.
Учитывая, что символы C на самом деле являются всего лишь 8-битными int (игнорируя широкие наборы символов на данный момент), вы можете создать массив из 256 байтов, содержащий альтернативный набор символов, а в функции преобразования использовать символы в вашей строке как индексов в матрицу преобразования.
Вместо сопоставления 1-for-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.
Есть ли альтернатива, которая работает 100% времени?
нет
Есть несколько вопросов, которые вы должны задать себе, прежде чем выбрать метод в нижнем регистре.
Получив ответы на эти вопросы, вы можете начать искать решение, соответствующее вашим потребностям. Нет единого размера, который подходит всем, кто работает везде!
// 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++ не имеет методов 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;
}
Для символьной операции над строкой: для каждого символа в строке
Это может быть еще одна простая версия для преобразования верхнего и нижнего регистров и наоборот. Я использовал версию сообщества 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;
}
Примечание: если есть специальные символы, тогда их нужно обрабатывать с помощью проверки условий.
Скопируйте, потому что было отказано в улучшении ответа. Спасибо, 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
.
Я делаю что-то вроде этого...
void toLower(string &str)
{
for(int i=0;i<strlen(str.c_str());i++)
{
str[i]= tolower(str[i]);
}
}
Используйте 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");
#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;
}
На платформах 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 );
}
Я пробовал 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;
}
//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