Как преобразовать std :: string в нижний регистр? - PullRequest
686 голосов
/ 24 ноября 2008

Я хочу преобразовать std::string в нижний регистр. Мне известна функция tolower(), однако в прошлом у меня были проблемы с этой функцией, и в любом случае она вряд ли идеальна, поскольку использование std::string потребовало бы итерации по каждому символу.

Есть ли альтернатива, которая работает 100% времени?

Ответы [ 25 ]

831 голосов
/ 24 ноября 2008

С это :

#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.

295 голосов
/ 24 ноября 2008

Для этого есть алгоритм 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);
202 голосов
/ 05 июня 2014

ТЛ; др

Использование библиотеки 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 DOTLESS I) - это ожидаемый результат (который, опять же, составляет более одного байта в кодировке UTF-8).

Тогда есть смысл, что стандартная библиотека зависит от того, какие локали поддерживаются на машине, на которой работает ваше программное обеспечение ... и что вы делаете, если это не так?

То, что вы действительно ищете, - это строковый класс, способный справиться со всем этим правильно, , то есть , а не std::string.

(примечание C ++ 11: std::u16string и std::u32string лучше , но все еще не идеально.)

В то время как Boost выглядит красиво, с точки зрения API, Boost.Locale по сути является оберткой для ICU . Если Boost скомпилирован с поддержкой ICU ... если нет, Boost.Locale ограничен поддержкой локали, скомпилированной для стандартной библиотеки.

И поверьте мне, получение Повышение при компиляции с 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 LETTER SHARP S может быть официально использован, как вариант, помимо традиционной конверсии «SS», чтобы избежать двусмысленности, например в паспортах (где имена пишутся с большой буквы). Мой прекрасный пример, устарел по решению комитета ...

29 голосов
/ 10 октября 2012

Если строка содержит символы 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

26 голосов
/ 09 октября 2013

Используя основанный на диапазоне для цикла 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);
}
14 голосов
/ 28 марта 2013

Это продолжение ответа Стефана Мая: если вы хотите поместить результат преобразования в другую строку, вам необходимо предварительно выделить место для хранения перед вызовом 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;
}
8 голосов
/ 10 января 2017

Другой подход с использованием диапазона на основе для цикла с контрольной переменной

string test = "Hello World";
for(auto& c : test)
{
   c = tolower(c);
}

cout<<test<<endl;
7 голосов
/ 05 августа 2012

Насколько я вижу, библиотеки 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, поэтому мне не нужно тестировать его на моей машине.

5 голосов
/ 12 июня 2015

Самый простой способ преобразовать строку в 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;
}
5 голосов
/ 29 января 2016

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