web-dev-qa-db-ja.com

C ++の文字列を大文字に変換する

文字列を大文字に変換する方法私がグーグルから見つけた例は文字だけを扱う必要があります。

245

ブーストストリングアルゴリズム:

#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");
200
Tony Edgecombe
#include <algorithm>
#include <string>

std::string str = "Hello World";
std::transform(str.begin(), str.end(),str.begin(), ::toupper);
447
Pierre

C++ 11とtoupper()を使用した簡単な解決策。

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());

注:トップソリューションにはいくつか問題があります。

21.5ヌル終了シーケンスユーティリティ

これらのヘッダの内容は、標準Cライブラリのヘッダ<ctype.h>、<wctype.h>、<string.h>、<wchar.h>、および<stdlib.h>と同じでなければなりません[...]

  • これはcctypeメンバーが標準のアルゴリズムで直接消費するのには適していないマクロである可能性が高いことを意味します。

  • 同じ例に関するもう1つの問題は、引数をキャストしないこと、またはこれが負でないことを確認しないことです。これは普通のcharが署名されているシステムにとっては特に危険です。 (理由は、これがマクロとして実装されている場合、おそらくルックアップテーブルを使用し、引数をそのテーブルにインデックス付けすることです。負のインデックスはUBを返します。)

27
dirkgently

文字列にASCIIまたは国際文字がありますか。

後者の場合、「大文字」はそれほど単純ではなく、使用されるアルファベットによって異なります。二院制と一院制のアルファベットがあります。大文字と小文字で異なる文字を持つのは、2文字のアルファベットだけです。また、ラテン大文字の 'DZ'(\ u01F1 'DZ')のように、いわゆるタイトルケースを使用する複合文字もあります。これは、最初の文字(D)だけが変更されることを意味します。

ICU 、および単純マッピングとフルケースマッピングの違いを検討することをお勧めします。これは役に立つかもしれません:

http://userguide.icu-project.org/transforms/casemappings

20
Milan Babuškov

この問題は、ASCII文字セット用のSIMDでベクトル化できます。


スピードアップ比較:

Core2Duo(Merom)でのx86-64 gcc 5.2 -O3 -march=nativeによる予備テスト。 120文字の同じ文字列(小文字と非小文字の混合ASCII)。40M回のループで変換されます(ファイル間のインライン化は行われないため、コンパイラは最適化したり、ループの外に持ち上げたりすることはできません)。ソースバッファと宛先バッファが同じなので、mallocのオーバーヘッドやメモリ/キャッシュの影響はありません。データは常にL1キャッシュでホットであり、純粋にCPUに依存しています。

  • boost::to_upper_copy<char*, std::string>()198.0秒。はい、Ubuntu 15.10のBoost 1.58は本当に遅いです。私はデバッガでasmをプロファイリングしてシングルステップ実行しましたが、本当に本当に悪い結果です。文字ごとにロケール変数が動的に発生するdynamic_castがあります。 (dynamic_castはstrcmpを複数回呼び出します)。これはLANG=CLANG=en_CA.UTF-8で起こります。

    私はstd :: string以外のRangeTを使ってテストしませんでした。 to_upper_copy の他の形式の方が最適化されているかもしれませんが、コピー用に常にnew/mallocスペースになるので、テストするのは難しいです。たぶん私がしたことが通常のユースケースとは異なっていて、たぶん通常止められているg ++は文字ごとのループからロケール設定のものを持ち上げることができる。私のstd::stringからの読み取りとchar dstbuf[4096]への書き込みのループはテストには意味があります。

  • glibc toupper6.67sを呼び出すループ(ただし、潜在的なマルチバイトUTF-8についてintの結果をチェックしないでください。これはトルコ語に関係します。)

  • ASCIIのみのループ:8.79秒(以下の結果に対する私のベースラインバージョン。)どうやらテーブルルックアップはcmovよりも高速です。
  • ASCIIのみの自動ベクトル化:2.51秒。 (120文字は最悪の場合と最良の場合の中間です。下記参照)
  • 手動でASCII化されたASCIIのみ:1.35秒

ロケールが設定されているときにWindowsでtoupper()が遅くなることについてのこの質問も参照してください


Boostが他のオプションよりも桁違いに遅いことにショックを受けました。私は-O3が有効になっていることを再確認し、asmをシングルステップで実行してそれが何をしているのかを確認しました。 clang ++ 3.8とほぼ同じ速度です。それは文字ごとのループの内側に大きなオーバーヘッドがあります。 report perfイベントのperf record/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は、繰り返し回数がループの前でわかっている場合にのみループを自動ベクトル化します。 (つまり、strlenのプレーンC実装のような検索ループは自動ベクトル化されません。)

したがって、キャッシュに収まるほど十分に小さい文字列の場合、最初にstrlenを実行することにより、最大128文字の文字列を大幅に高速化できます。これは、明示的な長さの文字列(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には、一度に1バイトずつループするよりもはるかに高速な効率的なstrlenがあるため、個別のベクトル化されたstrlenループとtoupperループは高速です。

ベースライン:終了する0をその場でチェックするループ。

Core2(Merom)2.4GHzでの40M繰り返しの時間。 gcc 5.2 -O3 -march=native。 (Ubuntu 15.10) dst != src(それでコピーを作成します)しかし、それらは重なりません(そして近くにありません)。両方とも整列しています。

  • 15文字のストリング:ベースライン:1.08s。自動移動:1.34秒
  • 16文字列:ベースライン:1.16秒。自動移動:1.52秒
  • 127文字列:ベースライン:8.91秒。 autovec:2.98秒//非ベクトルクリーンアップの処理文字数が15文字
  • 128文字のストリング:ベースライン:9.00秒。 autovec:2.06秒
  • 129文字列:ベースライン:9.04秒。 autovec:2.07s //非ベクトルクリーンアップの処理文字数は1文字

一部の結果はclangとは少し異なります。

関数を呼び出すマイクロベンチマークループは別のファイルにあります。そうでなければ、インライン展開され、strlen()がループから抜け出し、劇的に速く実行されます。 16文字の文字列(0.187秒)の場合.

これは、gccがあらゆるアーキテクチャに対して自動ベクトル化できるという大きな利点がありますが、小さな文字列の一般的なケースでは遅いという大きな欠点があります。


それで大きなスピードアップがあります、しかし、コンパイラ自動ベクトル化は素晴らしいコードを作りません、特に。最後の最大15文字のクリーンアップ用。

SSE組み込み関数を使用した手動ベクトル化

My case-flip関数 に基づいて、すべてのアルファベット文字の大文字と小文字を反転させます。これは "unsigned compare trick"を利用しています。ここでは、範囲シフトによる単一の符号なし比較でlow < a && a <= highを実行できるため、lowより小さい値はhighより大きい値に折り返されます。 (lowhighがそれほど離れていない場合、これは機能します。)

SSEには符号付き比較のみが含まれていますが、符号付き範囲の末尾に範囲をシフトして「符号なしの比較」トリックを使用することもできます。 +25(-128 + 'z' - 'a')

128を加算して128を減算することは、8ビット整数の場合も同じです。キャリーを入れる場所がないので、xor(キャリーレス加算)で、上位ビットを反転させるだけです。

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

1つのベクトルに対して機能するこの関数があれば、文字列全体を処理するためにループ内で呼び出すことができます。既にSSE2をターゲットにしているので、同時にベクトル化された文字列の終わりのチェックを行うことができます。

また、16Bのベクトルを実行した後に残った最後の最大15バイトの「クリーンアップ」の方がはるかにうまくいくことができます。大文字と小文字の区別は同じであるため、入力バイトの再処理は問題ありません。ソースの最後の16Bを整列させずにロードし、ループの最後の16Bストアと重なるようにdestバッファに格納します。

これがうまくいかないのは、文字列全体が16B未満の場合だけです。dst=src、非アトミックなread-modify-writeがでない場合同じでないことは同じです。すべて、そしてマルチスレッドコードを壊すことができます。

そのためのスカラループがあり、またsrcを整列させることもできます。終端の0がどこになるかわからないので、srcからの整列されていないロードが次のページに入り込んでセグメンテーション違反になるかもしれません。整列16Bチャンクにバイトが必要な場合は、整列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(それでコピーを作成します)しかし、それらは重なりません(そして近くにありません)。両方とも整列しています。

  • 15文字のストリング:ベースライン:1.08s。 autovec:1.34秒。マニュアル:1.29s
  • 16文字列:ベースライン:1.16秒。 autovec:1.52秒マニュアル:0.335秒
  • 31文字列:マニュアル:0.479s
  • 127文字列:ベースライン:8.91秒。 autovec:2.98秒マニュアル:0.925s
  • 128文字のストリング:ベースライン:9.00秒。 autovec:2.06秒マニュアル:0.931s
  • 129文字列:ベースライン:9.04秒。 autovec:2.07秒マニュアル:1.02秒

(実際にはループ内の_mm_storeではなく、アドレスが揃っていてもstoreuがMerom上で遅くなるので、_mm_storeuとタイミングを合わせています。Nehalem以降では問題ありません。失敗を修正する代わりに、コードを現状のまま残しました。場合によっては、末尾の0をコピーします。すべての時間を再設定したくないためです。)

そのため、16Bより長い短い文字列の場合、これは自動ベクトル化よりもはるかに高速です。長さがベクトル幅より1小さい場合、問題はありません。店での転送が失速しているために、それらがその場で動作しているときに問題になる可能性があります。 (ただし、toupperはべき等なので、元の入力ではなく、独自の出力を処理しても問題ありません)。

周囲のコードが何を望んでいるか、そしてターゲットとなるマイクロアーキテクチャに応じて、さまざまなユースケースに合わせてこれを調整する余地がたくさんあります。クリーンアップ部分に対してNiceコードをコンパイラーに発行させるのは難しいです。 ffs(3)(x86ではbsfまたはtzcntにコンパイルします)を使用するのは良いようですが、この答えの大部分を書いた後にバグに気付いたので(FIXMEのコメントを見てください).

movqまたはmovdロード/ストアを使用すると、さらに小さい文字列でもベクトルの高速化を実現できます。ユースケースに合わせてカスタマイズしてください


UTF-8:

上位ビットが設定されたバイトがベクターに含まれている場合は検出できます。その場合は、そのベクターのスカラutf-8対応ループにフォールバックします。 dstポインターはsrcポインターとは異なる量だけ進むことができますが、いったん整列されたsrcポインターに戻っても、まだ整列されていないベクトルストアをdstに行うだけです。

UTF-8であるが、大部分がUTF-8のASCIIサブセットで構成されているテキストの場合、これは良いことです。一般的なケースでは高性能、すべてのケースで正しい動作。 ASCII以外のものがたくさんあるときは、常にスカラーUTF-8対応ループに留まるよりもおそらく悪いでしょう。

他の言語を犠牲にして英語を早くすることは、欠点が大きい場合には将来を見越した決断ではありません。


ロケール対応:

トルコ語ロケール(tr_TR)では、toupper('i')の正しい結果は'İ'(プレーンASCII)ではなく'I'(U0130)です。 Windowsでtolower()が遅いという質問については、 Martin Bonnerのコメント を参照してください。

マルチバイトのUTF-8入力文字の場合のように、例外リストをチェックし、そこでスカラーにフォールバックすることもできます。

これほど複雑なので、SSE4.2 PCMPISTRMなどで、一度に多くのチェックを実行できる可能性があります。

18
Peter Cordes
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;
}
18
user648545

ASCII文字のみを使用した場合の速い方:

for(i=0;str[i]!=0;i++)
  if(str[i]<='z' && str[i]>='a')
    str[i]-=32;

このコードは高速に実行されますが、ASCIIでのみ機能し、「抽象的」な解決策ではありません。

あなたがUNICODEの解決策あるいはもっと慣習的で抽象的な解決策を必要とするならば、他の答えを求めてC++文字列の方法で働きなさい。

15
Luca C.

以下は私のために働きます。

#include <algorithm>
void  toUpperCase(std::string& str)
{
    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
}

int main()
{
   std::string str = "hello";
   toUpperCase(&str);
}
12
Pabitra Dash

ラムダを使う.

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);
11
Byron

あなたがASCIIのみで大丈夫で、RWメモリへの有効なポインタを提供できる限り、Cにはシンプルで非常に効果的なワンライナーがあります。

void strtoupper(char* str)
{ 
    while (*str) *(str++) = toupper((unsigned char)*str);
}

これは、ASCII識別子のように、同じ文字ケースに正規化したい単純な文字列には特に便利です。その後、バッファを使用してstd:stringインスタンスを構築できます。

10
k3a
//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;
}
10
David
#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が呼び出しごとにロケールを調べる必要があるためです。これは、呼び出しごとに別のスレッドによって変更されている可能性があるからです。そしてロケールを調べるのは、一般的にロックをかけることです。

これは、autoを置き換え、新しい非const str.data()を使用し、次のようにテンプレートを閉じるためのスペース( ">>"から ">>")を追加した後もC++ 98で機能します。

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());
9
Glen Knowles
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 );
7
bayda
std::string value;
for (std::string::iterator p = value.begin(); value.end() != p; ++p)
    *p = toupper(*p);
4

これがC++ 11の最新のコードです。

std::string cmd = "Hello World";
for_each(cmd.begin(), cmd.end(), [](char& in){ in = ::toupper(in); });
2
user2787620

toupper()関数(#include <ctype.h>)を試してください。それは引数として文字を受け入れます、文字列は文字で構成されているので、一緒になったときに文字列を構成する個々の文字ごとに繰り返す必要があります。

2
zmf

私の解決策(alphaの6ビット目をクリアする):

#include <ctype.h>

inline void toupper(char* str)
{
    while (str[i]) {
        if (islower(str[i]))
            str[i] &= ~32; // Clear bit 6 as it is what differs (32) between Upper and Lowercases
        i++;
    }
}
0
Antonin GAVREL

組み込み関数があるかどうかわからない。これを試して:

Ctype.h OR 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
}
0
Brandon Stewart