レポート課題

 

 これまで学んできたテクニックを使って、簡単な「数値データ処理プログラム」を作ります。

 

 最終的な目標は、以下のような機能を持つプログラムにすることです。

 

1.数値データの演算・処理(四則演算、平滑化など)と表示

2.データファイルからの読み込み、処理結果のファイルへの書き出し

 

 目指すプログラムの概要はこちら

 

 それぞれの内容についてはリンク先を参照して下さい。

 

 必要と思われるところではプログラム例を示してありますが、必ずしもそれに従って書く必要はありません。むしろ自分自身で工夫したプログラムのほうが、自ずと評価は高くなります。

 

 レポート提出方法などについてはこちらで確認して下さい。

 

 

0 準備

 

 大きなプログラムの書き進め方には「トップ・ダウン」式と「ボトム・アップ」式があります。前者はプログラムの大枠を始めにつくり、あとから細かい部分を作っていく方式で、後者は個々の細かい機能のプログラムを作ってから、全体をまとめる方式です。

 

 ここではトップ・ダウン式で進めます。

 

0.1 プログラムの構造

 

0.1.1 プログラムの全体構造

 

 全体の動作を規定する構造として、例えば以下のようなプログラムを作っておきます。

 

#include <stdio.h>

 

#define LOAD 1

#define SAVE 2

#define PROCESS 3

#define DISPLAY 4

#define ABORT 5

 

void do_load(void);

void do_save(void);

void do_process(void);

void do_display(void);

void do_abort(void);

 

int main(void) {

         int op;

 

         do {

                 /* 行う操作をキーボードから入力 */

                 printf("Select operation:\n");

                 printf(" %d: Load\n", LOAD);

                 printf(" %d: Save\n", SAVE);

                 printf(" %d: Process\n", PROCESS);

                 printf(" %d: Display\n", DISPLAY);

                 printf(" %d: Abort\n", ABORT);

                 scanf("%d", &op);

 

                 /* 入力された値に従って、各処理へと分岐 */

                 switch (op) {

                 case LOAD:      do_load();      /* ファイルからのデータの読み込み */

                                  break;

                 case SAVE:      do_save();      /* ファイルへの演算結果の書き込み */

                                  break;

                 case PROCESS:  do_process();  /* データ演算等の処理 */

                                  break;

                 case DISPLAY:  do_display();  /* データや演算結果の表示 */

                                  break;

                 case ABORT:     do_abort();     /* 終了前の処理 */

                                  break;

                 default:        printf("Input number %d-%d\n", LOAD, ABORT);

                  }      

 

         } while (op != ABORT); /* ABORTが入力されるまでループを繰り返す */

         return 0;

}

 

/* 以下、各関数を定義する */

void do_load(void) {

         printf("Data loaded.\n");

}

 

void do_save(void) {

         printf("Data saved.\n");

}

 

void do_process(void) {

         printf("Data processed.\n");

}

 

void do_display(void) {

         printf("Data displayed.\n");

}

 

void do_abort(void) {

         printf("Aborted.\n");

}

 

[解説] エラーコードとエラー処理

Advanced Topic] 関数へのポインタ

 

 

 このプログラムは、「キーボードから入力されたコマンドに従って幾つかの関数に分岐させることを、終了のコマンドが入力されるまで繰り返す」というだけの動作をします。

 

 この時点では、個々の関数は単に文字列を表示するだけものですが、これからそれぞれを実際に動作するものに作り上げていくわけです。もちろん、これらの関数の中でさらに分岐させて別の関数を呼び出すようにしたほうがよいケースもあるでしょう。

 

 各関数の引数はvoidにしてありますが、実際には適切な引数を渡すようにする必要があります。どのような引数を渡せばよいか良く考えて、各関数を作成するようにして下さい。

 

 各関数の返却値もvoidにしてありますが、これも状況に応じてエラーコードを返すようにするとよいでしょう。関数が返してきた値によってエラー処理を行うことができるようになります。(上の[解説]を参照)

 

 なお、上の例のdo_abortに相当する関数は別に無くても構いませんが、動的に確保したメモリの解放など、プログラム終了前に行う処理のために使うようにしてもいいでしょう。

 

0.1.2 関数作成の順番

 

 それぞれの関数は、どのような順番で作成しても構いません。ただ、データ配列の中身を確認できた方が何かと便利なので、データや結果の配列の中身をモニタに出力する関数(上の例ではdo_display)を始めに作っておくといいでしょう。

 

 演算用の関数に取りかかるのは、ファイルからのデータの読み込みをできるようにしてからのほうが、動作チェックがやり易いと思います。ただし、ファイルの取り扱いに自信のないひとは、例えば以下のように適当な値で初期化した配列を使って、先に演算の関数を作っても構いません。

 

#define SIZE 10

double data1[SIZE] = {0.12, 1.23, 2.34, 3.45, 4.56, 5.67, 6.78, 7.89, 8.90, 9.01};

double data2[SIZE] = {9.87, 8.76, 7.65, 6.54, 5.43, 4.32, 3.21, 2.10, 1.09, 0.98};

 

 

0.2 データの取り扱い

 

 個々の関数を作っていくにあたり、取り扱うデータの構造についての基本方針を決めておかなくてはなりません。最低限の要求として、

 

元データの格納用に2つのdouble型1次元配列

演算結果の格納用に1つのdouble型1次元配列

 

を使うものとします。あとは自分自身のレベルにあわせて方針を決めて下さい。

 

方法(1):メモリ領域の動的確保に自信がないひとは、例えば、

 

#define ARRAY_SIZE 10

...

double data1[ARRAY_SIZE], data2[ARRAY_SIZE], result[ARRAY_SIZE];

 

などのように、データサイズを固定して、配列を静的に定義してしまうとよいでしょう。

 

方法(2):任意のサイズのデータに対応できるようにするには、例えば、

 

double *data[2], *result;

 

と、ポインタを宣言しておき、適宜メモリを動的に確保して使うようにします。

 

方法(3):データのサイズなどをメンバに含めた構造体をつかうのも良い方法です。例えば、

 

typedef struct data_record {

         long length;

         double *buf;

} myDATA;

...

myDATA *data[2], *result;

 

 どのような構造体にするかは工夫次第です。例えば、元のファイル名とか、セーブしたかを示すフラグとかもメンバにするといいかも知れません。

 

方法(4):データと演算結果には構造上の違いはないので、単に3つの等価なデータバッファを使う、という方針でもよいでしょう。

 

myDATA *buf[3];

 

 例えば「buf[0]buf[1]の演算結果をbuf[2]にいれる」とか、「ファイルの中身をbuf[1]に読み込む」とか、「buf[2]の中身をファイルに書き出す」とか、「buf[2]の中身をbuf[0]に入れる」といった操作を、各関数内で行えばよいわけです。

 

 

 なお、いずれの場合にも、データ配列(あるいは構造体)は、グローバル変数ではなく、main関数内のローカル変数として宣言して下さい。

 

[解説] ローカル変数とグローバル変数

 

 

0.3 アップグレード

 

 プログラムが一通り完成し、その上でなお余力がある人は、さらに機能をアップグレードしてみましょう。

 

 例えば、二次元配列データも扱えるプログラムにチャレンジしてみるのもよいでしょう。

 

 また、扱えるデータの数を2つに限定せず、多数のデータを動的に扱えるプログラムにしてみるというのも、やりがいがあると思います。