关于c ++:如何将std :: string转换为小写?

How to convert std::string to lower case?

我想将std::string转换为小写。 我知道函数tolower(),但是在过去我遇到了这个函数的问题,并且它无论如何都不是理想的,因为使用std::string需要迭代每个字符。

有没有一种方法可以100%的时间运作?


改编自不那么常见问题:

1
2
3
4
5
6
7
#include
#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替代方案,我不建议你使用:

1
2
3
4
5
6
7
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()只能执行每单字节字符替换,这对于许多脚本来说都是不合适的,特别是如果使用像UTF-8这样的多字节编码。


Boost为此提供了一个字符串算法:

1
2
3
4
#include <boost/algorithm/string.hpp>

std::string str ="HELLO, WORLD!";
boost::algorithm::to_lower(str); // modifies str

或者,对于非就地:

1
2
3
4
#include <boost/algorithm/string.hpp>

const std::string str ="HELLO, WORLD!";
const std::string lower_str = boost::algorithm::to_lower_copy(str);


TL;博士

使用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::basic_string<>变体。

(C ++ 11注意:std::u16stringstd::u32string更好,但仍然不完美.C ++ 20带来了std::u8string,但所有这些都指定了编码。在许多其他方面,他们仍然不知道Unicode机制,如规范化,整理,...)

虽然Boost看起来不错,但API智能,Boost.Locale基本上是ICU的包装器。如果使用ICU支持编译Boost ...如果不是,则Boost.Locale仅限于为标准库编译的语言环境支持。

并且相信我,让Boost与ICU一起编译可能是一个真正的痛苦。 (Windows没有预编译的二进制文件,因此您必须将它们与您的应用程序一起提供,这会打开一整套新的蠕虫......)

所以我个人建议直接从马口获得完整的Unicode支持并直接使用ICU库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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" ) <<"
"
;
    std::cout << someUString.toUpper("de_DE" ) <<"
"
;
    return 0;
}

编译(在此示例中使用G ++):

1
g++ -Wall example.cpp -licuuc -licuio

这给出了:

1
2
eidenges??
EIDENGES?SS

[1] 2017年,德国正字法委员会裁定"?" U + 1E9E LATIN CAPITAL LETTER SHARP S可以正式使用,作为传统"SS"转换旁边的选项,以避免歧义,例如:在护照中(名称大写)。我美丽的例子,由委员会决定过时了......


如果字符串包含ASCII范围之外的UTF-8字符,则boost :: algorithm :: to_lower将不会转换这些字符。当涉及UTF-8时,最好使用boost :: locale :: to_lower。请参阅http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/conversions.html


使用基于范围的C ++ 11循环,一个更简单的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>       // std::cout
#include <string>         // std::string
#include <locale>         // std::locale, std::tolower

int main ()
{
  std::locale loc;
  std::string str="Test String.
"
;

 for(auto elem : str)
    std::cout << std::tolower(elem,loc);
}


这是Stefan Mai的回复的后续行动:如果您想将转换结果放在另一个字符串中,则需要在调用std::transform之前预先分配其存储空间。由于STL将转换后的字符存储在目标迭代器中(在循环的每次迭代中将其递增),因此目标字符串将不会自动调整大小,并且存在内存占用风险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <string>
#include
#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
2
3
4
5
6
7
string test ="Hello World";
for(auto& c : test)
{
   c = tolower(c);
}

cout<<test<<endl;

据我所知,Boost库的性能非常糟糕。我已经测试了他们的unordered_map到STL,平均慢了3倍(最好的情况2,最差的是10次)。此算法看起来也太低了。

差异是如此之大,以至于我确信无论你需要做什么来增加tolower以使其等于提升"满足你的需求"将比提升更快。

我已经在Amazon EC2上完成了这些测试,因此在测试过程中性能会有所不同,但您仍然可以理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
./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这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./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

资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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所以我真的不需要在我的机器上测试它。


来自标准C ++ Localization库的std::ctype::tolower()将正确地为您执行此操作。以下是从tolower参考页面中提取的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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:
"
;
  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 <<"'
"
;
}


在不打扰std命名空间的情况下将字符串转换为loweercase的最简单方法如下

1:带/不带空格的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
#include
#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:没有空格的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
#include
#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;
}

有一种方法可以将大写字母转换为低级而不进行测试,而且非常简单。 isupper()函数/宏使用clocale.h应该处理与你的位置有关的问题,但如果没有,你可以随时调整UtoL []到你心脏的内容。

鉴于C的字符实际上只是8位整数(暂时忽略了宽字符集),你可以创建一个256字节的数组,其中包含一组替代字符,并且在转换函数中使用字符串中的字符作为下标。转换数组。

而不是1对1映射,为大写数组成员提供小写字符的BYTE int值。你可能会发现islower()和isupper()在这里很有用。

enter image description here

代码看起来像这样......

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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;
}

同时,此方法允许您重新映射您希望更改的任何其他字符。

当在现代处理器上运行时,这种方法具有一个巨大的优势,不需要进行分支预测,因为如果测试包括分支则不存在。这为其他循环保存了CPU的分支预测逻辑,并且倾向于防止流水线停顿。

这里的一些人可能认为这种方法与用于将EBCDIC转换为ASCII的方法相同。


Boost的另一种选择是POCO(pocoproject.org)。

POCO提供两种变体:

  • 第一个变体制作副本而不更改原始字符串。
  • 第二个变体将原始字符串更改为适当位置。
    "就地"版本的名称中始终包含"InPlace"。
  • 两个版本如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #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);

    如果你想要一些简单的东西,这是一个宏技术:

    1
    2
    3
    #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字符。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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.
    "
    ;
      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/


    Is there an alternative which works 100% of the time?

    没有

    在选择小写方法之前,您需要先问几个问题。

  • 字符串是如何编码的?纯ASCII? UTF-8?某种形式的扩展ASCII遗留编码?
  • 无论如何,小写是什么意思?案例映射规则因语言而异!您想要本地化为用户区域设置的内容吗?你想要在你的软件运行的所有系统上表现一致吗?您是否只想小写ASCII字符并通过其他所有内容?
  • 有哪些库可用?
  • 一旦你有了这些问题的答案,你就可以开始寻找适合你需求的解决方案了。没有一种适合所有人适合所有人的尺寸!


    由于没有一个答案提到了即将推出的Ranges库,它在C ++ 20之后的标准库中可用,并且目前在GitHub上单独提供为range-v3,我想添加一种方法来使用它来执行此转换。

    要就地修改字符串:

    1
    str |= action::transform([](unsigned char c){ return std::tolower(c); });

    要生成新字符串:

    1
    2
    auto new_string = original_string
        | view::transform([](unsigned char c){ return std::tolower(c); });

    (不要忘记#include 和所需的Ranges标题。)

    注意:使用unsigned char作为lambda的参数受cppreference的启发,cppreference指出:

    Like all other functions from , the behavior of std::tolower is undefined if the argument's value is neither representable as unsigned char nor equal to EOF. To use these functions safely with plain chars (or signed chars), the argument should first be converted to unsigned char:

    1
    2
    3
    4
    char my_tolower(char ch)
    {
        return static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
    }

    Similarly, they should not be directly used with standard algorithms when the iterator's value type is char or signed char. Instead, convert the value to unsigned char first:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }

    我自己的模板函数,执行大小写。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <string>
    #include

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

    代码片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<bits/stdc++.h>
    using namespace std;


    int main ()
    {
        ios::sync_with_stdio(false);

        string str="String Convert
    "
    ;

        for(int i=0; i<str.size(); i++)
        {
          str[i] = tolower(str[i]);
        }
        cout<<str<<endl;

        return 0;
    }

    使用fplus :: to_lower_case()。

    (fplus:https://github.com/Dobiasd/FunctionalPlus。

    在http://www.editgym.com/fplus-api-search/中搜索"to_lower_case"

    1
    fplus::to_lower_case(std::string("ABC")) == std::string("abc");


    复制,因为不允许改善答案。谢谢你

    1
    2
    3
    4
    5
    string test ="Hello World";
    for(auto& c : test)
    {
       c = tolower(c);
    }

    说明:

    for(auto& c : test)是一种基于范围的for循环for ( range_declaration : range_expression ) loop_statement

  • range_declarationauto& c
    这里自动说明符用于自动类型推导。因此,类型从变量初始值设定项中扣除。

  • range_expressiontest
    在这种情况下,范围是字符串test的字符。

  • 字符串test的字符可作为for循环中的标识符c的引用。


    C ++没有为字符串实现tolower或toupper方法,但它可用于char。可以轻松读取字符串的每个字符串,将其转换为必需的大小写并将其放回字符串中。
    不使用任何第三方库的示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #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;
    }

    对于字符串的基于字符的操作:对于字符串中的每个字符


    在Microsoft平台上,您可以使用strlwr系列函数:http://msdn.microsoft.com/en-us/library/hkxwh33z.aspx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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
    "
    , string );
       printf("Lower: %s
    "
    , copy1 );
       printf("Upper: %s
    "
    , copy2 );

       free( copy1 );
       free( copy2 );
    }

    这可能是另一个将大写转换为小写的简单版本,反之亦然。我使用VS2017社区版来编译这个源代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #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;
    }

    注意:如果有特殊字符,则需要使用条件检查进行处理。


    我试过std :: transform,我得到的是令人讨厌的stl criptic编译错误,只有200年前的德鲁伊才能理解(不能转换为flibidi flabidi flu)

    这工作正常,可以很容易地调整

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }


    1
    2
    3
    4
    5
    6
    7
    //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