[前へ]   [目次へ]   [次へ]

文字の扱い(1)(C/C++)

(06/08/09)日本語第二バイトに入りうる値について修正
(08/10/17)文字コードについて加筆



今回からは、文字をどうやって扱えばいいかについて解説します。

まず、「文字」と「文字列」についてです。
C/C++言語では「文字」と言った場合は単独の文字を指します。
そしてそれは、文字コードの値そのものです。

ただし文字列の場合、文字コードがShift_JIS等の場合は「文字」は1バイトを指す場合と1文字を指す場合があります。
この違いは、文字列中の全角文字の換算数に関係します。
1バイトを指すなら、半角は1文字、全角は2文字として扱うことになります。
1文字を指すなら、半角全角を問わず、1文字は1文字として扱います。

このあたりはネット上等の関数の解説などを読む時に注意すべき点です。
どちらの意味で言っているかは周辺の文脈から解釈する必要があるため分かりにくいですが、
分からない時は "あa" のような文字列を入れて実行してみれば見極めることができます。
これで2文字として扱うか、3文字として扱うかを見ればどちらの意味かは分かるはずです。

また、C/C++言語では「文字列」は「文字」の集合です。
以前にも書きましたが変数として扱う時は「配列」に入れます。

「文字列」は複数の種類がありますが、当面は最も基本的な「C文字列」(ナル終端文字列)を扱います。

C文字列では、配列の0番から順番に、1バイトずつ格納されています。
文字列の終端は「ナル文字」を見つけた時点です。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
#include <stdio.h>

int main(void)
{
   
char str[64]="abcde";//文字列
   
printf("%c:%d\n",str[0],str[0]);
   printf(
"%c:%d\n",str[1],str[1]);
   printf(
"%c:%d\n",str[2],str[2]);
   printf(
"%c:%d\n",str[3],str[3]);
   printf(
"%c:%d\n",str[4],str[4]);
   printf(
" :%d\n",str[5]);
   
//終了待ち
   
getchar();
   
return 0;
実行例:
a:97
b:98
c:99
d:100
e:101
 :0

printf 関数の %c 指定は、与えた引数を1バイト、そのまま出力します。
文字コードを出力した場合は文字として表示されますが、文字コード以外を画面に出力すると正しく表示されません。
その後に同じ変数を数値としても出力しています。

この例は文字コードがShift_JISの場合なのでもしかしたら違う値が表示されるかもしれません。
その時は、コンパイラの設定を変えるなどしてなんとかShift_JISで扱えないか試してみてください。
(文字コードが異なると直接操作の文字列処理が変化してしまうためです。
   この講座では当分Shift_JIS以外の文字コードを扱う予定はありません)

上の実行例と同じに表示されましたでしょうか。
無事に表示されたなら次に進みます。

さて、実行例を見ると、 str に入れた文字列は str の各要素に順番に代入されているのがお分かりでしょうか。
最後の0ってのは「ナル文字」、ようは文字列の終端文字です。
ただし、文字列終端としての0はただの0ではなく、意味のある値なので、
通常、ナル文字を明記して使用するときは '\0' と表記して「ナル文字」であることをソース上で宣言します。
'\0' はコンパイラは単に0として解釈するので同じと言えば同じなのですが、
人間が読む時には「これはナル文字なのだな」ということがすぐわかるのでバグ発見や解析などが楽になります。

ちなみに日本語文字はどうなってるかというと、要素を2個使用しています。
どちらか片方だけ画面に出力しても上手く表示されません。
両方を順番を維持したまま、連続して出力しないといけません。

そのため、上のソースの "abcde" を "あいうえお" に変更してもそのまま上手く動いてはくれません。
オチとしては文字表示の部分がぐちゃぐちゃになるだけです。
ちゃんと表示するには要素2個を連続して出力するように変更します。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
#include <stdio.h>

int main(void)
{
   
char str[64]="あいうえお";//文字列
   
printf("%c%c:%d,%d\n",str[0],str[1],str[0],str[1]);
   printf(
"%c%c:%d,%d\n",str[2],str[3],str[2],str[3]);
   printf(
"%c%c:%d,%d\n",str[4],str[5],str[4],str[5]);
   printf(
"%c%c:%d,%d\n",str[6],str[7],str[6],str[7]);
   printf(
"%c%c:%d,%d\n",str[8],str[9],str[8],str[9]);
   printf(
"  :%d\n",str[10]);
   
//終了待ち
   
getchar();
   
return 0;

実行例:
あ:-126,-96
い:-126,-94
う:-126,-92
え:-126,-90
お:-126,-88
  :0

2個ずつ出力しているので、ちゃんと日本語文字が表示されています。
右側の値が負になっていますが、これは char が符号付きのコンパイラでこうなります。
char が符号なしのコンパイラでは以下のように表示されるはずです。

実行例:
あ:130,160
い:130,162
う:130,164
え:130,166
お:130,168
  :0

この辺は変数の保持範囲の問題なのですが、文字の比較を行う時は注意しないと影響を受けることがあります。
例えば、日本語文字はShift_JISでは128以上の文字コードを第一バイトに含んでいるので、半角英数文字より大きいはずです。
しかし、 char が符号付きである場合、保持範囲は大抵-128〜127であり、オーバーフロー(上限突破)してしまいます。
オーバーフローした結果、値は保持範囲に入るように切り捨てられます。(具体的にどうして上記のようになるかは次節にて解説します)

符号付きの場合、今回は上記のように全てマイナスになってしまっています。
結果として、比較結果が逆転してしまい、日本語文字は半角英数文字より小さいことになってしまいます。

このような現象を避けるため、文字コードの比較などのように符号の有無で結果が変化する可能性がある場合は、
unsigned を明示的に宣言しておく方法が使われます。
signed を使わないのは、そもそも文字コードに負値なんて存在しないので unsigned を使用した方が自然だからです。

unsigned char buf[64];//符号なしを明示して定義 

しかし、C言語はこれでもいいのですが、
C++言語では大半の文字列操作関数は unsigned char 型の文字列を受け付けてくれない(エラーになる)んです。
という訳で、面倒でも unsigned char 型の変数を用意して、最初にそれに代入してから比較を行います。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
#include <stdio.h>

int main(void)
{
   
char str[64]="あいうえお";//文字列
   
unsigned char ch1,ch2;
   ch1=str[0];
   ch2=str[1];
   printf(
"%c%c:%d,%d\n",str[0],str[1],str[0],str[1]);
   printf(
"%c%c:%d,%d\n",ch1,ch2,ch1,ch2);
   
//終了待ち
   
getchar();
   
return 0;

実行例:
あ:-126,-96
あ:130,160

これは char が符号付きの場合の結果です。
符号なしだった場合は

実行例:
あ:130,160
あ:130,160

のように表示されるはずです。

この方法は面倒ですが、C++でも問題なく動作します。
もちろん、符号の有無で結果が変化しない比較ならば、このような面倒な手段を講じる必要はありません。
(現実にこのような手段を使用する必要があることは  だったりするので、手法まで覚えておく必要はないかもしれません)


さて、なんだか話が逸れていたような気がしますが、続けます。

まず、Shift_JIS文字コードは、Windowsでよく使われる文字コードです。
(Windows XP等はUnicodeを主としようとしていますが、現状はまだまだShift_JISが多用される状態のようです)

(08/10/17) 追記ここから

↑最近ではかなりUnicodeの利用率も上がってきて、そろそろ無視できない感じになってきています。

(08/10/17) 追記ここまで

そして、127(0x7F)以下の範囲は ASCIIコード と同じです。
128(0x80)〜255(0xFF)には「日本語第一バイト」と「半角カタカナ」が割り当てられています。
「日本語第一バイト」に遭遇した時は次のバイトは無条件に「日本語第二バイト」となり、
この2バイトの並びで全角(日本語)文字は決定されます。

(06/08/09) 日本語第二バイトに入りうる値について修正

これだけですが、日本語第二バイトには半角アルファベットや一部の記号、日本語第一バイト、半角カタカナなどの値が入る可能性があります。

(06/08/09) 修正ここまで

そのため、日本語文字の境界を見失うと、どのバイトが文字の区切りになるかは全く分からなくなります。
同様に、途中の文字が日本語かどうかを確認する場合も、文字列の先頭から調べていかなければなりません。
また、日本語第二バイトにはエスケープやフォルダ区切りを意味する \ が入る可能性がある点も注意です。( 0x5c問題 参照)

と、このように書くとイロイロ面倒そうに見えるかもしれませんし、実際面倒です。
とはいえ、現実に簡単に扱える文字コードというのはなかなかありません。

Shift_JISの弱点は上記のように任意位置が日本語文字の一部なのか、
そうでないのかを先頭(または境界を確定している位置)から調べる必要があることですが、
他の文字コードには「ページ切替」とか「制御文字」などの制御コードを持つものも少なくありません。
これらの中には適切に処理しないと、後続の文字列全てが化けてしまうものもあり、これはこれで面倒です。
(全てを提供されているライブラリ中で処理できているうちはそのような問題は見えないのですが・・・)


次回は、様々な文字の扱い方の予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17