C言語 標準関数 | 応用 | サンプル
例えば、ファイルのレコード数がいくつあるか分からない時に、ファイルからレコードを読み込んでプログラムの変数に保持したい場合に使用する一般的な手法にデータチェインがあります。
ファイルに限らず、oracleやsybaseなどのリレーショナルデータベース(RDB)から読み込んだレコード(ロー:rowとも言う)でも同様です。
以下の図でRecord_1・・・Record_nは、ファイルなどから読み出したレコードを意味します。
Nextは次のデータへのポインタ(次データの先頭アドレス)を意味します。
処理自体は簡単です。
ファイルからレコードを読み出す毎に、mallocやcallocでメモリを確保して、そこに読み込んだレコードを代入し、前回データのNextに今回確保した領域の先頭アドレスを代入するだけです。
このデータチェインは良く使う手法なのでご存知ない方は覚えておくと良いでしょう。
/* イメージ */
+--------+
|Record_1|
+--------+ +--------+
| Next |-->|Record_2|
+--------+ +--------+ +-----------+
| Next |・・・-->|Record(n-1)|
+--------+ +-----------+ +--------+
| Next |-->|Record_n|
+-----------+ +--------+
| NULL |
+--------+
/* [レコード+次データへのポインタ]の組合せを構造体で表現する */
/* (これをデータセル(cell:細胞)と言います) */
typedef struct {
rec[1024];/* レコード */
struct DataChain * next;/* 次データへのポインタ */
} DataChain
以下のサンプルを見てください。
入力ファイルを読み込みためにオープンし、fgetsでファイルを読み込みながらcallocで領域を確保しています。callocを使用したのは確保した領域をクリアしてくれるからであり、ソースコードで明示的にクリアするならばmallocでも構いません。
次に読み込んだデータを確保した領域に代入しています。→①
ここで次のデータへのポインタは未だ未定なのでNULLを代入します。→②
初回(一件目のレコード読み込み)ならば、確保した領域のアドレスを変数dataに代入します。→③
2回目以降であれば、前回データのNextに、今回確保した領域のアドレスを代入します。→④
尚、前回データのNextは⑤で保存してあります。
ここまでで、ファイルからのデータの読み込みは終了です。
⑥はfeofでファイル読み込みが成功したかを判定しています。
⑦は読み込んだデータを表示しています。通常のプログラムであれば、この部分が最も重要になります。
⑧はファイルクローズです。一般的にfcloseではエラーチェックは行わないことが多いようです。
⑨は確保した領域を開放freeします。この開放では再帰を使って領域を開放しています。
再帰に詳しくない方は、このような方法もあるのだと覚えておいて損は無いと思います。
この再帰の動作をイメージする場合は、別の同じ関数が複数あり、再帰する毎に順次それらの関数を呼び出すものだと考えれば良いと思います。自分自身を呼び出すよりもいつものように別関数を呼び出すことをイメージすれば分かりやすいです。
再帰の説明で多いのが、階乗計算やアッカーマン関数、ハノイの塔などがありますが、より実用に近いと思います。
具体的には、(ここでのテストデータの場合)memfree関数を呼び出すと、
1件目は⑩を通過して再帰します。→(a)
また2件目も⑩で再帰します。→(b)
次の3件目ですが(データ件数が3件なので)その次のデータを指すNextはNULLになっていますので、今回は⑪を通過して関数は終了します。
この関数が終了したということは、(b)で再帰したときの関数が終了したことを意味するので、⑫を実行して関数が終了します。
更に、この関数が終了したということは、(a)で再帰したときの関数が終了したことを意味するので、⑫を実行して関数が終了します。
これで、main関数に戻ってプログラムは終了します。
This is 1st row.
This is 2nd row.
This is 3rd row.
[EOF]
#include <stdio.h>
#include <stdlib.h>
/* 構造体の定義 */
typedef struct chain {
char rec[1024];
struct chain * next;
} dataChain;
/* 領域開放関数 */
void memfree( dataChain * ptr ) {
if( ptr->next == ( struct chain * )NULL ) {
free(( void * )ptr );/* ⑪ */
}
else {
memfree(( dataChain * )ptr->next );/* ⑩ */
free(( void * )ptr );/* ⑫ */
}
}
/* メイン関数 */
main() {
FILE * fp;
dataChain * data = NULL;/* 常に1件目のデータのアドレス */
dataChain * ptr;/* 領域確保したデータのアドレス(ワーク域に相当) */
dataChain * lastptr;/* 直近最終データのアドレス */
char rec[1024];/* 読み込みワーク域 */
int ret = 0;
/* Windowsの場合は"test.dat" */
fp = fopen( "./test.dat" , "r" );
if( fp == NULL ) {
printf( "ファイルオープンエラー\n" );
return -1;
}
/* ファイル読み込み */
while( fgets( rec , sizeof( rec ) , fp ) != NULL ) {
/* 領域確保(clear付き) */
ptr = ( dataChain * )calloc( 1 , sizeof( dataChain ));
if( ptr == ( dataChain * )NULL ) {
printf( "領域確保エラー\n" );
ret = -1;
break;
}
/* 確保した領域に読み込みデータと次データポインタ(=NULL)を設定 */
strncpy( ptr->rec , rec , sizeof( rec )-1 );/* ① */
ptr->next = ( struct chain * )NULL;/* ② */
if( data == ( dataChain * )NULL ) {
/* 初回の場合 */
/* dataは常に先頭データのアドレス */
data = ptr;/* ③ */
}
else {
/* 2回目以降の場合 */
/* 最終データ次ポインタに確保した領域アドレスを設定 */
lastptr->next = ( struct chain * )ptr;/* ④ */
}
/*直近最終データアドレスとして保存 */
lastptr = ptr;/* ⑤ */
}
/* ⑥ */
if( ret == 0 ) {
/* 正確な判定を行う */
if( feof( fp ) == 0 ) {
printf( "ファイル読み込みエラー\n" );
ret = -1;
}
else {
printf( "ファイル読み込み終了\n" );
ret = 0;
}
}
/* ⑦ 読み込んだデータを表示 */
for( ptr = data ; ptr != NULL
; ptr = ( dataChain * )ptr->next ) {
printf( ptr->rec );
}
/* ⑧ クローズ */
fclose( fp );
/* ⑨ 領域開放(再帰の練習の意味を込めて) */
if( data != NULL ) memfree( data );
return ret;
}
ファイル読み込み終了
This is 1st row.
This is 2nd row.
This is 3rd row.