15.ファイル操作と関数

 

 15.1 ファイル名を変数に代入して扱う
 この章では,ファイル関連の処理を関数で実現することを考えてみます.
例として,2次間数
    y = x*x + 3
を取りあげ,計算結果をファイルに記録したり,ファイルに記録されたデータを読みとったりするプログラムを 関数を使って構成することを試みます.ファイル操作と関数の勉強を兼ねた説明だと思ってください.

 前章で紹介したプログラムでは,ファイル名を””で囲み,定数として扱いましたが,ここでは,文字型変数に代入して扱うこと を考えてみます. fopen()関数の引数は,文字列型のポインターと定義されていますから,ファイル名を変数に代入して渡すことも出来るわけです.

まずは,「ドライブ名+フォルダー名+ファイル名」を一連の文字列として,キーボードから文字型変数に代入し,fopen()関数に 渡す例です.ここでは,さらに,ファイル名入力部分を1つの関数にまとめてみました.

キーボードからフォルダー名とファイル名を入力する時は,それらの境界を示す'\'記号は'\'文字一個で十分であることに注意してください.

ファイルが作成されたかどうかは,メモ帳で確認してください.


 プログラム例 15.1.1  実行結果
#include <stdio.h>
void get_file_name(char *fnamep);

void main(void){
  int x, y;
  char file_name[51];
  FILE *fp;

  get_file_name(file_name);
  
  if((fp = fopen(file_name,"w"))!=NULL){
    for(x = -5; x<=0; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
  else{
      printf("パスが見つかりません\n");
  }     
}

void get_file_name(char *fnamep)
{
  printf("パスを含むファイル名を入力して下さい\n");
  gets(fnamep);
}
パスを含むファイル名を入力して下さい
c:\data\second.txt
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3


 もし,データファイルを作成するフォルダーが決まっているのなら,ファイル名を入力する関数は次のように 書き換えることも可能です.入力の手間を減らすことができ,パス名を間違える可能性を減らすこともできます.

ファイルが作成されたかどうかは,メモ帳(NotePad)で確認してください.


 プログラム例 15.1.2  実行結果
#include <stdio.h>
#include <string.h>
void get_file_name(char *fnamep);

void main(void){
  int x, y;
  char file_name[51];
  FILE *fp;

  get_file_name(file_name);
  
  if((fp = fopen(file_name,"w"))!=NULL){
    for(x = -5; x<=0; x++)
    {
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}

void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
ファイル名を入力してください
sec_order.txt
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3



 15.2 データ書込み操作の関数化の例
 次に,前節で作成したプログラムの2次関数の値を計算しファイルに書き込む部分を関数化を試みてみます.
作成する関数部分の処理は,以下の4つの処理で構成してみました.
   1)ファイルを開く
   2)2次関数を計算する
   3)計算結果をファイルに書き込む
   2)ファイルを閉じるる
くどいようですが,関数自体の構成のしかた(関数内での処理内容)はいろいろ考えられます.したがって,必ずしも, プログラム例のような構成が絶対ではなく,プログラムの作成者が都合の良いように作成すれば良いのです.

ここでは,1個の関数のまとめかたより,処理を関数化することで,main()関数内部の記述がどのように変わるか ということに注目して欲しいと思います.


 プログラム例 15.2.1  実行結果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void get_file_name(char *fnamep);
void write_data(char *fnamep, int xs, int xe);

void main(void){
  int xs, xe;
  char file_name[51];

  xs=-5;  xe=0;
  get_file_name(file_name);
  write_data(file_name, xs, xe);
}

void write_data(char *fnamep, int xs, int xe)
{
  int x, y; 
  FILE *fp;
  if((fp = fopen(fnamep,"w"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}

void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
ファイル名を入力してください
abc.txt
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3


プログラム例15.2.1で作成したプログラムを,xの範囲を指定できるように書き換えたのが次のプログラム例15.2.2です.
これまでの知識で理解できるはずです.ここでもメイン関数の記述に注意しましょう.

ついでに,このプログラムに一般的な2次関数,y=a*x*x+b*x+cの係数a,b,cを 入力する機能を追加すれば,利用範囲が広がります.それは,皆さんで試してみてください.


 プログラム例 15.2.2  実行結果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void get_file_name(char *fnamep);
void write_data(char *fnamep, int xs, int xe);
void get_xstart_xend(int *xsp, int *xep);

void main(void){
  int xs, xe;
  char file_name[51];

  get_file_name(file_name);
  get_xstart_xend(&xs, &xe);
  write_data(file_name, xs, xe);
}

void get_xstart_xend(int *xsp, int *xep)
{
   char x[18];

   printf("xの範囲を指定して下さい.\n");
   printf("計算開始点:"); gets(x);
   *xsp = atoi(x);
   printf("計算終了点:"); gets(x);
   *xep = atoi(x);
}

void write_data(char *fnamep, int xs, int xe)
{
  int x, y; 
  FILE *fp;
  if((fp = fopen(fnamep,"w"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
ファイル名を入力してください
abcd.txt
xの範囲を指定して下さい.
計算開始点:-7
計算終了点:7
   -7,   52
   -6,   39
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
    4,   19
    5,   28
    6,   39
    7,   52



 15.3 データ読み込み操作の関数化の例
 今度は,作成されたファイルからデータを読み込む関数を作成してみましょう.基本的なことは「14.ファイル」で説明した とおりです.関数の引数や内部の処理についても特別説明することはないと思います.

でも,main()関数内部の記述はちょっとおかしいですね.これだと,ファイル作成後,いきなり読み込みを開始することになります. そうすると,ここで作成した関数の役割は,ただ単に,作成したばかりのファイルの内容を確認するだけの意味しかありません.


 プログラム例 15.3.1  実行結果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void get_file_name(char *fnamep);
void write_data(char *fnamep, int xs, int xe);
void get_xstart_xend(int *xsp, int *xep);
void read_data(char *fnamep);

void main(void){
  int xs, xe;
  char file_name[51];

  get_file_name(file_name);
  get_xstart_xend(&xs, &xe);
  write_data(file_name, xs, xe);
  read_data(file_name);
}

void read_data(char *fnamep)
{
  int x, y;
  FILE *fp;
  printf("データ読み込み\n");
  if((fp = fopen(fnamep,"r"))!=NULL){
    while(fscanf(fp,"%5d,%5d\n",&x,&y)!=EOF)
    { 
      printf("%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}

void write_data(char *fnamep, int xs, int xe)
{
  int x, y; 
  FILE *fp;
  if((fp = fopen(fnamep,"w"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void get_xstart_xend(int *xsp, int *xep)
{
   char x[18];

   printf("xの範囲を指定して下さい.\n");
   printf("計算開始点:"); gets(x);
   *xsp = atoi(x);
   printf("計算終了点:"); gets(x);
   *xep = atoi(x);
}
void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
ファイル名を入力してください
abc.txt
xの範囲を指定して下さい.
計算開始点:-5
計算終了点:7
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
    4,   19
    5,   28
    6,   39
    7,   52
データ読み込み
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
    4,   19
    5,   28
    6,   39
    7,   52



 15.4 先へ進む前にswitch()文を紹介します
 ファイル操作の話は,いったん置いて,ここでは,次節で登場するswitch()文の説明をします. switch(引数)文は,次に示すように引数として整数値をとります.この引数の値によって,いくつかの caseの中から対応するcaseとbreakではさまれる範囲を実行するわけです.
もちろん,このような処理はif()文を使って実現することもできます.

   switch(n)
   {
   case 0:
     nの値が0の時実行する内容
   break;
   case 1:
     nの値が1の時実行する内容
   break;
   case 2:
     nの値が2の時実行する内容
   break;
   default:
     nの値が上記のcaseのどれにも
     該当しない場合実行する内容
     (必要なければ書かなくとも良い)
   }

 switch()文使用例をプログラム例15.4.1と15.4.2に示します.


 プログラム例 15.4.1  実行結果
#include <stdio.h>
#include <stdlib.h>
void main(void){
    char c[3];
    int n;

    /* 1)整数値をキー入力する */
    gets(c);   n = atoi(c);

    /* 2)nの値で指定されるcaseを実行 */
    switch(n)
    {
    case 0:
       printf("nは0でした\n");
    break;
    case 1:
       printf("nは1でした\n");
    break;
    case 2:
       printf("nは1でした\n");
    break;
    default:
       printf("0,1,2以外の値でした\n");
    }
}
整数値を入力して下さい
1
nは1でした
Press any key to continue


整数値を入力して下さい
4
0,1,2以外の値でした
Press any key to continue



 次の例は,選択肢を表示する関数を作成し,メニュー形式で実行内容を選択できるようにしたものです.


 プログラム例 15.4.2  実行結果
#include <stdio.h>
#include <stdlib.h>
void menu(int *np);
void main(void){
    int n;
    menu(&n);
    switch(n)
    {
    case 0:
        printf("終 了\n");
    break;
    case 1:
        printf("得意科目:英語\n");
    break;
    case 2:
        printf("得意科目:数学\n");
    break;
    }
}

void menu(int *np){
    char c[3];
    /* 1)整数値をキー入力する */
    printf("得意な科目は?\n");
    printf("1)英 語\n");
    printf("2)数 学\n");
    printf("0)終 了\n");
    gets(c);   *np = atoi(c);
}
得意な科目は?
1)英 語
2)数 学
0)終 了
2
得意科目:数学



 15.5 本題にもどって
 switch()文を使って13節のプログラム例を書き換えてみました. これで,ファイルへの書き込み操作と読み込み操作が独立して実行できるようになります.ここでも, main()関数をよくながめてください.



 プログラム例 15.5.1  実行結果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void show_menu(int *np);
void get_file_name(char *fnamep);
void write_data(char *fnamep, int xs, int xe);
void get_xstart_xend(int *xsp, int *xep);
void read_data(char *fnamep);

void main(void){
  int xs, xe;
  char file_name[51];
  int jno;

  show_menu(&jno);
  switch(jno){
    case 1:
      get_file_name(file_name);
      get_xstart_xend(&xs, &xe);
      write_data(file_name, xs, xe);
    break;
    case 2:
      get_file_name(file_name);
      read_data(file_name);
    break;
  }
}

void show_menu(int *np){
   char c[3];
   printf("<ファイル処理の選択>\n");
   printf("1)ファイルの作成\n");
   printf("2)ファイルの読み込み\n");
   printf("0)終了\n");
   printf("\n処理を番号で選択して下さい:");
   gets(c);  *np=atoi(c);
}

void read_data(char *fnamep)
{
  FILE *fp;
  int x, y;
  printf("\nデータ読み込み\n"); 
  if((fp = fopen(fnamep,"r"))!=NULL){
    while(fscanf(fp,"%5d,%5d\n",&x,&y)!=EOF){ 
      printf("%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void write_data(char *fnamep, int xs, int xe)
{
  FILE *fp;
  int x, y; 
  printf("\nデータ書き込み\n"); 
  if((fp = fopen(fnamep,"w"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void get_xstart_xend(int *xsp, int *xep)
{
   char x[18];

   printf("xの範囲を指定して下さい.\n");
   printf("計算開始点:"); gets(x);
   *xsp = atoi(x);
   printf("計算終了点:"); gets(x);
   *xep = atoi(x);
}
void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
<ファイル処理の選択>
1)ファイルの作成
2)ファイルの読み込み
0)終了

処理を番号で選択して下さい:1
ファイル名を入力してください
abcde.txt
xの範囲を指定して下さい.
計算開始点:-3
計算終了点:3

データ書き込み
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
Press any key to continue


<ファイル処理の選択>
1)ファイルの作成
2)ファイルの読み込み
0)終了

処理を番号で選択して下さい:2
ファイル名を入力してください
abcde.txt

データ読み込み
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
Press any key to continue



 15.6 ついでに,データ追加機能も
 ついでに,既存のファイルへデータを追加する機能を組み込んでみます.ここでは,write_data()とファイルモードが 異なるだけのappend_data()関数を作ってみましたが,ファイルを新規に作成する機能との違いは,ファイルモードのみで,”w”と ”a”と異なるだけです.
ファイルモードは,””で囲まれていることからもわかるように,文字列としてfopen()関数に渡されます.
このことを考慮すれば,write_data()関数の引数にファイルモードを追加すれば,ファイルの新規作成と データ追加機能が1個の関数で実現できることになります.皆さんで試みてほしいと思います.
 


 プログラム例 15.6.1  実行結果
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void append_data(char *fnamep,int xs,int xe);
void show_menu(int *np);
void get_file_name(char *fnamep);
void write_data(char *fnamep, int xs, int xe);
void get_xstart_xend(int *xsp, int *xep);
void read_data(char *fnamep);

void main(void){
  int xs, xe;
  char file_name[51];
  int jno;

  show_menu(&jno);
  switch(jno){
    case 1:
      get_file_name(file_name);
      get_xstart_xend(&xs, &xe);
      write_data(file_name, xs, xe);
    break;
    case 2:
      get_file_name(file_name);
      read_data(file_name);
    break;
    case 3:
      get_file_name(file_name);
      get_xstart_xend(&xs, &xe);
      append_data(file_name, xs, xe);
    break;
  }
}

void append_data(char *fnamep,int xs,int xe)
{
  int x, y; 
  FILE *fp;
  printf("\nデータの追加書き込み\n"); 
  if((fp = fopen(fnamep,"a"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}

void show_menu(int *np){
   char c[3];
   printf("<ファイル処理の選択>\n");
   printf("1)ファイルの作成\n");
   printf("2)ファイルの読み込み\n");
   printf("3)データの追加書き込み\n");
   printf("0)終了\n");
   printf("\n処理を番号で選択して下さい:");
   gets(c);  *np=atoi(c);
}
void read_data(char *fnamep)
{
  FILE *fp;
  int x, y;
  printf("\nデータ読み込み\n"); 
  if((fp = fopen(fnamep,"r"))!=NULL){
    while(fscanf(fp,"%5d,%5d\n",&x,&y)!=EOF){ 
      printf("%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void write_data(char *fnamep, int xs, int xe)
{
  FILE *fp;
  int x, y; 
  printf("\nデータ書き込み\n"); 
  if((fp = fopen(fnamep,"w"))!=NULL){
    for(x = xs; x<=xe; x++){
      y = x * x + 3; 
      printf("%5d,%5d\n", x, y);
      fprintf(fp,"%5d,%5d\n", x, y);
    }
    fclose(fp);
  }
}
void get_xstart_xend(int *xsp, int *xep)
{
   char x[18];

   printf("xの範囲を指定して下さい.\n");
   printf("計算開始点:"); gets(x);
   *xsp = atoi(x);
   printf("計算終了点:"); gets(x);
   *xep = atoi(x);
}
void get_file_name(char *fnamep)
{
  char fnameonly[21];

  strcpy(fnamep, "c:\\data\\");  
  printf("ファイル名を入力してください\n");
  gets(fnameonly);
  strcat(fnamep, fnameonly);
}
<ファイル処理の選択>
1)ファイルの作成
2)ファイルの読み込み
3)データの追加書き込み
0)終了

処理を番号で選択して下さい:1
ファイル名を入力してください
abc.txt
xの範囲を指定して下さい.
計算開始点:-7
計算終了点:0

データ書き込み
   -7,   52
   -6,   39
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3
Press any key to continue

<ファイル処理の選択>
1)ファイルの作成
2)ファイルの読み込み
3)データの追加書き込み
0)終了

処理を番号で選択して下さい:3
ファイル名を入力してください
abc.txt
xの範囲を指定して下さい.
計算開始点:1
計算終了点:7

データの追加書き込み
    1,    4
    2,    7
    3,   12
    4,   19
    5,   28
    6,   39
    7,   52
Press any key to continue

<ファイル処理の選択>
1)ファイルの作成
2)ファイルの読み込み
3)データの追加書き込み
0)終了

処理を番号で選択して下さい:2
ファイル名を入力してください
abc.txt

データ読み込み
   -7,   52
   -6,   39
   -5,   28
   -4,   19
   -3,   12
   -2,    7
   -1,    4
    0,    3
    1,    4
    2,    7
    3,   12
    4,   19
    5,   28
    6,   39
    7,   52
Press any key to continue



 15.7 この章では
 ファイル処理を対象に,C言語らしく関数でプログラムを構成することをこころみました.
関数は,プラモデルや機械装置のパーツのようなものだということが理解できたら十分なのですが...
Cでプログラムを書くときは,処理全体を複数の処理に分割し,関数(パーツ)で構成するように心がけてください.