我想将 std::string
转换为小写。我知道函数 tolower()
。然而,在过去我遇到过这个函数的问题,无论如何它都不是理想的,因为将它与 std::string
一起使用需要遍历每个字符。
有没有 100% 有效的替代方案?
改编自 Not So Frequently Asked Questions:
#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()
只能执行每个单字节字符的替换,这对于许多脚本来说是不合适的,尤其是在使用像 UTF-8 这样的多字节编码时。
Boost provides a string algorithm for this:
#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);
tl;博士
使用ICU library。如果您不这样做,您的转换例程将在您可能甚至不知道存在的情况下静默中断。
首先您必须回答一个问题:您的 std::string
的编码是什么?是 ISO-8859-1 吗?或者也许是 ISO-8859-8?还是 Windows 代码页 1252? 您用于将大写转换为小写的任何东西都知道吗?(或者对于超过 0x7f
的字符它是否会失败?)
如果您使用 UTF-8 (the only sane choice among the 8-bit encodings) 和 std::string
作为容器,那么如果您认为您仍然可以控制事物,那么您已经在欺骗自己了。您将多字节字符序列存储在不了解多字节概念的容器中,并且您可以对其执行的大多数操作也不是!即使像 .substr()
这样简单的东西也可能导致无效(子)字符串,因为您在多字节序列的中间进行拆分。
一旦您尝试使用 any 编码的 std::toupper( 'ß' )
或 std::tolower( 'Σ' )
之类的东西,您就有麻烦了。因为 1),标准一次只对一个字符进行操作,所以它根本无法将 ß
转换为 SS
是正确的。并且 2),该标准一次只对一个字符进行操作,因此它无法确定 Σ
是在单词的中间(其中 σ
是正确的),还是在末尾(ς
) .另一个示例是 std::tolower( 'I' )
,它应该会产生不同的结果取决于区域设置 - 几乎在您期望的任何地方 i
,但在土耳其 ı
(拉丁小写字母 DOTLESS I)是正确答案(同样,在 UTF-8 编码中超过一个字节)。
因此,任何 一次作用于一个字符,或者更糟的是,一次作用于一个 字节 的大小写转换,都被设计破坏了。包括目前存在的所有 std::
变体。
然后有一点是,标准库的能力取决于运行您的软件的机器上支持哪些语言环境......如果您的目标语言环境不受支持,您会怎么做在您客户的机器上?
所以您真正要寻找的是一个能够正确处理所有这些问题的字符串类,不是任何std::basic_string<>
变体< /强>。
(C++11 注意:std::u16string
和 std::u32string
更好,但仍然不完美。C++20 带来了 std::u8string
,但所有这些都是指定 encoding< /em>。在许多其他方面,他们仍然对 Unicode 机制一无所知,例如规范化、排序规则……)
虽然 Boost 看起来不错,但在 API 方面,Boost.Locale 基本上是对 ICU 的封装。 如果 Boost 是编译 ICU 支持...如果不是,Boost.Locale 仅限于为标准库编译的语言环境支持。
相信我,让 Boost 与 ICU 一起编译有时会很痛苦。 (没有包含 ICU 的 Windows 预编译二进制文件,因此您必须将它们与您的应用程序一起提供,这会打开一个全新的蠕虫罐......)
因此,我个人建议直接从马口中获得完整的 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
这给出了:
ὀδυσσεύς
注意单词中间的Σ<->σ转换,单词末尾的Σ<->ς转换。没有基于 <algorithm>
的解决方案可以为您提供这一点。
使用 C++11 的基于范围的 for 循环,更简单的代码将是:
#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);
}
如果字符串包含 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
另一种使用基于范围的for循环和参考变量的方法
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
cout<<test<<endl;
这是 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;
}
将字符串转换为小写而不用担心 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::tolower
不能与 char
一起使用,它只支持 unsigned char
。因此,如果 str
包含 0x00-0x7F 之外的字符,则此代码为 UB。
Boost 的替代方案是 POCO (pocoproject.org)。
POCO 提供了两种变体:
第一个变体在不更改原始字符串的情况下进行复制。第二个变体更改了原始字符串。 “就地”版本的名称中始终包含“就地”。
两个版本如下所示:
#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);
我写了这个简单的辅助函数:
#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.
我自己的执行大写/小写的模板函数。
#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 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 s2;
}
towlower
用于支持 UTF-16 的宽字符。
标准 C++ 本地化库中的 std::ctype::tolower()
将为您正确执行此操作。这是从 tolower reference page 中提取的示例
#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";
}
const
怎么办?这似乎使它有点混乱(例如,它看起来不像您可以使用 f.tolower()
),因为您需要将字符放入一个新字符串中。您会为运算符使用 transform()
和类似 std::bind1st( std::mem_fun() )
的东西吗?
tolower
和 locale
参数中,对 use_facet
的隐式调用似乎是性能瓶颈。我的一位同事通过将 boost::iequals
(存在此问题)替换为仅在循环外调用一次的版本,实现了 100% 的速度提升。
由于没有一个答案提到即将推出的 Ranges 库,该库自 C++20 起在标准库中可用,并且目前作为 range-v3
单独提供 on GitHub,我想添加一种使用它执行此转换的方法。
要就地修改字符串:
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 启发的,它指出:
与
有一种方法可以在不进行 if 测试的情况下将大写字母转换为小写字母,而且非常简单。 isupper() 函数/宏对 clocale.h 的使用应该可以解决与您的位置相关的问题,但如果没有,您可以随时调整 UtoL[] 以适应您的需要。
鉴于 C 的字符实际上只是 8 位整数(暂时忽略宽字符集),您可以创建一个 256 字节数组来保存一组替代字符,并在转换函数中使用字符串中的字符作为下标到转换数组。
但是,不是 1 对 1 映射,而是为大写数组成员提供小写字符的 BYTE int 值。您可能会发现这里的 islower() and isupper() 很有用。
https://i.stack.imgur.com/DBIXl.png
代码看起来像这样......
#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;
}
同时,这种方法将允许您重新映射您希望更改的任何其他字符。
这种方法在现代处理器上运行时有一个巨大的优势,不需要进行分支预测,因为没有包含分支的 if 测试。这为其他循环节省了 CPU 的分支预测逻辑,并倾向于防止流水线停顿。
有些人可能会认为这种方法与用于将 EBCDIC 转换为 ASCII 的方法相同。
在 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 );
}
如果你想要一些简单的东西,这里有一个宏技术:
#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 对 this answer 的评论仍然是一个重要的考虑因素,但是,如果您正在处理的不仅仅是 ASCII 字符。
void strtoupper(std::string& x) { std::transform (x.begin(), x.end(), x.begin(), ::toupper); }
x
可能是一个有效的表达式,它恰好编译正确,但由于宏的原因会给出完全虚假的结果。
有没有 100% 有效的替代方案?
不
在选择小写方法之前,您需要问自己几个问题。
字符串是如何编码的?纯ASCII? UTF-8?某种形式的扩展 ASCII 传统编码?无论如何,小写是什么意思?大小写映射规则因语言而异!你想要一些本地化到用户区域的东西吗?你想要在你的软件运行的所有系统上表现一致的东西吗?您是否只想将 ASCII 字符小写并通过其他所有字符?有哪些库可用?
一旦您对这些问题有了答案,您就可以开始寻找适合您需求的解决方案。没有一种尺寸适合所有地方的所有人!
// 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/
复制,因为它不允许改进答案。谢谢所以
string test = "Hello World";
for(auto& c : test)
{
c = tolower(c);
}
解释:
for(auto& c : test)
是
for (
range_declaration
:
range_expression
)
loop_statement
< /em>:
range_declaration: auto& c 这里的 auto 说明符用于自动类型推导。所以类型会从变量初始化器中扣除。 range_expression: test 本例中的范围是字符串test的字符。
字符串 test
的字符可通过标识符 c
用作 for 循环内的引用。
C++ 没有为 std::string
实现 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;
}
对于字符串的基于字符的操作:For every character in string
试试这个功能:)
string toLowerCase(string str) {
int str_len = str.length();
string final_str = "";
for(int i=0; i<str_len; i++) {
char character = str[i];
if(character>=65 && character<=92) {
final_str += (character+32);
} else {
final_str += character;
}
}
return final_str;
}
使用 fplus 库中的 fplus::to_lower_case()
。
在 fplus API Search 中搜索 to_lower_case
例子:
fplus::to_lower_case(std::string("ABC")) == std::string("abc");
看看优秀的 c++17 cpp-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à!
Google 的 absl
库有 absl::AsciiStrToLower
/ absl::AsciiStrToUpper
代码片段
#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;
}
我写了一个适用于任何字符串的模板版本:
#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
使用此代码更改 C++ 中字符串的大小写。
#include<bits/stdc++.h>
using namespace std;
int main(){
string a = "sssAAAAAAaaaaDas";
transform(a.begin(),a.end(),a.begin(),::tolower);
cout<<a;
}
#include <bits/stdc++.h>
。你会被否决。
这可能是将大写转换为小写的另一个简单版本,反之亦然。我用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;
}
注意:如果有特殊字符则需要使用条件检查来处理。
我尝试了 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;
}
char
传递给::tolower(int)
。)您需要确保不传递负值。::tolower
可能会崩溃,它是非 ASCII 输入的 UB。