Как преобразовать std :: string в нижний регистр?

672

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

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

  • 27
    Как еще вы могли бы преобразовать каждый элемент списка чего-либо во что-то еще, не просматривая список? Строка - это просто список символов. Если вам нужно применить какую-то функцию к каждому символу, вам придется перебирать строку. Обойти это невозможно.
  • 11
    Почему именно этот вопрос заслуживает рейтинга? У меня нет проблем с повторением моей строки, но я спрашиваю, есть ли другие функции, кроме tolower (), toupper () и т. Д.
Показать ещё 11 комментариев
Теги:
string
c++-standard-library
tolower

24 ответа

772
Лучший ответ

Из этого:

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

  • 4
    Это удивительно, я всегда задавался вопросом, как лучше всего это сделать. Я понятия не имел, использовать std :: transform. :)
  • 0
    uberjumper: На самом деле с вызовами STL связано много накладных расходов, особенно для небольших строк "ish". Решения, использующие цикл for и tolower, вероятно, намного быстрее.
Показать ещё 24 комментария
306

Для этого существует строковый алгоритм 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);
  • 2
    Я полагаю, что это не имеет те же проблемы, что и Tolower с входом ASCII?
  • 15
    Не для ASCII-7.
Показать ещё 2 комментария
205

ТЛ; др

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

  • 14
    Это правильный ответ в общем случае. Стандарт не дает ничего для обработки чего-либо, кроме "ASCII", кроме лжи и обмана. Это заставляет вас думать, что вы можете иметь дело с UTF-16, но не можете. Как говорится в этом ответе, вы не можете получить правильную длину символа (не длину байта) строки UTF-16, не выполняя собственную обработку Unicode. Если вам приходится иметь дело с реальным текстом, используйте ICU. Спасибо, @DevSolar
  • 0
    @Chase: я нахожу это удивительным, так как я скомпилировал его на Linux Mint IIRC и не могу вспомнить когда-либо, используя locid.h на Mint, SLES, Aix или Windows. Что не работает на вашей машине, если вы пропустите этот заголовок?
Показать ещё 8 комментариев
30

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

26

Используя диапазон, основанный на цикле С++ 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);
}
  • 8
    Однако на французском компьютере эта программа не преобразует символы, не входящие в ASCII, разрешенные на французском языке. Например, строка «Test String123. É Ï \ n 'будет преобразовано в:' тестовая строка123. É Ï \ n ', хотя символы É Ï и их строчные буквы' é 'и' ï 'разрешены на французском языке. Похоже, что никакие решения для этого не были предоставлены другими сообщениями этой темы.
  • 0
    Я думаю, что вам нужно установить правильную локаль для этого.
Показать ещё 2 комментария
14

Это продолжение ответа 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;
}
  • 1
    Это не изменило размер Ä в ä для меня
  • 0
    Здесь также можно использовать итератор с обратной вставкой вместо ручного изменения размера.
8

Другой подход, использующий диапазон, основанный на цикле с ссылочной переменной

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

cout<<test<<endl;
7

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

  • 1
    Открывали ли вы опции оптимизации при его компиляции? Я думаю, что библиотека STL Boost должна работать лучше с высоким уровнем оптимизации.
  • 1
    Я использовал -O2 в одном из тестов, и ничего больше.
Показать ещё 1 комментарий
5

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";
}
  • 0
    Приятно, если вы можете конвертировать символы на месте. Что если ваша исходная строка - const ? Это, кажется, делает его немного более запутанным (например, не похоже, что вы можете использовать f.tolower() ), так как вам нужно поместить символы в новую строку. Вы бы использовали transform() и что-то вроде std::bind1st( std::mem_fun() ) для оператора?
  • 0
    Для константной строки мы можем просто сделать локальную копию и затем преобразовать ее на место.
Показать ещё 3 комментария
5

Самый простой способ преобразовать строку в 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;
}
3

Альтернативой 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);
2

Вот макро техника, если вы хотите что-то простое:

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

  • 0
    Я опровергаю это за предоставление макросов, когда существует совершенно хорошее решение - вы даже даете эти решения.
  • 2
    Макро-техника означает меньше набирать код для чего-то, что обычно используется в программировании. Почему бы не использовать это? Иначе зачем вообще макросы?
Показать ещё 6 комментариев
2

Есть способ конвертировать верхний регистр в нижний БЕЗ выполнения, если тесты, и это довольно прямолинейно. Функция isupper()/macro использования clocale.h должна заботиться о проблемах, связанных с вашим местоположением, но если нет, вы всегда можете настроить UtoL [] на содержание вашего сердца.

Учитывая, что символы C на самом деле являются всего лишь 8-битными int (игнорируя широкие наборы символов на данный момент), вы можете создать массив из 256 байтов, содержащий альтернативный набор символов, а в функции преобразования использовать символы в вашей строке как индексов в матрицу преобразования.

Вместо сопоставления 1-for-1, дайте элементам массива верхнего регистра значения BYTE int для символов нижнего регистра. Здесь вы можете найти islower() и isupper().

Изображение 1198

Код выглядит так:

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

  • 2
    «Есть способ преобразовать верхний регистр в нижний БЕЗ выполнения, если тесты» когда-либо слышали о таблицах поиска?
  • 7
    Это таблица поиска. Вы не читали код?
Показать ещё 1 комментарий
1

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

нет

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

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

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

1
// 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/

0

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

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

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

Примечание: если есть специальные символы, тогда их нужно обрабатывать с помощью проверки условий.

0

Скопируйте, потому что было отказано в улучшении ответа. Спасибо, 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.

  • 0
    Пожалуйста, уточните, откуда вы скопировали свой ответ.
0

Я делаю что-то вроде этого...

void toLower(string &str)
{
        for(int i=0;i<strlen(str.c_str());i++)
            {
               str[i]= tolower(str[i]);
            }
}
0

Используйте 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");
0

Фрагмент кода

#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;
}
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 );
}
-9

Я пробовал 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;
}
-10
//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
  • 3
    Я бы сказал, что это не очень хорошая идея.
  • 0
    Как это не хорошая идея?
Показать ещё 8 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню