今回の珠玉の力作をリスト9−8に示します。じっくり鑑賞してください。すばらしい作品です ので、以下の説明を読む前に、ぜひじっくりと味わってください。
この関数は、XViewのノーティファイ関数と呼ばれるものです。ここで示すものは、ウィンド内 部に並べた項目のうち、キーボードからの入力文字を受け付ける「テキスト項目」に対するノーティ ファイ関数です。何かキーが押されると直ちに呼び出されます。押した文字が画面に表示(エコー バック)される前に呼ばれるので、入力されたキーを無視したり、キーに応じて何か特殊な処理を させることができます。
第1引数のitemは、このノーティファイ関数を呼び出すことになったパネル項目(この場合はテ キスト項目)を示します。このプログラムでは、テキスト項目が多数あり、そのいずれであるかを itemで識別できます。
第2引数のpeventは、この関数を呼び出す原因になったイベント(出来事)構造体へのポインタ です。これにより、より詳しい原因を調べ、適切な処置を取ることができます。
リスト9−8 オリジナルプログラム 珠玉の力作 |
1 Panel_setting TextCheck(item, pevent) 2 Panel_item item; /*アイテムのハンドル*/ 3 Event *pevent; /*イベントのポインタ*/ 4 { 5 unsigned char input_character; /*入力された文字*/ 6 7 input_character = (unsigned char)event_action(pevent); 8 9 if (item == window_tbl.panel.text[0].handle) 10 { 11 if ((input_character >= 0x00) && 12 (input_character <= 0x1F)) 13 { 14 return((Panel_setting)panel_text_notify(item, pevent)); 15 } 16 else if ((input_character >= 0x20) && 17 (input_character <= 0x7E)) 18 { 19 if ((input_character >= 0x30) && 20 (input_character <= 0x39)) 21 { 22 return((Panel_setting)panel_text_notify(item, pevent)); 23 } 24 } 25 else if ((input_character >= 0x7F) && 26 (input_character <= 0xFF)) 27 { 28 return((Panel_setting)panel_text_notify(item, pevent)); 29 } 30 } 31 else if (item == window_tbl.panel.text[1].handle) 32 { 33 if ((input_character >= 0x00) && 34 (input_character <= 0x1F)) 35 { 36 return((Panel_setting)panel_text_notify(item, pevent)); 37 } 38 else if ((input_character >= 0x20) && 39 (input_character <= 0x7E)) 40 { 41 if ((input_character >= 0x30) && 42 (input_character <= 0x39)) 43 { 44 return((Panel_setting)panel_text_notify(item, pevent)); 45 } 46 } 47 else if ((input_character >= 0x7F) && 48 (input_character <= 0xFF)) 49 { 50 return((Panel_setting)panel_text_notify(item, pevent)); 51 } 52 } 53 else if (item == window_tbl.panel.text[2].handle) 54 { 55 if ((input_character >= 0x00) && 56 (input_character <= 0x1F)) 57 { 58 return((Panel_setting)panel_text_notify(item, pevent)); 59 } 60 else if ((input_character >= 0x20) && 61 (input_character <= 0x7E)) 62 { 63 if ((input_character >= 0x30) && 64 (input_character <= 0x39)) 65 { 66 return((Panel_setting)panel_text_notify(item, pevent)); 67 } 68 } 69 else if ((input_character >= 0x7F) && 70 (input_character <= 0xFF)) 71 { 72 return((Panel_setting)panel_text_notify(item, pevent)); 73 } 74 } 75 else if (item == window_tbl.panel.text[3].handle) 76 { 77 if ((input_character >= 0x00) && 78 (input_character <= 0x1F)) 79 { 80 return((Panel_setting)panel_text_notify(item, pevent)); 81 } 82 else if ((input_character >= 0x20) && 83 (input_character <= 0x7E)) 84 { 85 if ((input_character >= 0x30) && 86 (input_character <= 0x39)) 87 { 88 return((Panel_setting)panel_text_notify(item, pevent)); 89 } 90 } 91 else if ((input_character >= 0x7F) && 92 (input_character <= 0xFF)) 93 { 94 return((Panel_setting)panel_text_notify(item, pevent)); 95 } 96 } 97 else if (item == window_tbl.panel.text[4].handle) 98 { 99 if ((input_character >= 0x00) && 100 (input_character <= 0x1F)) 101 { 102 return((Panel_setting)panel_text_notify(item, pevent)); 103 } 104 else if ((input_character >= 0x20) && 105 (input_character <= 0x7E)) 106 { 107 if ((input_character >= 0x30) && 108 (input_character <= 0x39)) 109 { 110 return((Panel_setting)panel_text_notify(item, pevent)); 111 } 112 } 113 else if ((input_character >= 0x7F) && 114 (input_character <= 0xFF)) 115 { 116 return((Panel_setting)panel_text_notify(item, pevent)); 117 } 118 } 119 else if (item == window_tbl.panel.text[5].handle) 120 { 121 if ((input_character >= 0x00) && 122 (input_character <= 0x1F)) 123 { 124 return((Panel_setting)panel_text_notify(item, pevent)); 125 } 126 else if ((input_character >= 0x20) && 127 (input_character <= 0x7E)) 128 { 129 if ((input_character >= 0x30) && 130 (input_character <= 0x39)) 131 { 132 return((Panel_setting)panel_text_notify(item, pevent)); 133 } 134 } 135 else if ((input_character >= 0x7F) && 136 (input_character <= 0xFF)) 137 { 138 return((Panel_setting)panel_text_notify(item, pevent)); 139 } 140 } 141 else if (item == window_tbl.panel.text[6].handle) 142 { 143 if ((input_character >= 0x00) && 144 (input_character <= 0x1F)) 145 { 146 return((Panel_setting)panel_text_notify(item, pevent)); 147 } 148 else if ((input_character >= 0x20) && 149 (input_character <= 0x7E)) 150 { 151 if ((input_character >= 0x30) && 152 (input_character <= 0x39)) 153 { 154 return((Panel_setting)panel_text_notify(item, pevent)); 155 } 156 } 157 else if ((input_character >= 0x7F) && 158 (input_character <= 0xFF)) 159 { 160 return((Panel_setting)panel_text_notify(item, pevent)); 161 } 162 } 163 else if (item == window_tbl.panel.text[7].handle) 164 { 165 if ((input_character >= 0x00) && 166 (input_character <= 0x1F)) 167 { 168 return((Panel_setting)panel_text_notify(item, pevent)); 169 } 170 else if ((input_character >= 0x20) && 171 (input_character <= 0x7E)) 172 { 173 if ((input_character >= 0x30) && 174 (input_character <= 0x39)) 175 { 176 return((Panel_setting)panel_text_notify(item, pevent)); 177 } 178 } 179 else if ((input_character >= 0x7F) && 180 (input_character <= 0xFF)) 181 { 182 return((Panel_setting)panel_text_notify(item, pevent)); 183 } 184 } 185 else if (item == window_tbl.panel.text[8].handle) 186 { 187 if ((input_character >= 0x00) && 188 (input_character <= 0x1F)) 189 { 190 return((Panel_setting)panel_text_notify(item, pevent)); 191 } 192 else if ((input_character >= 0x20) && 193 (input_character <= 0x7E)) 194 { 195 if ((input_character >= 0x30) && 196 (input_character <= 0x39)) 197 { 198 return((Panel_setting)panel_text_notify(item, pevent)); 199 } 200 } 201 else if ((input_character >= 0x7F) && 202 (input_character <= 0xFF)) 203 { 204 return((Panel_setting)panel_text_notify(item, pevent)); 205 } 206 } 207 else if (item == window_tbl.panel.text[9].handle) 208 { 209 if ((input_character >= 0x00) && 210 (input_character <= 0x1F)) 211 { 212 return((Panel_setting)panel_text_notify(item, pevent)); 213 } 214 else if ((input_character >= 0x20) && 215 (input_character <= 0x7E)) 216 { 217 if ((input_character >= 0x30) && 218 (input_character <= 0x39)) 219 { 220 return((Panel_setting)panel_text_notify(item, pevent)); 221 } 222 } 223 else if ((input_character >= 0x7F) && 224 (input_character <= 0xFF)) 225 { 226 return((Panel_setting)panel_text_notify(item, pevent)); 227 } 228 } 229 else if (item == window_tbl.panel.text[10].handle) 230 { 231 if ((input_character >= 0x00) && 232 (input_character <= 0x1F)) 233 { 234 return((Panel_setting)panel_text_notify(item, pevent)); 235 } 236 else if ((input_character >= 0x20) && 237 (input_character <= 0x7E)) 238 { 239 if ((input_character >= 0x30) && 240 (input_character <= 0x39)) 241 { 242 return((Panel_setting)panel_text_notify(item, pevent)); 243 } 244 } 245 else if ((input_character >= 0x7F) && 246 (input_character <= 0xFF)) 247 { 248 return((Panel_setting)panel_text_notify(item, pevent)); 249 } 250 } 251 else if (item == window_tbl.panel.text[11].handle) 252 { 253 if ((input_character >= 0x00) && 254 (input_character <= 0x1F)) 255 { 256 return((Panel_setting)panel_text_notify(item, pevent)); 257 } 258 else if ((input_character >= 0x20) && 259 (input_character <= 0x7E)) 260 { 261 if ((input_character >= 0x30) && 262 (input_character <= 0x39)) 263 { 264 return((Panel_setting)panel_text_notify(item, pevent)); 265 } 266 } 267 else if ((input_character >= 0x7F) && 268 (input_character <= 0xFF)) 269 { 270 return((Panel_setting)panel_text_notify(item, pevent)); 271 } 272 } 273 else if (item == window_tbl.panel.text[12].handle) 274 { 275 if ((input_character >= 0x00) && 276 (input_character <= 0x1F)) 277 { 278 return((Panel_setting)panel_text_notify(item, pevent)); 279 } 280 else if ((input_character >= 0x20) && 281 (input_character <= 0x7E)) 282 { 283 if ((input_character >= 0x30) && 284 (input_character <= 0x39)) 285 { 286 return((Panel_setting)panel_text_notify(item, pevent)); 287 } 288 } 289 else if ((input_character >= 0x7F) && 290 (input_character <= 0xFF)) 291 { 292 return((Panel_setting)panel_text_notify(item, pevent)); 293 } 294 } 295 else if (item == window_tbl.panel.text[13].handle) 296 { 297 if ((input_character >= 0x00) && 298 (input_character <= 0x1F)) 299 { 300 return((Panel_setting)panel_text_notify(item, pevent)); 301 } 302 else if ((input_character >= 0x20) && 303 (input_character <= 0x7E)) 304 { 305 if ((input_character >= 0x30) && 306 (input_character <= 0x39)) 307 { 308 return((Panel_setting)panel_text_notify(item, pevent)); 309 } 310 } 311 else if ((input_character >= 0x7F) && 312 (input_character <= 0xFF)) 313 { 314 return((Panel_setting)panel_text_notify(item, pevent)); 315 } 316 } 317 else if (item == window_tbl.panel.text[14].handle) 318 { 319 if ((input_character >= 0x00) && 320 (input_character <= 0x1F)) 321 { 322 return((Panel_setting)panel_text_notify(item, pevent)); 323 } 324 else if ((input_character >= 0x20) && 325 (input_character <= 0x7E)) 326 { 327 if ((input_character >= 0x30) && 328 (input_character <= 0x39)) 329 { 330 return((Panel_setting)panel_text_notify(item, pevent)); 331 } 332 } 333 else if ((input_character >= 0x7F) && 334 (input_character <= 0xFF)) 335 { 336 return((Panel_setting)panel_text_notify(item, pevent)); 337 } 338 } 339 else if (item == window_tbl.panel.text[15].handle) 340 { 341 if ((input_character >= 0x00) && 342 (input_character <= 0x1F)) 343 { 344 return((Panel_setting)panel_text_notify(item, pevent)); 345 } 346 else if ((input_character >= 0x20) && 347 (input_character <= 0x7E)) 348 { 349 if ((input_character >= 0x30) && 350 (input_character <= 0x39)) 351 { 352 return((Panel_setting)panel_text_notify(item, pevent)); 353 } 354 } 355 else if ((input_character >= 0x7F) && 356 (input_character <= 0xFF)) 357 { 358 return((Panel_setting)panel_text_notify(item, pevent)); 359 } 360 } 361 362 return(PANEL_NONE); 363 } |
リスト9−8は、とっても美しいパターンの繰り返しが見えます。診断室用に適当なプログラム はないかとソースファイルを次々と画面で流し読みしているとき、「オーッ、何だこりゃ!?画面は ちゃんとスクロールしているのかな」ということで発見しました。
とても整然としています。9,31,53...,339行のif文中の配列の添字が0,1,2...,15と変化してい る以外同じに見えるでしょう。もしかして、途中のどこかでよく似ているけれども、わずかな相違 があると思いませんか。添字だけの相違なら、for文でループしてしまうのが常識ですね。
見ただけでは添え字以外の相違が無いようですが、見ただけでそう判断するのは危険です。それ で、UNIXのdiffコマンドを使い、ちゃんと確認しました。すると発見できました。空白、タブも含 めて添え字以外は「完全一致」していたのです。ここまで一致しているのですから、きっとエディ タのコピーをいっぱい活用して作ったのだと思います。まさかここまで一致したものをキー入力す るとは思えませんよね。
同じことの繰り返しは当然forループです。この関数は、for文を使うと一気に圧縮できる典型的 な例です。というより、エディタでコピーを何度もやるときに、普通ならfor文にしようと気づく ものです。16回の繰り返しをforに変えたら、リスト9−9になりました。オリジナルでは入力文 字を変数input_characterに代入してから判定していますが、短い変数名cで何も迷うことはないの で変更しました。
リスト9−9 修正版(その1)繰り返しをforループに変更 |
1 Panel_setting TextCheck(item, pevent) 2 Panel_item item; /* アイテムのハンドル */ 3 Event *pevent; /* イベントのポインタ */ 4 { 5 unsigned char c; 6 int i; 7 8 c = (unsigned char)event_action( pevent ); 9 10 for( i=0 ; i<=15 ; ++i ) { 11 if( item == window_tbl.panel.text[i].handle ) { 12 if( (c >= 0x00) && (c <= 0x1F) ) { 13 return (Panel_setting)panel_text_notify( item, pevent ); 14 } else if( (c >= 0x20) && (c <= 0x7E) ) { 15 if( (c >= 0x30) && (c <= 0x39) ) { 16 return (Panel_setting)panel_text_notify( item, pevent ); 17 } 18 } else if( (c >= 0x7F) && (c <= 0xFF) ) { 19 return (Panel_setting)panel_text_notify(item, pevent); 20 } 21 break; 22 } 23 } 24 25 return PANEL_NONE; 26 } |
こんなにプログラムを短くできる例はめったに発見できるものではありません。これだけ圧縮で きると、「やった!」という気分になります。
リスト9−9をさらに調べてみましょう。for文の中の文字の大小比較は何をしようとしている のでしょうか。12〜20行のifで、文字型変数cの値により処理を分けています。return文が3つあ りますが、returnに書かれた関数(式)は引数も含めて一致しています。panel_text_notify関数 を実行し、その戻り値をノーティファイ関数の戻り値とすると、キー入力された文字が有効になり、 テキスト項目に表示されます。では、cがどのようなの値のときにこれらのreturn文が実行され、 キー入力が有効になるのでしょうか。
cの値により、以下のように処理が分かれます。
a) 0x00 〜 0x1F return b) 0x20 〜 0x2F break c) 0x30 〜 0x39 return d) 0x3A 〜 0x7E break e) 0x7F 〜 0xFF return
cはunsigned char型ですから、これですべての場合が尽くされています。上の範囲は16進数で書
かれているので分かりづらいのですが、c) は'0'〜'9'の10進数の文字を示しています。文字のテ
ストを不等号でやっていますが、C言語には文字クラスをテストをする関数がセットで用意されて
います。詳しくは、ヘッダーファイル
b),c),d)の3つ合わせると0x20〜0x7Eになります。これは空白を含む印字可能文字のことで、こ
の判定は、isprint(c) でできます。
したがって、b)かd)になる文字のとき、21行のbreak文でforループを抜け、return PANEL_NONE;
を実行します。テキスト項目のノーティファイ関数の戻り値がPANEL_NONEであると、入力したキー
入力は無視されます。ということは、印字できる文字のうち、数字以外は無視されるということで
す。
a)は制御文字なので、このテキスト項目は制御文字を受け付けるのです。e)の0x7FはDELコード
であり、これも制御文字です。0x80〜0xFFは最上位ビットが立っていて、漢字コードか何かで、こ
れも有効になります。
以上のことから、このノーティファイ関数は、印字可能文字のうち、数字だけを有効にし、他を
無効にするものです。文字比較を、不等号から文字クラステスト関数に変更したものがリスト9−
10です。
文字の型はunsigned charからcharに変えました。forと外側のifもなくなりました。ノーティファ
イ関数なので、いずれかのパネルアイテムと一致するに決まっているので、判定そのものが無意味
です。
すっかり短くなりましたね。圧縮効果にご満足いただけましたでしょうか。
もう圧縮率は1/30になっています。ここまでソースプログラムを圧縮できるツールはなかなかあ
りませんね。この圧縮率は、ファックスの圧縮に匹敵する驚異的なものです。ということは、元の
プログラムは驚異的に膨張しているということです。
では、もう圧縮はこれで終わりでしょうか。元のリスト9−8とリスト9−10を見比べる限り、
これ以上の圧縮は難しいと思えます。
マニュアルを見ると、テキスト項目を説明している節のタイトルは「テキスト項目と数値テキス
ト項目」となっています。「数値テキスト項目」は、テキスト項目と同様の機能をもち、数値のみ
を扱います。ということは、テキスト項目にノーティファイ関数を登録し、数字以外を無視するよ
うにプログラムしていたことが、実は標準提供の「数値テキスト項目」の機能だったのです。つま
り、363行もかけて書いた関数そのものが『無駄』だったのです。
分かりましたね、もっともっと圧縮できることが。圧縮率は∞(無限大)になってしまいました。
究極の圧縮率が達成できました。マニュアルはちゃんと読みましょう。ウィンドシステムなどでは、
誰もが欲しいなと思う機能は、たいてい用意されているものです。自分の頭で考えるより、マニュ
アルをパラパラめくりましょう。自分でプログラムを書くとデバッグをしなければならないし、動
いてもその後の保守や責任が発生して面倒ではありませんか。そんなことをしたらサボれなくなり
ます。
サボると、さらに便利な機能が多数生きてきます。「テキスト項目」を使っていると、入力した
数値を表す文字列は、あくまで文字列です。本当は数値が欲しいので、関数atoiを使って自分で数
値に変換しなければなりません。ところが、「数値テキスト項目」を使うと、自分で変換する必要
はありません。「値はいくつかな?」と問い合わせると、整数値で値を返してくれます。
その他にも便利な機能が提供されます。有効な数値範囲をPANEL_MIN_VALUE,PANEL_MAX_VALUEの2
つで指定することもできます。これにより、プログラムにより範囲をチェックする必要もなくなり
ます。
リスト9−10 修正版(その2)
1 Panel_setting TextCheck(item, pevent)
2 Panel_item item; /* アイテムのハンドル */
3 Event *pevent; /* イベントのポインタ */
4 {
5 char c;
6
7 c = (char)event_action( pevent );
8 if( isprint(c) && !isdigit(c) )
9 return PANEL_NONE; /* 0..9以外の印字可能文字は無視 */
10 else
11 return (Panel_setting)panel_text_notify( item, pevent );
12 }
■無■
Copyright1996 Hirofumi Fujiwara. No reproduction or republication
without written permission
『Cプログラミング診断室』目次/
次(第9章 珠玉の力作 サボリの美徳)