『Cプログラミング診断室』目次次(第9章 珠玉の力作 サボリの美徳)

第9章 珠玉の力作

力作


今回の珠玉の力作をリスト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言語には文字クラスをテストをする関数がセットで用意されて います。詳しくは、ヘッダーファイル を見て調べてください。10進数字の判定は isdigit(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です。

リスト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  }

文字の型はunsigned charからcharに変えました。forと外側のifもなくなりました。ノーティファ イ関数なので、いずれかのパネルアイテムと一致するに決まっているので、判定そのものが無意味 です。

すっかり短くなりましたね。圧縮効果にご満足いただけましたでしょうか。

■無■

もう圧縮率は1/30になっています。ここまでソースプログラムを圧縮できるツールはなかなかあ りませんね。この圧縮率は、ファックスの圧縮に匹敵する驚異的なものです。ということは、元の プログラムは驚異的に膨張しているということです。

では、もう圧縮はこれで終わりでしょうか。元のリスト9−8とリスト9−10を見比べる限り、 これ以上の圧縮は難しいと思えます。

マニュアルを見ると、テキスト項目を説明している節のタイトルは「テキスト項目と数値テキス ト項目」となっています。「数値テキスト項目」は、テキスト項目と同様の機能をもち、数値のみ を扱います。ということは、テキスト項目にノーティファイ関数を登録し、数字以外を無視するよ うにプログラムしていたことが、実は標準提供の「数値テキスト項目」の機能だったのです。つま り、363行もかけて書いた関数そのものが『無駄』だったのです。

分かりましたね、もっともっと圧縮できることが。圧縮率は∞(無限大)になってしまいました。 究極の圧縮率が達成できました。マニュアルはちゃんと読みましょう。ウィンドシステムなどでは、 誰もが欲しいなと思う機能は、たいてい用意されているものです。自分の頭で考えるより、マニュ アルをパラパラめくりましょう。自分でプログラムを書くとデバッグをしなければならないし、動 いてもその後の保守や責任が発生して面倒ではありませんか。そんなことをしたらサボれなくなり ます。

サボると、さらに便利な機能が多数生きてきます。「テキスト項目」を使っていると、入力した 数値を表す文字列は、あくまで文字列です。本当は数値が欲しいので、関数atoiを使って自分で数 値に変換しなければなりません。ところが、「数値テキスト項目」を使うと、自分で変換する必要 はありません。「値はいくつかな?」と問い合わせると、整数値で値を返してくれます。

その他にも便利な機能が提供されます。有効な数値範囲をPANEL_MIN_VALUE,PANEL_MAX_VALUEの2 つで指定することもできます。これにより、プログラムにより範囲をチェックする必要もなくなり ます。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第9章 珠玉の力作 サボリの美徳)