将C ++中的string转换为大写
如何将一个string转换为大写。 我从Google上find的例子只能处理字符。
提升stringalgorithm:
#include <boost/algorithm/string.hpp> #include <string> std::string str = "Hello World"; boost::to_upper(str); std::string newstr = boost::to_upper_copy<std::string>("Hello World");
#include <algorithm> #include <string> std::string str = "Hello World"; std::transform(str.begin(), str.end(),str.begin(), ::toupper);
使用C ++ 11和toupper()的简短解决scheme。
for (auto & c: str) c = toupper(c);
struct convert { void operator()(char& c) { c = toupper((unsigned char)c); } }; // ... string uc_str; for_each(uc_str.begin(), uc_str.end(), convert());
注意:顶级解决scheme存在一些问题:
21.5空终止的序列实用程序
这些标题的内容应与标准C库标题<ctype.h>,<wctype.h>,<string.h>,<wchar.h>和<stdlib.h>
-
这意味着
cctype
成员可能是不适合标准algorithm直接使用的macros。 -
同样的例子的另一个问题是,它不会投下参数或validation这是否定的; 这对于有简单
char
被签名的系统是特别危险的。 (原因是:如果这是作为一个macros实现的,它可能会使用一个查找表和你的参数索引到这个表中,一个负的索引会给你UB。)
你有ASCII或国际字符的string?
如果是后一种情况,“大写”不是那么简单,而是取决于使用的字母表。 有两院和一院的字母表。 只有两个字母的大写字母和小写字母才具有不同的字符。 此外,还有复合字符,如拉丁大写字母“DZ”(\ u01F1'DZ'),它们使用所谓的标题情况 。 这意味着只有第一个字符(D)被改变。
我build议你看看ICU ,以及简单和完整病例映射的区别。 这可能有助于:
string StringToUpper(string strToConvert) { for (std::string::iterator p = strToConvert.begin(); strToConvert.end() != p; ++p) *p = toupper(*p); return p; }
要么,
string StringToUpper(string strToConvert) { std::transform(strToConvert.begin(), strToConvert.end(), strToConvert.begin(), ::toupper); return strToConvert; }
如果只使用ASCII字符,则速度更快:
for(i=0;str[i]!=0;i++) if(str[i]<=122 && str[i]>=97) str[i]-=32;
请注意,这个代码运行得更快,但只能在ASCII上工作,而不是一个“抽象”的解决scheme。
如果您需要UNICODE解决scheme或更传统的抽象解决scheme,请参阅其他答案并使用C ++string的方法。
这个问题可以用 ASCII字符集的SIMD进行vector化 。
加速比较:
使用x86-64 gcc 5.2 -O3 -march=native
初步testing-O3 -march=native
Core2Duo(Merom)上的-O3 -march=native
。 相同的120个字符的string(混合的小写字母和非小写字母的ASCII),循环转换40M次(没有跨文件内联,因此编译器无法优化或提升循环中的任何内容)。 相同的源和目标缓冲区,所以没有malloc开销或内存/高速caching效果:整个时间数据在L1caching中很热,而且我们纯粹是CPU绑定的。
-
boost::to_upper_copy<char*, std::string>()
: 198.0s 。 是的,Ubuntu 15.10上的Boost 1.58真的很慢。 我在一个debugging器中对asm进行了configuration和单步debugging,确实非常糟糕:每个angular色都有一个localevariables的dynamic_cast! (dynamic_cast需要多次调用strcmp)。 这发生在LANG=C
和LANG=en_CA.UTF-8
。我没有使用除std :: string之外的RangeT进行testing。 也许
to_upper_copy
的其他forms可以更好地进行优化,但是我认为它总是会有new
/malloc
空间用于复制,所以很难testing。 也许我所做的与普通的用例不同,也许通常停止g ++可以提升每个字符循环中的语言环境设置。 我的循环读取std::string
并写入char dstbuf[4096]
是有意义的testing。 -
循环调用glibc
toupper
: 6.67s (不检查可能的多字节UTF-8的int
结果,这对于土耳其语来说很重要)。 - ASCII-only循环: 8.79s (我的基准版本为下面的结果。)显然,表格查找比
cmov
快,无论如何表中的表格cmov
热。 - 仅ASCII码自动vector化: 2.51s 。 (120个字符是最坏情况和最好情况之间的一半,见下文)
- 仅ASCII手动vector化: 1.35s
当设置语言环境时,请参阅关于toupper()
在Windows上慢的问题 。
我震惊的是Boost比其他选项慢了一个数量级。 我仔细检查了我是否启用了-O3
,甚至单步启动,看看它在做什么。 这与铿锵语++ 3.8的速度几乎完全相同。 它在每个字符循环中有巨大的开销。 性能perf record
/ report
结果(对于cycles
事件)是:
32.87% flipcase-clang- libstdc++.so.6.0.21 [.] _ZNK10__cxxabiv121__vmi_class_type_info12__do_dyncastElNS_17__class_type_info10__sub_kindEPKS1_PKvS4_S6_RNS1_16 21.90% flipcase-clang- libstdc++.so.6.0.21 [.] __dynamic_cast 16.06% flipcase-clang- libc-2.21.so [.] __GI___strcmp_ssse3 8.16% flipcase-clang- libstdc++.so.6.0.21 [.] _ZSt9use_facetISt5ctypeIcEERKT_RKSt6locale 7.84% flipcase-clang- flipcase-clang-boost [.] _Z16strtoupper_boostPcRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE 2.20% flipcase-clang- libstdc++.so.6.0.21 [.] strcmp@plt 2.15% flipcase-clang- libstdc++.so.6.0.21 [.] __dynamic_cast@plt 2.14% flipcase-clang- libstdc++.so.6.0.21 [.] _ZNKSt6locale2id5_M_idEv 2.11% flipcase-clang- libstdc++.so.6.0.21 [.] _ZNKSt6locale2id5_M_idEv@plt 2.08% flipcase-clang- libstdc++.so.6.0.21 [.] _ZNKSt5ctypeIcE10do_toupperEc 2.03% flipcase-clang- flipcase-clang-boost [.] _ZSt9use_facetISt5ctypeIcEERKT_RKSt6locale@plt 0.08% ...
自动向量化
当迭代计数在循环之前已知时,Gcc和clang将只会自动vector化循环。 (即search循环像strlen
纯C实现不会自动化)。
因此,对于足够小以适应高速caching的string,我们得到了显着的加速string,从strlen
开始到128字符长。 对于显式长度的string(如C ++ std::string
),这不是必需的。
// char, not int, is essential: otherwise gcc unpacks to vectors of int! Huge slowdown. char ascii_toupper_char(char c) { return ('a' <= c && c <= 'z') ? c^0x20 : c; // ^ autovectorizes to PXOR: runs on more ports than paddb } // gcc can only auto-vectorize loops when the number of iterations is known before the first iteration. strlen gives us that size_t strtoupper_autovec(char *dst, const char *src) { size_t len = strlen(src); for (size_t i=0 ; i<len ; ++i) { dst[i] = ascii_toupper_char(src[i]); // gcc does the vector range check with psubusb / pcmpeqb instead of pcmpgtb } return len; }
任何体面的libc都会有一个有效的strlen
,比一次循环一个字节要快得多,所以单独的向量化的strlen和toupper循环会更快。
基线(Baseline):一个循环,用于检查终止0。
在Core2(Merom)2.4GHz上迭代40M次。 gcc 5.2 -O3 -march=native
。 (Ubuntu 15.10)。 dst != src
(所以我们做一个副本),但是它们不重叠(并且不在附近)。 两者都alignment。
- 15个string:基准线:1.08s。 autovec:1.34s
- 16个string:baseline:1.16s。 autovec:1.52s
- 127string:基准:8.91s。 autovec:2.98s //非vector清理有15个字符要处理
- 128个string:baseline:9.00s。 autovec:2.06s
- 129string:基线:9.04s。 autovec:2.07s //非vector清理有1个字符要处理
一些结果与叮当有点不同。
调用函数的microbenchmark循环是在一个单独的文件中。 否则,内联和strlen()
会从循环中被吊起来,并且运行得更快,特别是。 为16个string(0.187s)。
这有一个主要的优点,gcc可以自动为任何体系结构进行vector化,但是主要的缺点是它对于小string通常是比较慢的。
所以有很大的加速,但是编译器的自动vector化并不是很好的代码, 清理最后的15个字符。
使用SSE内在函数进行手动vector化:
基于我的翻箱function , 翻转每个字母字符的情况。 它利用了“无符号比较技巧”,您可以通过范围移位来执行low < a && a <= high
和单个无符号比较,因此任何小于low
的值都会包含大于high
的值。 (这个工作,如果low
和high
不相距太远。)
SSE只有一个带符号的compare-greater,但我们仍然可以使用“无符号比较”技巧,通过范围移位到带符号范围的底部:减去'a'+ 128,所以字母字符的范围从-128到-128 +25(-128 +'z' – 'a')
请注意,添加128和减128是8位整数相同的东西。 无处可去,所以它只是xor(carryless add),翻转高位。
#include <immintrin.h> __m128i upcase_si128(__m128i src) { // The above 2 paragraphs were comments here __m128i rangeshift = _mm_sub_epi8(src, _mm_set1_epi8('a'+128)); __m128i nomodify = _mm_cmpgt_epi8(rangeshift, _mm_set1_epi8(-128 + 25)); // 0:lower case -1:anything else (upper case or non-alphabetic). 25 = 'z' - 'a' __m128i flip = _mm_andnot_si128(nomodify, _mm_set1_epi8(0x20)); // 0x20:lcase 0:non-lcase // just mask the XOR-mask so elements are XORed with 0 instead of 0x20 return _mm_xor_si128(src, flip); // it's easier to xor with 0x20 or 0 than to AND with ~0x20 or 0xFF }
给定这个函数适用于一个向量,我们可以在一个循环中调用它来处理整个string。 既然我们已经瞄准了SSE2,我们可以同时进行向量化的string结尾检查。
我们也可以在做16B的vector之后最后15字节的“清除”方面做得更好:上层是幂等的,所以重新处理一些input字节是可以的。 我们对源的最后16B进行未alignment的加载,并将其存储在与循环中最后的16B存储重叠的目标缓冲区中。
唯一不行的是当整个string在16B以下时:甚至当dst=src
,非primefaces读取 – 修改 – 写入与根本不接触某些字节是不一样的,并且可以破坏multithreading代码。
我们有一个标量循环,也是为了使src
alignment。 由于我们不知道终止0的位置,因此来自src
的未alignment的负载可能会跨越下一页和段错误。 如果我们需要alignment的16B块中的任何字节,加载整个alignment的16B块总是安全的。
完整的来源: 在github的要点 。
// FIXME: doesn't always copy the terminating 0. // microbenchmarks are for this version of the code (with _mm_store in the loop, instead of storeu, for Merom). size_t strtoupper_sse2(char *dst, const char *src_begin) { const char *src = src_begin; // scalar until the src pointer is aligned while ( (0xf & (uintptr_t)src) && *src ) { *(dst++) = ascii_toupper(*(src++)); } if (!*src) return src - src_begin; // current position (p) is now 16B-aligned, and we're not at the end int zero_positions; do { __m128i sv = _mm_load_si128( (const __m128i*)src ); // TODO: SSE4.2 PCMPISTRI or PCMPISTRM version to combine the lower-case and '\0' detection? __m128i nullcheck = _mm_cmpeq_epi8(_mm_setzero_si128(), sv); zero_positions = _mm_movemask_epi8(nullcheck); // TODO: unroll so the null-byte check takes less overhead if (zero_positions) break; __m128i upcased = upcase_si128(sv); // doing this before the loop break lets gcc realize that the constants are still in registers for the unaligned cleanup version. But it leads to more wasted insns in the early-out case _mm_storeu_si128((__m128i*)dst, upcased); //_mm_store_si128((__m128i*)dst, upcased); // for testing on CPUs where storeu is slow src += 16; dst += 16; } while(1); // handle the last few bytes. Options: scalar loop, masked store, or unaligned 16B. // rewriting some bytes beyond the end of the string would be easy, // but doing a non-atomic read-modify-write outside of the string is not safe. // Upcasing is idempotent, so unaligned potentially-overlapping is a good option. unsigned int cleanup_bytes = ffs(zero_positions) - 1; // excluding the trailing null const char* last_byte = src + cleanup_bytes; // points at the terminating '\0' // FIXME: copy the terminating 0 when we end at an aligned vector boundary // optionally special-case cleanup_bytes == 15: final aligned vector can be used. if (cleanup_bytes > 0) { if (last_byte - src_begin >= 16) { // if src==dest, this load overlaps with the last store: store-forwarding stall. Hopefully OOO execution hides it __m128i sv = _mm_loadu_si128( (const __m128i*)(last_byte-15) ); // includes the \0 _mm_storeu_si128((__m128i*)(dst + cleanup_bytes - 15), upcase_si128(sv)); } else { // whole string less than 16B // if this is common, try 64b or even 32b cleanup with movq / movd and upcase_si128 #if 1 for (unsigned int i = 0 ; i <= cleanup_bytes ; ++i) { dst[i] = ascii_toupper(src[i]); } #else // gcc stupidly auto-vectorizes this, resulting in huge code bloat, but no measurable slowdown because it never runs for (int i = cleanup_bytes - 1 ; i >= 0 ; --i) { dst[i] = ascii_toupper(src[i]); } #endif } } return last_byte - src_begin; }
在Core2(Merom)2.4GHz上迭代40M次。 gcc 5.2 -O3 -march=native
。 (Ubuntu 15.10)。 dst != src
(所以我们做一个副本),但是它们不重叠(并且不在附近)。 两者都alignment。
- 15个string:基准线:1.08s。 autovec:1.34s。 手册:1.29s
- 16个string:baseline:1.16s。 autovec:1.52s。 手动:0.335s
- 31个string:手动:0.479s
- 127string:基准:8.91s。 autovec:2.98s。 手动:0.925s
- 128个string:baseline:9.00s。 autovec:2.06s。 手动:0.931s
- 129string:基线:9.04s。 autovec:2.07s。 手动:1.02s
(实际上,在循环中使用_mm_store
进行计时,而不是_mm_storeu
,因为即使在地址alignment的情况下,在Merom上storeu也是比较慢的,在Nehalem和之后的版本上都没有问题,我现在还是保留了代码,而不是修复失败在某些情况下复制终止0,因为我不想重新计时。
所以对于长度超过16B的短string,这比自动vector化要快得多。 长度小于vector宽度不存在问题。 由于存储转发的原因,在原地进行操作可能会造成问题。 (但请注意,处理我们自己的输出,而不是原来的input,因为toupper是幂等的)。
对于不同的使用情况,根据周围代码的需求以及目标微体系结构,有很多范围可以调整。 让编译器为清理部分发出漂亮的代码是非常棘手的。 使用ffs(3)
(编译为x86上的bsf或tzcnt)似乎是好的,但显然这一点需要重新考虑,因为我在写出大部分答案后发现了一个错误(请参阅FIXME注释)。
使用movq
或movd
加载/存储可以获得更小的string的vector加速。 根据需要定制您的用例。
UTF-8:
我们可以检测到我们的向量何时有高位设置的任何字节,在这种情况下,回落到该向量的标量utf-8感知循环。 dst
点可以比src
指针增加一个不同的数量,但是一旦我们回到一个alignment的src
指针,我们仍然只是做不alignment的vector存储到dst
。
对于UTF-8文本,但主要由UTF-8的ASCII子集组成,这可能是好的:在所有情况下都具有正确行为的常见情况下的高性能。 当有很多非ASCII的时候,它可能会比始终保持在标量的UTF-8感知循环中更糟糕。
以牺牲其他语言为代价来提高英语速度并不是面向未来的决定。
感知语言区域:
在土耳其语语言环境( tr_TR
)中, toupper('i')
的正确结果是'İ'
(U0130),而不是'I'
(纯ASCII)。 请参阅Martin Bonner关于tolower()
在Windows上缓慢的问题的评论 。
我们也可以检查一个exception列表并回退到标量,就像多字节的UTF8input字符一样。
有了这么多的复杂性,SSE4.2 PCMPISTRM
或者其他什么东西或许可以PCMPISTRM
做很多我们的检查。
//works for ASCII -- no clear advantage over what is already posted... std::string toupper(const std::string & s) { std::string ret(s.size(), char()); for(unsigned int i = 0; i < s.size(); ++i) ret[i] = (s[i] <= 'z' && s[i] >= 'a') ? s[i]-('a'-'A') : s[i]; return ret; }
使用lambda。
std::string s("change my case"); auto to_upper = [] (char_t ch) { return std::use_facet<std::ctype<char_t>>(std::locale()).toupper(ch); }; std::transform(s.begin(), s.end(), s.begin(), to_upper);
typedef std::string::value_type char_t; char_t up_char( char_t ch ) { return std::use_facet< std::ctype< char_t > >( std::locale() ).toupper( ch ); } std::string toupper( const std::string &src ) { std::string result; std::transform( src.begin(), src.end(), std::back_inserter( result ), up_char ); return result; } const std::string src = "test test TEST"; std::cout << toupper( src );
inline void strtoupper(char* str) { while (*str) { *str = toupper(*str); str++; } }
以下为我工作。
void toUpperCase(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::toupper); } int main() { std::string str = "hello"; toUpperCase(&str); }
std::string value; for (std::string::iterator p = value.begin(); value.end() != p; ++p) *p = toupper(*p);
#include <string> #include <locale> std::string str = "Hello World!"; auto & f = std::use_facet<std::ctype<char>>(std::locale()); f.toupper(str.data(), str.data() + str.size());
这将比使用全局toupper函数的所有答案更好,大概是boost :: to_upper在底下做什么。
这是因为:toupper必须查找locale – 因为它可能已经被不同的线程改变 – 对于每个调用,而在这里只有对locale()的调用有这个惩罚。 而查找语言环境通常涉及到locking。
这也适用于C ++ 98,replaceauto后,使用新的非const str.data(),并添加一个空格来打破模板closures(“>>”到“>>”),如下所示:
std::use_facet<std::ctype<char> > & f = std::use_facet<std::ctype<char> >(std::locale()); f.toupper(const_cast<char *>(str.data()), str.data() + str.size());
尝试toupper()
函数( #include <ctype.h>
)。 它接受字符作为参数,string由字符组成,所以你必须遍历每个单独的字符,当放在一起组成string
这是C ++ 11的最新代码
std::string cmd = "Hello World"; for_each(cmd.begin(), cmd.end(), [](char& in){ in = ::toupper(in); });
不知道有一个内置的function。 尝试这个:
包括ctype.h或cctype库以及stdlib.h作为预处理器指令的一部分。
string StringToUpper(string strToConvert) {//change each element of the string to upper case for(unsigned int i=0;i<strToConvert.length();i++) { strToConvert[i] = toupper(strToConvert[i]); } return strToConvert;//return the converted string } string StringToLower(string strToConvert) {//change each element of the string to lower case for(unsigned int i=0;i<strToConvert.length();i++) { strToConvert[i] = tolower(strToConvert[i]); } return strToConvert;//return the converted string }
这个页面上的所有这些解决scheme都比他们需要的更困难。
做这个
RegName = "SomE StRing That you wAnt ConvErTed"; NameLength = RegName.Size(); for (int forLoop = 0; forLoop < NameLength; ++forLoop) { RegName[forLoop] = tolower(RegName[forLoop]); }
RegName
是你的string
。 得到你的string大小不要使用string.size()
作为你的实际testing者,非常混乱,可能会导致问题。 然后。 最基本for
循环。
记住string大小也会返回分隔符,所以在循环testing中使用<而不是<=。
输出将是:你想要转换的string
不使用任何库:
std::string YourClass::Uppercase(const std::string & Text) { std::string UppperCaseString; UppperCaseString.reserve(Text.size()); for (std::string::const_iterator it=Text.begin(); it<Text.end(); ++it) { UppperCaseString.push_back(((0x60 < *it) && (*it < 0x7B)) ? (*it - static_cast<char>(0x20)) : *it); } return UppperCaseString; }
如果您只关心8位字符(除了米兰巴布什科夫以外的所有其他答案),您可以通过使用元编程在编译时生成查找表来获得最快的速度。 在ideone.com上,这比库函数运行速度快7倍,比手写版本( http://ideone.com/sb1Rup )快3倍。 它也可以通过不减速的特性来定制。
template<int ...Is> struct IntVector{ using Type = IntVector<Is...>; }; template<typename T_Vector, int I_New> struct PushFront; template<int ...Is, int I_New> struct PushFront<IntVector<Is...>,I_New> : IntVector<I_New,Is...>{}; template<int I_Size, typename T_Vector = IntVector<>> struct Iota : Iota< I_Size-1, typename PushFront<T_Vector,I_Size-1>::Type> {}; template<typename T_Vector> struct Iota<0,T_Vector> : T_Vector{}; template<char C_In> struct ToUpperTraits { enum { value = (C_In >= 'a' && C_In <='z') ? C_In - ('a'-'A'):C_In }; }; template<typename T> struct TableToUpper; template<int ...Is> struct TableToUpper<IntVector<Is...>>{ static char at(const char in){ static const char table[] = {ToUpperTraits<Is>::value...}; return table[in]; } }; int tableToUpper(const char c){ using Table = TableToUpper<typename Iota<256>::Type>; return Table::at(c); }
用例:
std::transform(in.begin(),in.end(),out.begin(),tableToUpper);
对于一个深入的(很多页面)如何工作,使我无耻地插入我的博客: http : //metaporky.blogspot.de/2014/07/part-4-generating-look-up-tables-at.html
template<size_t size> char* toupper(char (&dst)[size], const char* src) { // generate mapping table once static char maptable[256]; static bool mapped; if (!mapped) { for (char c = 0; c < 256; c++) { if (c >= 'a' && c <= 'z') maptable[c] = c & 0xdf; else maptable[c] = c; } mapped = true; } // use mapping table to quickly transform text for (int i = 0; *src && i < size; i++) { dst[i] = maptable[*(src++)]; } return dst; }
我使用这个解决scheme。 I know you're not supposed to modify that data area…. but I think that's mostly for buffer overrun bugs and null character…. upper casing things isn't the same.
void to_upper(const std::string str) { std::string::iterator it; int i; for ( i=0;i<str.size();++i ) { ((char *)(void *)str.data())[i]=toupper(((char *)str.data())[i]); } }
In all the machines I tested, it was faster. Perhaps because he is not concerned with a very wide range of characters. Or because using switch() it makes a jump table, do not know how it works in the assembly … just know that is faster 😛
string Utils::String::UpperCase(string CaseString) { for (unsigned short i = 0, tamanho = CaseString.length(); i < tamanho; i++) { switch (CaseString[i]) { case 'a': CaseString[i] = 'A'; break; case 'b': CaseString[i] = 'B'; break; case 'c': CaseString[i] = 'C'; break; case 'd': CaseString[i] = 'D'; break; case 'e': CaseString[i] = 'E'; break; case 'f': CaseString[i] = 'F'; break; case 'g': CaseString[i] = 'G'; break; case 'h': CaseString[i] = 'H'; break; case 'i': CaseString[i] = 'I'; break; case 'j': CaseString[i] = 'J'; break; case 'k': CaseString[i] = 'K'; break; case 'l': CaseString[i] = 'L'; break; case 'm': CaseString[i] = 'M'; break; case 'n': CaseString[i] = 'N'; break; case 'o': CaseString[i] = 'O'; break; case 'p': CaseString[i] = 'P'; break; case 'q': CaseString[i] = 'Q'; break; case 'r': CaseString[i] = 'R'; break; case 's': CaseString[i] = 'S'; break; case 't': CaseString[i] = 'T'; break; case 'u': CaseString[i] = 'U'; break; case 'v': CaseString[i] = 'V'; break; case 'w': CaseString[i] = 'W'; break; case 'x': CaseString[i] = 'X'; break; case 'y': CaseString[i] = 'Y'; break; case 'z': CaseString[i] = 'Z'; break; } } return CaseString; }