コンピューター:C言語講座:プログラム作成の手順
学生や研究者の方に多いのですが、mainだけのプログラムを組まれる方がいます。ちょっとした物を個人のみで使う場合にはそれでも良いのですが、仕事でプログラムを組む方や、作ったものを他の人がみる可能性がある場合は問題です。個人でも、あとになってその機能を他のプログラムに組み込みたい場合などにも手間がかかります。
ここでは、プログラムをゼロから組む場合にどういう手順で組んで行けばmainだけのプログラムではなく、あとで使いまわしのきくモジュールに出来るかを考えてみたいと思います。
ここでは課題として16bitの整数値を2進数で表示するプログラムを作ってみます。
とりあえずmain
まず、やはりエディターを開いたらmainを作りたくなります。紙にあらかじめフローチャートを書いてから組む場合はサブルーチンから作り始め、最後にmainを書いたほうがきれいに出来る場合もありますが、たいていはフローチャートなど書かず(書いている暇が無いorめんどくさい)、いきなりエディターでプログラムを組み始めます。
課題の16bitの整数値を2進数で表示する程度のプログラムですと、ついついmainのなかに全て書いて、以下のようなプログラムにしてしまいます。
#include <stdio.h>
void main()
{
short num;
int i;
while(1){
printf("Input = ");
scanf("%hd",&num);
for(i=15;i>=0;i--){
printf("%d",(num>>i)&0x01);
}
printf("\n");
}
}
これでも課題に対する答としては問題無いのですが、これでは使い回しが面倒で、次に似たような物がほしい時にまた作るようになります(このくらいなら毎回作ってもたいした手間ではありませんが)。
構造化プログラミングへ
そこでお勧めしたいのが、下のように、まずは何をするのかを言葉で書いてしまい、それから順に中身を作って行く方法です。
#include <stdio.h>
void main()
{
short num;
while(1){
/* shortを標準入力から得て、numに格納 */
/* numを2進数表示 */
}
}
言葉を関数で置き換えます。関数はこれから作るので、適当な名前です。ついでにプロトタイプ宣言と、関数の実体を空で作っておきます。
なお、プロトタイプ宣言と関数の引数の書き方はK&RのCと、ANSIのCで異なります。私の仕事場では両方で動く必要があることからK&Rタイプで書く場合が多いです。ここでは一応K&Rとしておきます。
関数の型も人それぞれポリシーがあるのですが、私は値を返さなくても良い場合でもvoidはめったに使いません。Cではint以外の関数はプロトタイプ宣言が必要です。ANSIの場合は引数のプロトタイプが無いと警告がうるさくでるコンパイラーもあるので、結局プロトタイプしたほうが良いのですが。プロトタイプ宣言は作った関数全て行なうのは面倒なので、たいていはintにしてしまいます。また、intにしておけば、後で気が変わってエラーフラグなどを返す場合などにもそのまま使えます。とりあえずはreturn(0)で0を返しておきます。したのサンプルではShortBinPrint()は値を返さないので本来はvoidですが、プロトタイプ宣言を省く為にintにしてあります。
#include <stdio.h>
short GetShort();
void main()
{
short num;
while(1){
/* shortを標準入力から得て、numに格納 */
num=GetShort();
/* numを2進数表示 */
ShortBinPrint(num);
}
}
short GetShort()
{
return(0);
}
int ShortBinPrint(num)
short num;
{
return(0);
}
これから、関数の中身を作りますが、ここでのポイントは空の関数ははじめに作っておくことで、こうすればとりあえずコンパイル・リンクは通りますので、作った部分のチェックは順に出来ます。出来るだけこまめにコンパイルをかけてエラーは早めに取り除いておきます。
さて、中身ですが、まず、GetShort()です。はじめの例のようにただscanf()で読み込んでも良いのですが、scanf()した時に数字以外の入力があると、scanf()は暴走します(やってみてください)。そこで、少しは安全にする為に一旦文字列で読み込んで、それを整数に変換する方法が良く使われます。
short GetShort()
{
char buf[80];
short num;
gets(buf);
num=(short)atoi(buf);
return(num);
}
bufは文字列バッファーですが、このサイズが問題で、大きすぎても無駄ですし、スタックも消費します。かといって小さすぎると入力する人がたくさん入れる意地悪をすると溢れてしまいます。厳密には1文字づつ入力をチェックし、入力長制限を行なうか、入力に応じてバッファーを拡大するのが正当な方法ですが、いずれにしろ、このように関数にしておけば後でいくらでもこだわって改良できます。今回はとりあえず気にせずに80バイトだけ取っておきます。文字列から整数にはatoi()で変換します。atoi()はint型なので念の為キャストしておきます。
続いてShortBinPrint()です。これははじめのサンプルのようにループでシフトしながら標準出力に書き出しても良いのですが、どうせなら変換だけするような関数も使いそうな気がするので、とりあえず、文字列のバッファーに変換する関数を用意して、それを表示するようにしてみました。こうしておけば2つ以上1行に表示したい場合や、出し先を変える時にもそのまま使えます。
int ShortBinPrint(num)
short num;
{
char buf[20];
ShortToBinString(num,buf);
printf("%s\n",buf);
return(0);
}
int ShortToBinString(num,buf)
short num;
char *buf;
{
int i;
char *ptr;
ptr=buf;
for(i=15;i>=0;i--){
*ptr=(char)(((num>>i)&0x01)+'0');
ptr++;
}
*ptr='\0';
return(0);
}
ShortToBitString()はちょっとインチキをしていて、numをシフトして1ビット目だけを取り出した後に、'0'(文字のゼロ)を加えてcharにキャストしています。これは整数を文字に直しているのですが、これが通用するのは文字コードが0から9まで順番に並んでいる場合のみです。ASCIIコードではOKですが、他ではダメなものも多いです(ISO,EIAなど)。しかし、最近でC言語を使うシステムでASCII以外はほとんど見かけないので、注意を忘れなければこれが簡単で良いと思います。文字列の最後に'\0'を付けておくのを忘れないように。
これで課題のプログラムが構造的に作れたわけですが、いかがでしょうか?ちょっと課題が単純すぎるので構造的に作って関数を使い回しできるようにしてもあまりありがたみは無いかも知れませんが、ShortToBinString()などはバイナリデータのデバッグ用くらいには使えそうです。こういうものは1つ作った時に一緒にLongToBinString(),FloatToBinString(),DoubleToBinString()など、他の型の物も一緒に作っておくと後で便利かも知れません。もしくは、short
numなどのように実体を渡さず、値をアドレス渡しにしてビット長を引数で一緒に渡して汎用の関数にしても良いかも知れません。
このように、開発現場ではフローチャートを書いて部品から作って行くような本格的なプロジェクト以外ではコーディングしながらアルゴリズムを考え、更に使い回しがきくような構造化を心がけて開発するのが小規模のプロジェクトには向いていると私は考えています。BottomUpの方がきれいに組めることが多いとは思いますが、TopDownでもちょっとした心がけでmainだけのプログラムから抜けでることができると思います。