今回は文字種別の判別と変換のためのヘッダである<ctype.h>についてお話します。<ctype.h>で宣言される関数は、"C"ロケールに限定すれば非常に単純なものばかりです。今回は、とりあえずは"C"ロケールにしか対応しませんが、他のロケールへの拡張性も視野に入れていますので、ちょっと複雑になります。
まずはis〜系関数の実装方法について考えてみましょう。実装方法はいくつか考えられますが、ロケールの切り替えが簡単なことや、実行効率のことを考えて、表引きを採用したいと思います。この方法は、多くの標準Cライブラリで採用されている最も一般的な方法です。
表引きは、is〜系関数の引数が取りうる値ごとに、文字種別を表すビットパターンの配列を参照することで行います。文字種別を表すビットパターンは、下記のように、
#define _CNTRL 0x01 #define _LOWER 0x02 #define _UPPER 0x04 #define _DIGIT 0x08 #define _PUNCT 0x10 #define _SPACE 0x20 #define _BLANK 0x40 #define _XDIGIT 0x80
といった、文字種別の基本要素ごとにビットを割り当ててやれば、8ビットで済みます。すなわち、unsigned char型の配列にできるので、非常に効率のよい実装が可能になります。ここで、_BLANKは空白 ' ' を表すものであって、isblank関数が真を返すものではありません。
表引き用の配列は、添え字として-1〜255を使えるようにしなければなりません。-1はEOFに、255はUCHAR_MAXに相当します。このため、257要素の配列を作成して、参照する時点で1ずらすようにします。
const unsigned char __ctype_C[257] = { ... }; const unsigned char *__ctype = __ctype_C + 1;
char型が符号付きの場合、負の値をそのまま配列の添え字にすると問題が発生します。しかし、is〜系関数の引数は、EOFまたは0〜UCHAR_MAXでなければならず、それ以外の値を与えると規格上も未定義なので、今回は放置します。これは、安全対策のために余計なことをすると、オーバーヘッドが非常に大きいためです。
ヘッダ側では、配列およびいったんそれを受けるポインタ変数を以下のように宣言します。
#ifdef __cplusplus extern "C" { #endif extern const unsigned char __ctype_C[]; extern const unsigned char *__ctype; #ifdef __cplusplus } #endif
#ifdef __ONLY_C_LOCALE #define __ctype (__ctype_C+1) #endif
このようにしておくことで、is〜系関数をインライン関数として実装することも簡単になります。
<ctype.h>には、is〜系関数のほかにto〜系関数も含まれています。通常、to〜系関数は、
int tolower(int c) { return isupper(c) ? c - 'A' + 'a' : c; }
のように実装されるのですが、この方法には問題があります。英文字の連続性については、文字コードを特定することで回避できますが、"C"ロケール以外をにらんだ場合、例えばドイツ語のß(エスツェット)は小文字しかないため、対応する大文字が存在しません。日本語の半角カナも類似の問題があります。
そこで、to〜関数も表引きの手法をとることにします。to〜系関数はEOFを受け入れる必要はないのですが、念のためis〜系関数と同様に、-1〜255までの257要素の配列にしたいと思います。
const unsigned char __tolower_C[257] = { ... }; const unsigned char *__tolower = __tolower_C + 1;
ヘッダ側は、 #ifdef __cplusplus extern "C" { #endif extern const unsigned char __tolower_C[]; extern const unsigned char *__tolower; #ifdef __cplusplus } #endif
__ONLY_C_LOCALEが定義されている場合には、表引きではなく、先述したように、文字判別を行った上で、計算による変換を行う方がよいでしょう。また、上記ではtolower関数だけを取り上げましたが、toupper関数についても同様です。
<ctype.h>で宣言される各関数の詳細については、次回以降にお話します。
▽続きを読む▽
<ctype.h>も、例によってC++対応するために、いったん<cctype>を作ってから<ctype.h>でそれをインクルードすることになります。 なお、GCCでは、関数以外はC結合もC++結合も区別しないようですが、外部変数である__ctype_Cや__ctypeなども、extern "C"で括っておいた方が無難です。
|