さて、個々のメッセージに名前がついていて、この名前はプログラムの多数の個所から参照され ています。ですから、この名前を変えてしまうことは相当危険を伴うので、メッセージと名前の対 応はそのまま活かすことにしてみます。もちろん、安全のためです。
表示位置データを使うとき、いつもこの値( W3XOFFSET,W3YOFFSET)を引かなければなりません。 ご覧いただいているリストでは、下駄を脱がせている個所は3つだけですが、プログラム全体では いっぱい出てきます。
データとして、初めから欲しい値が入っていれば、いちいち下駄を脱がせる手間はいりません。 こんな手間がかかるようにしていると、下駄を脱がせ忘れて「バグ」の原因になりやすいです。
データは、必ず「本当」のデータをそのまま入れるように心がけるべきです。本当のデータに何 らかの加工をしてしまうには、相当重大な理由がなければなりません。
char str[] = "\0\1ABC"; printf( "%d\n", sizeof( str ) ); printf( "%d\n", strlen( str ) );はどうなるでしょう。「文字列としての長さ」は strlen(str)で、文字列の最初に'\0'が入ってい るので、0になります。しかし、sizeof(str)は6 になります。これは、文字列中に'\0'が含まれ ていても、文字列データとして確保する領域が終るわけではありません。この文字列では、\で始 まる文字コードが2文字、その後に普通の文字が3文字あり、最後に文字列の終了を示す'\0'が付加 されるので、全体で6バイトの文字列になります。もちろん、sizeof(str)とsizeof("\0\1ABC")は 同じ値になります。
重要なことなので、もういちど繰り返します。2重引用符(″)で囲まれた文字列リテラルの中に は、任意の文字コードが書けます。'\0'が途中にいくつ入ってもだいじょうぶです。文字列リテラ ルは、さらに最後に'\0'が付加されてメモリ上に確保されます。
文字列として扱うとき、たとえばstrで始まる文字列操作関数などでは、'\0'を文字列の終りと 判定するので、それより後ろのデータは存在しても無視されます。
というわけで、″ でくくった文字列中には、任意の文字コードを入れることができ、表示位置デー タをそのまま入れてだいじょうぶです。
メッセージデータの先頭2バイト分を修正したヘッダーファイルが、リスト7−4です。リスト 7−1では、M720とM730の先頭2バイトが抜けていましたが、正しい値がわからないので、適当な 値(0)をセットしておきました。
リスト7−4 ヘッダファイル(漢字メッセージの定義)【修正版】 |
1 /********************************************************************************/ 2 /* */ 3 /* コマンドメッセージの定義 */ 4 /* */ 5 /********************************************************************************/ 6 #ifndef _KANJI_H 7 #define _KANJI_H 8 9 /*** 変数名,x,y,メッセージ ***/ 10 11 typedef struct { 12 unsigned char x, y; 13 char text[1]; 14 } message; 15 16 #define M140 "\d034\d017■MOD" 17 #define M150 "\d003\d017■中止" 18 #define M160 "\d010\d017■実行" 19 #define M170 "\d017\d017■印字" 20 #define M180 "\d024\d017■次へ" 21 #define M190 "\d003\d017■中断" 22 #define M192 "\d031\d017■設定" 23 #define M194 "\d031\d017■全削除" 24 25 #define M200 "\d000\d000子局名選択" 26 #define M210 "\d000\d000制御項目選択" 27 #define M220 "\d000\d000受信中" 28 #define M222 "\d000\d000受信できませんでした。" 29 #define M224 "\d000\d000ステーション・エラーが生じました。" 30 #define M226 "\d000\d000ライン・エラーが生じました。" 31 32 #define M692 "\d005\d002運用開始" 33 #define M694 "\d005\d003運用終了" 34 35 #define M720 "\d000\d000運用時間" 36 #define M730 "\d000\d000運用時間設定" 37 38 #define M900 "\d025\d002●0" 39 #define M910 "\d025\d003●1" 40 #define M920 "\d025\d004●2" 41 #define M930 "\d025\d005●3" 42 #define M940 "\d025\d006●4" 43 #define M950 "\d025\d007●5" 44 #define M960 "\d025\d008●6" 45 #define M970 "\d025\d009●7" 46 #define M980 "\d025\d010●8" 47 #define M990 "\d025\d011●9" 48 49 #endif _KANJI_H 50 51 /********************************************************************************/ 52 /* End of File "kanji.h" */ 53 /********************************************************************************/ |
ところで、余談ですが、
char *str = "\0\1ABC";となったときには、sizeof(str) と strlen(str) の値はどうなるでしょうか。str[] と *str は 同じはずだから、値は同じでしょうか。str[]は配列で、宣言の場所により、静的データエリアで あったり、スタック上であったりしますが、とにかくメモリ上に文字列リテラルのサイズ分の領域 が確保されます。しかし、*str はポインタなので、sizeof(str)は文字列リテラルのサイズとは無 関係にポインタのバイト数の2とか4になります。
typedef struct { unsigned char x, y; char text[1]; } message;を用意します。
メッセージ表示関数は、DispMsg( M150 ); のように呼び出します。M150は、もちろんマクロ定 義されていて、実際は文字列です。
さて、リスト7−5の関数DispMsgを見てください。この引数は、message型へのポインタmesに なっています。mesには、当然、文字列M150のアドレスが入っています。しかし、関数DispMsgでは、 message型へのポインタとみなし、mex->xで先頭の 1バイトに入っているx座標値を、mex->yで次の 1バイトに入っているy座標値を参照しています。
メッセージの文字列は、mes->textで参照しています。message型の文字配列textのサイズは1で すが、mes->textで文字列の先頭アドレスを示しているだけです。この関係を図7−1に示します。
|
定義 ←───→ 参照 char[] message +----------+ 0 | 43 | x +- -+ 1 | 17 | y +- -+ 2 | +--+ | text[0] +- | | -+ 3 | +--+ | 1 +- -+ 4 | | 2 +- 中 -+ 5 | | 3 +- -+ 6 | | 4 +- 止 -+ 7 | | 5 +- -+ 8 | 0 | 6 +----------+ |
リスト7−5 漢字メッセージの表示関数【修正版】 |
1 /********************************************************************************/ 2 /* */ 3 /* コマンド表示 */ 4 /* */ 5 /********************************************************************************/ 6 7 #include "mydefs.h" 8 9 /*******************************************************************************/ 10 /* ライトペン入力のためのメッセージ表示 */ 11 /* DispMsg 1メッセージのみの表示 */ 12 /* DispMsgs 複数メッセージ表示 */ 13 /*******************************************************************************/ 14 15 DispMsg( mes ) 16 message *mes; 17 { 18 locate( stderr, mes->x, mes->y ); 19 fprintf( wpath, "%s", mes->text ); 20 fflush( wpath ); 21 } 22 23 DispMsgs( n, messages ) 24 int n; 25 message **messages; 26 { 27 while( n-- > 0 ) { 28 DispMsg( *messages++ ); 29 } 30 } 31 32 /********************************************************************************/ 33 /* 標準データ表示(受信中、中止、印字、次へ) */ 34 /********************************************************************************/ 35 StandDsp(timeup) 36 int timeup; /* time until time up */ 37 { 38 static char *messages[] = { M150, M170, M180 }; 39 int d3_cnt; /* d3_ data count */ 40 int d3_offset; /* d3_ structure offset */ 41 int getd3data; /* get d3 data count */ 42 43 cls2(); 44 DispMsg( M220 ); 45 46 /* 応答データの取得 */ 47 switch( Receive(timeup) ) { 48 case TIMEUP: 49 cls2(); 50 DispMsg( M222 ); 51 tsleep(3 * TPS); 52 return(RETURN); 53 case STD_ERROR: 54 switch(TdRpt.tr_lineno.str2[1]){ 55 case CE_STATERR: 56 cls2(); 57 DispMsg( M224 ); 58 break; 59 case CE_LINEERR: 60 cls2(); 61 DispMsg( M226 ); 62 break; 63 default: 64 break; 65 } 66 tsleep(3 * TPS); 67 return(RETURN); 68 } 69 70 /* 表示 */ 71 d3_cnt = d3expand(d3_count); 72 d3_offset = 0; 73 74 while( d3_cnt > 0 ) { 75 cls2(); 76 locate( stderr, DSPLINEX, DSPLINEY ); 77 getd3data = d3disp(wpath, d3_offset, d3_cnt, OFF); 78 79 DispMsgs( Number(messages), messages ); 80 81 switch( ReadLPen( Number(messages), messages, 60 * 1) ) { 82 case 1: /* 中止 */ 83 return(RETURN); 84 case 2: /* 印字 */ 85 if(PaperChk() == ON){ 86 PaperChkPutc(CR); 87 d3disp(ppath, d3_offset, d3_cnt, ON); 88 PaperChkPutc(CR); 89 } 90 break; 91 case 3: /* 次へ */ 92 case TIMEUP: 93 break; 94 } 95 d3_offset += getd3data; 96 d3_cnt -= getd3data; 97 } 98 99 return(CONTINUE); 100 } 101 102 /********************************************************************************/ 103 /* プリンタチェック後に1文字印字 */ 104 /********************************************************************************/ 105 PaperChkPutc( c ) 106 char c; 107 { 108 if( PaperChk() ) 109 putc( c, ppath ); 110 } 111 112 /********************************************************************************/ 113 /* End of File "cmdmake.c" */ 114 /********************************************************************************/ |
文字配列textのサイズは不用なので、
char text[];と指定したいところですが、これでは構造体のサイズが不定になり、コンパイルエラーがでます。 それを避けるために、[1]としているだけで、[2] でも、[100]でも、実際には何でもかまいません。
元の文字列データの変更は面倒なので、下駄だけとり除きました。こうすると、可能な限り少な い変更で、カーソル位置とメッセージ文字列が構造体のメンバーとして取り扱えるようになります。
本来は違うものを引数で渡し、関数の側で、都合の良い型とみなして処理すると、非常に便利に なることがあります。今回のは、ちょっと「せこい」とは思いますが、すっきりします。
メッセージデータ配列は、実行の途中では変更されないので、関数内部にstatic配列として確保 し、初期値式として ={}の中にメッセージデータのアドレスを並べます。もともとメッセージデー タはマクロ定義された文字列なので、マクロ名を並べるだけです。これで冗長なコーディングを短 く、すっきりできます。
それに、メッセージデータの配列は固定したものですから、関数の途中で代入するのは不自然で す。初期化によってデータを設定し、実行中に変化しないことを示す方が良いでしょう。
ここで作った配列を使ってメッセージ表示を行なう関数がDispMsgsです。この関数は、
DispMsgs( n, messages ) int n; message **messages;なので、メッセージデータへのポインタの配列が渡されたものとみなします。この関数は、元の DispMsgと同じ機能です。
もっとエレガントなDispMsgsの仕様は、メッセージ個数の n をなくしてしまうことです。その ためには、メッセージの最後を示すための NULLポインタを最後のメッセージの次に入れておく必 要があります。つまり、
static char *mes_coms[] = { M150, M170, M180, NULL };のようにするのです。このためのDispMsgsの変更は読者への課題としましょう。
また、ANSIのCでは文字列リテラルを並べる(間に , を入れない)と、全体で1つの文字列リ テラルになるので、それを活かすと、位置データとメッセージ自体を分離できます。つまり、
#define M150 "\d003\d017■中止"を
#define M150 "\d003" "\d017" "■中止"と書けます。さらにマクロを使えば、
#define POS( x,y ) #x #y #define M150 POS(\d003,\d017) "■中止"とも書けます。本当は、POS(3,17)と書きたいのですが、どうもそれは無理のようです。
でも、文字列として細工をするのは、この辺りが限界でしょうか。理想は、始めから構造体デー タにしてしまうことでしょう。