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

半角と全角の混在するShiftJIS文字コードの扱い方(C/C++)


今回は、文字列を直接操作するにあたって、
日本語を含むShiftJIS文字列を操作するための扱い方についてです。


ShiftJIS文字コードでは日本語は2バイトで表現され、1バイト目に決まった値の範囲が入ります。
半角文字と全角文字の区別は、1バイト目の値を見て判断します。
問題は2バイト目だけを見ても判断できないことで、文字列の途中のあるバイトが半角文字なのか、
全角文字の1バイト目なのか、2バイト目なのかをそのバイトだけでは判断できません。
このため、途中のあるバイトの文字を区別したい場合でも、文字列の最初から見ていかなければいけません。

さて、その「全角文字の1バイト目」として決まっている範囲は 0x81-0x9F と 0xE0-0xFC の範囲です。
中途半端なこと この上ないのですが、こういうのは関数化しておけばいいですね。
<  1>
<  2>
<  3>
<  4>
int SJISMultiCheck(unsigned char c){
   
if(((c>=0x81)&&(c<=0x9f))||((c>=0xe0)&&(c<=0xfc)))return 1;
   
else return 0;
この関数にある条件式で、あるバイトが日本語第1バイトかどうかを判断します。
ただし、日本語第2バイトでも1が返される可能性があるので、
日本語第2バイトである可能性がある状況では使えません。


それでは、「日本語も含めて、文字列の文字数を数える関数 StringCount 」を作ってみます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
#include <stdio.h>

int SJISMultiCheck(unsigned char c){
   
if(((c>=0x81)&&(c<=0x9f))||((c>=0xe0)&&(c<=0xfc)))return 1;
   
else return 0;
}
int StringCount(const char *str){
   
/*****************************
   strの文字列の文字数を返す関数(日本語対応)
   
   戻り値:strの文字数
   
   const char *str:文字数を数える文字列
   *****************************/
   
int i,cnt=0;
   
for(i=0;str[i]!='\0';){//(1)
      
if(SJISMultiCheck(str[i]))i+=2;
      
else i++;
      cnt++;
   }
   
return cnt;
}
int main(void){
   
char str1[11]="abcde";
   
char str2[11]="あいうえお";
   
char str3[21]="あ1\nい23\nう456";
   
   printf(
"%sの長さ:%d\n\n",str1,StringCount(str1));
   printf(
"%sの長さ:%d\n\n",str2,StringCount(str2));
   printf(
"%sの長さ:%d\n\n",str3,StringCount(str3));
   
//終了待ち
   
getchar();
   
return 0;
実行結果:
abcdeの長さ:5

あいうえおの長さ:5

あ1
い23
う456の長さ:11


今回は以前の StringLength 関数と違い、日本語文字列である「あいうえお」も5になっています。
また、日本語英語混合の「あ1\nい23\nう456」も11(改行文字も1文字として数えます)になっています。

今回の処理は(1)16〜19行のループがメインです。
用意している変数は「文字列の添え字に使う i 」と「検出した文字数を示す cnt 」です。
1ループは1文字分の処理に相当し、17行目で SJISMultiCheck 関数を呼び出して、
今見ている文字が半角なのか、全角なのかを判断しています。

SJISMultiCheck 関数はSJISの全角の1バイト目を示す値を与えると1、つまり真を返します。
そこで、17行の条件式が真になったらその文字を全角の1バイト目と判断し、
2バイトずらして全角の2バイト目が判断に含まれないようにします。
逆に、17行の条件式が偽になったらその文字を半角文字と判断し、1バイトだけずらします。

すなわち、文字列「あ1\nい23\nう456」の場合、↓の図のように位置がずれていきます。
「あ1\nい23\nう456」を与えた場合のループ進行の概要

処理としては今回のような形で、半角文字が出た場合は1バイト単位、
全角文字が出た場合は2バイト単位で処理していけば、基本的に問題はありません。

なお、今回のコードで問題があるケースは文字列が壊れている場合です。
ネットワークアプリだと攻撃として意図的に壊れた文字列を渡されることもあるので、
そういうアプリではもっと厳格に処理する必要があります。

余談ですが、昔「UnicodeのUTF-16を使うと全て2バイト単位でいいから扱いが楽!」という話がありましたが、
今のUTF-16には4バイト文字も定義されているので、結局似たような処理を組み込む必要があります。
(4バイト文字の前2バイトと後2バイトの値は明確に分かれているのでShiftJISより楽と言えば楽ですが・・・)


次回は単語当てゲームLv2の設計の予定です。

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

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

最終更新 2010/01/01