我想转换一个std::字符串小写。我知道tolower()函数。然而,在过去,我有这个函数的问题,它几乎不是理想的无论如何使用std::string将需要迭代每个字符。
有没有一种替代方案能100%有效?
我想转换一个std::字符串小写。我知道tolower()函数。然而,在过去,我有这个函数的问题,它几乎不是理想的无论如何使用std::string将需要迭代每个字符。
有没有一种替代方案能100%有效?
当前回答
我写了一个模板版本,适用于任何字符串:
#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
其他回答
在微软平台上,您可以使用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 );
}
由于没有一个答案提到即将到来的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>和所需的Ranges头。)
注意:使用unsigned char作为lambda的参数是受cppreference的启发,它声明:
Like all other functions from <cctype>, 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: 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: 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; }
博士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
这给:
ὀδυσσεύς
注意,单词中间的Σ<-> Σ转换,单词末尾的Σ<->ς转换。没有<算法>的解决方案可以给你。
我写了这个简单的helper函数:
#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.
如果你想要一些简单的东西,这里有一个宏观的技巧:
#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)
但是,请注意,如果您正在处理的不仅仅是ASCII字符,那么@AndreasSpindler对这个答案的评论仍然是一个重要的考虑因素。