Как преобразовать 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.

Ответ 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", чтобы избежать двусмысленности, например, в паспортах (где имена пишутся с большой буквы)). Мой прекрасный пример, устарел по решению комитета...

Ответ 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% времени?

нет

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

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

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

Ответ 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;
}

Ответ 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:

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

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