C 文字列操作のソースを表示
新しいページはコチラ
移動:
案内
,
検索
※このページではC言語にも存在していたという意味で記事タイトルがC 文字列操作になっていますが、<br /> [[C PlusPlus|C++]]でも同様です。[[C PlusPlus|C++]]だけの機能がある場合は明記します。<br /> <br /> == '''文字列操作''' == 文字列のコピー、連結、比較、一致、区切り文字分割、探索、文字列長取得といった操作を文字列操作と呼びます。上記のような操作はCRT関数として提供されているため、関数の使い方さえ覚えれば簡単に操作できます。 更に、文字列の型変換、文字コード変換、大文字小文字変換、半角文字、全角文字変換、ファイルパス操作、ファイル名操作、拡張子取得といった文字列操作の応用もプログラミングでは必要になってきますし、文字列の検索と置換、ファイルへの入出力といった操作までを習得すると概ねの操作ができるようになります。<br /> 上記のように日本語でまとめて言い換えるのは簡単ですが、一つ一つを実際にやってみるとなると、そう簡単でもありません。メモリに保管される文字列を操作するというのは、手続きが複雑です。コピーひとつにしても、どういった文字列を基にして、何文字目から何文字目までをコピーするのか?それにはどれくらいの大きさの文字列長があるのか?どういった変数に格納するのか?領域はちゃんと確保されているのか?手続きがうまくいかなかった場合はどうするのか?と何気に細かいことを教えてあげないと動かないのがコンピュータです。コンピュータ側からすると、人間のようには解釈は出来ない、そのかわりわからないことがあれば、どんな地道なことをしててでも調べてやるから、コンピュータに指令するのに必要な情報をそろえてほしい。<br /> まとめては動作させれないけど、内部的にはひとつづつ着実にコピーして、最終的には全部をコピーしてくれる。なんだか新入社員のようにいちいち指示しないといけないけど、まぁ的確に指示をすればやる気だけはあって、こなしてくれる。新入社員はほっといても成長しますが、コンピュータはほっとくと延々と成長しません。新入社員さんでも、あまりに放置すると教育不足ということで、指導者の責任問題とか、新人さんの人生を曲げてしまうという強烈な負のスパイラルが発生します。人間にはコンピュータにはまだまだ備えることのできない心って奴があります。大事にしないとね。ともかく、コンピュータが勝手にやってくれるようになるには、勝手にやれるようになるための成長するプログラムが必要になります。偉い人たちは地道にやっているようです。<br /> 成長云々の件はここでは関係ありませんので、置いておくとして、文字列の操作をひとつづつやってみましょう。(書きかけの記事です。最終更新131128)<br /> 文字列操作に関する関数は以下のアドレスのとおりです。<br /> http://msdn.microsoft.com/ja-jp/library/f0151s4x.aspx<br /> 上記があれば、自分が説明する必要はもうないかなと思うわけですが、一応、自分なりに。<br /> =='''文字列長取得'''== 文字列の長さを知りたいと思うことは、日常ではほとんどないですが、プログラムでは必要になるパターンが多いです。例えば、どれくらいのメモリサイズを確保すれば、いいのか?とか、文字列から抜き出した文字数が長い場合にはスクロールバーをつけたりとか、文字列の長さによって処理を分岐させたりすることもあります。この他、C言語においては、ほとんどの文字列操作関数で、その操作する文字列変数の文字列の長さを引数として与えなければならなかったりと、文字列操作の中でも基本中の基本となる部分です。知能指数の低い自分は文字数の足し算、引き算で間違えたりすることも多いです。うまく数えないと、枠にぴったりはまらないのがプログラム。<br /> リファレンスは以下のとおりです。<br /> http://msdn.microsoft.com/ja-jp/library/78zh94ax.aspx<br /> http://msdn.microsoft.com/ja-jp/library/z50ty2zh.aspx<br /> _tcslen(TCHA型設定時)/wcslen(Unicode設定)/strlen(マルチバイト設定)マルチバイト設定時の日本語対応関数 _mbslenになります。第一引数がNULLのとき、アクセス違反が発生します。<br /> lenの前にnが付くものは第二引数に最大文字数を設定する必要があり、文字列がそれを超えた場合は最大文字数を返します。<br /> _tcsnlen(TCHA型設定時)/wcsnlen(Unicode設定)/strnlen(マルチバイト設定) サフィックスに_sが付く関数では、第一引数がNULLときは戻り値が0になります。<br /> マルチバイト設定時の日本語対応関数 _mbsnlen<br /> <br /> _mbstrlenおよび_mbstrnlen は無効なマルチバイトを含む場合に戻り値-1を返します。_mbstrnlen は最大文字を超えると例外処理が発生し戻り値に-1とするとともにEINVAL に errnoを格納します。<br /> 更にサフィックスに_lが付くものは最後の引数として、ロケール設定ができます。ロケール設定の無い関数はsetlocale関数で設定された値に従います。<br /> 全部で<br /> strlen、strlen_l、wcslen、wcslen_l、_mbslen、_mbslen_l、_mbstrlen、_mbstrlen_l , strnlen、strnlen_s、strnlen_l、wcsnlen、wcsnlen_s、wcsnlen_l、_mbsnlen、_mbsnlen_l、_mbstrnlen、_mbstrnlen_l <br /> これだけの種類があります。<br /> 要するに?<br /> _tcslenを使うのが良いでしょう。Locale設定はあらかじめ実施しておくのが良いと思います。strnlen_sやwcsnlen_sはNULLの文字列の場合に0を返してくれるのはなんか良さげですが、文字列の終端が必ず\0で終わっていさえすれば必要のないことです。それはヌル文字でもいいわけです。アドレスがヌルのヌルポインターのときにプログラムが止まるよって言ってます。なんか、いっぱいありすぎて迷うけど…。 _tcslen!<br /> _lが付く関数は個別にLocale変換が必要な場合にのみ使えば良いでしょう。_tcsnlenで最大文字数を設定するのは、一見安全そうですが、なんか無駄なような気がします。あきらかに最大文字数がわかるような場合には、なんか入れておいて_tcsnlen_sってのはいい選択肢だと思います。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <tchar.h> int main() { _tsetlocale(LC_ALL, _T("Japanese")); const TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")}; for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){ _tprintf(_T("%2d/%2d:cStr0[%2d]=%s\nStrCount=%d\n\n"),i, sizeof(cStr0)/sizeof(*cStr0),i, cStr0[i],_tcslen(cStr0[i])); } } </syntaxhighlight> 出力結果 <syntaxhighlight lang="cpp"> 0/ 3:cStr0[ 0]=表示:よねウィキの機能<yonewiki> StrCount=21 1/ 3:cStr0[ 1]=表示:よねウィキの機能1<yonewiki> StrCount=22 2/ 3:cStr0[ 2]=表示:よねウィキの機能2<yonewiki> StrCount=22 </syntaxhighlight> _mb系の関数を利用するには、<nowiki>#include <mbstring.h></nowiki>をインクルードする必要があります。<br /> 更に_mbslenは文字列長探索をしたい文字列変数の引数が、unsigned char型ですので、wchar_t型、char型の引数では文字列長を探索することは出来ません。あえて実施するならば、reinterpret_cast<unsigned char*>(※charポインタ型※)と強制的なキャストを実施することで、動作させることができます。_mbstrlenはchar型を引数にしますので、wchar_t型を使わない場合の日本語文字列長検索として使うことが出来ます。<br /> 但し、この強制キャストは定数文字列const宣言のある文字列に対しては操作が出来ないため、文字列のコピーを作成する必要があります。文字列コピー操作の詳細は次の項目に記載予定なので、順番に読み進めている人は、コピー操作について理解してから戻ってきた方が良いかもしれません。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <tchar.h> #include <mbstring.h> int main() { _tsetlocale(LC_ALL, _T("Japanese")); const char *cStr1[]={"", "表示:よねウィキの機能<yonewiki>", "表示:よねウィキの機能1<yonewiki>", "表示:よねウィキの機能2<yonewiki>"}; char **ppcStr1 = new char* [sizeof(cStr1)/sizeof(*cStr1)]; _tprintf(_T("const マルチバイト文字→マルチバイト文字コピー→_mbslen関数2バイト文字認識文字列長探索\n")); for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ ppcStr1[i] = new char[strlen(cStr1[i]) + 1];//まずは単純に文字列をコピーするための領域を確保。 strcpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i]);//コピー } for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ //強制キャストでunsigned char*型に変換して文字列長を探索。 printf("%2d/%2d:cStr1[%2d]=%s\nStrCount=%d\n\n",i, sizeof(cStr1)/sizeof(*cStr1),i, ppcStr1[i],_mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i]))); } for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ delete[] *(ppcStr1 + i); } delete[] ppcStr1; } </syntaxhighlight> という具合にして、const char→char→_mabslenで文字列長探索が出来ます。出力結果は以下のとおりです。<br /> <syntaxhighlight lang="cpp"> const マルチバイト文字→マルチバイト文字コピー→_mbslen関数2バイト文字認識文字列長探索 0/ 4:cStr1[ 0]= StrCount=0 1/ 4:cStr1[ 1]=表示:よねウィキの機能<yonewiki> StrCount=21 2/ 4:cStr1[ 2]=表示:よねウィキの機能1<yonewiki> StrCount=22 3/ 4:cStr1[ 3]=表示:よねウィキの機能2<yonewiki> StrCount=22 </syntaxhighlight> _mbstrlen関数を使う場合はコピー操作が必要にならず、const宣言している文字列変数を使っての文字列長探索ができます。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <tchar.h> #include <mbstring.h> int main() { _tsetlocale(LC_ALL, _T("Japanese")); const char *cStr1[]={"", "表示:よねウィキの機能<yonewiki>", "表示:よねウィキの機能1<yonewiki>", "表示:よねウィキの機能2<yonewiki>"}; _tprintf(_T("const マルチバイト文字→_mbstrlen関数2バイト文字認識文字列長探索\n")); for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ printf("%2d/%2d:cStr1[%2d]=%s\nStrCount=%d\n\n",i, sizeof(cStr1)/sizeof(*cStr1),i, cStr1[i],_mbstrlen(cStr1[i])); } } </syntaxhighlight> 出力結果は以下のとおりです。<br /> <syntaxhighlight lang="cpp"> const マルチバイト文字→_mbstrlen関数2バイト文字認識文字列長探索 0/ 4:cStr1[ 0]= StrCount=0 1/ 4:cStr1[ 1]=表示:よねウィキの機能<yonewiki> StrCount=21 2/ 4:cStr1[ 2]=表示:よねウィキの機能1<yonewiki> StrCount=22 3/ 4:cStr1[ 3]=表示:よねウィキの機能2<yonewiki> StrCount=22 </syntaxhighlight> といった具合にいろいろな関数があるので、一つづつ掘り下げて理解しておくのも、必要です。こういう違いがあるということを把握していれば、臨機応変に対応しやすくなるかもです。自分は分かったつもりにならずに、常に謙虚に確かめてみる姿勢ってのは、必要なんじゃないかと思います。そして常に、自分が実施するであろうパターンを網羅して確かめる。だから、あえて文字列の配列の場合にどうやればいいのかを探ります。 =='''文字列コピー'''== 文字列のコピー?なんでコピーするの?ってなりそうですけど、変数の代入みたいなものです。元の文字列はとっておいて、コピーした方を加工するということです。あるいはその逆。紙のコピーだと原版が綺麗だけど、原版もコピーも同じだから…<br /> a=13;<br /> b=a;<br /> b=b*b;<br /> b=a;<br /> b=b*b*b;<br /> ってな感じのことやったりしません?aの値は取っておきたい。そういうときに使ったりするのかなぁと思います。<br /> リファレンスは以下のとおりです。<br /> http://msdn.microsoft.com/ja-jp/library/kk6xf663.aspx<br /> http://msdn.microsoft.com/ja-jp/library/td1esda9.aspx<br /> http://msdn.microsoft.com/ja-jp/library/xdsywd25.aspx<br /> http://msdn.microsoft.com/ja-jp/library/5dae5d43.aspx<br /> また、例によってマルチバイト文字版strcpy、ワイド文字版wcscpy、マルチバイト2バイト文字認識版_mbscpy、セキュリティ強化版の_sサフィックスがあります。_s無し版の引数はコピー先文字列変数先頭char型アドレス、コピー元文字列変数先頭char型アドレス(文字列の終端に\0があること)となっています。_mbscpyの引数は第一引数、第二引数共にunsigned charになっています。この微妙に使いにくい引数の異なり…。しょうがないのかなぁ。<br /> そして、_s版は第2引数にコピー先文字列変数先頭char型アドレスの大きさを必要とします。_sが付かない関数で第二引数だったコピー元は第三引数にずれます。strncpyのようにcpyの前にnが付くパターンでマルチバイト文字版、ワイド文字版、マルチ2バイト文字対応版と更には_lサフィックスでロケール個別設定ができる関数があります。マルチバイト2バイト文字対応版にはバイト長を引数とするcpyの前にbが付いたものもあります。> でも…マルチバイト2バイト文字対応文字ってもともと配列の要素一つに1バイトを格納しているから、バイト長で扱っても、要素数で扱っても変わらないから何が違うのか?よくわからない。_mbs系のcpy関数の利点ってなんだろう。このへんはまた今度、考えてみよう。 全部で、<br /> strcpy、wcscpy、_mbscpy , strcpy_s、wcscpy_s、_mbscpy_s ,<br /> strncpy、_strncpy_l、wcsncpy、_wcsncpy_l、_mbsncpy、_mbsncpy_l , strncpy_s、_strncpy_s_l、wcsncpy_s、_wcsncpy_s_l、_mbsncpy_s、_mbsncpy_s_l<br /> _mbsnbcpy、_mbsnbcpy_l , _mbsnbcpy_s、_mbsnbcpy_s_l <br /> これだけあります。<br /> で、結局は_tcsncpy_sを使いなさいってことになります。Unicode設定ならワイド文字版で、文字列長に厳しい設定が必要なwcsncpy_sだね。第二引数は配列の大きさ+終端\0のための要素1つ分。バイト数ではないです。動的に生成した変数の場合は配列の大きさが取得できないので、wcslen(_tcslen)のような文字列長取得関数を使って、戻ってきた値に\0の要素のために1を加算した値が良いです。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <iostream> //#include<locale.h> tchar.hがインクルードされていれば、いらない。 #include<tchar.h> int main() { _tsetlocale( LC_ALL, _T("Japanese")); size_t requiredSize = 0; size_t sizecStr0=0; TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")}; const char *cStr1[]={" ","表示:よねウィキの機能<yonewiki>","表示:よねウィキの機能1<yonewiki>","表示:よねウィキの機能2<yonewiki>"}; TCHAR **ppcStr0 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)]; char **ppcStr1 = new char*[sizeof(cStr1)/sizeof(*cStr1)]; unsigned char **ppucStr2; ppucStr2 = new unsigned char*[sizeof(cStr1)/sizeof(*cStr1)]; //_tcscpy_s(wcscpy_s)関数での文字列コピー for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){ ppcStr0[i] = new TCHAR[_tcslen(cStr0[i]) + 1]; _tcscpy_s(ppcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i]);//ココ! } //strcpy_s関数での文字列コピー for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ ppcStr1[i] = new char[strlen(cStr1[i]) + 1]; strcpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i]);//ココ! } //_mbscpy_s関数での文字列コピー for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ sizecStr0 = strlen(ppcStr1[i]) + 1; ppucStr2[i] = new unsigned char[sizecStr0]; _mbscpy_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]));//ココ! } //出力部分 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ _tprintf(_T("_tcslen(cStr0[i])=%d\nppcStr0=%s,\n cStr0=%s,\n\n"),_tcslen(cStr0[i]),*(ppcStr0 + i),*(cStr0 + i)); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ printf("strlen(cStr1[i])=%d\nppcStr1=%s,\n cStr1=%s,\n\n",strlen(cStr1[i]),*(ppcStr1 + i),*(cStr1 + i)); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ printf("_mbslen(cStr1[i])=%d\nppucStr2=%s,\n cStr1=%s,\n\n",_mbslen(ppucStr2[i]),ppucStr2[i],*(cStr1 + i)); } //動的に確保したメモリの解放処理 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ delete[] *(ppcStr0 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppcStr1 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppucStr2 + i); } delete[] ppucStr2; delete[] ppcStr0; delete[] ppcStr1; printf("\n"); return 0; } </syntaxhighlight> という感じでしたね。無理やり_mbscpy関数を使ってみるとこんな感じですね。unsigned char型は宣言と同時にnew演算子の定義は出来ないようです。const型のcharからはキャストできないから、結局は一度、strcpy_sを使って、char型の文字列に置き換えないといけないです。マルチバイト型の2バイト文字認識操作関数って結局は、char型と同じなので、文字数を数えるときはstrlenで数えないといけない。char型と_mbsxxx関数を行ったり来たりするだけです。<br /> 出力結果は以下のとおり<br /> <syntaxhighlight lang="cpp"> _tcslen(cStr0[i])=21 ppcStr0=表示:よねウィキの機能<yonewiki>, cStr0=表示:よねウィキの機能<yonewiki>, _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能1<yonewiki>, cStr0=表示:よねウィキの機能1<yonewiki>, _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能2<yonewiki>, cStr0=表示:よねウィキの機能2<yonewiki>, strlen(cStr1[i])=1 ppcStr1= , cStr1= , strlen(cStr1[i])=32 ppcStr1=表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, _mbslen(cStr1[i])=1 ppucStr2= , cStr1= , _mbslen(cStr1[i])=21 ppucStr2=表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, _mbslen(cStr1[i])=22 ppucStr2=表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, _mbslen(cStr1[i])=22 ppucStr2=表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, </syntaxhighlight> 上記に加えて、コピーする文字数を引数とするcpyの前にnが付く関数_tcsncpy_s/wcsncpy_s/strncpy_s/_mbsncpy_sにする場合は<br /> <syntaxhighlight lang="cpp"> … … //_tcscpy_s(ppcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i]);//ココ! _tcsncpy_s(ppcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! … … //strcpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i]);//ココ! strncpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i],strlen(cStr1[i]) + 1);//ココ! … … //_mbscpy_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]));//ココ! _mbsncpy_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]), _mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i])));//ココ! … … </syntaxhighlight> と、4つの引数をとるように記述します。第4引数が出力する最大文字の配列数です。出力する文字数と考えることもできます。マルチバイト文字の場合には出力する文字数を指定できるのは効果的で、先頭から何バイト目で区切れば日本語文字が分断されないかの判断もしてくれながらの出力となります。この出力する文字数をあえてバイト単位で指定する_mbsnbcpy_sもあります。ただし、コピー先の文字列の配列はstrlenのようなバイト数分で準備する必要があることに注意が必要です。出力文字数を指定する場合は文字列全体の長さではなく、指定した文字数で必要な文字列バイト数を算出しておいて、メモリを確保するように処理を記述するのが良いかもしれません。ここでは強制キャストを使いましたが、もともとの文字列がunsigned charとして定義されているものをコピーするときに_mbs系の文字列コピーを利用するというのが自然な使い方になります。<br /> =='''文字列連結'''== 文字列の処理において、文字列を抜き出すことと、連結することはプログラミングにおいて、かなり頻繁に必要となる処理です。設定ファイルのようなものを書き出す場合に、設定1の値をAとユーザが指定した場合、文字列リテラルとして、「<設定1="」と「A」と「">」とを繋ぎ合わせて、記憶しておいて、あとで書き出し処理をしたり、あとで設定ファイルの中身を見る時に設定1の値を抜き出すためにAという値を抜き出す。そういう感じです。上記のようなXML形式の設定方法ならパースという手法によって、既に設定属性値と設定値を書き込んだり、読み込んだりするための関数が提供されていたりしますが、同じようなことを自分でやっていくということはあります。<br /> 連結は英語でcatenateと言うため、strcatのようなcatというキーワードが関数に使われます。<br /> リファレンスは以下の通りです。<br /> http://msdn.microsoft.com/ja-jp/library/tbyd7s1y.aspx<br /> http://msdn.microsoft.com/ja-jp/library/w6w3kbaf.aspx<br /> http://msdn.microsoft.com/ja-jp/library/td1esda9.aspx<br /> http://msdn.microsoft.com/ja-jp/library/d45bbxx4.aspx<br /> http://msdn.microsoft.com/ja-jp/library/5c6eeh98.aspx<br /> http://msdn.microsoft.com/ja-jp/library/h1x0y282.aspx<br /> 多い…<br /> strncat、_strncat_l、wcsncat、wcsncat_l、_mbsncat、_mbsncat_l , strncat_s、_strncat_s_l、wcsncat_s、_wcsncat_s_l、_mbsncat_s、_mbsncat_s_l <br /> strcat、wcscat、_mbscat , strcat_s、wcscat_s、_mbscat_s <br /> _mbsnbcat、_mbsnbcat_l , _mbsnbcat_s、_mbsnbcat_s_l<br /> <br /> 例によって_sのついた連結先文字列の配列サイズを明記する関数に_lのついた個別ロケール設定関数。そして連結させたい文字列の文字数を記述するncatに文字数をバイト数で指定するnbcat。あとはワイド文字のwcsで始まる関数。マルチバイトのstrで始まる関数。マルチバイト2バイト文字対応の_mbsで始まる関数。全部で22種類。引数は文字列コピーと同じですが、コピー先の変数が既に文字列が格納されてる\0で終わる文字列になっていて、処理をした結果、第一引数の文字列先頭アドレスの中身が文字列連結した結果になるという点が異なると覚えれば、連結とコピーは似ていると覚えればよいかと思います。先の文字列コピーのプログラムのコピーcpy系関数の後ろに連結cat系関数を追加して、文字列を2回繰り返されるようにしたサンプルです。所謂(いわゆる)、手抜きです。ただし、連結関数では連結後の文字列の配列大きさを要求されるため、あらかじめ動的に確保する領域が2倍になっていることに注意して下さい。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <iostream> //#include<locale.h> tchar.hがインクルードされていればいらない。 #include<tchar.h> int main() { _tsetlocale( LC_ALL, _T("Japanese")); size_t requiredSize = 0; size_t sizecStr0=0; TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")}; const char *cStr1[]={" ","表示:よねウィキの機能<yonewiki>","表示:よねウィキの機能1<yonewiki>","表示:よねウィキの機能2<yonewiki>"}; TCHAR **ppcStr0 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)]; char **ppcStr1 = new char*[sizeof(cStr1)/sizeof(*cStr1)]; unsigned char **ppucStr2; ppucStr2 = new unsigned char*[sizeof(cStr1)/sizeof(*cStr1)]; //_tcscpy_s(wcscpy_s)関数での文字列コピー for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){ ppcStr0[i] = new TCHAR[(_tcslen(cStr0[i]) * 2) + 1]; _tcsncpy_s(ppcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! _tcsncat_s(ppcStr0[i], (_tcslen(cStr0[i]) * 2) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! } //strcpy_s関数での文字列コピー for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ ppcStr1[i] = new char[(strlen(cStr1[i]) * 2) + 1]; strncpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i],strlen(cStr1[i]) + 1);//ココ! strncat_s(ppcStr1[i], (strlen(cStr1[i]) * 2) + 1,cStr1[i],strlen(cStr1[i]) + 1);//ココ! } //_mbscpy_s関数での文字列コピー strcat関数部分ですでに2回繰り返しになった文字をさらに繰り返し連結する処理になってます。4回繰り返し文字列になります。 for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ sizecStr0 = (strlen(ppcStr1[i]) * 2) + 1; ppucStr2[i] = new unsigned char[sizecStr0]; _mbsncpy_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]), _mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i])));//ココ! _mbsncat_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]), _mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i])));//ココ! } //出力部分 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ _tprintf(_T("_tcslen(cStr0[i])=%d\nppcStr0=%s,\n cStr0=%s,\n\n"),_tcslen(cStr0[i]),*(ppcStr0 + i),*(cStr0 + i)); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ printf("strlen(cStr1[i])=%d\nppcStr1=%s,\n cStr1=%s,\n\n",strlen(cStr1[i]),*(ppcStr1 + i),*(cStr1 + i)); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ printf("_mbslen(cStr1[i])=%d\nppucStr2=%s,\n cStr1=%s,\n\n",_mbslen(ppucStr2[i]),ppucStr2[i],*(cStr1 + i)); } //動的に確保したメモリの解放処理 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ delete[] *(ppcStr0 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppcStr1 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppucStr2 + i); } delete[] ppucStr2; delete[] ppcStr0; delete[] ppcStr1; printf("\n"); return 0; } </syntaxhighlight> 出力結果<br /> <syntaxhighlight lang="cpp"> _tcslen(cStr0[i])=21 ppcStr0=表示:よねウィキの機能<yonewiki>表示:よねウィキの機能<yonewiki>, cStr0=表示:よねウィキの機能<yonewiki>, _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能1<yonewiki>表示:よねウィキの機能1<yonewiki>, cStr0=表示:よねウィキの機能1<yonewiki>, _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>, cStr0=表示:よねウィキの機能2<yonewiki>, strlen(cStr1[i])=1 ppcStr1= , cStr1= , strlen(cStr1[i])=32 ppcStr1=表示:よねウィキの機能<yonewiki>表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能1<yonewiki>表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, _mbslen(cStr1[i])=4 ppucStr2= , cStr1= , _mbslen(cStr1[i])=84 ppucStr2=表示:よねウィキの機能<yonewiki>表示:よねウィキの機能<yonewiki>表示:よねウィキの機能<yonewiki>表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, _mbslen(cStr1[i])=88 ppucStr2=表示:よねウィキの機能1<yonewiki>表示:よねウィキの機能1<yonewiki>表示:よねウィキの機能1<yonewiki>表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, _mbslen(cStr1[i])=88 ppucStr2=表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, </syntaxhighlight> =='''文字列比較'''== 数値の比較は配列でないかぎりは単純な1変数同志の比較ですが、文字列は配列全体が一致しているかを確認することになります。かといって、自分で配列全体を相互に比較するようなプログラムを組むという必要はなく、標準で準備されている関数を使う事で比較できます。VB、PHP、Perlのような関数では関数を使わずに、数値と数値を1変数で比較するかのように比較ができる仕組みになっていますが、C言語では関数を使って比較する必要があります。面倒だ(´Д`;)。<br /> この厳密さも、C言語を難しく感じる一つの要素だと思います。配列をひとつづつ確認する作業なんだという意識づけを忘れないという意味では大事なようにも感じます。だから、ちょっとした変更の伴う比較でも、どうすればよいかを考えることができるのだと思います。VBやPHP、Perlのような便利な比較ばかりをやっているとふと、大文字小文字区別なし変換をしたいとか、半角全角区別なし変換をしたいときに、思考が止まってしまう。 そういうときにはPHPやPerl、VBでも一生懸命調べて、C言語の考え方にたどり着いて、比較することになるんでしょうけど…さいしょから、文字列比較とは、こういうものだと知っておけば、それでいいのですから、ネガティブに考えず、これを覚えれば、潰しが効くとポジティブにとらえてやっていきましょう。比較は英語でcompairと表現するため、関数名にはcmpが使われます。半導体製造工程のcmpとは違います。Chemical Micro Polisherだっけ?違った。Chemical Mechanical Polishingだった。<br /> 例によって比較の関数もマルチバイト文字、ワイド文字、マルチバイト2バイト文字対応といろいろな関数があります。<br /> strncmp、wcsncmp、_mbsncmp、_mbsncmp_l<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/eywx8zcx.aspx<br /> _strnicmp、_wcsnicmp、_mbsnicmp、_strnicmp_l、_wcsnicmp_l、_mbsnicmp_l<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/chd90w8e.aspx<br /> _stricmp、_wcsicmp、_mbsicmp、_stricmp_l、_wcsicmp_l、_mbsicmp_l<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/k59z8dwe.aspx<br /> strcmp、wcscmp、_mbscmp<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/e0z9k731.aspx<br /> _mbsnbicmp、_mbsnbicmp_l <br /> http://msdn.microsoft.com/ja-jp/library/vstudio/dyhkb6c5.aspx<br /> _mbsnbcmp、_mbsnbcmp_l <br /> http://msdn.microsoft.com/ja-jp/library/vstudio/hce588f2.aspx<br /> strcoll、wcscoll、_mbscoll、_strcoll_l、_wcscoll_l、_mbscoll_l , _stricoll、_wcsicoll、_mbsicoll、_stricoll_l、_wcsicoll_l、_mbsicoll_l, _strncoll、_wcsncoll、_mbsncoll、_strncoll_l、_wcsncoll_l、_mbsncoll_l, _strnicoll、_wcsnicoll、_mbsnicoll、_strnicoll_l、_wcsnicoll_l、_mbsnicoll_l<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/a7cwbx4t.aspx<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/2w46a1da.aspx<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/72c43s4t.aspx<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/a697c234.aspx<br /> <br /> 多い!更に多い。<br /> けどicmpってついてるやつは、大文字小文字を区別しないと考えればいいし、cmpで終わらない、collはコードページという特殊な考え方が使われること、ncpmは比較する文字数指定あり、_lはロケール個別指定、と考えればかなりすっきり。collによる比較はあとで考えましょう。<br /> <syntaxhighlight lang="cpp" line start="1"> #include <iostream> //#include<locale.h> wchar.hがインクルードされていれば、いらない。 #include<wchar.h> int main() { _wsetlocale( LC_ALL, L("Japanese")); size_t sizecStr0=0; long iCmpResult =0; TCHAR *cStr0[]={_T("表示:よねウィキの機能0<yonewiki>"),_T("表示Nよねウィキの機能12<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")}; const char *cStr1[]={" ","表示:よねウィキの機能<yonewiki>","表示:よねウィキの機能1<yonewiki>","表示:よねウィキの機能2<yonewiki>"}; TCHAR **ppcStr0 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)]; char **ppcStr1 = new char*[sizeof(cStr1)/sizeof(*cStr1)]; unsigned char **ppucStr2; ppucStr2 = new unsigned char*[sizeof(cStr1)/sizeof(*cStr1)]; TCHAR **ppcStr10 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)]; char **ppcStr11 = new char*[sizeof(cStr1)/sizeof(*cStr1)]; unsigned char **ppucStr20; ppucStr20 = new unsigned char*[sizeof(cStr1)/sizeof(*cStr1)]; for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){ ppcStr0[i] = new TCHAR[(_tcslen(cStr0[i]) * 2) + 1]; ppcStr10[i] = new TCHAR[(_tcslen(cStr0[i]) * 2) + 1]; _tcsncpy_s(ppcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! _tcsncpy_s(ppcStr10[i], _tcslen(cStr0[i]) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! _tcsncat_s(ppcStr0[i], (_tcslen(cStr0[i]) * 2) + 1,cStr0[i], _tcslen(cStr0[i]) + 1);//ココ! } //strcpy_s関数での文字列コピー for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ ppcStr1[i] = new char[(strlen(cStr1[i]) * 2) + 1]; ppcStr11[i] = new char[(strlen(cStr1[i]) * 2) + 1]; strncpy_s(ppcStr1[i], strlen(cStr1[i]) + 1,cStr1[i],strlen(cStr1[i]) + 1);//ココ! strncpy_s(ppcStr11[i], strlen(cStr1[i]) + 1,cStr1[i],strlen(cStr1[i]) + 1);//ココ! } //_mbscpy_s関数での文字列コピー for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ sizecStr0 = (strlen(ppcStr1[i]) * 2) + 1; ppucStr2[i] = new unsigned char[sizecStr0]; ppucStr20[i] = new unsigned char[sizecStr0]; _mbsncpy_s(ppucStr2[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]), _mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i])));//ココ! _mbsncpy_s(ppucStr20[i], sizecStr0,reinterpret_cast<unsigned char*>(ppcStr1[i]), _mbslen(reinterpret_cast<unsigned char*>(ppcStr1[i])));//ココ! } //出力部分 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ _tprintf(_T("_tcslen(cStr0[i])=%d\nppcStr0=%s,\n cStr0=%s,\n"),_tcslen(cStr0[i]),*(ppcStr0 + i),*(cStr0 + i)); for(int k = 0; k < sizeof(cStr0)/sizeof(*cStr0); k++){ iCmpResult = _tcsncmp(*(ppcStr0 + i),*(ppcStr10 + k),_tcslen(cStr0[i])); _tprintf(_T("i=%d, k=%d, iCmpResult=%ld,\n"),i,k,iCmpResult); } _tprintf(_T("\n")); } _tprintf(_T("\n")); for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ printf("strlen(cStr1[i])=%d\nppcStr1=%s,\n cStr1=%s,\n\n",strlen(cStr1[i]),*(ppcStr1 + i),*(cStr1 + i)); for(int k = 0; k < (sizeof(cStr1)/sizeof(*cStr1)); k++){ iCmpResult = strncmp(*(ppcStr1 + i),*(ppcStr11 + k),strlen(cStr1[i])); _tprintf(_T("i=%d, k=%d, iCmpResult=%ld,\n"),i,k,iCmpResult); } _tprintf(_T("\n")); } for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){ printf("_mbslen(cStr1[i])=%d\nppucStr2=%s,\n cStr1=%s,\n\n",_mbslen(ppucStr2[i]),ppucStr2[i],*(cStr1 + i)); for(int k = 0; k < (sizeof(cStr1)/sizeof(*cStr1)); k++){ iCmpResult = _mbsncmp(ppucStr2[i],ppucStr20[k],_mbslen(ppucStr2[i])); _tprintf(_T("i=%d, k=%d, iCmpResult=%ld,\n"),i,k,iCmpResult); } _tprintf(_T("\n")); } //動的に確保したメモリの解放処理 for(int i = 0; i < sizeof(cStr0)/sizeof(*cStr0); i++){ delete[] *(ppcStr0 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppcStr1 + i); } for(int i = 0; i < sizeof(cStr1)/sizeof(*cStr1); i++){ delete[] *(ppucStr2 + i); } delete[] ppucStr2; delete[] ppcStr0; delete[] ppcStr1; printf("\n"); return 0; } </syntaxhighlight> 出力結果 <syntaxhighlight lang="cpp"> _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能0<yonewiki>表示:よねウィキの機能0<yonewiki>, cStr0=表示:よねウィキの機能0<yonewiki>, i=0, k=0, iCmpResult=0, i=0, k=1, iCmpResult=-20, i=0, k=2, iCmpResult=-2, _tcslen(cStr0[i])=23 ppcStr0=表示Nよねウィキの機能12<yonewiki>表示Nよねウィキの機能12<yonewiki>, cStr0=表示Nよねウィキの機能12<yonewiki>, i=1, k=0, iCmpResult=20, i=1, k=1, iCmpResult=0, i=1, k=2, iCmpResult=20, _tcslen(cStr0[i])=22 ppcStr0=表示:よねウィキの機能2<yonewiki>表示:よねウィキの機能2<yonewiki>, cStr0=表示:よねウィキの機能2<yonewiki>, i=2, k=0, iCmpResult=2, i=2, k=1, iCmpResult=-20, i=2, k=2, iCmpResult=0, strlen(cStr1[i])=1 ppcStr1= , cStr1= , i=0, k=0, iCmpResult=0, i=0, k=1, iCmpResult=-1, i=0, k=2, iCmpResult=-1, i=0, k=3, iCmpResult=-1, strlen(cStr1[i])=32 ppcStr1=表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, i=1, k=0, iCmpResult=1, i=1, k=1, iCmpResult=0, i=1, k=2, iCmpResult=1, i=1, k=3, iCmpResult=1, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, i=2, k=0, iCmpResult=1, i=2, k=1, iCmpResult=-1, i=2, k=2, iCmpResult=0, i=2, k=3, iCmpResult=-1, strlen(cStr1[i])=33 ppcStr1=表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, i=3, k=0, iCmpResult=1, i=3, k=1, iCmpResult=-1, i=3, k=2, iCmpResult=1, i=3, k=3, iCmpResult=0, _mbslen(cStr1[i])=1 ppucStr2= , cStr1= , i=0, k=0, iCmpResult=0, i=0, k=1, iCmpResult=-1, i=0, k=2, iCmpResult=-1, i=0, k=3, iCmpResult=-1, _mbslen(cStr1[i])=21 ppucStr2=表示:よねウィキの機能<yonewiki>, cStr1=表示:よねウィキの機能<yonewiki>, i=1, k=0, iCmpResult=1, i=1, k=1, iCmpResult=0, i=1, k=2, iCmpResult=1, i=1, k=3, iCmpResult=1, _mbslen(cStr1[i])=22 ppucStr2=表示:よねウィキの機能1<yonewiki>, cStr1=表示:よねウィキの機能1<yonewiki>, i=2, k=0, iCmpResult=1, i=2, k=1, iCmpResult=-1, i=2, k=2, iCmpResult=0, i=2, k=3, iCmpResult=-1, _mbslen(cStr1[i])=22 ppucStr2=表示:よねウィキの機能2<yonewiki>, cStr1=表示:よねウィキの機能2<yonewiki>, i=3, k=0, iCmpResult=1, i=3, k=1, iCmpResult=-1, i=3, k=2, iCmpResult=1, i=3, k=3, iCmpResult=0, </syntaxhighlight> と、こんな感じです。結果を見るとわかるのですが、1,-1,0で結果が表現され、先頭から比較して、一番最初に異なる文字同志を比較したときの結果、文字コードの値が大きかったか小さかったかで、1と-1とに結果が分かれます。これを使って昇順に並べたりすることも出来ます。もちろん0が返ってきたときは、完全一致です。等しかったということになります。<br /> 少し変わっているのは、関数によっては、文字コードの差分値を返すものもあるという点です。_mbs系では文字コードの差分を返すことは無いようです。cmpの前にiが付く関数は差分を返すようです。ただし_mbsnicmpや_mbsicmpのような関数は差分は返しません。wcs系の関数は差分を返します。このことで差分が数値コードによる部分である場合は、その差は数字の差でもあり、一文字(一桁)の数学的な差を計算するのと同様の処理になったり、アルファベットによる連番と考えた場合でも、その差を計算していることにもなります。2バイト文字では差が大きな数値になりますが、符号付きint型で十分に収まる値です。今回は実験的にlongを使いましたが、無駄足でした。仕様のとおりに動作しますね。<br /> 但し、差分の件については、仕様に明記されていませんので、実際に利用する関数での動作を確かめてから使う方が良いと思います。仕様に明記されているのは0より大きい値を返すか小さい値を返すかのどちらかとなっています。正確なVisual C++に限らない言語仕様を検索してみたのですが、わかりませんでした。coll系の比較関数は現在のコードページに従いますので、locale情報を指定しない状態では、各PCのコードページに従います。コードページというのはcp932のような具体的な文字コードのことです。cpの後に続く数字で文字コードセットは分類されています。特段の理由がなければ、使うことは無いでしょう。selocale関数でも引数を指定しなければ各PCのコードページに従いますから、setlocaleで指定できない理由があるという特殊なケースになりそうです。それか、cmpという関数が嫌いでcollが良いと思うかとかですね。ひょっとしたら差分戻り値の件で動作が異なるかもしれません。あとで確認をしておきたいと思います。とてつもなく長いですが、その確認をしてみた結果が以下のものでして、<br /> <br /> [[文字列操作 文字列比較 実行結果1]]<br /> <br /> 528行目から534行目のように同じ'Y'0x79大文字と'y'0x89小文字の比較でありながらも、strcollでは1とstrcmpでは-1とでは結果が異なります。asciiコードでは小文字の方が大きな数字の文字コードが割り振られているため、strcmpのように Y と y の差は0x89 - 0x79で負の数値となり -1 となることを期待しますが、collは現在のコードページかつ辞書式順序を使うために小文字よりも大文字が後ということで1になります。辞書式順序って何だ?と思いつつあるのが131202時点の状況でして、さらに調査をすすめたものが<br /> <br /> [[文字列操作 文字列比較 実行結果2]]<br /> <br /> で、 *大小のみの判定<br /> :wcscmp<br /> :<br /> :wcsicoll/wcscoll/wcsnicoll/_wcsncoll<br /> :<br /> :strcmp/strncmp<br /> :<br /> :strcoll/stricoll/<br /> :<br /> :_mbscmp/_mbsicmp/_mbsncmp/_mbsnicmp/_mbscoll/_mbsicoll/_mbsncoll/_mbsnicoll<br /> <br /> :但し、半角スペースと-全角の比較が発生すると符号付き4byteの最大値を返す。2147483647=21億4748万4647<br /> :_strnicoll/_strncoll<br /> <br /> *差分を返す<br /> :wcsicmp/wcsnicmp/wcsncmp/<br /> <br /> :stricmp/_strnicmp<br /> <br /> という具合の動作であります。collの特徴的なのは辞書順と呼んでいる比較の概念だと思います。ASCIIコードでは大文字と小文字とでは、小文字の方が大きい文字コード番号が付与されていますが、wcs**coll系の比較をする関数では、小文字の方が値が小さいものとして判定してくれます。漢字の範囲になるとロケールで指定したcp932の文字コード順で比較してくれます。阿という文字と哀という文字はUnicode(UTF16)では阿-0x963F 哀-0x54C0 表-0x8868であり、Shift_JISのcp932では阿-0x88A2 哀-0x88A3 表-0x955Cと定義されていますから、阿<nowiki><</nowiki>哀<nowiki><</nowiki>表 のように比較をしてくれます。coll系の関数を使わない場合は 哀<nowiki><</nowiki>表<nowiki><</nowiki>阿のように比較されます。この阿や哀や表という名前のファイル名のテキストをWindowsのエクスプローラで昇順表示すると、この順番になることも確認できます。Shift_JISコード順でソートされてるんだなぁと確認が出来ると思います。Cp932の半角記号あたりの辞書順ってのは、どうなってるんでしょうね。これもまた今度しらべてみたいと思います。ファイル名に使えない文字あたりはどんな順番なんだろうか? '''★豆知識''' '''Unicodeの符号化方式UTF-16とは…''' 日本語の範囲ではUTF16は2バイト文字と考えて良いですが、Unicode全体では0x10FFFFまで利用することになっていますので、0x10000以上の値を持つ文字コードは4バイト文字になります。この0x10000以上の値になる場合は、ビット列で表現すると110110????xxxxxx 110111xxxxxxxxxxのような形式になり????の部分のには上位ビットと下位ビットを16ビットずつに分けた時に0xUUUUDDDDと表記するならば、0xUUUUは0x0001~0x0010の値となり得て、そのUUUU-1の値が????に入ります。xの部分には下位DDDDのビット列そのものが 入ったような値になります。そういう意味ではUnicodeのUTF-16という符号化方式では16bit(2byte)だったり32bit(4byte)のコードになります。 '''Unicodeの符号化方式UTF-8とは…''' UTF-8はASCIIコードの7ビット文字は1バイトで表し、それ以降の2バイト文字は3バイトで表すような符号化方式です。この方式でUnicode全体を表現するには最大6byteを使います。Unicodeの上位2Byteが0x1~0x10までの値しか使わないので6バイトで表せます。しかしながら日本語のほとんどは2byte文字で表現できるので、1文字あたり3バイトになるのはUTF16が2倍増しなのに対して、2.5倍増しとなるのがデメリットです。但し、半角英数文字が登場する比率で、そのデメリットは削減できる可能性もあります。日本語を10文字打ったら20byte、7文字打ったら、14byteだから+6文字を半角文字という具合です。7+6だったから、おおよそ半分半分の文字数がいいみたいね。それほど半角英数文字を使うのは技術情報を書く文書くらいだと思いますが…。その人の趣味によって、仕事によって、損得が変わるというものです。UTF-8はかなり広い範囲で使われています。それは英語圏の人が、日本語のような2バイト文字による被害をまったく被らないからだと思います。 ちなみに符号化方式によると2バイト文字はビット列を x0,x1,x2,x3, x4,x5,x6,x7, x8,x9,xA,xB, xC,xD,xE,xFと表現すると、 (1110),(x0,x1,x2,x3),(10,x4,x5),(x6,x7, x8,x9),(10,xA,xB),(xC,xD,xE,xF)となります。 文字の先頭に3バイトなら3つの1が付き、1バイト目を構成し、 次のバイト以降はバイト先頭に全て10をつける。おかげさまで、1バイト目の下位4bitと、3バイト目の下位4bitの16進数だけはそのままだけど、他のbitは16進表記が、変化します。読み起こすのは大変ですね。文字列の最初を見つけたら、そこから文字列に起こしていけば良いですが、新しい変換表を考えないと直感的にはUnicode文字のコード表には表現できないですね。 =='''文字列区切り文字分割'''== 文字列の中に区切りがある場合ってのは、例えば、<br /> Sweet Refrain/Perfume,雨のち晴レルヤ/ゆず,東京デスティニー/ポルノグラフィティ,熱愛発覚中/椎名林檎と中田ヤスタカ(CAPSULE),STORY OF MY LIFE/ONE DIRECTION,THE SEVEN SEAS/THE BAWDIES,手紙/ナオト・インティライミ,SLY/RIP SLYME,閃光 feat.10-FEET/東京スカパラダイスオーケストラ,風は西から/奥田民生,DIAMOND SKIN/GLAY,SO RIGHT/三代目 J Soul Brothers from EXILE TRIBE,White Winter Love。/ハジ→,APPLAUSE/LADY GAGA,ピタカゲ from「COUP D'ETAT[+ONE OF A KIND&HEARTBREAKER]」/G-DRAGON (from BIGBANG),LEMON/The Birthday,もったいないとらんど/きゃりーぱみゅぱみゅ,僕らの物語/GReeeeN,Lily/Dragon Ash,START IT AGAIN/AK-69,Very Merry Xmas/東方神起,SURVIVAL/EMINEM,ウォーリーヒーロー/KANA-BOON,あなたへ/エレファントカシマシ,キラーボール/ゲスの極み乙女。,Babies are popstars/松任谷由実,ROCK N ROLL(日本語字幕入り)/AVRIL LAVIGNE,HOT SHOT/GENERATIONS from EXILE TRIBE,守ってあげたい/JUJU,3 2 1/SHINee,Always/斉藤和義,ファンファーレがきこえる/Base Ball Bear,X'masラブストーリー。/ソナーポケット,Missing/androp,ラストバージン/RADWIMPS,エデン(lyric ver)/Aqua Timez,クルクル/e-girls,WHO'S NEXT/SiM,Time goes by/URATA NAOYA,Bi-Li-Li Emotion/Superfly,One day/TOKYO No.1 SOUL SET,粉雪/BENI,FEEVEER/MO'SOME TONEBENDER,No.525300887039/supercell,Hello\,999/N'夙川BOYS,Every Hero/kaho,黒猫?Adult Black Cat?/Acid Black Cherry,指でキスしよう/東京カランコロン,m@u/後藤まりこ,海と花束/きのこ帝国<br /> のようなヒットチャートベスト50があったとしたら , カンマでチャートが区切られていて、 / スラッシュで曲名とアーチスト名が区切られていることになります。<br /> こういった区切り文字毎に見ると意味が発生する最小の単位をトークンと言います。区切る文字によっては更に小さな意味を持つこともありますが、区切り文字毎に文字を抜き出そうとする文字分割処理そのものをトークンと呼んでいます。従って、関数名にはtokenのtokをとったような関数名が使われます。これまでの関数もそうでしたが、やっぱりいろんな種類のstrtok関数が準備されていまして…<br /> strtok、_strtok_l、wcstok、_wcstok_l、_mbstok、_mbstok_l ,<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/2c8d19sb.aspx<br /> strtok_s、_strtok_s_l、wcstok_s、_wcstok_s_l、_mbstok_s、_mbstok_s_l<br /> http://msdn.microsoft.com/ja-jp/library/vstudio/ftsafwz3.aspx<br /> 比較関数よりは少ないけど、やっぱりマルチバイト版、マルチバイト2バイト文字対応版、ワイド文字版、それに_sをつけた文字列長を指定するセキュア版と個別ロケール指定をする_lのロケール版があります。_l版についてはココまで使い方を触れてこなかったのですが、 <syntaxhighlight lang="cpp"> _locale_t locale; locale = _tsetlocale(LC_ALL, _T("Japanese")); </syntaxhighlight> として、localeという変数に_locale_t型の値を_tsetlocaleの返り値として格納しておいたものを関数の最後の引数にします。<br /> コマンドプロンプトで動作確認してるだけの段階でlocale変数を利用しても、文字化けしか起こせないと思うので、テキスト出力とかを覚えてから試してみるといいかもしれません。<br /> strtok系の関数では主要な引数が2つで、第1引数には区切り文字を含んだ大元の文字列変数の先頭アドレスの値。第2引数には区切り文字の先頭アドレスの値。","のように文字列リテラルを直接指定することもできますし、複数の区切り文字を含めて",/"とすることもできます。このような文字列が格納されてる変数の先頭アドレスを含んだポインタ変数あるいは、アドレスを返す配列名を指定すること(結局ポインタ変数みたいなものですが…)ができます。第1引数の文字列は書き換え可能な変数でないと駄目です。const char[]="…"のように定義している書き換えできない変数を引数にするとエラーになります。なぜならば、この関数は第2引数で指定している区切り文字を発見したら、その文字が格納されているアドレス部分、配列要素部分の中身を文字列終端を意味する\0に書き換えるからです。つまり、第1引数は、元の状態ではなくなるので、あらかじめバックアップが必要ならコピーをして、保管しておかなければなりません。そして、区切り文字が見つかると先頭アドレスから\0に置き換えた部分までの文字列を返り値として処理を終了します。<br /> え?1つ目以降の区切り文字の結果は?<br /> そうですよね…。2回目の区切り文字探索以降について、この関数の使い方は特殊でして、最初の1回目の区切り文字探索時は上記に記述したような引数で良いのですが、2回目以降の区切り文字探索では第1引数にNULLを設定し、最初に探索した際に\0置き換えた区切り文字の次のアドレスから、第2引数で指定した区切り文字を探索します。それで見つかった区切り文字をまた\0に置き換えて、探索開始アドレスから、\0までの文字列を返り値として処理が終わります。<br /> え?それ以降は?<br /> そうですよね…。探索の結果、区切り文字がみつからなくなるまで、繰り返すようなプログラムにして、ひとつづつ掘り起こすという地味な処理になります。 結果を文字列配列に格納したい場合は、自分で文字列配列を準備して、順番に格納しなければならないのです。区切り文字が何回現れて、いくつの文字が区切られた文字列が返ってきたのかも知りたい場合は、自分で回数を記録する処理が必要になります。<br /> Perlなら見つかった分だけ勝手に配列を作ってくれるような関数だったぞ!って思っている人もおられるかもしれませんが、C++ではそこまで便利な標準関数はないです。ご自分でどうぞお好きなように使って下さい。みたいな感じ?そういうことです。2回目以降で元の文字列を指定せず、NULLを指定することからも、察知できることですが、二つ以上の区切り文字を含めた文字列を交互に、作業することもできませんのであしからず。一つ目が終わってから次の文字列って感じです。不思議な仕組みですよね。想像するに、文字列の後ろからなんらかの文字が現れて次に現れる\0がトークン検索開始位置の文字列になってるのかもしれません。このあたりは実験してみることによってあきらかになるかと思います。<br /> 区切り文字として複数の文字を指定できますが、それぞれ1文字が独立した区切り文字ですので、// のような2文字以上で構成される区切り文字はこの関数だけでは対応できません。新しい手法が必要になります。 <syntaxhighlight lang="cpp" line start="1"> #include <iostream> #include <tchar.h> #include <mbstring.h> //#include <locale.h> int main() { _tsetlocale(LC_ALL, _T("Japanese")); _locale_t locale; locale = _create_locale(LC_ALL, "Japanese"); int iCmpComma=0; int iCmpYen=0; int iCmpSlash=0; int iChartCommaCnt=0; TCHAR *cStr0 = _T("Sweet Refrain/Perfume,雨のち晴レルヤ/ゆず,東京デスティニー/ポルノグラフィティ,\\熱愛発覚中/椎名林檎と中田ヤスタカ(CAPSULE),STORY OF MY LIFE/ONE DIRECTION,THE SEVEN SEAS/THE BAWDIES,手紙/ナオト・インティライミ,SLY/RIP SLYME,閃光 feat10-FEET/東京スカパラダイスオーケストラ,風は西から/奥田民生,DIAMOND SKIN/GLAY,SO RIGHT/三代目 J Soul Brothers from EXILE TRIBE,White Winter Love。/ハジ→,APPLAUSE/LADY GAGA,ピタカゲ from「COUP D'ETAT[+ONE OF A KIND&HEARTBREAKER]」/G-DRAGON (from BIGBANG),LEMON/The Birthday,もったいないとらんど/きゃりーぱみゅぱみゅ,僕らの物語/GReeeeN,Lily/Dragon Ash,START IT AGAIN/AK-69,Very Merry Xmas/東方神起,SURVIVAL/EMINEM,ウォーリーヒーロー/KANA-BOON,あなたへ/エレファントカシマシ,キラーボール/ゲスの極み乙女。,Babies are popstars/松任谷由実,ROCK N ROLL(日本語字幕入り)/AVRIL LAVIGNE,HOT SHOT/GENERATIONS from EXILE TRIBE,守ってあげたい/JUJU,3 2 1/SHINee,Always/斉藤和義,ファンファーレがきこえる/Base Ball Bear,X'masラブストーリー。/ソナーポケット,Missing/androp,ラストバージン/RADWIMPS,エデン(lyric ver)/Aqua Timez,クルクル/e-girls,WHO'S NEXT/SiM,Time goes by/URATA NAOYA,Bi-Li-Li Emotion/Superfly,One day/TOKYO No1 SOUL SET,粉雪/BENI,FEEVEER/MO'SOME TONEBENDER,No525300887039/supercell,Hello\\,999/N'夙川BOYS,Every Hero/kaho,黒猫?Adult Black Cat?/Acid Black Cherry,指でキスしよう/東京カランコロン,m@u/後藤まりこ,海と花束/きのこ帝国"); TCHAR *pcStr0 = new TCHAR[(_tcslen(cStr0)) + 1]; TCHAR *pcStr1 = new TCHAR[(_tcslen(cStr0)) + 1]; TCHAR *pcStr2 = new TCHAR[(_tcslen(cStr0)) + 1]; TCHAR *pcStrTmp; TCHAR *pcStrTmp2; TCHAR *tcharComma = _T(","); TCHAR *tcharYen = _T("\\"); TCHAR *tcharSlash = _T("/"); for(unsigned int i = 0; i < _tcslen(cStr0); i++){ iCmpComma = _tcsncmp(&cStr0[i], tcharComma, 1); if(i > 0){ iCmpYen = _tcsncmp(&cStr0[i - 1] , tcharYen, 1);//コンマ区切りだけど曲名やアーチスト名そのものに,が使われている場合は //予めデータを\,のようにエスケープするというデータ規則が必要。,の前に\が無いか確認して配列サイズを確保。 } if( iCmpComma == 0 && iCmpYen != 0){ iChartCommaCnt++; } } TCHAR ***ppcStrChart = new TCHAR**[iChartCommaCnt + 1]; TCHAR **ppcStrChartTmp = new TCHAR*[iChartCommaCnt + 1]; for(int i = 0; i < iChartCommaCnt + 1;i++){ *(ppcStrChart + i) = new TCHAR*[2];//曲名とアーチスト名の2要素固定 } TCHAR *next_token1 = NULL; TCHAR *next_token2 = NULL; _tcsncpy_s(pcStr0, _tcslen(cStr0) + 1,cStr0, _tcslen(cStr0) + 1);//ココ! pcStr1 = _tcstok_s(pcStr0,_T(","), &next_token1);//establish 1回目の呼び出し { int i = 0; bool bStopIncriment = false; bool bStopIncriment2 = false; while(pcStr1 != NULL && i < (iChartCommaCnt + 1)){ if (bStopIncriment)//区切り文字の前に\が見つかった場合はiを加算しない。 { pcStrTmp = new TCHAR[_tcslen(ppcStrChartTmp[i]) + 1];//現在の文字列最後に区切り文字ではない,があるtmp文字列値バックアップをとるための容量を確保。 _tcsncpy_s(pcStrTmp, _tcslen(ppcStrChartTmp[i]) + 1, ppcStrChartTmp[i], _tcslen(ppcStrChartTmp[i]) + 1 );//そしてバックアップ変数へコピー delete[] ppcStrChartTmp[i];//tmp値を一旦クリア。 ppcStrChartTmp[i] = new TCHAR[_tcslen(pcStrTmp) + _tcslen(pcStr1) + 1];//もう一度容量を確保しなおす。バクアップ変数の長さ+次の区切り文字までの長さ _tcsncpy_s(ppcStrChartTmp[i], _tcslen(pcStrTmp) + _tcslen(pcStr1) + 1 ,pcStrTmp, _tcslen(pcStrTmp));//バックアップ変数と今回の区切り文字を繋ぐ。 _tcsncat_s(ppcStrChartTmp[i], _tcslen(pcStrTmp) + _tcslen(pcStr1) + 1 ,pcStr1, _tcslen(pcStr1));//バックアップ変数と今回の区切り文字を繋ぐ。 _tprintf(_T("i=%2d,ChartTmp1=%s; StrTemp=%s;\n"),i,ppcStrChartTmp[i],pcStrTmp); delete[] pcStrTmp;//バックアップ変数は利用終了。解放。 } iCmpYen = _tcsncmp(&pcStr1[_tcslen(pcStr1)-1],tcharYen,1);//お次の区切り文字は,区切りにしたpcStr1の最後の文字は\ではなかったか確認? if(iCmpYen != 0){ //\以外の通常処理 if(!bStopIncriment){ ppcStrChartTmp[i] = new TCHAR[_tcslen(pcStr1) + 1];//区切り文字の長さで文字配列配列i番目を確保 _tcsncpy_s(ppcStrChartTmp[i],_tcslen(pcStr1) + 1,pcStr1,_tcslen(pcStr1) + 1);//正式な区切り文字なので、区切りまでをコピーして格納。i番目の処理は終了。 } pcStrTmp2 = new TCHAR[_tcslen(ppcStrChartTmp[i]) + 1]; _tcsncpy_s(pcStrTmp2,_tcslen(ppcStrChartTmp[i]) + 1,ppcStrChartTmp[i],_tcslen(ppcStrChartTmp[i]) + 1);//正式な区切り文字なので、区切りまでをコピーして格納。i番目の処理は終了。 ppcStrChart[i][0] = new TCHAR[_tcslen(pcStrTmp2) + 1]; //完全な入れ子状態。, 区切り処理を / 区切り処理に変えただけ。関数化すればスッキリする。けどそんな関数を作るのが目的ではないので、敢えてコレ。***************** pcStr2 = _tcstok_s(pcStrTmp2, _T("/"), &next_token2);//establish 1回目の呼び出し while(pcStr2 != NULL){ if(bStopIncriment2){ pcStrTmp = new TCHAR[_tcslen(ppcStrChart[i][0]) + 1];//現在の文字列最後に区切り文字ではない,があるtmp文字列値バックアップをとるための容量を確保。 _tcsncpy_s(pcStrTmp, _tcslen(ppcStrChart[i][0]) + 1, ppcStrChart[i][0], _tcslen(ppcStrChart[i][0]) + 1 );//そしてバックアップ変数へコピー delete[] ppcStrChart[i][0];//tmp値を一旦クリア。 ppcStrChart[i][0] = new TCHAR[_tcslen(pcStrTmp) + _tcslen(pcStr2) + 1];//もう一度容量を確保しなおす。バクアップ変数の長さ+次の区切り文字までの長さ _tcsncpy_s(ppcStrChart[i][0], _tcslen(pcStrTmp) + _tcslen(pcStr2) + 1 ,pcStrTmp, _tcslen(pcStrTmp));//バックアップ変数と今回の区切り文字を繋ぐ。 _tcsncat_s(ppcStrChart[i][0], _tcslen(pcStrTmp) + _tcslen(pcStr2) + 1 ,pcStr2, _tcslen(pcStr2));//バックアップ変数と今回の区切り文字を繋ぐ。 delete[] pcStrTmp;//バックアップ変数は利用終了。解放。 } iCmpSlash = _tcsncmp(&pcStr2[_tcslen(pcStr2) - 1],tcharSlash,1); if(iCmpSlash != 0){ if(!bStopIncriment2){ ppcStrChart[i][0] = new TCHAR[_tcslen(pcStr2) + 1]; _tcsncpy_s(ppcStrChart[i][0],_tcslen(pcStr2) + 1,pcStr2,_tcslen(pcStr2) + 1);//正式な区切り文字なので、区切りまでをコピーして格納。i 0番目の処理は終了。 } bStopIncriment2 = false; } else{ ppcStrChart[i][0] = new TCHAR[_tcslen(pcStr2) + 1];//まずは区切り文字までの長さより1文字、短い領域を確保。 _tcsncpy_s(ppcStrChart[i][0],_tcslen(pcStr2) + 1 ,pcStr1,_tcslen(pcStr2) - 1);//[*]~[*] [\][/][\0]の\以降を削ってコピー。[*]~[*][\0]になる。 _tcsncat_s(ppcStrChart[i][0],_tcslen(pcStr2) + 1 ,tcharSlash,_tcslen(tcharSlash) + 1);//カンマを付け足す。[*]~[*][/][\0]になる。 bStopIncriment2 = true; } pcStr2 = _tcstok_s(NULL,_T("/"), &next_token2);//2回目以降の呼び出し ppcStrChart[i][1] = new TCHAR[_tcslen(pcStr2) + 1]; _tcsncpy_s(ppcStrChart[i][1], _tcslen(pcStr2) + 1, pcStr2, _tcslen(pcStr2) + 1); pcStr2 = _tcstok_s(NULL,_T("/"), &next_token2);//2回目以降の呼び出し } //完全な入れ子状態ここまで************************************************************************************************************************************** i++; bStopIncriment = false; delete pcStrTmp2; } else{ //\なら,は区切りではないので、ppcStrChartTmp[i]を再作成して、1文字分短くして、\,→,と扱いtmpへ格納。 ppcStrChartTmp[i] = new TCHAR[_tcslen(pcStr1) + 1];//まずは区切り文字までの長さより1文字、短い領域を確保。 _tcsncpy_s(ppcStrChartTmp[i],_tcslen(pcStr1) + 1 ,pcStr1,_tcslen(pcStr1) - 1);//[*]~[*] [\][,][\0]の\以降を削ってコピー。[*]~[*][\0]になる。 _tcsncat_s(ppcStrChartTmp[i],_tcslen(pcStr1) + 1 ,tcharComma,_tcslen(tcharComma) + 1);//カンマを付け足す。[*]~[*][,][\0]になる。 bStopIncriment = true; } pcStr1 = _tcstok_s(NULL,_T(","),&next_token1);//2回目以降の呼び出し } } _tprintf(_T("{| style=\"color:black; background-color:#ffffff;\" cellpadding=\"3\" cellspacing=\"0\" border=\"1\"\n")); for(int i = 0; i < iChartCommaCnt;i++){ _tprintf(_T("|%s\n|%s\n|-\n"),ppcStrChart[i][0],ppcStrChart[i][1]); } _tprintf(_T("|%s\n|%s\n|}\n"),ppcStrChart[iChartCommaCnt][0],ppcStrChart[iChartCommaCnt][1]); printf("%d\n",iChartCommaCnt); for(int i = 0;i < iChartCommaCnt + 1;i++){ delete[] *(ppcStrChartTmp + i); } delete[] ppcStrChartTmp; for(int i = 0;i < iChartCommaCnt + 1 ;i++){ for(int j = 0;j < 2 ;j++){ delete[] *(*(ppcStrChart + i) + j); } } for(int i = 0;i < iChartCommaCnt + 1 ;i++){ delete[] *(ppcStrChart + i); } delete[] ppcStrChart; delete[] pcStr0; delete[] pcStr1; delete[] pcStr2; return 0; } </syntaxhighlight> という具合にtcstok_s関数を使えば、並列してトークン処理が進められるようです。第3引数の参照というC++独自の型を使って、44行目 78行目での最初の呼び出し(エスタブリッシュとも表現します。エスタブリッシュショットというと撮影なんかでワンシーンの手前に一枚絵の風景絵を置くことで、時間帯を表現したりする手法を指します。夜景がシーンの前に入れば室内に映像が切り替わっても夜の出来事であるように示唆するものです。)をして、111、114、131行目のように連続して現れるであろう区切り文字を検索します。111,114行目では、もう区切り文字が現れることがないことがわかっているのですが、2個目のトークンを取得するために実行したり、ループ処理を終わらせるために再度、実行したりという手法で使っています。アイデア次第でなんでもありです。最初にカンマ区切りでトークンを取得するのですが、その間で、さらにカンマ区切りトークンの中に必ず一度現れる、スラッシュによるトークン処理を入れこんでいます。区切り文字が違うだけで全く同じ処理です。こういうプログラム記法は普通はしません。通常は、関数のようなサブプログラムにして、関数を呼び出すことで同じ処理になるように記述します。C++の場合は関数でなくても、クラスのメンバ関数にしても良い訳です。結局同じことですが…。<br /> このような下手くそなプログラムを動かした結果が以下の通りになります。 {| style="color:black; background-color:#ffffff;" cellpadding="3" cellspacing="0" border="1" |Sweet Refrain |Perfume |- |雨のち晴レルヤ |ゆず |- |東京デスティニー |ポルノグラフィティ |- |\熱愛発覚中 |椎名林檎と中田ヤスタカ(CAPSULE) |- |STORY OF MY LIFE |ONE DIRECTION |- |THE SEVEN SEAS |THE BAWDIES |- |手紙 |ナオト・インティライミ |- |SLY |RIP SLYME |- |閃光 feat.10-FEET |東京スカパラダイスオーケストラ |- |風は西から |奥田民生 |- |DIAMOND SKIN |GLAY |- |SO RIGHT |三代目 J Soul Brothers from EXILE TRIBE |- |White Winter Love。 |ハジ→ |- |APPLAUSE |LADY GAGA |- |ピタカゲ from「COUP D'ETAT[+ONE OF A KIND&HEARTBREAKER]」 |G-DRAGON (from BIGBANG) |- |LEMON |The Birthday |- |もったいないとらんど |きゃりーぱみゅぱみゅ |- |僕らの物語 |GReeeeN |- |Lily |Dragon Ash |- |START IT AGAIN |AK-69 |- |Very Merry Xmas |東方神起 |- |SURVIVAL |EMINEM |- |ウォーリーヒーロー |KANA-BOON |- |あなたへ |エレファントカシマシ |- |キラーボール |ゲスの極み乙女。 |- |Babies are popstars |松任谷由実 |- |ROCK N ROLL(日本語字幕入り) |AVRIL LAVIGNE |- |HOT SHOT |GENERATIONS from EXILE TRIBE |- |守ってあげたい |JUJU |- |3 2 1 |SHINee |- |Always |斉藤和義 |- |ファンファーレがきこえる |Base Ball Bear |- |X'masラブストーリー。 |ソナーポケット |- |Missing |androp |- |ラストバージン |RADWIMPS |- |エデン(lyric ver) |Aqua Timez |- |クルクル |e-girls |- |WHO'S NEXT |SiM |- |Time goes by |URATA NAOYA |- |Bi-Li-Li Emotion |Superfly |- |One day |TOKYO No.1 SOUL SET |- |粉雪 |BENI |- |FEEVEER |MO'SOME TONEBENDER |- |No.525300887039 |supercell |- |Hello,999 |N'夙川BOYS |- |Every Hero |kaho |- |黒猫?Adult Black Cat? |Acid Black Cherry |- |指でキスしよう |東京カランコロン |- |m@u |後藤まりこ |- |海と花束 |きのこ帝国 |} 何コレ?って思いました?実はMediaWikiの表形式に変換するプログラムだったりして、出力結果をコピペすると、こういう表になるということです。これは某番組の先週のヒットチャートTOP50ですね。SuperCellのNo.って曲はカッコいいね。曲名の数字は読まなくていいらしい。椎名林檎にきゃりーぱみゅぱみゅ、Greeeen、ゆず、Perfume、東京スカパラダイスオーケストラ。最高っす( ・∀・)b意外と10位以下のあまり、もてはやされない曲でいいのあったりする。JPOPはある程度のカタチがあるよね。そのカタチにはまると、なんか好きな感じになる。歌声だったり、曲の構成だったり、意外性も案外と新しいファクターだよ。この意外性をもとめてるのが若い世代なのかもしれないね。でも、無理しないで王道ってのもいいと思うよ。みんなが好きそうな曲作るってのは、それはそれで凄い事。一部の人間の心を捉える曲もあっていいけど、やっぱ前にでれないのが現実。その間をとってる人もいれば、かたくなに自分のスタイルを貫く人もいる。お金儲け主義の曲って言われてもいいじゃん。みんなが好きな曲ってことだからね。みんなの心を捉えないとお金にならないのが音楽業界。音楽を作りもしないレーベル側に搾取される部分ってのは、なんだかもどかしいよね。でも、育ててもらった恩とか、準備してもらった機材、走り回ってくれるスタッフ、その手配をやってるのはレーベルの努力だからね。会社でいうところの特許は会社員が発明して、お金は8割くらい会社が持っていくっていうそんな感じ。研究させてもらえる環境を提供してるだけでもありがたいと思いなさいということです。それが嫌なら自分で会社たててやってみ?ろくなことなんないぜ?ってそんな感じ。自分で音楽事務所たててうまくいく人なんてそうそういないんだよ。オレは、なんかもうそういうのどうでもよくなってきて、惰性で人生を回転させてるだけ。うっかり幸せな気分でも向上するような出来事にたどり着けたら、人生っては、それなり頑張れば、それなりになるってことを確信して死ねるんだろうし、そうでないとしたら、まぁやっぱこんなもんだよな人生ってのはって心の隅で感じて、半笑いくらいで死ねるような気がする。この先も、どうでもいい。<br /> 別にプログラムつくりたいわけでもなし、こんな文書かいてんだから、まぁ分かる人には分かるでしょ。それでいい。マルチバイト版とマルチバイト2バイト対応版の動作も確認したいよね。まぁ時間があったらですね。それと表の先頭に - があったら、空白を付け足す処理と 文字列の中に| があったら、&#x7c;に置き換える処理を入れないと、 mediawikiのXHTML表記の表にならないので、もう少し改良が必要です。今回はたまたま、N'夙川BOYS(ん しゅくがわ ボーイズ)の曲名に , が使われていたので、流石にカンマ区切りのトークン処理を不完全にさせておくのはまずいと思い、ここまでで記述してきた文字列操作だけを使って、対応版のサンプルプログラムを作っておきました。N'夙川BOYS恐るべし!Hello,999か…。曲名って何でもありの自由空間だから規制がない。Unicodeで全部表せたらいいほう。なんかよくわからない記号とか使うこともあるし。なんらかの規制がないとデータベース化する人は大変だろうなぁ。 ※文字列配列を2次元配列に、つまり3次元配列にしたあたりから、動的なメモリの解放処理ができなくなった(;´・д・)ナンデ。スキル不足でした。役に立ちそうもないし、挫折かな。変に動的なメモリ確保に拘ったのはコレが初めてだから、ショウガナイか。できるとおもってたけど出来ない。そういうことなのかもしれない。一番したの層の文字列事態の解放はできるんですけど、アドレスが格納された部分の解放を記述するとコンパイルエラーにはならないけど、実行時にプログラムが止まる。 ほんとは delete[] *(ppcStrChart + i); ってやって50個の配列に要素[0]と[1]っていうのがぶら下がっているのを解放して、最後に delete[] ppcStrChart ってな具合にしなきゃいけいないはずなんですけど…strtokを使うとゴミが残りやすいのかな?解放できなくなる。生成した直後なら消せるから、やっぱなんかあるなコレ。リファレンス書くつもりがどんどん疑問が増えて課題が増えてるという…無力だな。また時間があるときにでも、原因を調べてみよう。恐るべしstrtok…。しばらくは、OS側にあとかたづけは任せてみる。ごめんなさい。ときどき再起動しとけばいいのかな…(´Д`;) おそらくは、一度生成したものを再作成するかもしれないという作り方に問題があるのかもしれない。文字列の長さが確定してから容量を確保するようなプログラムにするべきなんだろうね。次から気を付ける。って今回のサンプルはなおさないらしい。コイツ。 と悩んでおりましたが、解決しました。サンプルプログラムも更新済みなので、なんのこっちゃわからないと思いますが、配列は使用する領域よりひとつ多くとっておかないとdeleteできなくなるみたいです。文字列の終端にあるであろう\0と同じですね。ヌルポインターになるまで消去するということかもしれません。TOP50のランキングなのでTCHAR ***ppcStrChart = new TCHAR**[iChartCommaCnt];としてiChartCommaCnt=49の大きさ50の配列にしていましたが、TCHAR ***ppcStrChart = new TCHAR**[iChartCommaCnt '''+ 1'''];としたところ、プログラムの最後に記述したdelete[] 関数が動作するようになりました。ふぅ。同じように曲名とアーチスト名ということで*(ppcStrChart + i) = new TCHAR*[1];としていましたが、ここも*(ppcStrChart + i) = new TCHAR*['''2'''];ですね。消すときは、ひとつ少ない値のループ回数になっています。for(int j = 0;j < 2 ;j++)のとおりです。少しすっきりしたけど、なんつうか基本中の基本も知らなかった。いかに動的なメモリの確保をやってこなかったかってことか考えさせられます。プログラムの中でガッツリ確保してました。面倒だもん。でも、ちょっと不思議。それこそなんかゴミが残りそうだけどねぇ。結局また調べなきゃいけないのか(;´・д・)。でも、まぁ上のレベルの配列を消したときに、ぶら下がってる配列が最後まで消えるんだから、普通に考えても消えるわな。本当にそうなのかという、確認だけか。大変ではないね。次の事に進もう。日々、勉強だねぇ。つうか、手遅れ的な感じが否めない。最近、何を学んでも手遅れだと感じてしまう。いやいや、知らないままよりはいいか。ポジティブ。ポジティブ。ん~でも。なんつうか60過ぎてから、プロサッカー選手を目指すみたいな要素も含まれてるような気もする。無理だろみたいなね。プログラムは、まぁ歳食ってからでもやってていいから、気付くのが遅くてもいいことにしとこかな。 =='''文字列の型変換'''== 文字列の型にはchar/*char(LPSTR)、WCHAR/*WCHAR(LPWSTR)、TCHAR/*TCHAR(LPTSTR)がありました。これに変更できない文字列としてconst付きで定義された文字列がありました。これらの型に収められた文字列を相互にやり取りする手法について考えようと思います。文字列操作自体はどれか一つの型を採用するのですが、誰かが作ったクラスやDLLはどの文字列の型を採用しているかはわかりません。あらゆる型から、目的の型に変換できないと、ひとつの型に統一して文字列を操作するということができないので、必須の知識になると思います。そもそもcharやWCHARやTCHARの違いとは何だったのか?覚えていますか?charはマルチバイト文字列、WCHARはワイド文字列、TCHARはプロジェクトの設定に従うマルチバイトあるいはワイド文字列でした。さらに復習しておくと、マルチバイトってのは全角文字を扱うときUnicodeではなくJISコードを拡張した日本語JISコードであり、Windowsならばマイクロソフトが提供しているCP932という文字コードセットで英数字は1バイト、全角文字は2バイトで表される文字、一方、ワイド文字ってのは、UnicodeでUTF-16という方式で英数、日本語は常に2バイトで表現される文字列です(あまり使わない諸外国の文字では4バイトになることもありますが…)。扱う文字コードの違いにより、変数に格納された数値の扱いが異なるため、それぞれの型は動作が異なるため、単純に代入するだけではだめです。だったら最初から代入するような表現だけで変換できるような仕組みを作ってくれれば良かったのですが、そうもいかないようです。そして、ここまで述べてきた型にマイクロソフトが独自に提供しているMFCで定義しているCString(マルチバイトのCStringA、ワイドのCStringW)からの変換や_bstr_t、CComBSTR、basic_string、System::Stringについても考えておきたいと思います。ほかにも独自に考えられた文字列型があれば、その独自の文字列型からの変換についても熟知する必要がありますが、独自の文字列型を提供している人や提供を受けた人が変換について考えれば良いので、ここでは有名どころだけを網羅することにします。マイクロソフトが提供している文字列型の詳細については以下のとおりです。 _bstr_t COM(Component Object Model アプリケーション同士のやりとりをするための技術)サポートクラス BSTR型の文字列クラス<br /> http://msdn.microsoft.com/ja-jp/library/zthfhkd6.aspx<br /> プリプロセッサ:comutil.h<br /> ライブラリ:comsuppw.lib または comsuppwd.lib<br /> BSTR型の解説:http://msdn.microsoft.com/ja-jp/library/ms221069.aspx<br /> 特徴:文字列長を常に保有し続けるBSTR型文字列のための文字列クラス<br /> CComBSTR ATL(Active Template Library MFCの小型版と思っていいと思います。)として提供されるBSTR型の文字列クラス<br /> http://msdn.microsoft.com/ja-jp/library/zh7x9w3f.aspx<br /> basic_string 文字列テンプレートSTL(Standard Template Library ANSI標準テンプレートのライブラリ。)<br /> http://msdn.microsoft.com/ja-jp/library/syxtdd4f.aspx<br /> System::String C++/CLI(Common Language Infrastructure)の文字列型<br /> http://msdn.microsoft.com/ja-jp/library/system.string(v=vs.110).aspx<br /> MFC STL COM ATL CLIと短い略称ですが、それぞれは膨大なライブラリでして、その技術の応用についても、本当は知っていく必要がありますが、ActiveXやらDirectXやらと言い出したらきりがないほど情報技術は歴史が深いし、細かい。Webサービス、SOAP、ネットワーク、暗号化、セキュア…鬼のように沢山の既存技術がある。結構使われてる。ここで触れようとしていること自体もかなり多いけど、それでも、その中のほんの一部に過ぎない。 ちなみにワイド文字とマルチバイト文字の相互変換の簡単な例については<br /> [[C 日本語文字列#マルチバイト文字列、ワイド文字列の相互変換]]<br /> に記述があります。ここでは、更に踏み込んだ変換について触れたいと思います。 というわけで、これくらいできて当然という変換をやってみます。 <syntaxhighlight lang="cpp" line start="1"> #include <string> #include <stdlib.h> #include <iostream> #include <tchar.h> #include <mbstring.h> #include "atlstr.h"//CString,CStringA,CStringWおよびLPTSTR,LPWSTR,LPSTR,LPCTSTR,LPCWSTR,LPCSTRの定義プリプロセッサ #include "cstringt.h"//CStringTの定義プリプロセッサ #include "atlbase.h" //_bstr_t型を利用するプリプロセッサ #include "comutil.h" # ifdef _DEBUG # pragma comment(lib, "comsuppwd.lib") # else # pragma comment(lib, "comsuppw.lib") # endif using namespace std; using namespace System; using namespace System::Runtime::InteropServices; int main() { _tsetlocale(LC_ALL, _T("Japanese")); _locale_t locale; locale = _create_locale(LC_ALL, "Japanese"); //プロジェクトの設定の文字セットでunicode文字セットを使用するに設定しているので、TCHAR→wchar_t型と定義される。 //プロジェクトの設定をかえるとTCHAR→char型になるので、設定によってはTCHAR型に変換する方式を切り替えないとダメ。 //実際の変換処理は#ifdef UNICODEで分けた関数を作る必要がある。 size_t *sizeReturnValue; sizeReturnValue = new size_t; const char *pcStrMoto[] ={"char型の変数 配列要素1","char型の変数 配列要素2"}; const LPSTR pLPSTRStrMoto[] = {"LPSTR型の変数 配列要素1","LPSTR型の変数 配列要素2"}; TCHAR **pptcStrCnv = new TCHAR*[sizeof(pcStrMoto)/sizeof(*pcStrMoto)]; LPTSTR *ppLPTSTRStrCnv = new TCHAR*[sizeof(pcStrMoto)/sizeof(*pcStrMoto)]; _tprintf(_T("文字列の型変換char→TCHAR\n")); for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ #ifdef UNICODE //配列要素i番目の変換後のサイズ取得処理と要素iの実体化と文字列長さ設定。 mbstowcs_s(sizeReturnValue, NULL, 0,pcStrMoto[i], 0); pptcStrCnv[i] = new TCHAR[*sizeReturnValue]; ppLPTSTRStrCnv[i] = new TCHAR[*sizeReturnValue]; //変換処理。 mbstowcs_s(sizeReturnValue, pptcStrCnv[i], *sizeReturnValue, pcStrMoto[i], *sizeReturnValue); mbstowcs_s(sizeReturnValue, ppLPTSTRStrCnv[i], *sizeReturnValue, pcStrMoto[i], *sizeReturnValue); #else //マルチバイト定義の場合はchar型同志のコピーになるので変換不要。 pptcStrCnv[i] = new TCHAR[_tcslen(pcStrMoto[i]) + 1]; ppLPTSTRStrCnv[i] = new TCHAR[_tcslen(pcStrMoto[i]) + 1]; _tcscpy_s(pptcStrCnv[i],_tcslen(pcStrMoto[i]) + 1,pcStrMoto[i]); _tcscpy_s(ppLPTSTRStrCnv[i],_tcslen(pcStrMoto[i]) + 1,pcStrMoto[i]); #endif //出力 _tprintf(_T("要素[%d]\n"),i); for(unsigned int j = 0; j < *sizeReturnValue; j++){ _tprintf(_T("[%2d]=%x,"),j,*(*(pptcStrCnv + i) + j)); } _tprintf(_T("\n%s\n"),*(pptcStrCnv + i)); } _tprintf(_T("\n文字列の型変換TCHAR→char\n")); const TCHAR *ptcStrMoto[] = {_T("TCHAR型の変数 配列要素1"),_T("TCHAR型の変数 配列要素2")}; const LPTSTR pLPTSTRStrMoto[] = {_T("LPTSTR型の変数 配列要素1"),_T("LPTSTR型の変数 配列要素2")}; char **ppcStrCnv = new char*[sizeof(ptcStrMoto)/sizeof(*ptcStrMoto)]; LPSTR *ppLPSTRStrCnv = new char*[sizeof(ptcStrMoto)/sizeof(*ptcStrMoto)]; for(int i = 0; i < (sizeof(ptcStrMoto)/sizeof(*ptcStrMoto)); i++){ #ifdef UNICODE //配列要素i番目の変換後のサイズ取得処理と要素iの実体化と文字列長さ設定。 wcstombs_s(sizeReturnValue, NULL, 0,ptcStrMoto[i], 0); ppcStrCnv[i] = new char[*sizeReturnValue]; ppLPSTRStrCnv[i] = new char[*sizeReturnValue]; //変換処理。 wcstombs_s(sizeReturnValue, ppcStrCnv[i], *sizeReturnValue, ptcStrMoto[i], *sizeReturnValue); wcstombs_s(sizeReturnValue, ppLPSTRStrCnv[i], *sizeReturnValue, ptcStrMoto[i], *sizeReturnValue); #else //マルチバイト定義の場合はchar型同志のコピーになるので変換不要。 ppcStrCnv[i] = new TCHAR[_tcslen(ptcStrMoto[i]) + 1]; ppLPSTRStrCnv[i] = new TCHAR[_tcslen(ptcStrMoto[i]) + 1]; _tcscpy_s(ppcStrCnv[i],_tcslen(ptcStrMoto[i]) + 1,ptcStrMoto[i]); _tcscpy_s(ppLPSTRStrCnv[i],_tcslen(ptcStrMoto[i]) + 1,ptcStrMoto[i]); #endif //出力 _tprintf(_T("要素[%d]\n"),i); for(unsigned int j = 0; j < *sizeReturnValue; j++){ printf("[%2d]=%x,",j,*(*(ppcStrCnv + i) + j)); printf("[%2d]=%x,",j,*(*(ppLPSTRStrCnv + i) + j)); } printf("\n%s\n",*(ppcStrCnv + i)); printf("%s\n",*(ppLPSTRStrCnv + i)); } //_bstr_t,CComBSTR,CString,System::Stringいずれもcharの先頭アドレスを代入すれば格納できる。 _tprintf(_T("\n文字列の型変換char→_bstr_t\n")); _bstr_t bstrt[(sizeof(pcStrMoto)/sizeof(*pcStrMoto))]; for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ bstrt[i] = pcStrMoto[i];//代入するだけ std::cout << bstrt[i] << std::endl; printf("_bstr_t %s\n",(LPCSTR)bstrt[i]); } _tprintf(_T("\n文字列の型変換char→CComBSTR\n")); CComBSTR ccombstr[(sizeof(pcStrMoto)/sizeof(*pcStrMoto))]; for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ ccombstr[i] = pcStrMoto[i];//代入するだけ //マクロにより出力用の変数に文字列を移す必要がある。これはCComBSTR型の決まり。 CW2A printstr(ccombstr[i]); std::cout << printstr << std::endl; printf("CComBSTR %s\n",printstr); } _tprintf(_T("\n文字列の型変換char→CStringA\n")); CStringA cstringa[(sizeof(pcStrMoto)/sizeof(*pcStrMoto))]; for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ cstringa[i] = pcStrMoto[i];//代入するだけ std::cout << cstringa[i] << std::endl; printf("CStringA %s\n",cstringa[i]); } _tprintf(_T("\n文字列の型変換char→CStringW\n")); CStringW cstringw[(sizeof(pcStrMoto)/sizeof(*pcStrMoto))]; for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ cstringw[i] = pcStrMoto[i];//代入するだけ std::wcout << (LPCTSTR)cstringw[i] << std::endl; _tprintf(_T("CStringW %s\n"),(LPCTSTR)cstringw[i]); } _tprintf(_T("\n文字列の型変換char→string\n")); string basicstring[(sizeof(pcStrMoto)/sizeof(*pcStrMoto))]; for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ basicstring[i] = pcStrMoto[i];//代入するだけ cout << basicstring[i] << endl; printf("string %s\n",basicstring[i].c_str()); } _tprintf(_T("\n文字列の型変換char→SystemString\n")); array<String^> ^systemstring = gcnew array<String^>(sizeof(pcStrMoto)/sizeof(*pcStrMoto)); for(int i = 0; i < (sizeof(pcStrMoto)/sizeof(*pcStrMoto)); i++){ char* pcStrTemp = new char[strlen(pcStrMoto[i]) + 1]; strcpy_s(pcStrTemp,strlen(pcStrMoto[i]) + 1,pcStrMoto[i]); System::IntPtr ptr(pcStrTemp);//この関数がconst charの変換に対応していないので、上の2行でconst無しの変数に移し替えた。 systemstring[i] = Marshal::PtrToStringAnsi(ptr);//IntPtrポインタを引数として変換するメソッド。 Console::WriteLine("{0}", systemstring[i]); delete[] pcStrTemp; } } </syntaxhighlight> 上から<br /> char(LPSTR)→TCHAR(LPTSTR)<br /> TCHAR(LPTSTR)→char(LPSTR)<br /> の変換ここでは#ifndef UNICODEディレクティブによりTCHARの扱いをプロジェクトでどのように定義したか?によって動作が切り替わるように処理を組み込んでいます。以前に記述したサンプル[[C 日本語文字列#マルチバイト文字列、ワイド文字列の相互変換]]では、そこまでは言及していませんでした。プロジェクトの設定の文字セットでunicode文字セットを使用するに設定していれば、上側の#ifndef UNICODEから#elseまでに囲まれた部分が処理される。変換が必要かどうか?確認するのがよいです。<br /> その次に<br /> charから_bstr_tやCComBSTRやCStringA、CStringWのような変換について記述しています。このあたりの変数はchar型のポインタを代入するだけで変換をしてくれます。但し、出力するときには少し工夫が必要になります。これはどちらかというと、独自の文字列クラスの仕様によるものなので、それぞれの文字クラスについて学習を進める必要があります。先に示したリンクからそれぞれのクラスについて学習してみて下さい。こちらのサイトでもいつかは触れていきたいですが、まだまだ先になりそうです。<br /> それから、<br /> charからstring、System::Stringへの変換について記述しています。<br /> System::Stringは共通言語ランタイムですので、動作させるにはプロジェクトの設定を変更しないといけません。<br /> しかも、設定を変更しても従来のC言語の記法と共通言語ランタイムの記法を混同させるとVisual C++のコードインテリジェンス機能が完全停止しますのでコード編集が大変になりますので、このサンプルのような混同したプリプロセッサ定義をして無理やり、いろいろな変換のサンプルを記述するのは良くないです。 [[file:IntelliSenseOff.png]] 上記の図のようなエラーになります。共通言語ランタイムとプリプロセッサの混同に関する詳細な条件もまた後程調査ですかね。ほんとに調べるのかは定かでありませんが… ※コードインテリセンスは、例えばにstrcpyのようなよく使う関数を使うときに引数には何を定義すればよいのか、ヒントを与える機能です strcpy( までタイプすると第一引数には char* 型のコピー先の変数、第二引数にはコピー元のchar* 型の変数、第三引数にはコピーする文字の配列の長さを指定するべきだとポップアップ表示がされます。クラス変数や構造体変数を使う場合にも 変数名. あるいは変数名-> までタイプするとメンバ変数や、メソッドのリストが表示されます。メソッドの引数のヒントも関数と同様にポップアップ表示されます。 複数の文字列プリプロセッサを混同しないのが本来のやりかたになります。System::Stringへのchar変数の代入だけは複数の処理ステップが必要になります。System::String用に準備されているメソッドがconst char*の引数に対応していなかったり、独自の仕組みでしか引数をとることができないからです。ちなみに、文字列の配列でなければ、もう少し簡単に変換ができて、<br /> System::String ^systemstring = gcnew String(char*型変数);<br /> のように記述するだけで、コンストラクタの初期値によって変換自体は完了させることができます。と、ここまでchar型からの変換だけで、ずいぶんと長い説明になってしまいました。このあたりの配列を使った場合の文字列変換についてまとめている文書は意外と少ないと思います。次は逆変換ですかね。こっちのほうが大変なんだよね。そしたら次はTCHARからの変換か…で、その逆変換。ふむー。先は長い。普段こんなに変換ばっかりやんないので、この記事キツイっすね。なんでこんなん書いてるんやろ。役に立つんか?ほんまに… マネージコードを利用するために実施した設定変更<br /> C/C++ <br /> 全般<br /> デバッグ情報の形式 プログラムデータベース /Zi ← エディットコンティニュのプログラムデータベース /ZI<br /> 共通言語ランタイム サポート 共通言語ランタイムサポート /clr ←共通言語ランタイムをサポートしない<br /> コード生成<br /> 最小リビルドを有効にする。 いいえ /Gm- ←はい /Gm<br /> C++の例外を有効にする はい SEHの例外あり /EHa ← はい /EHsc<br /> 基本ランタイムチェック 規定 ← 両方 /RTC1<br /> VisualStudioのコードインテリセンスを有効にしたままC++とC++/CLI(共通言語)の勉強したい場合は、C++/CLIプロジェクトでもC++言語は使えるので、プロジェクトの作成からやりなおしてみると良いです。 更に変換を続けたのものが以下になります。 <syntaxhighlight lang="cpp" line start="1"> _tsetlocale(LC_ALL, _T("Japanese")); _locale_t locale; locale = _create_locale(LC_ALL, "Japanese"); const char *pcStrMoto[] ={"char型の変数 配列要素1","char型の変数 配列要素2"}; const LPSTR pLPSTRStrMoto[] = {"LPSTR型の変数 配列要素1","LPSTR型の変数 配列要素2"}; const TCHAR *ptcStrMoto[] = {_T("TCHAR型の変数 要素1"),_T("TCHAR型の変数 要素2")}; const LPTSTR pLPTSTRStrMoto[] = {_T("LPTSTR型の変数 要素1"),_T("LPTSTR型の変数 要素2")}; size_t *sizeReturnValue; sizeReturnValue = new size_t; int *pnStrArrayElement; pnStrArrayElement = new int; *pnStrArrayElement = sizeof(ptcStrMoto)/sizeof(*ptcStrMoto); //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・TCHAR→char変換 //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("文字列の型変換TCHAR→char\n")); char **ppcStrCnv = new char*[*pnStrArrayElement];//要素数分の大きさ+1を確保 LPSTR *ppLPSTRStrCnv = new char*[*pnStrArrayElement];//要素数分の大きさ+1を確保 #ifdef UNICODE //変換 for(int i = 0; i < *pnStrArrayElement; i++){ wcstombs_s(sizeReturnValue, NULL, 0,ptcStrMoto[i], 0); ppcStrCnv[i] = new char[*sizeReturnValue]; ppLPSTRStrCnv[i] = new char[*sizeReturnValue]; wcstombs_s(sizeReturnValue, ppcStrCnv[i], *sizeReturnValue, ptcStrMoto[i], *sizeReturnValue); wcstombs_s(sizeReturnValue, ppLPSTRStrCnv[i], *sizeReturnValue, ptcStrMoto[i], *sizeReturnValue); } #else //無変換 for(int i = 0; i < *pnStrArrayElement; i++){ ppcStrCnv[i] = new char[*sizeReturnValue]; ppLPSTRStrCnv[i] = new char[*sizeReturnValue]; _tcscpy_s(ppcStrCnv[i], _tcslen(ptcStrMoto[i]) + 1, ptcStrMoto[i]); _tcscpy_s(ppLPSTRStrCnv[i], _tcslen(ptcStrMoto[i]) + 1, ptcStrMoto[i]); } #endif //char出力 for(int i = 0; i < *pnStrArrayElement; i++){ printf("%s\n",*(ppcStrCnv + i)); } //LPSTR出力 for(int i = 0; i < *pnStrArrayElement; i++){ printf("%s\n",*(ppLPSTRStrCnv + i)); } printf("%d\n", sizeof(**ppcStrCnv)); printf("%d\n", sizeof(*ppcStrCnv)); printf("%d\n", sizeof(ppcStrCnv));//動的に確保した配列の大きさは取得できない。 printf("%d\n", sizeof(**ptcStrMoto)); printf("%d\n", sizeof(*ptcStrMoto)); printf("%d\n", sizeof(ptcStrMoto)); //動的確保変数の解放 for(int i = 0;i < *pnStrArrayElement ;i++){ delete[] *(ppcStrCnv + i); delete[] *(ppLPSTRStrCnv + i); } delete[] ppcStrCnv; delete[] ppLPSTRStrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→char //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→char\n")); const _bstr_t bstrtOrigin[] = {_T("_bstr_t型の変数 配列要素1"),_T("_bstr_t型の変数 配列要素2"),_T("_bstr_t型の変数 配列要素3")}; printf("%d\n", sizeof(*bstrtOrigin)); printf("%d\n", sizeof(bstrtOrigin)); *pnStrArrayElement = sizeof(bstrtOrigin)/sizeof(*bstrtOrigin); ppcStrCnv = new char*[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ *sizeReturnValue = strlen((char*)bstrtOrigin[i]) + 1; ppcStrCnv[i] = new char[*sizeReturnValue]; strcpy_s(ppcStrCnv[i], *sizeReturnValue, (char*)bstrtOrigin[i]); } //char出力 for(int i = 0; i < *pnStrArrayElement; i++){ printf("%s\n",*(ppcStrCnv + i)); } //動的確保変数の解放 for(int i = 0;i < *pnStrArrayElement ;i++){ delete[] *(ppcStrCnv + i); } delete[] ppcStrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→TCHAR //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→TCHAR\n")); TCHAR **pptcStrCnv = new TCHAR*[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ *sizeReturnValue = _tcslen((TCHAR*)bstrtOrigin[i]) + 1; pptcStrCnv[i] = new TCHAR[*sizeReturnValue]; _tcscpy_s(pptcStrCnv[i], *sizeReturnValue, (TCHAR*)bstrtOrigin[i]); } //char出力 for(int i = 0; i < *pnStrArrayElement; i++){ _tprintf(_T("%s\n"),*(pptcStrCnv + i)); } //動的確保変数の解放 for(int i = 0;i < *pnStrArrayElement ;i++){ delete[] *(pptcStrCnv + i); } delete[] pptcStrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→CComBSTR(マルチバイト文字ワイド文字両対応 プロジェクト設定に従う) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→CComBSTR\n")); CComBSTR *pccombstr_StrCnv = new CComBSTR[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ pccombstr_StrCnv[i] = (char*)bstrtOrigin[i]; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 CW2A printstr(*(pccombstr_StrCnv + i)); printf("%s\n",printstr); //無変換出力 _tprintf(_T("%s\n"),*(pccombstr_StrCnv + i)); } //動的確保変数の解放 delete[] pccombstr_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→CStringA(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→CStringA\n")); CStringA *pcstringa_StrCnv = new CStringA[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ pcstringa_StrCnv[i] = (char*)bstrtOrigin[i]; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",pcstringa_StrCnv[i]); } //動的確保変数の解放 delete[] pcstringa_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→CStringW(ワイド文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→CStringW\n")); CStringW *pcstringw_StrCnv = new CStringW[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ pcstringw_StrCnv[i] = (char*)bstrtOrigin[i]; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //wchar_t出力 wprintf(L"%s\n",pcstringw_StrCnv[i]); } //動的確保変数の解放 delete[] pcstringw_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→string(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→string\n")); string *pstring_StrCnv = new string[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ pstring_StrCnv[i] = (char*)bstrtOrigin[i]; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",pstring_StrCnv[i].c_str()); } //動的確保変数の解放 delete[] pstring_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・_bstr_t→SystemString(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→SystemString\n")); array<String^> ^psysstring_StrCnv = gcnew array<String^>(*pnStrArrayElement); //変換 for(int i = 0; i < *pnStrArrayElement; i++){ char* pcStrTemp = new char[strlen((char*)bstrtOrigin[i]) + 1]; strcpy_s(pcStrTemp,strlen((char*)bstrtOrigin[i]) + 1,(char*)bstrtOrigin[i]); System::IntPtr ptr(pcStrTemp); psysstring_StrCnv[i] = Marshal::PtrToStringAnsi(ptr); delete[] pcStrTemp; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",psysstring_StrCnv[i]); //TCHAR変換出力 #ifdef UNICODE _tprintf(_T("%s\n"),PtrToStringChars(psysstring_StrCnv[i])); #else _tprintf(_T("%s\n"),psysstring_StrCnv[i]); #endif } //動的確保変数の解放 delete[] psysstring_StrCnv; delete[] sizeReturnValue; delete[] pnStrArrayElement; </syntaxhighlight> _bstr_t型からの変換に関するサンプルを追加しました。補足しておきますと、文字列配列の大きさは動的に生成した文字列配列からは取得することはできません。動的に生成したときの大きさをプログラマ自身が管理する必要があります。そのことを知ることができるのが上記の64行目のprintf文です。ここで動的に生成した文字列配列の大きさをsizeof関数で表示していますが、配列の大きさは64bitPCならば、要素数2つなので8byte(64bit 8bit=1byte)になるはずですが、4byteになっています。これはポインタ変数の大きさ、アドレス記憶変数ひとつ分の大きさです。 上記のように変換を進めていて気づくことですが、それぞれの型は取り扱える文字体系が異なります。_bstr_tやCComBSTRはマルチバイト文字とワイド文字の両方に対応していて、プロジェクト設定にしたがって記憶する文字型を切り替えています。CStringAはマルチバイト文字、CStringWはワイド文字です。stringとSystem::Stringはマルチバイト文字を扱うようになっています。したがって、変換時にはマルチバイト文字、ワイド文字変換が必要になるケースと必要ないケース。あるいは、プロジェクトの設定によって決まるケースがあるわけです。そのことから、_bstr_tやCComBSTを利用しておくという、ある程度、文字列の世界を熟知している人だけが選ぶ道があるようにも思えるわけです。マイクロソフトの容易したものを使うにしても賢い選択というのが、その時代、時代によって存在することになります。開発するプログラムの枠組みによってはマルチバイトやワイド文字といった切り替えは気にしないという人もいるのかもしれません。それと、wchar_tとcharの変換をおぼえれば、それぞれの特殊な型をchar型に変換する手法とcharからそれぞれの型に変換する手法をマスターすれば、変換手順は長くなる可能性がありますが、とにかくすべてを網羅できるわけです。特殊な型から特殊な型へのダイレクトな変換は覚えなくてもなんとかなる。覚えた方が楽になる。そんな感じです。 <syntaxhighlight lang="cpp" line start="1"> //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→char //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→char\n")); const CComBSTR ccombstrOrigin[] = {_T("CComBSTR型の変数 配列要素1"),_T("CComBSTR型の変数 配列要素2"),_T("CComBSTR型の変数 配列要素3")}; printf("%d\n", sizeof(*ccombstrOrigin)); printf("%d\n", sizeof(ccombstrOrigin)); *pnStrArrayElement = sizeof(ccombstrOrigin)/sizeof(*ccombstrOrigin); ppcStrCnv = new char*[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); *sizeReturnValue = strlen(strtemp) + 1; ppcStrCnv[i] = new char[*sizeReturnValue]; strcpy_s(ppcStrCnv[i], *sizeReturnValue, strtemp); } //char出力 for(int i = 0; i < *pnStrArrayElement; i++){ printf("%s\n",*(ppcStrCnv + i)); } //動的確保変数の解放 for(int i = 0;i < *pnStrArrayElement ;i++){ delete[] *(ppcStrCnv + i); } delete[] ppcStrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→TCHAR //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→TCHAR\n")); pptcStrCnv = new TCHAR*[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ #ifdef UNICODE *sizeReturnValue = _tcslen(ccombstrOrigin[i]) + 1; pptcStrCnv[i] = new TCHAR[*sizeReturnValue]; _tcscpy_s(pptcStrCnv[i], *sizeReturnValue, ccombstrOrigin[i]); #else CW2A strtemp(ccombstrOrigin[i]); *sizeReturnValue = strlen(strtemp) + 1; pptcStrCnv[i] = new TCHAR[*sizeReturnValue]; _tcscpy_s(pptcStrCnv[i], *sizeReturnValue, strtemp); #endif } //char出力 for(int i = 0; i < *pnStrArrayElement; i++){ _tprintf(_T("%s\n"),*(pptcStrCnv + i)); } //動的確保変数の解放 for(int i = 0;i < *pnStrArrayElement ;i++){ delete[] *(pptcStrCnv + i); } delete[] pptcStrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→_bstr_t(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換_bstr_t→CComBSTR\n")); _bstr_t *pbstrt_StrCnv = new _bstr_t[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); pbstrt_StrCnv[i] = strtemp; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //無変換出力 printf("%s\n",(char*)pbstrt_StrCnv[i]); _tprintf(_T("%s\n"),(wchar_t*)pbstrt_StrCnv[i]); } //動的確保変数の解放 delete[] pbstrt_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→CStringA(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→CStringA\n")); pcstringa_StrCnv = new CStringA[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); pcstringa_StrCnv[i] = strtemp; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",pcstringa_StrCnv[i]); } //動的確保変数の解放 delete[] pcstringa_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→CStringW(ワイド文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→CStringW\n")); pcstringw_StrCnv = new CStringW[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); pcstringw_StrCnv[i] = strtemp; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //wchar_t出力 wprintf(L"%s\n",pcstringw_StrCnv[i]); } //動的確保変数の解放 delete[] pcstringw_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→string(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→string\n")); pstring_StrCnv = new string[*pnStrArrayElement];//要素数分の大きさ+1を確保 //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); pstring_StrCnv[i] = strtemp; } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",pstring_StrCnv[i].c_str()); } //動的確保変数の解放 delete[] pstring_StrCnv; //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ //・CComBSTR→SystemString(マルチバイト文字) //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ _tprintf(_T("\n文字列の型変換CComBSTR→SystemString\n")); psysstring_StrCnv = gcnew array<String^>(*pnStrArrayElement); //変換 for(int i = 0; i < *pnStrArrayElement; i++){ CW2A strtemp(ccombstrOrigin[i]); System::IntPtr ptr(strtemp); psysstring_StrCnv[i] = Marshal::PtrToStringAnsi(ptr); } //出力 for(int i = 0; i < *pnStrArrayElement; i++){ //char変換出力 printf("%s\n",psysstring_StrCnv[i]); //TCHAR変換出力 #ifdef UNICODE _tprintf(_T("%s\n"),PtrToStringChars(psysstring_StrCnv[i])); #else _tprintf(_T("%s\n"),psysstring_StrCnv[i]); #endif } //動的確保変数の解放 delete[] psysstring_StrCnv; </syntaxhighlight> 引き続き、変換のサンプルを記述していきます。残るはCComBSTR、CStringA、CStringW、string、System::String基準の変換ですね。 ※2014/09/21 ここまでで、疲れたから、また暫くオヤスミ。次に気が向くのはいつになるのか、誰も知らない。 =='''文字列文字コード変換'''== =='''文字列大文字小文字変換'''== =='''文字列半角文字全角文字変換'''== =='''文字列ファイルパス操作'''== =='''文字列ファイル名操作'''== =='''文字列の検索と置換'''== =='''文字列ファイルへの入出力'''== *比較 *一致 *区切り文字分割 *探索 *正規表現 regex_match *文字列の型変換 *文字コード変換 *大文字小文字変換 *半角文字全角文字変換 *ファイルパス操作 *ファイル名操作 *拡張子取得 *文字列の検索と置換 *ファイルへの入出力 <!-- {|class="wikitable" !上位ビッツ\下位ビッツ!!0!!1!!2!!3!!4!!5!!6!!7!!8!!9!!A!!B!!C!!D!!E!!F |- |0||NUL||SOH||STX||ETX||EOT||ENQ||ACK||BEL|||BS||HT||LF||VT||FF||CR||SO||SI |- |1||DLE||DC1||DC2||DC3||DC4||NAK||SYN||ETB||CAN||EM||SUB||ESC||IS4||IS3||IS2||IS1 |- |2||SP||!||"||#||$||%||&||'||(||)||*||+||,||-||.||/ |- |3||0||1||2||3||4||5||6||7||8||9||:||;||<||=||>||? |- |4||@||A||B||C||D||E||F||G||H||I||J||K||L||M||N||O |- |5||P||Q||R||S||T||U||V||W||X||Y||Z||[||\||]||^||_ |- |6||`||a||b||c||d||e||f||g||h||i||j||k||l||m||n||o |- |7||p||q||r||s||t||u||v||w||x||y||z||{|| | ||} ||~||DEL |- |} {|class="wikitable" !上位ビッツ\下位ビッツ!!0!!1!!2!!3!!4!!5!!6!!7!!8!!9!!A!!B!!C!!D!!E!!F |- |8||||||BPH||NBH||||NEL||SSA||ESA||HTS||HTJ||VTS||PLD||PLU||RI||SS2||SS3 |- |9||DCS||PU1||PU2||STS||CCH||MW||SPA||EPA||SOS||||SCI||CSI||ST||OSC||PM||APC |- |A||||。||「||」||、||・||ヲ||ァ||ィ||ゥ||ェ||ォ||ャ||ュ||ョ||ッ |- |B||ー||ア||イ||ウ||エ||オ||カ||キ||ク||ケ||コ||サ||シ||ス||セ||ソ |- |C||タ||チ||ツ||テ||ト||ナ||ニ||ヌ||ネ||ノ||ハ||ヒ||フ||ヘ||ホ||マ |- |D||ミ||ム||メ||モ||ヤ||ユ||ヨ||ラ||リ||ル||レ||ロ||ワ||ン||゙||゚ |- |E|||||||||||||||||||||||||||||||| |- |F|||||||||||||||||||||||||||||||| |- -->
C 文字列操作
に戻る。
個人用ツール
ログイン
名前空間
ページ
議論
変種
表示
閲覧
ソースを表示
履歴表示
操作
検索
案内
メインページ
コミュニティ・ポータル
最近の出来事
最近の更新
おまかせ表示
ヘルプ
ツールボックス
リンク元
関連ページの更新状況
特別ページ