マルチバイト文字を使ったプログラム

printf("日本語");などと、printf()で日本語を扱うことには違和感があるが、 実際、使えると非常に便利。 「ANSI規格ではpritnf()で日本語は使えないんだぞ」などと言っても、 じゃぁ日本語はどうやって書くんだと言われるとよく知らない。 なので、ちょっと調べて整理します。

文字の種類

語弊があるかもしれないが大雑把に以下の4種類ある
種類 使える文字列処理の関数 説明
1Byte文字 charを使う str系の関数を使う C言語では普通キャラクタというと、これになる。 ASCII文字とよく言うが、厳密には日本人が使ってる文字コードでは バックスラッシュがエンマークになってるのでASCIIコードじゃない。(JIS X0201だっけ?) ASCIIというのはちょっと変かも。
2Byte文字 wchar_tを使う wの付いてる関数 漢字を2Byteで表現するから2Byte文字と呼んでる。 UNICODEはこれで表現するらしい。 処理系によってはwcharは4byteの場合もあるので、2Byte文字=wchar_tというのはおかしいが、まぁ、そんなものとだけ覚えている。 これを みるかぎり、wcahr_tを使ったプログラムでUNICODEしか考えてなく、localeがまともに機能しない プログラムがあるらしい。
マルチバイト文字 charと言いたいところだが、charを複数個使って1文字を表すのでchar*というべきか? mbの付いてる関数 複数Byteで1文字を表す。 日本語は2Byteしか使わないと思いがちだが、実はEUCコードには3Byte文字もある。 と、いってもEUCにはEUC-JP,eucJP-open,eucJP-ms,CP51932など種類があるらしく、 普段EUCだと思って使ってるLocaleはEUC-JPでは3Byte文字は使わないらしい.(本当か?) 文字列としては、1byte文字とまぜて使う場合があるので、固定Byte数とはならない。 なので、文字列を走査するプログラムを書くときに、1文字後ろを読むつもりでポインタを2Byte後にずらして読むと 2文字後の場合や2Byte文字の後ろ半分Byteにポインタが移動してしまい、とんでもないことになる。 1byte文字だったら当たり前だったs[i]= '\0'とかif(s[i]=='-')なんて記述ができない。 mb系の関数は、標準のCにはない。(あってもwcharと変換する関数くらいで、まともに使える状況ではない)。
ジェネリック文字 TCHARを使う。これ、実はマクロ。defineされている内容に応じてcharやwchar_tに化ける _tの付いてる関数 TCHARというマクロをつかう。TCHARは処理系や定義にあわせて上記3つのどれかに切り替えてコンパイルしてくれる。 VC++依存になるので、Windowsしか使えない。 文字列も_T("文字列")のように_T()マクロを使って表現する。 文字列操作もジェネリックテキストの関数(といってもマクロ?)を使う。 ジェネリックテキストを使う関数への表を見ながらじゃないと関数名が判らず、使いこなせない。

tcharを使ったプログラムの注意

charで文字を表す時にはポインタを使えば簡単にできた操作が、全て関数を使って行う羽目になる。 1文字のサイズが1byteの場合と複数byteの場合があるからしょうがないんだけど、やっぱり面倒。

1文字シフト

char文字列の場合、文字列をなめて処理するようなプログラムを書くときに、 処理してる文字をポインタで指し示し、p++のようにインクリメントで1文字ずらすのがイディオムになっているが、 tcharではこの方法が使えずp = _tcsinc(p)のように関数を使う。
1文字戻るのは p = _tcsdec(start, p)のように文字列の先頭のポインタも指定しないと使えない。

1文字の比較

_tccmp(a, b)を使います。
charの場合if(*a == *b)と書けたものが、 if(_tccmp(a,b) == 0)になります。

1文字のコピー

_tccpy(a, b)を使います。
*a = *b_tccpy(a,b)になります。 また、 *a = 'b'_tccpy(a,"b")になります。 また、 *a = '\\'_tccpy(a,"\\")*a = '\0'_tccpy(a,"")となります。 これが気持ち悪い!。でもしょうがない。
もしかしたら、もっとましな書き方があるのかも知れないけど、知らない。

X文字目を書き換える

char だったら p[x] = 'z'とかけたが、マルチバイトだと難しい。

_tcsinc()で文字を動かしてから、_tccpy()を使うなんて方法が思いつくが、 これじゃだめ。 書き換える前の文字が1byteなのに2byte文字を書いたとか、その逆をやってしま可能性があるから。

無理やりやるとしたら、書き換えたい文字のところで文字列を区切り、 「書き換えたい文字の前の文字まで」+「書き換える文字」+「書き換えた文字から文字列の最後まで」の 3つをつないだ文字列を別に作ることになる。

操作する文字のbyte数の違いに起因する問題に引っかからない為の考え方としては、 マルチバイト文字列(ジェネリック文字列も)を扱っている時は、1文字を操作するという考えではなく、 文字列を操作するつもりで考え、その対象の文字列長が1だっただけとみなすのが良いでしょう。

n文字コピーする

Cの標準関数にある文字列系の関数は、コピー先にnullがつく場合とつかない場合があるという、 使いがっての悪さがある。 ジェネリック文字列の関数も、その使い勝手の悪さを踏襲した_tcsnccpy()があるので、 これでコピーした後、気をつけてnullをつける。

ファイルを開く

fp = _tfopen(_T("日本語ファイル.txt"),_T("w"))と使う。 fclose()はtが付かない。

日本語でprintf()やfpritnf()

_tprintf(_T("日本語"));とか_ftprintf(stderr, _T("日本語")); てなかんじ

localeについて

日付の表示をどうするとか、数値の表示をどうするとか、localeで切り替えられるものが いくつかあり、LC_CTYPE, LC_COLLATE, Collation order, LC_TIME, LC_NUMERIC, LC_MONETARY, LC_MESSAGES という変数(環境変数?)で変更できるように分かれているみたいだが、 LC_ALLだけ設定しておけば全部かえられるみたい。 localeの説明参照

リンク