C 日本語文字列

提供:yonewiki

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC 日本語文字列になっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

日本語文字列

char型の配列で1byte、1文字を表現できるascii文字コードというものを使っていましたが、問題点はいくつかありますが、そのままでも日本語全角文字は表現できます。日本語文字コードは2Byteで表現されます。 全角のアルファベットからカタカナ、ひらがな、漢字、記号、あまり使わない漢字を含めて65536種類を00 00からFF FFまでで、表現しています。最初の1byteが0x80~0x9F、0xE0~0xFCならばasciiコードでは定義していないため、その後ろの1byteも読み込んで1文字だと処理してくれます。マルチバイト文字と呼んでいます。asciiコードですでに使われている部分は使えないとしても、日本語文字コードは15616種類を使えます。これでも十分です。


問題点1は文字数と配列の大きさが一致しないことです。加えて、0x80 0x5cのように2byte文字コードの中に0x5cが登場すると、0x5c='\'ですので、次の文字はエスケープシーケンスだとみなされ2byteずつ出力してくれる手順より優先して、その次にくる文字の1byteの処理をしようとします。この結果文字化けが発生します。2byte文字の下位byteに0x5cが登場するのは、以下のような文字です。但し、現在のVisualStudioC++2012では、内部的に解決するので、この問題は発生しません。とは言え、文字列検索\の検索とかで支障があるやもしれません。


―,ソ,Ы,Ⅸ,噂,浬,欺,圭,構,蚕,十,申,曾,箪,貼,能,表,暴,予,禄,兔,喀,媾,彌,拿,杤,歃,濬,畚,秉,綵,臀,藹,觸,軆,鐔,饅,鷭,偆,砡,纊,犾


普段使わない文字が多いですが、使いそうなのは―,ソ,Ⅸ,噂,欺,圭,構,蚕,十,申,曾,貼,能,表,暴,予,といったあたりでしょうか。使う使わないは、それぞれの立場で異なるので、そういった分類は意味を成しませんが、ともかくこれらの文字が出てきたらエスケープシーケンスを打ち切るために'\\'という形にすればよく、十\個,表\示,能\力とかにする手法があります。出力に限ればそれで済むことですが、検索処理でこれにはまると、面倒過ぎる。


※勘違いしてはいけないことですが、printf文で出力するときに必要となる手法としてです。テキスト文書が能\力といった形で保存しておくということを言っているのではないです。プログラム側だけで対処すべきことです。それはメモ帳のようなテキストエディタで「表示」と記述してANSI(実際はShift_JISでCP932)形式で保存しても、バイナリエディタで開いたら95 5C 8E A6と保存されることでも理解できると思います。95 5C 5C 8E A6と保存していたら、あるテキストエディタ「表\示」とそのまま画面に出力され、適当なプログラムの方では「表示」とされることになります。このような問題から、日本語をそのままchar型でマルチバイト文字列として扱うのはよろしくないということがわかります。※0x5cをみつけたら、もう一個\を加える処理とか、マルチバイトの先頭にくる文字コードが来たら2byteで1文字とか計算したりして、昔はPerlとかでCGI組むときでは、このままつかってたこともありました。(汗


今はいろいろ便利な関数が用意されているので、そういったことは自分で対処しなくてよくなりましたね。


それで、解決策は?


実はいくつかありまして…。どれがいいとか悪いとかは、それぞれなんですけど…あの~その~面倒ですが、そのいくつかを紹介しようかと思います。「おいおい…一つにしてよ。どうすればいいかわからんやん。」という声が聞こえてきますが、全くその通りなんですけど、いまだに宙に浮いている問題でして、統一まではできないかと思います。でも一番いいのはこれってのはあります。でも、それはそれで、「そんなことせなあかんの?話が違う、帰るわ!」って思われるかもしれません。


マルチバイト文字列、ワイド文字列

  • マルチバイト文字列

マルチバイト文字列はchar型でやってきたこと。それに加えて、マルチバイト処理用の文字列操作関数で取り扱うということです。strlen関数に相当するのは _mbslen です。ワイド文字ならwcslenです。

  • ワイド文字列

固定の2byte以上のメモリ空間に英数字ascii範囲とか関係なくすべて同じ固定の領域に格納する方法。デメリットとしては、文字列でメモリ空間をかなり無駄にしてしまうということ。あえて英数字のみ対応のアプリを作るのは、このデメリットと引き換えにして、メモリの節約をしていることになります。それか面倒だから、asciiコードのみ対応にしたとか…。でも、言いにくいことなんですけど…、いろんな方式をいろんな人が使っている昨今でして、全体的に網羅しておかないと変換とかしないとダメなので、覚えることが多いのも事実です。Unicodeって言う方式もこれです。Unicodeの符号化方式のUTF-16は、基本的には半角の英数字も含めて、2byteで1文字を表すためメモリは無駄にしますが、変換効率が高いために実行速度が速くなるというメリットがあります。実行速度の速さにあやかれるほどのプログラムを作るかどうかは未知数です。


VisualSutdioのプロジェクトの設定でもUnicode文字列とマルチバイト文字列、どっちも使わないってのが設定できます。VisualStudioをアクティブにしている状態で[Alt]+[F7]でプロジェクトのプロパティが表示されるダイアログが表示されます。左側のツリー構造から構成プロパティの中の全般を選択すると右側のビュー表示されるリストに文字セットというのがあります。リストには「Unicode文字セットを使用する/マルチバイト文字セットを使用する/設定なし」の3つがあり、ここで選択した設定にしたがって、Visual C++だけの機能になりますが、TCHAR型という文字列変数を定義することで、設定にしたがって、マルチバイトおよびUnicodeを判別してコンパイルしてくれます。因みにワイド文字列型では、wchar_t型というのを使うのですが、TCHAR型は、Unicode設定だとwchar_tに置き換わることになります。


どっちも使わない、設定なしを選択した方がよい場合もあります。それはワイド文字、Unicodeとか、マルチバイト文字を使わないアプリ構成にしたい場合です。Windows.hで提供されるようなAPIでさえも文字列を引数にとることはありますが、この引数の受け取り方が設定なしの場合、なにかとchar型のままでいいよ。という具合になります。そうするとwchar型とかtchar型を使わなくてもよいので、ひたすら文字列はchar型のみを扱えばよいことになります。最初のうちは英語のみ対応のアプリにして練習するのもありかなと思いますので、設定なしという道もあっていいかと思います。むしろ学術的なことに専念するアプリやそういった専門書なんかでは、設定なしを使って説明されるパターンがあります。これは文字列うんぬんを語ると限られたページの中で専門的なことが説明できなくなるからだと思います。ここで書いたことを知っていればあとで、ワイド文字対応に応用することなんかは簡単なことですから、別の専門的なことを勉強するにあたっては、設定なしを選択して、英語版アプリ(1バイト文字、ASCIIコード範囲)を作る覚悟で取り組むということもあっていいかと思います。      


ワイド文字列では、文字列リテラルへは L"yonewiki" としなければならないし、例えば文字列を引数にする関数もwprintf(L"cStr=%s",cStr );とかに多数の書き換えが必要になります。そして、ワイド文字がどこの国の言葉を扱うのかを定義するには、setlocale関数で設定が必要で、setlocale( LC_ALL, "Japanese" );※リテラルの設定では省略されていますが、setlocale( LC_ALL, "Japanese_Japan.932" ); /> と設定していることになります。出力するときには日本語のShift_JISにしてねということです。今はWindowsPCのコマンドプロンプトで動作を確認しているからです。


※ちなみに関数のリファレンスはmsdnが最強だと思うわけでして、

  • C++Runtimeリファレンス(※実行時に動的に呼び出せるという意味でRuntimeとついています。通常はRuntimeライブラリはWindowsのバージョンによって異なるライブラリが入っていますが、Versionが上がっても基本的には、同じようなものだと考えて差し支えないです。静的にもコンパイルできますので、Windows系 Visual C++で使えるC++リファレンスと思ってよいかと。でもいろんなバージョンがあるからこそ、MSVCRTxx.dllが無いのでエラーってなったりする不具合もネット界隈を賑わしています。全部、最初から配ればいいのにMicrosoft…何やってんだ…素人には手ごわいエラーだけに印象悪いっしょ。ちょっと調べることができたら解決できるんですけどね。まぁなんのこっちゃわからん人には難しいわな。)

http://msdn.microsoft.com/ja-jp/library/634ca0c2(v=vs.90).aspx

  • setlocale

http://msdn.microsoft.com/ja-jp/library/x99tb11d(v=vs.90).aspx

のようにsetlocale関数の記述が必要になります。setlocale関数を使うには#include<locale.h>が必要です。setlocale関数でも引数に文字列リテラルを与えるので、そこでワイド文字列を使うために_wsetlocale関数もあります。


_wsetlocale( LC_ALL, L"Japanese" );


とします。こちらはwchar.hをインクルードするだけで使えるとのこと。 wcslenは文字列の文字数をカウントする関数です。通常のマルチバイト文字でもワイド文字でもない、何もしない関数はstrlen関数になります。


さらにwcher_t型を使うには、プリプロセッサでwchar.hを読み込まなくてはならず、


#include<wchar.h>と記述しないといけないです。
従って、ワイド文字列版のプログラムは以下のようになります。


#include <iostream>
//#include<locale.h> wchar.hがインクルードされていれば、いらない。
#include<wchar.h>
int main() {
    _wsetlocale( LC_ALL, L"Japanese");
    wchar_t cStr[]=L"よねウィキyonewiki";
    wprintf(L"cStr0=%s sizeof=%d StrCount=%d", cStr0,sizeof(cStr0),wcslen(cStr0));
}


TCHAR型も同じようなものですが、プロジェクトの設定次第で置き換わるということが特徴です。これでプロジェクトの設定をUnicodeの文字セットにしていれば、wchar_t型になり、さらにTCHAR型を使うには、プリプロセッサでtchar.hを読み込まなくてはならず、

#include<tchar.h>

と記述しないといけないです。setlocale関数も_tsetlocale関数として置き換えておくことができます。Unicodeセットのプロジェクト設定なら結局_wsetlocaleに置き換わるだけです。文字列リテラルは_T(" ")といった形式で記述しておきます。_tcslen関数はwcslen関数と同じです。このようにマルチバイト文字列版、ワイド文字列版、プロジェクト設定に従うTCHAR版の3つの関数があります。cout関数にはTCHAR版が無いようなので、最初からstd::wcoutと記述するか、wcout関数と_tcout関数が同じになるように定義する必要があります。実用的にはあまり使わない関数なので、あってもなくても困りません。


C++標準関数の文字列操作系の関数を使わないようにするのが、自分は良いと思います。自分のパソコンの環境だけで、ちょっとしたことのために、マクロ定義するような手法は嫌いです。何をやってるのかが分からなくなっていきやすいような気がします。長い置き換えが必要な部分だけをプログラムにするようにしています。

従って、TCHAR版のプログラムは以下のようになります。

#include <iostream>
//#include<locale.h> tchar.hがインクルードされていれば、いらない。
#include<tchar.h>
int main() {
    _tsetlocale( LC_ALL, _T("Japanese"));
    TCHAR cStr0[]=_T("表示:よねウィキの機能<yonewiki>");
    _tprintf(_T("cStr0=%s sizeof=%d StrCount=%d"), cStr0,sizeof(cStr0),_tcslen(cStr0));
}

出力結果は

cStr0=表示よねウィキの機能<yonewiki> sizeof=44 StrCount=21

となります。 TCHAR型のユニコード(UTF-16)設定にした場合、wchar_t型のcStr0は配列番号0~21の22の大きさを持つ配列になります。Unicode文字セットですのでShft_JISとは異なる数値が使われますが、0番から順に

0x8868(表),0x793A(示),0xFF1A(:),0x3088(よ),0x306D(ね),0x30A6(ウ),0x30A3(ィ),0x30AD(キ),0x306E(の),0x6A5F(機),0x80FD(能)
0x003C(<),0x0079(y),0x006F(o),0x006E(n),0x0065(e),0x0077(w),0x0069(i),0x006B(k),0x0069(i),0x003E(>),0x0000(\0)

のように格納されています。あんまり変わり映えしませんが、プロジェクトの設定に従うという意味ではTCHAR型を使うのが少しお勧めです。気を付けなければならないのは、Shift_JIS(CP932)コード文字セットで格納された配列をユニコード文字セットのプロジェクトで受け取ったり、その逆のことが起こったりする場合には変換が必要になったりと、いいことばかりではありません。この他、文字コードにはShift_JIS(CP932)ではないShift_JISやEUCやMac日本語といった文字コードもあり、可能性だけで考えると


Unicode:UTF-8/UTF-16/UTF-32
Shift_JIS:CP932/shift_jis/shift_jis2000/shift_jis2004
Mac:x-mac-japanese
EUC:CP51932/CP20932/euc-jp2000/euc-jp2004/eucJP-ms
JIS:CP50220/ISO-2022-JP/ISO-2022-JP-1/ISO-2022-JP-2004/ISO-2022-JP-3
JIS 1Byte:ISO-2022-JP/CP50221


といった文字コードが存在し、外国語文字コードセットも含めるとかなりの数の文字コードが世界に広がっています。Unicodeも扱いがいくつかあるため、実はもう少し勉強しないといけないです。VisualStudioでUnicodeと選択した場合は、UTF-16で扱われているということになります。UTF-8では英数字が1Byte、日本語は3Byteを使います。テキストエディタは、このあたりの解釈が厳密で、ファイルを保存するときにも文字コードセットと符号化方式を遵守していますし、互換性を高めているものが、多いです。複雑過ぎて、自分には作れそうもないですね。


WindowsのVisualStudioなどでWin32 APIを使える環境であれば、以下のような型もあります。
LPCSTR =const CHAR * 
LPCWSTR=const WCHAR *
LPCTSTR=const TCHAR *
LPSTR =CHAR *
LPWSTR =WCHAR *
LPTSTR =TCHAR *
それぞれの正式な名称は
Long Pointer to Constant null-terminated STRing
Long Pointer to Constant null-terminated Wide STRing
Long Pointer to Constant null-terminated Template STRing
Long Pointer to null-terminated STRing
Long Pointer to null-terminated Wide STRing
Long Pointer to null-terminated Template STRing
であり、その略の型名が対応しているとのこと。
PCSTR/PCWSTR/PCTSTR/PSTR/PWSTR/PTSTRと先頭にLが着かないものはWin16時代に使われたものですが、今では差がありません。


MFC(MicrosoftFaundationClass)というMicrosoftが提供しているクラスの文字列型には文字列の操作を強化したCStringもあります。有名なので手ごわいですが、これはMFCです。MFCは無料では配られていないクラスライブラリでして、Microsoftが定義した型ってのはすごく一般的にも使われるくらい流行します。ということで、使うべき型は、WIN32 APIでもLPCTSTR/PCTSTRあるいはLPTSTR/PTSTRということになりましょうか。とはいっても、世界中にはいろんな型の文字列で操作されますので、いろいろな変換が必要になります。


ここではwchar_t型で文字列を使うために、TCHAR型を使い、ワイド文字列用の各関数wcsで始まるような関数を使います。マルチバイト文字列とした場合はchar型でマルチバイト文字列用の各関数_wbやwbで始まるような関数に置き換えられることを意味します。ってことはstrcpyという文字列コピー関数や、strcat(文字列連結)、strlen(文字列カウント)のようなstrで始まるようなC標準ライブラリ関数は使われないことになります


最近は文字列操作関数にxxx_sやxxx_lのようなセキュア思想(本当に安全なのかどうかは不明?使い方よっては間違いだらけのプログラムになる可能性もある)の文字列操作関数がありまして、大雑把に分類すると文字列操作の関数は上記のようなことから、以下のような文字列操作関数に分類できます。

  • strxxx系関数
  • マルチバイト文字_mbxxxx関数/mbxxxx関数
  • ワイド文字_wcxxxx関数
  • Visual C++の _tcxxxx関数

と、_lや_sで終わる関数で

  • strxxx_s系関数
  • マルチバイト文字_mbxxxx_l関数/mbxxxx_l関数
  • ワイド文字_wcxxxx_l関数
  • Visual C++の _tcxxxx_l関数

といった同じことをする関数だけで大体8種類くらいあります。
文字列の型で言うと

  • char型
  • wchar_t型
  • TCHAR型(Visual C++)

そして、WinAPIで定義されている
LPCSTR/LPCWSTR/LPCTSTR/LPSTR/LPWSTR/LPTSTR/PCSTR/PCWSTR/PCTSTR/PSTR/PWSTR/PTSTR/
という型…


どれか一つに決めて使うのは自分次第ですが、世界中のプログラマがどれを使うかは、わかりません。しかもどの関数も微妙に動作が違ったりするので、完全な理解をしようとすると、途方に暮れそうになります。でも、まぁそれだけの種類があるんだなぁとわかってさえいれば、その都度、変換方法を調べるということができるので、それでいいのかなぁと思ったりもします。で、文字コードセット変換…リトルエンディアン…ビッグエンディアン…文字コードセットの符号化方式UTF-8/UTF-16/UTF-32その他…もうね。なんか崩壊してるような気もするけど、やるしかないわなorz


学校で教えてくれるプログラミングがいかにその一部分だけを勉強していたのかを思い知らされる現実。そして、これを覚えたところでWindowsプログラミングができるわけでもないという、目指すべき頂上の遠さ。全部を理解しようなんて思わないことですね。必要最低限の装備でどこまで登れるか?登山はあんまりやったことがないのでアレなんすけど


そんな軽装備で山登りなんて始めたら、間違いなく死ににいくようなもんです。それでも目指すべきところがあるのならば、頑張るしかないっすね。自分自身はMFCを使ったアプリも複雑過ぎて理解しきれなかったし、ある程度は理解しようとしたんですけど…。C++を理解して、MFCのちょいかじり、さらにその先にあるC++/CLI、DirectShowを使ってみたりもしましたが、いやはや。どこまで理解できたのやら(汗。Win32APIだけでUSBラジオを操作したり、音を出力する質素なアプリとか、そういうのを作るのが精一杯でしたね。あと面白いのはSteinBerg社の提供しているVSTiスケルトンから音楽やMIDI処理系のDLL開発をするのも楽しかったような。 自分もわからなかったことを、もう一度、理解してみようと思います。

マルチバイト文字列、ワイド文字列の相互変換

まずは、文字列の配列で以下のように格納されているものがあって、

TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")};


を単純にpcStr0というポインタ変数を使って、まるまるコピーする場合はこんな風に自分ならやります。動的にメモリを確保してもらいますが、それで効率が本当にいいのか悪いのかは知りません。なんとなくやりきった感じだけはします。

#include <iostream>
//#include<locale.h> tchar.hがインクルードされていれば、いらない。
#include<tchar.h>
int main() {
	_tsetlocale( LC_ALL, _T("Japanese"));
	TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")};
	TCHAR **pcStr0 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)];
	for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){
                _tprintf(_T("cStr0=%s sizeof=%d StrCount=%d\n"), cStr0[i], sizeof(cStr0)/sizeof(*cStr0),_tcslen(cStr0[i]));
		pcStr0[i] = new TCHAR[_tcslen(cStr0[i]) + 1];
		_tprintf(_T("cStr0=%s\n\n"),*(cStr0 + i));
		_tcscpy_s(pcStr0[i], _tcslen(cStr0[i]) + 1,cStr0[i]);
	}
	for(int i = 0; i < 3; i++){
		_tprintf(_T("_tcslen(cStr0[i])=%d\npcStr0=%s\n cStr0=%s\n\n"),_tcslen(cStr0[i]),*(pcStr0 + i),*(cStr0 + i));
	}
	for(int i = 0; i < 3; i++){
		delete[] *(pcStr0 + i);
	}
	delete[] pcStr0;
	printf("\n");
	return 0;
}

本当は12行目のコピーをする関数である_tcscpy_s関数は語尾の_sの無い関数を使いたいところですが、warningが表示されるので、あえて使いこなします。_tcscpy_sの第一引数はコピー先のTCHAR型先頭アドレスでコピーするのに必要な配列の大きさを持っていれば良いです。第二引数では、コピー先の配列の大きさを指定しています。第三引数はコピー元のTCHAR型の先頭アドレスです。


7行目のNew演算子では、TCHAR型のポインタのポインタ型の配列大きさを3を確保して、実体化しています。アドレスを保持できる4Byteの大きさの領域が配列番号0~3まで作られたことになります。10行目のNew演算子では、cStr[0]~[2]の文字列の文字数+1の大きさの配列をTCHAR型で確保します。ワイド文字列のwchar_t型でも21文字+1、22文字+1、22文字+1と順番に確保しますし、マルチバイト文字ならchar型ですので、すべて全角文字に対しては2byte、2文字分の領域が必要だと計算し、32文字+1、33文字+1、33文字+1を確保します。何故+1文字分を確保するかと言えば、文字列の最後には\0を格納してくれるので、その分をあけておかなければなりません。


18行目、20行目では、delete演算子で動的生成した領域の放棄を宣言して、リソースを返却します。使い終わったら速やかに、返す。
出力結果はマルチバイト文字設定だと

cStr0=表示よねウィキの機能<yonewiki> sizeof=3 StrCount=32
cStr0=表示よねウィキの機能<yonewiki>

cStr0=表示よねウィキの機能1<yonewiki> sizeof=3 StrCount=33
cStr0=表示よねウィキの機能1<yonewiki>

cStr0=表示よねウィキの機能2<yonewiki> sizeof=3 StrCount=33
cStr0=表示よねウィキの機能2<yonewiki>

_tcslen(cStr0[i])=32
pcStr0=表示よねウィキの機能<yonewiki>
 cStr0=表示よねウィキの機能<yonewiki>

_tcslen(cStr0[i])=33
pcStr0=表示よねウィキの機能1<yonewiki>
 cStr0=表示よねウィキの機能1<yonewiki>

_tcslen(cStr0[i])=33
pcStr0=表示よねウィキの機能2<yonewiki>
 cStr0=表示よねウィキの機能2<yonewiki>

となります。
ワイド文字列設定なら

cStr0=表示よねウィキの機能<yonewiki> sizeof=3 StrCount=21
sizecStr0=0,cStr0=表示よねウィキの機能<yonewiki>

cStr0=表示よねウィキの機能1<yonewiki> sizeof=3 StrCount=22
sizecStr0=0,cStr0=表示よねウィキの機能1<yonewiki>

cStr0=表示よねウィキの機能2<yonewiki> sizeof=3 StrCount=22
sizecStr0=0,cStr0=表示よねウィキの機能2<yonewiki>

_tcslen(cStr0[i])=21
pcStr0=表示よねウィキの機能<yonewiki>
 cStr0=表示よねウィキの機能<yonewiki>

_tcslen(cStr0[i])=22
pcStr0=表示よねウィキの機能1<yonewiki>
 cStr0=表示よねウィキの機能1<yonewiki>

_tcslen(cStr0[i])=22
pcStr0=表示よねウィキの機能2<yonewiki>
 cStr0=表示よねウィキの機能2<yonewiki>

となります。Visual C++をつかわれているのであれば、プロジェクトの設定をマルチバイトにしたり、Unicodeにしてみたりして下さい。TCHAR型で記述しているので、どちらでも動作すると思います。


それで、なんでしたっけ、ワイド文字列とマルチバイト文字列の相互互換なんですけど、ようするにこのコピーをするときにワイド文字列とマルチバイトが混じっているケースで考えて、どのように対処すればいいのかで、 説明ができると思ったのでした。mbstowcs関数とwcstombs関数でやりとりが出来るようになっています。実際、こんなケースがあるのかは知りませんが、こんな関数があるってことは、テキストをどこからか読み込んだ場合とか、そういったプログラムから引数として受け取るとか、そういったケースもあるのでしょう。関数の引数はそれぞれ異なるので、気を付けなければなりません。


で、無理矢理にそういったサンプルのプログラムを作るのは意外と面倒でして、サンプルは鋭意執筆中ということにしておきたいと思います。ぉぃ。っていうか、MSDNライブラリが落っこちてるみたいで、厳密な解釈を調べながら書くことができない。mbstowcs関数にも_tbstowcsとかmbstowcs_sとかmbstowcs_s_lとかあると思うんですけど、その辺りを知ることが出来ない。その逆もしかり。


ようはchar型に収められた文字列をwchar_t型に変換したり、wchar_t型で収められた文字列をchar型に変換したりするってことなんですけど。UnicodeとShift_JISの変換もここで知っておくべきか…。つうか、この記事、長すぎっ。誰も理解できないんじゃね?って思う今日この頃。


とかいいつつ、サンプルプログラムを作りました。
mbstowcs関数とwcstombs関数はやはり、mbstowcs_s関数とwcstombs_s関数があって更には、
個別にlocale設定するmbstowcs_s_l関数とwcstombs_s_l関数もありました。詳しくはmsdnのリファレンスのとおりです。

  • mbstowcs_s/mbstowcs_s_l

http://msdn.microsoft.com/ja-jp/library/vstudio/eyktyxsx(v=vs.110).aspx

  • wcstombs_s/wcstombs_s_l

http://msdn.microsoft.com/ja-jp/library/vstudio/s7wzt4be(v=vs.110).aspx
サンプルプログラムは以下のとおりです。

#include <iostream>
//#include<locale.h> tchar.hがインクルードされていれば、いらない。
#include<tchar.h>
int main() {
	_tsetlocale( LC_ALL, _T("Japanese"));
	size_t requiredSize = 0;
	size_t *sizeReturnValue;
	sizeReturnValue = new size_t;
	*sizeReturnValue=0;
	size_t sizecStr0=0;
	_tprintf(_T("%s\n"),cStr);
	const TCHAR *cStr0[]={_T("表示:よねウィキの機能<yonewiki>"),_T("表示:よねウィキの機能1<yonewiki>"),_T("表示:よねウィキの機能2<yonewiki>")};
	const char *cStr1[]={"表示:よねウィキの機能<yonewiki>","表示:よねウィキの機能1<yonewiki>","表示:よねウィキの機能2<yonewiki>"};

	TCHAR **pcStr0 = new TCHAR*[sizeof(cStr0)/sizeof(*cStr0)];
	char **pcStr1 = new char*[sizeof(cStr0)/sizeof(*cStr0)];
	TCHAR **pcStr2 = new TCHAR*[sizeof(cStr1)/sizeof(*cStr1)];

	//マルチバイト文字→ワイド文字変換
	_tprintf(_T("マルチバイト文字→ワイド文字変換\n"));
	for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){
    	printf("\n");
        printf("cStr1=%s sizeof=%d StrCount=%d\n", cStr1[i], sizeof(cStr1)/sizeof(*cStr1),strlen(cStr1[i]));
		mbstowcs_s(sizeReturnValue, NULL, 0,cStr1[i], 0);
		requiredSize = *sizeReturnValue;
		pcStr2[i] = new TCHAR[requiredSize];
		printf("requiredSize=%d,cStr0=%s\n", requiredSize,*(cStr1 + i));
		mbstowcs_s(sizeReturnValue, pcStr2[i], requiredSize, cStr1[i], requiredSize);
		printf("sizeReturnValue=%d\n\n",*sizeReturnValue);
		for(unsigned int j = 0; j < requiredSize; j++){
			printf("pcStr2[%d][%2d]=%x\n",i,j,*(*(pcStr2 + i) + j));
		}
	}
	printf("\n");
	for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){
		_tprintf(_T("_tcslen(cStr1[i])=%d\npcStr2=%s\n"), strlen(cStr1[i]), *(pcStr2 + i));
		printf(" cStr1=%s\n\n", *(cStr1 + i));
	}

	//ワイド文字変換→マルチバイト文字
	_tprintf(_T("ワイド文字変換→マルチバイト文字\n"));
	for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){
		sizecStr0 = 0;
        _tprintf(_T("cStr0=%s sizeof=%d StrCount=%d\n"), cStr0[i], sizeof(cStr0)/sizeof(*cStr0),_tcslen(cStr0[i]));
		wcstombs_s(sizeReturnValue, NULL, 0,cStr0[i],0);
		requiredSize = *sizeReturnValue;
		pcStr1[i] = new char[requiredSize];
		_tprintf(_T("requiredSize=%d, cStr0=%s\n"),requiredSize,*(cStr0 + i));
		wcstombs_s(sizeReturnValue, pcStr1[i], requiredSize, cStr0[i],requiredSize);
		printf("sizeReturnValue=%d\n\n",*sizeReturnValue);
		for(unsigned int k = 0; k < requiredSize; k++){
			printf("pcStr1[%d][%2d]=%x\n",i,k,*(*(pcStr1 + i) + k));
		}
	}
	for(int i = 0; i < 3; i++){
		_tprintf(_T("_tcslen(cStr0[i])=%d\npcStr0=%s\n"),_tcslen(cStr0[i]),*(cStr0 + i));
		printf(" cStr1=%s\n\n",*(pcStr1 + i));
	}
	for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){
		delete[] *(pcStr0 + i);
	}
	for(int i = 0; i < (sizeof(cStr0)/sizeof(*cStr0)); i++){
		delete[] *(pcStr1 + i);
	}
	for(int i = 0; i < (sizeof(cStr1)/sizeof(*cStr1)); i++){
		delete[] *(pcStr2 + i);
	}
	delete[] pcStr0;
	delete[] pcStr1;
	delete[] pcStr2;
	delete sizeReturnValue;
	return 0;
}

前半部分にマルチバイト文字→ワイド文字変換
後半部分にワイド文字→マルチバイト文字変換
の処理を記述しました。
mbstowcs_s(sizeReturnValue, NULL, 0,cStr1[i], 0);
のように第二引数には本来、変換後の文字列の先頭アドレスを記述しますが、NULLと指定して、第三引数の変換後の配列要素数も0にして、第四引数に変換元の文字列の先頭アドレスを記述し、変換する要素数も0を指定するとsize_t型のポインタ変数sizeReturnValueに変換に必要な配列サイズが返ってきます。その返却された値を


requiredSize = *sizeReturnValue;のようにしてコピーしておいて、


mbstowcs_s(sizeReturnValue, pcStr2[i], requiredSize, cStr1[i], requiredSize);


とすることで変換がされます。
逆のwcstombs_sも同様です。やったことないようなプログラムでしたので、たまには、こうして確かめてみるのも良いですね。

ちょっと出力結果に冗長さを設定したサンプルプログラムなので、以下のように出力が長くなりましたが、全部貼っておきます。

マルチバイト文字ワイド文字変換

cStr1=表示よねウィキの機能<yonewiki> sizeof=3 StrCount=32
requiredSize=22,cStr0=表示よねウィキの機能<yonewiki>
sizeReturnValue=22

pcStr2[0][ 0]=8868
pcStr2[0][ 1]=793a
pcStr2[0][ 2]=ff1a
pcStr2[0][ 3]=3088
pcStr2[0][ 4]=306d
pcStr2[0][ 5]=30a6
pcStr2[0][ 6]=30a3
pcStr2[0][ 7]=30ad
pcStr2[0][ 8]=306e
pcStr2[0][ 9]=6a5f
pcStr2[0][10]=80fd
pcStr2[0][11]=3c
pcStr2[0][12]=79
pcStr2[0][13]=6f
pcStr2[0][14]=6e
pcStr2[0][15]=65
pcStr2[0][16]=77
pcStr2[0][17]=69
pcStr2[0][18]=6b
pcStr2[0][19]=69
pcStr2[0][20]=3e
pcStr2[0][21]=0

cStr1=表示よねウィキの機能1<yonewiki> sizeof=3 StrCount=33
requiredSize=23,cStr0=表示よねウィキの機能1<yonewiki>
sizeReturnValue=23

pcStr2[1][ 0]=8868
pcStr2[1][ 1]=793a
pcStr2[1][ 2]=ff1a
pcStr2[1][ 3]=3088
pcStr2[1][ 4]=306d
pcStr2[1][ 5]=30a6
pcStr2[1][ 6]=30a3
pcStr2[1][ 7]=30ad
pcStr2[1][ 8]=306e
pcStr2[1][ 9]=6a5f
pcStr2[1][10]=80fd
pcStr2[1][11]=31
pcStr2[1][12]=3c
pcStr2[1][13]=79
pcStr2[1][14]=6f
pcStr2[1][15]=6e
pcStr2[1][16]=65
pcStr2[1][17]=77
pcStr2[1][18]=69
pcStr2[1][19]=6b
pcStr2[1][20]=69
pcStr2[1][21]=3e
pcStr2[1][22]=0

cStr1=表示よねウィキの機能2<yonewiki> sizeof=3 StrCount=33
requiredSize=23,cStr0=表示よねウィキの機能2<yonewiki>
sizeReturnValue=23

pcStr2[2][ 0]=8868
pcStr2[2][ 1]=793a
pcStr2[2][ 2]=ff1a
pcStr2[2][ 3]=3088
pcStr2[2][ 4]=306d
pcStr2[2][ 5]=30a6
pcStr2[2][ 6]=30a3
pcStr2[2][ 7]=30ad
pcStr2[2][ 8]=306e
pcStr2[2][ 9]=6a5f
pcStr2[2][10]=80fd
pcStr2[2][11]=32
pcStr2[2][12]=3c
pcStr2[2][13]=79
pcStr2[2][14]=6f
pcStr2[2][15]=6e
pcStr2[2][16]=65
pcStr2[2][17]=77
pcStr2[2][18]=69
pcStr2[2][19]=6b
pcStr2[2][20]=69
pcStr2[2][21]=3e
pcStr2[2][22]=0

_tcslen(cStr1[i])=32
pcStr2=表示よねウィキの機能<yonewiki>
 cStr1=表示よねウィキの機能<yonewiki>

_tcslen(cStr1[i])=33
pcStr2=表示よねウィキの機能1<yonewiki>
 cStr1=表示よねウィキの機能1<yonewiki>

_tcslen(cStr1[i])=33
pcStr2=表示よねウィキの機能2<yonewiki>
 cStr1=表示よねウィキの機能2<yonewiki>

ワイド文字変換マルチバイト文字
cStr0=表示よねウィキの機能<yonewiki> sizeof=3 StrCount=21
requiredSize=33, cStr0=表示よねウィキの機能<yonewiki>
sizeReturnValue=33

pcStr1[0][ 0]=ffffff95
pcStr1[0][ 1]=5c
pcStr1[0][ 2]=ffffff8e
pcStr1[0][ 3]=ffffffa6
pcStr1[0][ 4]=ffffff81
pcStr1[0][ 5]=46
pcStr1[0][ 6]=ffffff82
pcStr1[0][ 7]=ffffffe6
pcStr1[0][ 8]=ffffff82
pcStr1[0][ 9]=ffffffcb
pcStr1[0][10]=ffffff83
pcStr1[0][11]=45
pcStr1[0][12]=ffffff83
pcStr1[0][13]=42
pcStr1[0][14]=ffffff83
pcStr1[0][15]=4c
pcStr1[0][16]=ffffff82
pcStr1[0][17]=ffffffcc
pcStr1[0][18]=ffffff8b
pcStr1[0][19]=40
pcStr1[0][20]=ffffff94
pcStr1[0][21]=5c
pcStr1[0][22]=3c
pcStr1[0][23]=79
pcStr1[0][24]=6f
pcStr1[0][25]=6e
pcStr1[0][26]=65
pcStr1[0][27]=77
pcStr1[0][28]=69
pcStr1[0][29]=6b
pcStr1[0][30]=69
pcStr1[0][31]=3e
pcStr1[0][32]=0
cStr0=表示よねウィキの機能1<yonewiki> sizeof=3 StrCount=22
requiredSize=34, cStr0=表示よねウィキの機能1<yonewiki>
sizeReturnValue=34

pcStr1[1][ 0]=ffffff95
pcStr1[1][ 1]=5c
pcStr1[1][ 2]=ffffff8e
pcStr1[1][ 3]=ffffffa6
pcStr1[1][ 4]=ffffff81
pcStr1[1][ 5]=46
pcStr1[1][ 6]=ffffff82
pcStr1[1][ 7]=ffffffe6
pcStr1[1][ 8]=ffffff82
pcStr1[1][ 9]=ffffffcb
pcStr1[1][10]=ffffff83
pcStr1[1][11]=45
pcStr1[1][12]=ffffff83
pcStr1[1][13]=42
pcStr1[1][14]=ffffff83
pcStr1[1][15]=4c
pcStr1[1][16]=ffffff82
pcStr1[1][17]=ffffffcc
pcStr1[1][18]=ffffff8b
pcStr1[1][19]=40
pcStr1[1][20]=ffffff94
pcStr1[1][21]=5c
pcStr1[1][22]=31
pcStr1[1][23]=3c
pcStr1[1][24]=79
pcStr1[1][25]=6f
pcStr1[1][26]=6e
pcStr1[1][27]=65
pcStr1[1][28]=77
pcStr1[1][29]=69
pcStr1[1][30]=6b
pcStr1[1][31]=69
pcStr1[1][32]=3e
pcStr1[1][33]=0
cStr0=表示よねウィキの機能2<yonewiki> sizeof=3 StrCount=22
requiredSize=34, cStr0=表示よねウィキの機能2<yonewiki>
sizeReturnValue=34

pcStr1[2][ 0]=ffffff95
pcStr1[2][ 1]=5c
pcStr1[2][ 2]=ffffff8e
pcStr1[2][ 3]=ffffffa6
pcStr1[2][ 4]=ffffff81
pcStr1[2][ 5]=46
pcStr1[2][ 6]=ffffff82
pcStr1[2][ 7]=ffffffe6
pcStr1[2][ 8]=ffffff82
pcStr1[2][ 9]=ffffffcb
pcStr1[2][10]=ffffff83
pcStr1[2][11]=45
pcStr1[2][12]=ffffff83
pcStr1[2][13]=42
pcStr1[2][14]=ffffff83
pcStr1[2][15]=4c
pcStr1[2][16]=ffffff82
pcStr1[2][17]=ffffffcc
pcStr1[2][18]=ffffff8b
pcStr1[2][19]=40
pcStr1[2][20]=ffffff94
pcStr1[2][21]=5c
pcStr1[2][22]=32
pcStr1[2][23]=3c
pcStr1[2][24]=79
pcStr1[2][25]=6f
pcStr1[2][26]=6e
pcStr1[2][27]=65
pcStr1[2][28]=77
pcStr1[2][29]=69
pcStr1[2][30]=6b
pcStr1[2][31]=69
pcStr1[2][32]=3e
pcStr1[2][33]=0
_tcslen(cStr0[i])=21
pcStr0=表示よねウィキの機能<yonewiki>
 cStr1=表示よねウィキの機能<yonewiki>

_tcslen(cStr0[i])=22
pcStr0=表示よねウィキの機能1<yonewiki>
 cStr1=表示よねウィキの機能1<yonewiki>

_tcslen(cStr0[i])=22
pcStr0=表示よねウィキの機能2<yonewiki>
 cStr1=表示よねウィキの機能2<yonewiki>

ってな感じになります。マルチバイト文字に変換することでShift_JIS cp932になっていることもわかったかと思います。
人に見せるのに、出力部分の適当さ加減はすごいね。でも、これって内部的な努力は凄いけど、外部から見れば何も変化を感じないので、
さみしいね。次は文字列の操作の記事でも書きます。


C++へ戻る