パソコン活用研究シリコンバレー(C、C++、の活用研究)

ファイル入出力(ランダムアクセス関数)

=================================================================================================
ファイル入出力関数の中で、ファイルにランダムアクセスするための関数を取り上げてみます。fseek, ftell, rewind
です。これらの関数を使うと、オープンしたファイルの任意の位置にアクセスして、ファイルの読み書きができます。
旧BASICでは、ランダムファイルにアクセスするには、FIELD命令で、ランダムバッファにフィールド変数を割り当て
などの準備が必要で、かなり面倒な操作が必要でした。それに比べると、C言語はかなり自由度が高い気がします。
=================================================================================================

1 fseek, ftell, rewind

(1) fseek
ファイルの任意の位置にアクセスする時に使う関数がfseekです。fseekによりファイルポインタを任意の位置に移動
できます。ファイルポインタについては、「ファイル構造体」を参照して下さい。
書式は以下の通りです。

fseek
int fseek(fp, offset, mode);
FILE *fp;
long offset;
int mode;

fp で示すファイルポインタをmodeからoffsetバイト移動する。
modeの指定は以下の通り
0 ...ファイルのはじめ
1 ...現在のファイルポインタの位置
2 ...エンドオブファイル

関数の戻り値 0 実行終了   0以外 エラー

(2) ftell
ftell はファイルポインタの現在位置を調べます
書式は以下の通りです。

ftell
long ftell(fp);
FILE *fp;

fpで示すファイルポインタの現在位置を調べる。        
関数の戻り値  -1 エラー    -1以外 現在の位置

(3) rewind
rewind はファイルポインタをファイルの先頭位置に移動します。

rewind
int rewind(fp);
FILE *fp;

fpで示すファイルポインタをファイルの先頭に移動し、エラーフラグとエンドオブファイルフラグを
クリアします。
関数の戻り値 0 実行終了   0以外 エラー


2 プログラム例

サンプルとして、拡張Typeコマンドのプログラム(Ktype)を作成してみます。
機能としてはlessコマンドの簡易版のようなものです。

(1) Ktypeの仕様
Ktypeの仕様は以下の通りとします。

使い方は以下の通りです。
ktype のあとのコマンドラインに表示したいテキストファイル名を以下のように記述します。
>ktype FileName

その後は、以下のコマンドで、テキストファイルの前後を表示させます。
p  1行戻る
f  1行進む
b  1ページ戻る
n  1ページ進む
q  終了

(2) プログラム概説
プログラムの流れは単純です。指定されたファイルをオープンした後は以下の通りの流れです。
fopen
 |
clrsc スクリーンをクリア
 |
bufinit 変数初期化
 |
command コマンド処理  <-->( showcommand -----> forward ( <-------> locate) )
 |                 コマンド表示  ファイル読み込み、表示   表示位置設定
fclose

コンパイラはLSI Cを使用しています。clrsc, locate clrline 等の関数はエスケープシーケンスを利用して
います。
肝心なポイントは関数forwardです。他の部分は簡単なコードなので、説明しません。

forwardで使っている変数について
n 何行進むか、戻るのかを指定します。1,20,-1,-20 の値をとります。nは関数forwardの引数として与えられ
  ます。
line 現在読み込んでいる行
lp[line] 変数lineで示される行の次の行の先頭位置(=次に読み込む先頭の位置)を入れます

今、1行目から20行目までを表示していると仮定して、次の1行を読み込むというコマンド(コマンド 'f' )
が与えられた時の動きをトレースしてみます。
(A) このときには引数n=1でforward関数が呼ばれます。(command関数の中からforwardがコールされる)
  ((A)を参照

(B) line=line+n-20;
   20行目まで表示されているので、forward関数がコールされる以前はline=20となっているます。
   ここで、再計算されてline=1 となります。(B)を参照

(c) fseek(fp,lp[line],0);
   lp[line] はline=1 なので、次の行の先頭位置(すなわち、2行目の先頭)を示しています。
   fseek でファイルポインタの位置を2行目の先頭に移動します。 (C)を参照

(D) for(i=0;i<20;i++){
   if(fgets(s,inputmax,fp)==NULL) { if(feof(fp)){locate(1,23); printf("ファイルエンドです"); return;}}
    printf("%s",s);
    line++;
   1行分読み込んで、表示し、lineに1加えます。
   これを20回繰り返し(20行表示)ます。今回は2行目から表示開始され、21行目までが表示されます。
   (D)を参照

(E) lp[line]=ftell(fp);
   ftell で現在のファイルポインタ(次の行の先頭を指しています)を読み取り、lp[line]に代入します。
   これで、lp[line]には、次の行の先頭位置(のファイルポインタ)が代入されます。
   なお、bufinit関数で、lp[ ]は初期値として、-1に初期設定されています。従って、初めてアクセスされた
   行(lp[line] == -1)の場合のみ、lp[line]=ftell(fp) は実行されます。すでにアクセスしたことがある行の
   場合は、実行されません。   (E)を参照
   
   
               Ktype.c
/* Ktype(拡張Type)Ver0.0.1 coded by Tsuyoshi Kasai for LSIC */

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#define inputmax 81
#define linemax 10000

void command(void);   /*コマンド処理*/
void clrsc(void);     /*スクリーンをクリア*/
void locate(int,int);   /*表示位置設定*/
void showcommand(void);   /*コマンド表示*/
void forward(int);        
void clrline(int);    /*1行クリア*/
void bufinit(void);   /*変数初期化*/

FILE *fp;
long lp[linemax];
int line;


main( int argc, char **argv){
if (argc ==1){
    printf("\n拡張TYPE CODED BY TSUYOSHI KASAI \n使用方法:コマンドラインの引数に
  ファイル名を指定する\n"); exit(1);}
if ((fp=fopen(argv[1],"r"))==NULL){
  printf("ファイルが開けません\n");  exit(1);}
clrsc();
bufinit();
command();
fclose(fp);
}

void bufinit(){
int i;
for (i=0;i<=linemax;i++){lp[i]=-1;}
line=0;  lp[line]=0;
}

void command(){
int c;
for(;;){
showcommand();
c=getch();
clrsc();
switch(c){
case 'f':
case 'F':
   forward(1); break;   /*  (A)  */
case 'n':
case 'N':
   forward(20); break;
case 'p':
case 'P':
   forward(-1); break;
case 'b':
case 'B':
   forward(-20);  break;
case 'q':
case 'Q':
   return;
}}}
 
void clrsc(){
printf("\x1b[2J");}

void locate(int x, int y){
printf("\x1b[%d;%dH",y,x);}

void showcommand(){
locate(1,22); printf("\x1b[7;17m");
printf("コマンド p 1行戻る  f 1行進む  b 1ページ戻る  n 1ページ進む  q 終了 ");
printf("\x01b[0;23m");
}

void forward(int n){
int i;
char *s;
line=line+n-20;   /*  (B)  */
if (line <0) line=0;
if (line>=linemax){
locate(1,23); printf("これ以上読み込めません"); return;}
fseek(fp,lp[line],0);    /*  (C)  */
locate(1,1);
for(i=0;i<20;i++){          /*  (D)  */     
if(fgets(s,inputmax,fp)==NULL){
  if(feof(fp)){locate(1,23);  printf("ファイルエンドです"); return;}}
printf("%s",s);
line++;
if (line>=linemax){
locate(1,23); printf("これ以上読み込めません"); return;}
if(lp[line]==-1){ lp[line]=ftell(fp);}    /*  (E)  */
}}

void clrline(int n){
locate(1,n); printf("\x01b[2K");}



 



実行画面の例 (上のKtype.c を読み込んで表示させたもの)

void showcommand(void);
void forward(int);
void clrline(int);
void bufinit(void);

FILE *fp;
long lp[linemax];
int line;


main( int argc, char **argv){
if (argc ==1){
printf("\n拡張TYPE CODED BY TSUYOSHI KASAI \n使用方法:コマンドラインの引数
にファイル名を指定する\n"); exit(1);}
if ((fp=fopen(argv[1],"r"))==NULL){
printf("ファイルが開けません\n"); exit(1);}
clrsc();
bufinit();
command();
fclose(fp);
コマンド p 1行戻る f 1行進む b 1ページ戻る n 1ページ進む q 終了


TopPage