我想转换一个std::字符串小写。我知道tolower()函数。然而,在过去,我有这个函数的问题,它几乎不是理想的无论如何使用std::string将需要迭代每个字符。

有没有一种替代方案能100%有效?


当前回答

博士tl;

使用ICU图书馆。如果您不这样做,您的转换例程将在您可能甚至没有意识到存在的情况下无声地中断。


首先你必须回答一个问题:std::string的编码是什么?是ISO-8859-1吗?或者ISO-8859-8?或者Windows Codepage 1252?不管你用什么来转换大写字母还是小写字母,你知道吗?(或者对于0x7f以上的字符会失败吗?)

如果您使用UTF-8(8位编码中唯一明智的选择)和std::string作为容器,如果您认为您仍然在控制事情,那么您已经欺骗了自己。您正在将一个多字节字符序列存储在一个不知道多字节概念的容器中,您可以对其执行的大多数操作也不知道多字节的概念!即使是像.substr()这样简单的东西也可能导致无效的(子)字符串,因为您在多字节序列中间进行了分割。

As soon as you try something like std::toupper( 'ß' ), or std::tolower( 'Σ' ) in any encoding, you are in trouble. Because 1), the standard only ever operates on one character at a time, so it simply cannot turn ß into SS as would be correct. And 2), the standard only ever operates on one character at a time, so it cannot decide whether Σ is in the middle of a word (where σ would be correct), or at the end (ς). Another example would be std::tolower( 'I' ), which should yield different results depending on the locale -- virtually everywhere you would expect i, but in Turkey ı (LATIN SMALL LETTER DOTLESS I) is the correct answer (which, again, is more than one byte in UTF-8 encoding).

因此,任何一次处理一个字符的大小写转换,或者更糟,一次处理一个字节的大小写转换,都在设计上被破坏了。这包括目前存在的所有std::变体。

还有一点,标准库能够做什么,取决于运行软件的机器支持哪些地区…如果您的目标区域位于客户机上不支持的区域之一,该怎么办?

因此,您真正要寻找的是一个能够正确处理所有这些问题的字符串类,而不是std::basic_string<>变量。

(c++ 11注:std::u16string和std::u32string较好,但仍不完美。c++ 20带来了std::u8string,但所有这些都是指定编码。在许多其他方面,他们仍然对Unicode机制一无所知,比如标准化、排序……)

虽然Boost看起来不错,API方面,Boost。Locale基本上是ICU的包装器。如果Boost是使用ICU支持编译的……如果不是,Boost。区域设置仅限于为标准库编译的区域设置支持。

相信我,让Boost与ICU一起编译有时真的很痛苦。(Windows中没有包含ICU的预编译二进制文件,所以你必须在应用程序中提供它们,这就打开了一个全新的蠕虫…)

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

#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/locid.h>

#include <iostream>

int main()
{
    /*                          "Odysseus" */
    char const * someString = u8"ΟΔΥΣΣΕΥΣ";
    icu::UnicodeString someUString( someString, "UTF-8" );
    // Setting the locale explicitly here for completeness.
    // Usually you would use the user-specified system locale,
    // which *does* make a difference (see ı vs. i above).
    std::cout << someUString.toLower( "el_GR" ) << "\n";
    std::cout << someUString.toUpper( "el_GR" ) << "\n";
    return 0;
}

编译(本例中使用g++):

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

这给:

ὀδυσσεύς

注意,单词中间的Σ<-> Σ转换,单词末尾的Σ<->ς转换。没有<算法>的解决方案可以给你。

其他回答

将字符串转换为小写而不用担心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;
}

我尝试了std::transform,我得到的是可恶的stl criptic编译错误,只有200年前的德鲁伊才能理解(不能从flibidi flabidi流感转换)

这工作得很好,可以很容易地调整

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

看看优秀的c++17 cppp -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à!

我写了一个模板版本,适用于任何字符串:

#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

博士tl;

使用ICU图书馆。如果您不这样做,您的转换例程将在您可能甚至没有意识到存在的情况下无声地中断。


首先你必须回答一个问题:std::string的编码是什么?是ISO-8859-1吗?或者ISO-8859-8?或者Windows Codepage 1252?不管你用什么来转换大写字母还是小写字母,你知道吗?(或者对于0x7f以上的字符会失败吗?)

如果您使用UTF-8(8位编码中唯一明智的选择)和std::string作为容器,如果您认为您仍然在控制事情,那么您已经欺骗了自己。您正在将一个多字节字符序列存储在一个不知道多字节概念的容器中,您可以对其执行的大多数操作也不知道多字节的概念!即使是像.substr()这样简单的东西也可能导致无效的(子)字符串,因为您在多字节序列中间进行了分割。

As soon as you try something like std::toupper( 'ß' ), or std::tolower( 'Σ' ) in any encoding, you are in trouble. Because 1), the standard only ever operates on one character at a time, so it simply cannot turn ß into SS as would be correct. And 2), the standard only ever operates on one character at a time, so it cannot decide whether Σ is in the middle of a word (where σ would be correct), or at the end (ς). Another example would be std::tolower( 'I' ), which should yield different results depending on the locale -- virtually everywhere you would expect i, but in Turkey ı (LATIN SMALL LETTER DOTLESS I) is the correct answer (which, again, is more than one byte in UTF-8 encoding).

因此,任何一次处理一个字符的大小写转换,或者更糟,一次处理一个字节的大小写转换,都在设计上被破坏了。这包括目前存在的所有std::变体。

还有一点,标准库能够做什么,取决于运行软件的机器支持哪些地区…如果您的目标区域位于客户机上不支持的区域之一,该怎么办?

因此,您真正要寻找的是一个能够正确处理所有这些问题的字符串类,而不是std::basic_string<>变量。

(c++ 11注:std::u16string和std::u32string较好,但仍不完美。c++ 20带来了std::u8string,但所有这些都是指定编码。在许多其他方面,他们仍然对Unicode机制一无所知,比如标准化、排序……)

虽然Boost看起来不错,API方面,Boost。Locale基本上是ICU的包装器。如果Boost是使用ICU支持编译的……如果不是,Boost。区域设置仅限于为标准库编译的区域设置支持。

相信我,让Boost与ICU一起编译有时真的很痛苦。(Windows中没有包含ICU的预编译二进制文件,所以你必须在应用程序中提供它们,这就打开了一个全新的蠕虫…)

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

#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/locid.h>

#include <iostream>

int main()
{
    /*                          "Odysseus" */
    char const * someString = u8"ΟΔΥΣΣΕΥΣ";
    icu::UnicodeString someUString( someString, "UTF-8" );
    // Setting the locale explicitly here for completeness.
    // Usually you would use the user-specified system locale,
    // which *does* make a difference (see ı vs. i above).
    std::cout << someUString.toLower( "el_GR" ) << "\n";
    std::cout << someUString.toUpper( "el_GR" ) << "\n";
    return 0;
}

编译(本例中使用g++):

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

这给:

ὀδυσσεύς

注意,单词中间的Σ<-> Σ转换,单词末尾的Σ<->ς转换。没有<算法>的解决方案可以给你。