『Cプログラミング診断室』目次次(第1章 普通の初心者 名前)

第1章 普通の初心者

無駄な努力をやめよう


初心者のプログラムを見てつくづく思うのは、しなくても良いコーディングが実に多いことです。 1行で済むところを5行も10行もかけたり、C言語で標準サポートされている関数と同様のもの を作っているのをよく見かけます。誰しも欲しいと思われるものは、かなり用意されています。

自分で新たな関数を作ったりすると、そのデバッグが必要になりますが、標準を使えばその必要 もなくなります。当然プログラムも短くなり、バグも入りにくくなります。先人の知恵をしっかり 活用して「楽」をしましょう。

「作らなければ、バグは入らない。」という絶対的真理を悟るべきです。

■sprintf、sscanf を使おう■

標準入出力関数のprintfは誰でも知っていますね。それとほとんど同一の関数で、sprintfがあ りますが、この関数をなかなか使いこなしていません。

元のプログラムは、strcpy、strcat、strsetの洪水ですね。個々の関数の機能があまりにも小さ いために、もうプログラムの見通しが全然立ちません。典型的な例は、485〜492行の次の部分でしょ う。

    itoa(cn,tnn,16);
    if(cn < 16) {
	strcpy(tnnr,"0");
	strcat(tnnr,tnn);
	strcpy(tnn,tnnr);
    }
    tnn = strupr(tnn);
    strcpy(tn,tnn);

上の処理は255以下の正整数cnを16進数2桁の文字列にする処理です。あまりに複雑な処理を行なっ ているので、最初に見たとき、「何だ、この変な部分は?」と思いました。

以上のことは、

  sprintf( tn, "%02X", cn );

で済みます。printfは、変換制御文字列で指定したフォーマットに従って標準出力に文字列を送り 出します。sprintfは、第1引数で指定した文字列にその文字列を入れます。

printfなどは出力用でした。対応する入力用として、scanfなどの関数が用意されています。た いていの場合、scanfを使用しているようですが、scanfは入力をその場で解釈しながら処理するた め、オプション、エラーチェックなど十分にはできません。

一般に、データは改行が押されるまで、行末までが単位になっています。これにもっとも自然に 対応し、かつより柔軟なエラー対応などをするには、まず、1行分をバッファに読み込んでから、 じっくりと解析することでしょう。

それには、gets、fgetsでバッファに1行分読み込み、sscanfでバッファ内容を各変数に取り込 みます。

2つの整数値をaとbに入力するとき、

	scanf( "%d%d", &a, &b );
  

と書きますね。でも、こうすると、必ず2つのデータを入力しないとscanfから戻ってきません。 もし、2番目は不用とか、単に改行のみの入力も可能にしたいと思うと、scanfではどうにもなり ません。getsとsscanfを組み合わせた同様の例をリスト1−2に、その実行結果を図1−3に示し ます。


リスト1−2 

     1  #include        <stdio.h>
     2  
     3  main()
     4  {
     5          char    buf[80];
     6          int     a, b;
     7  
     8          for(;;) {
     9                  printf( "enter  a  b : " );
    10                  if( gets(buf) == NULL )
    11                          break;
    12                  printf( "length=%d  ", strlen(buf) );
    13                  switch( sscanf( buf, "%d%d", &a, &b ) ) {
    14                  case EOF:
    15                  case 0:
    16                          printf( "<CR>\n" );
    17                          break;
    18                  case 1:
    19                          printf( "a=%d\n", a );
    20                          break;
    21                  case 2:
    22                          printf( "a=%d b=%d\n", a, b );
    23                          break;
    24                  }
    25          }
    26          printf( "\n" );
    27  }

図1−3 実行結果
                       
  enter  a  b : 10 20
  length=5  a=10 b=20
  enter  a  b : 30
  length=2  a=30
  enter  a  b : 
  length=0  <CR>
  enter  a  b : abc
  length=3  <CR>
  enter  a  b : 

ここでは簡単な例しか示していませんが、一度バッファに読み込んでから解析すれば、非常に強 力で柔軟な処理ができることが分かるでしょう。対話処理をしたり、アスキーファイルの解釈をちゃ んとしたい場合はこの方法に限ります。

今回の修正プログラム(リスト1−5)は、この点については対処していません。これをしてし まうと、もうプログラムは原型を全くとどめなくなるためです。

■文字のテスト■

文字が英字か、数字か、16進数字かなどの判定が必要なことがよくあります。このとき、直接範 囲指定をしている人がいますね。元のプログラムの447行目から、

    if((dw>= 0x30) && (dw<= 0x39)) break;
    if((dw>= 0x41) && (dw<= 0x46)) break;
    if((dw>= 0x61) && (dw<= 0x66)) break;
という文字判定があります。ここは、char型変数dwの内容が16進数文字のときにbreakしたいので す。

まず、文字を表すのに16進数を直接用いるのはまずいですね。'0','9','A','F','a','f'とすべ きでしょう。

    if((dw>= '0') && (dw<= '9')) break;
    if((dw>= 'A') && (dw<= 'F')) break;
    if((dw>= 'a') && (dw<= 'f')) break;

でも、このようにすると、unsigned char型とchar型の違う型の比較になると思って、わざわざ16 進数表記にしたのでしょうか。でも、これでも正解ではありません。

C言語には、文字判定のためのマクロが用意されています。このマクロを使うには、

  #include   <ctype.h>

が必要になります。この関数(マクロ)を使うと、実際に不等号などによる条件判定を行なってい ません。配列を参照するだけなので、非常に高速です。ぜひ一度、ctype.hのファイルの中身を読 んで見てください。ちょっと難しいかもしれません。

このマクロのなかのよく忘れ去られている16進数文字判定の isxdigitマクロ関数を使うと上の 判定は、

    if( isxdigit( dw ) ) break;

だけになります。極めてシンプルでしょう。

ムダがいかに多いかが分かったでしょうか。ムダなことには努力しないで欲しい。しかし、いか にして楽をするかには努力して欲しい。楽をするためには、多くの知識と経験を必要とします。で も、楽をするための努力は、なかなかすぐには報われないけれど、数カ月、あるいは数年後には、 この努力の差が実力の差になってきます。

O'Reilly Japan刊

3/14発売
増刷:第3刷決定♪

オープンソース
Scheme言語処理系
Gaucheの愛好者団体

詳細はブログで



Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第1章 普通の初心者 名前)