[php] C言語とは直接関係ありませんが,文字列に関する基礎知識です.
본문
文字列とは
コンピュータが扱えるのは0と1のビット列のみです.このビット列をどのように解釈するか決めたものが基本データ型になりますが,基本データ型の中に,文字という概念はあっても,文字列という概念はありません.そのため,この文字という概念を並べることで文字列として扱うことになります.
さて,データ型の中に文字という概念はありますが,これは,1文字1バイトのASCIIコードを扱えるデータ型です.そのため,マルチバイト文字(2バイト以上の文字)である日本語は,この基本型である文字では扱うことができません.では,どのようなビット列のときに文字になるのでしょうか.
「abcあいう123」
ここに半角文字のアルファベット3文字,全角文字のひらがな3文字,半角文字の数字3文字があります.これはいったい何バイトでしょうか?
多くの人は,全角文字のひらがなは2バイト文字だから3+2*3+3で計12バイト必要だ,と判断すると思いますが,それはある意味正しくて,ある意味間違えています.
日本語を表すためのビット列との対応関係(コード)により,必要なバイト数は異なります.そのため,文字列に必要なバイト数を知るためには,まず,文字を表す文字コードを決める必要があります.
文字コード
文字を表現するためには,ビット列と文字の対応付けをする必要があります.その対応関係が文字コードになります.文字コードの詳しい説明は,Wikipediaの文字コードに任せます.
日本語の場合,WindowsやMacintoshではShift_JISコード,Linux(Unix系)ではEUCコード,メールの送受信ではJISコード, JavaではUnicodeを使うというように,簡単にあげただけでも4種類は存在します.これらの文字コードによっては,先ほどの「abcあいう123」は12バイトどころではなく,それ以上のメモリが必要になります.
JIS(ISO-2022-JP)
日本語文字(2バイト文字)とそれ以外の文字(1バイト文字)の間にコードを切り替えるエスケープシーケンスを入れ,コード体系を切り替える方式です.すべてのバイトを0x00-0x7Fの7ビットで表現できる特徴があります.
先ほどの文字列は「61 62 63 1B 24 42 24 22 24 24 24 26 1B 28 42 31 32 33」の18バイトになります.
ここで,4バイト目からの"1B 24 42"が2バイト文字コードに切り替えるシフトイン(SI), 13バイト目からの"1B 28 42"が1バイト文字コードに切り替えるシフトアウト(SO)になります.
これらのSI/SOが間に入るため,Shift_JISコードと比較して必要なメモリは増えてしまいます.
エスケープシーケンス
エスケープコード(1B)で始まる一連のコードを指しますが, JISコードでは,このエスケープシーケンスを利用してコードを切り替えています.
代表的なエスケープシーケンス 対象のコード エスケープシーケンス 続く文字
ASCII 1B 28 42 1バイト
JIS X 0201-Roman 1B 28 4A 1バイト
JIS X 0208-1978 1B 24 40 2バイト
JIS X 0208-1983 1B 24 42 2バイト
JIS X 0212-1990 1B 24 28 44 2バイト
文字数のカウント
すべての文字は7ビットであるため,8ビット目を利用した文字判断は出来ません.次に,文字数カウントのソースを載せておきます.適当に書いただけなのでどこまで正確か分かりません.ただ,流れは分かると思います.
int count_JIS(const unsigned char *string)
{
int len = 0;
int byte = 1;
while(*string){
if(*string == 0x1b){
++string;
if(*string == 0x28){ // 1バイト文字系
++string;
if(*string != 0x42 && *string != 0x4A)
break; // 不明なエスケープシーケンス
++string;
byte = 1;
}else if(*string == 0x24){ // 2バイト文字系
++string;
if(*string != 0x40 && *string != 0x42){
if(*string != 0x28 || *++string != 0x44)
break; // 不明なエスケープシーケンス
}
++string;
byte = 2;
}else
break; // 不明なエスケープシーケンス
}else{
if(*string < 0x1f || *string == 0x7f)
;// 制御文字
else{
if(0x80 < *string)
;// 半角カナ
else
++len;
}
string += byte;
}
}
return len;
}
補足
エスケープシーケンス"1B 28 4A"でJIS X 0201に切り替え,半角カナをサポートする亜種(CP50221)もありますが,本来このシーケンスで指定できるのはJIS X 0201中のラテン文字用図形文字集合だけです.原則として片仮名用図形文字集合(半角カナ)は8ビット目を必要とするため,7ビットコードであるJISでは使えません.
改行コードは制御コード(1バイト文字)であるため,改行の前には"1B 28 42"が必要です.つまり,JISコード中で改行を行うたびに6バイト分のエスケープシーケンスが紛れ込みます.
Shift_JIS
Shift_JISの場合,全角文字は2バイトで表されることになっています.
JISで使われていなかったビット列の部分に文字をシフトしてきたコードです. JISコードのように,エスケープシーケンスは必要ありませんが, 1バイト目が0x81-0x9F,0xE0-0xFC,2バイト目が0x40-0x7E,0x80-0xFCの範囲しか使えないため,表現できる文字は,11000文字程度になります.
先ほどの文字列をShift_JISで表現すると「61 62 63 82 A0 82 A2 82 A4 31 32 33」の12バイトになります.
文字数のカウント
1バイトずつ読み込み,0x81-0x9F,0xE0-0xFCの範囲の文字があるかチェックする必要があります.次に,文字数カウントのソースを載せておきます.適当に書いただけなのでどこまで正確か分かりません.ただ,流れは分かると思います.
int count_Shift_JIS(const unsigned char *string)
{
int len = 0;
while(*string){
if(*string < 0x1f || *string == 0x7f){
;// 制御文字
}else if((0x81 <= *string && *string <= 0x9F) || (0xE0 <= *string && *string <= 0xFC)){
// 2バイト文字
++string;
if((0x40 <= *string && *string <= 0x7E) || (0x80 <= *string && *string <= 0xFC))
++len;
else
break; // 不明な文字
}else{
// 1バイト文字
if(0x80 < *string)
;// 半角カナ
++len;
}
++string;
}
return len;
}
補足
Shift_JISの場合,一部のプログラム(perlなど)でダメ文字問題が発生することがあります.これは,2バイト文字の2バイト目の部分に0x5Cが使われていることに原因があります.
Shift_JISでは,2バイト目に0x40-0x7Eと0x80-0xFCが許可されています.しかし,この中の0x5Cはいわゆるエスケープ文字(\)を表しています.このエスケープ文字は次のバイトと組み合わせて解釈される文字です(2バイトを1バイトとして解釈するわけです).そのため,本来文字として正しいはずの1バイト目と2バイト目の組み合わせがずれてしまい, 1バイト目と次の文字の1バイト目(本来の2バイト目0x5Cが消えてしまう),次の文字の2バイト目とさらに次の1バイト目と順にずれてしまいます.
(本来,プログラム側が文字コードを認識して動作すべきだとは思いますが,)このような文字が来る場合,0x5C 0x5Cの組み合わせは0x5Cとして解釈されることを利用しあらかじめ0x5Cを追加しておくことが必要になります.
例えば,"予定表"の場合,1文字目と3文字目はダメ文字です.そのため,"予\定表\"のようにダメ文字の後に問題のエスケープ文字(0x5C)を追加することで正しく表示することができます.
ダメ文字リスト:―ソЫⅨ噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭偆砡
良く使われてしまうダメ文字は"ソ,十,能,表,予"あたりでしょうか.
EUC-JP
原則として日本語を2バイトとして表現した方法ですが,亜種として一部の拡張文字を3バイトで表現するコードもあります.日本語文字は,1バイト目,2バイト目共に0x80-0xFFの範囲にあることが特徴です.
3バイト必要とするのは,第4水準文字であり,通常使われることはないと思います.先ほどの文字列をEUCで表現すると「61 62 63 A4 A2 A4 A4 A4 A6 31 32 33」の12バイトになります.
文字数のカウント
1バイトずつ読み込み,0x80-0xFFの範囲の文字があるかチェックする必要があります.次に,文字数カウントのソースを載せておきます.適当に書いただけなのでどこまで正確か分かりません.ただ,流れは分かると思います.
int count_EUC(const unsigned char *string)
{
int len = 0;
while(*string){
if(*string < 0x1f || *string == 0x7f){
;// 制御コード
}else if(*string == 0x8E){
if(0xA1 <= *++string && *string <= 0xFE)
// 半角カナ
++len;
else
break; // 不明な文字
}else if(*string == 0x8F){
// 3バイト文字
if((0xA1 <= *++string && *string <= 0xFE) && (0xA1 <= *++string && *string <= 0xFE))
++len;
else
break; // 不明な文字
}else if(0xA1 <= *string && *string <= 0xFE){
// 2バイト文字
if(0xA1 <= *++string && *string <= 0xFE)
++len;
else
break; // 不明な文字
}else{
// 1バイト文字
++len;
}
++string;
}
return len;
}
Unicode(UTF-8)
ASCII文字は1バイト,それ以外の文字を2-6バイトで表現した方法です.日本語文字は大半が3バイトで表現されます.先ほどの文字列をUTF-8で表現すると「61 62 63 E3 81 82 E3 81 84 E3 81 86 31 32 33」の15バイトになります.
文字数のカウント
1バイト目を読めば何バイトの文字か分かるため非常にカウントが楽です.
1バイト目が0xxxxxxxの場合,1バイト文字でASCIIコードと同じです.
10xxxxxxの場合,他のマルチバイト文字の続き文字になります.
110xxxxxの場合,2バイト文字の先頭になります.
以下同様に,1110xxxxの場合は3バイト文字,11110xxxの場合は4バイト文字, 111110xxの場合は5バイト文字1111110xの場合は6バイト文字の先頭になりますが,実際には4バイト文字までしか定義されていないため,0xf8以上の文字が出た場合,エラーにしてしまっても構わないかもしれません.
一応定義上は存在するようです.
int count_UTF8(const unsigned char *string)
{
int len = 0;
while(*string){
if(*string < 0x1f || *string == 0x7f){
// 制御コード
}else if(*string <= 0x7f){
++len; // 1バイト文字
}else if(*string <= 0xbf){
; // 文字の続き
}else if(*string <= 0xdf){
++len; // 2バイト文字
}else if(*string <= 0xef){
++len; // 3バイト文字
}else if(*string <= 0xf7){
++len; // 4バイト文字
}else if(*string <= 0xfb){
++len; // 5バイト文字
}else if(*string <= 0xfd){
++len; // 6バイト文字
}else{
; // 使われていない範囲
}
++string;
}
return len;
}
관련링크
댓글목록 0
등록된 댓글이 없습니다.