Как преобразовать 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 предлагает два варианта:

  1. Первый вариант делает копию без изменения оригинальной строки.
  2. Второй вариант изменяет исходную строку на месте.
    Версии "на месте" всегда имеют в названии "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% времени?

нет

Есть несколько вопросов, которые вы должны задать себе, прежде чем выбрать метод в нижнем регистре.

  1. Как строка закодирована? простой ASCII? UTF-8? какая-то форма расширенного унаследованного кодирования ASCII?
  2. Что вы подразумеваете под строчными? Правила отображения дел варьируются в зависимости от языка! Вы хотите что-то, что локализовано в локали пользователей? Вы хотите что-то, что ведет себя согласованно на всех системах, на которых работает ваше программное обеспечение? Вы просто хотите использовать символы ASCII в нижнем регистре и проходить через все остальное?
  3. Какие библиотеки доступны?

Получив ответы на эти вопросы, вы можете начать искать решение, соответствующее вашим потребностям. Не существует одного размера, который подходит всем, кто работает везде!

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:

  1. range_declaration: auto& c
    Здесь автоматический спецификатор используется для автоматического вывода типа. Таким образом, тип вычитается из инициализатора переменных.

  2. 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
Другие вопросы по тегам