C++言語解説:Appendix.Cの入出力システム
2002-06-16

[概要] C++ではなくCの標準入出力、ファイル入出力について学習します。C++の入出力シ
    ステムを理解するにはもう少し学習が必要なので、この段階でCベースの利用法を
    習得し、早い段階からプログラミングの利益を享受できるようにします。

[構成]・Cの入出力ストリーム
     * ストリームとは
     * printf()関数
     * scanf()関数
     * printf()関数とscanf()関数の使用例
   ・Cのファイルシステム
     * 代表的関数群とファイルポインタ
     * fpoen(), fclose(), fgetc(), fputc()
     * fread(), fwrite(), fseek(), rewind(), feof()


Cの入出力ストリーム

 ストリームとは
 
  ・入出力システムの基本はストリーム(stream)です。ストリームとはデバイスへの一
   般的な論理インターフェース
を指します。
  
  ・CにおいてはTable1のストリームがテキストデータに対して定義付けられています。
   ヘッダとしては「stdio.h」が必要です。
   
   [Table1.Cの標準入出力デバイス]
   ┌───────────────────────────────────┐
    | ストリーム | デバイス     |
    |------------+------------------|
    | stdin   | キーボード    |
    | stdout   | コンソール(画面) |
    | stderr   | コンソール(画面) |
   └───────────────────────────────────┘
  
  ・DOS/UNIX/Windows等のオペレーティングシステムでは標準入出力にリダイレクト
   (redirection)
を使用することができます。よって標準の入出力ストリームを使用し
   た場合リダイレクトによって、入出力デバイスを変更することが可能です。


 printf()関数
 
  ・printf()コンソール(stdout)へデータ出力を行う関数です。printf()関数のプロ
   トタイプはList1のようになっています。
   
   [List1.printf()関数のプロトタイプ]
   ┌───────────────────────────────────┐
     int printf(const char* format, ..........);
            書式文字列    可変個引数

     戻り値 : 0以上 - 転送バイト数
          負値 - 転送失敗
   └───────────────────────────────────┘
  
  ・最初の引数formatは制御文字列(format string)と呼ばれます。この文字列にはテキ
   ストとフォーマット指定子(format specifier)が含まれています。Table2にフォーマ
   ット指定子の一覧を示します。
  
   [Table2.printf()関数のフォーマット指定子]
   ┌───────────────────────────────────┐
    | フォーマット指定子| 出力形式                  |
    |-------------------+--------------------------------------------|
    | %c        | 文字                    |
    | %d        | 符号付き10進整数              |
    | %i        | 符号付き10進整数              |
    | %e        | 指数表記(小文字e)             |
    | %E        | 指数表記(大文字E)             |
    | %f        | 10進浮動小数点               |
    | %g        | %e or %f の短い方             |
    | %G        | %E or %f の短い方             |
    | %o        | 符号無し8進数               |
    | %s        | 文字列へのポインタ             |
    | %u        | 符号無し10進整数              |
    | %x        | 符号無し16進整数(小文字表記)        |
    | %X        | 符号無し16進整数(大文字表記)        |
    | %p        | ポインタ                  |
    | %n        | これまでの出力文字数を渡す整数へのポインタ |
    | %%        | %記号                   |
   └───────────────────────────────────┘
  
  ・例えば、以下のようにprintf()関数を使用すると
     printf("Text %c %d %s", 'A', 100, "String");
   画面には以下のように出力されます。
     Text A 100 String

  ・Table2に示したフォーマット指定子には表示幅や右/左詰といった修飾子を付けるこ
   とができます。修飾子には様々なものがありますが、詳しくはコンパイラマニュア
   ルでprintf()関数の説明を読むようにして下さい。
  
  ・ポイントは、書式指定は文字列であるということです。書式文字列を作成して
   printf()関数へ渡せば状況に応じて出力フォーマットを変更できます。


 scanf()関数
 
  ・scanf()標準入力からのデータを読み込む関数です。組み込みの全てのデータを読
   み込むことができます。(*1)
  
  ・scanf()関数のプロトタイプを示します。

   [List2.scanf()関数のプロトタイプ]
   ┌───────────────────────────────────┐
     int scanf(const char* format, ..........);
              制御文字列  可変個引数

     戻り値 : 読み込みフィールド数
          ファイル末尾到達でEOFを返す (EOFはstdio.hで定義される)
   └───────────────────────────────────┘
  
  ・可変個引数には入力制御文字列に合わせてデータを格納するオブジェクトのアドレ
   スを渡します
。ポインタを介す又は&演算子を使用することになります。
  
  ・scanf()関数のフォーマット指定子をTable3に示します。

   [Table3.scanf()関数のフォーマット指定子]
   ┌───────────────────────────────────┐
    | フォーマット指定子| 入力形式                  |
    |-------------------+--------------------------------------------|
    | %c        | 文字                    |
    | %d        | 符号付き10進整数              |
    | %i        | 符号付き10進整数              |
    | %e        | 浮動小数点                 |
    | %f        | 浮動小数点                 |
    | %g        | 浮動小数点                 |
    | %o        | 8進数                   |
    | %s        | 文字列へのポインタ             |
    | %x        | 16進整数                  |
    | %p        | ポインタ                  |
    | %n        | これまでの出力文字数を渡す整数へのポインタ |
    | %u        | 符号無し10進整数              |
    | %[]        | 文字列集合をスキャンする          |
   └───────────────────────────────────┘
  
  ・制御文字列に含まれるフォーマット指定子以外の文字(ホワイトスペース含む)は読
   み捨てられ
、引数のオブジェクトには渡されません。
   

 printf()関数とscanf()関数の使用例

  ・それでは、printf()関数とscanf()関数を利用したいくつかの例を見てみましょう。
   詳しくはライブラリのマニュアルを参照してもらうとして、ここでは代表的使用例
   を示します。
  
   [List3.printf()関数/scanf()関数の使用例(1)]
   ┌───────────────────────────────────┐
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    int main()
    {
      char c_a[80];
      int i_b;
      int i_b2;
      
      printf("Input name age > "); //引数無し文字列のみ出力
      scanf("%s %d", c_a, &i_b); //c_aはアドレス
      printf("Your name : %s\n",c_a);
      printf("Your age : %d\n",i_b);
      
      printf("\nInput Value1/Value2 > ");
      scanf("%d/%d", &i_b, &i_b2); //セパレータに非ホワイトスペース指定
      printf("Value1 : %d\n",i_b);
      printf("Value2 : %d\n",i_b2);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
    Input name age > Suzuki 20
    Your name : Suzuki
    Your age : 20
    
    Input Value1/Value2 > 50/100
    Value1 : 50
    Value2 : 100
   └───────────────────────────────────┘
  
   [List4.printf()関数/scanf()関数の使用例(2)]
   ┌───────────────────────────────────┐
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    int main()
    {
      char c_a[80] = "Your age?";
      char c_a2[80] = "Who are you?";
      int i_ret;
      
      printf("Input age name > ");
      i_ret = scanf("%[0-9] %[a-zA-Z]", c_a, c_a2); //文字種限定
      if (i_ret != 2) printf("format error!!\n");
      
      printf("Your age : %s\n",c_a);
      printf("Your name : %s\n",c_a2);

      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
    C:\Tmp>prog
    Input age name > 20 Suzuki
    Your age : 20
    Your name : Suzuki

    C:\Tmp>prog
    Input age name > Suzuki 20
    format error!!
    Your age : Your age?
    Your name : Suzuki
   └───────────────────────────────────┘
  
  ・List3/List4はscanf()/printf()によるキーボードと画面の入出力制御です。stdinと
   stdoutはリダイレクトにより切り替えられるのでList5のような使い方もできます。

   [List5.printf()関数/scanf()関数の使用例(3)]
   ┌───────────────────────────────────┐
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    int main()
    {
      char c_a[80];
      int i_b;
      int i_ret;
      
      while (scanf("%d%s", &i_b, c_a)!=EOF){
        printf("%s, %d\n", c_a, i_b);
      }
      
      return 0;
      
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
    C:\Tmp>prog < data1.txt | data1.txtの中身
    宮下, 1         | 1  宮下
    柏木, 2         | 2  柏木
    山田, 3         | 3  山田
    竹田, 4         | 4  竹田
    佐藤, 5         | 5  佐藤
    藤井, 6         | 6  藤井
    猪又, 7         | 7  猪又
   └───────────────────────────────────┘

 
Cのファイルシステム

 代表的関数群とファイルポインタ
 
  ・CのファイルシステムはTable4に示す関数群で構成されています。これらの関数を使
   用するには「stdio.h」ヘッダが必要です。関数は他にもありますが、ここでは代表
   的なものを示します。
  
   [Table4.Cの代表的なファイルシステム関数]
   ┌───────────────────────────────────┐
    | 関数名  | 内容                  |
    |-----------+----------------------------------------|
    | fopen()  | ファイルへのストリームを開く      |
    | fclose() | ファイルへのストリームを閉じる     |
    | fputc()  | ストリームに文字を書き出す       |
    | fgetc()  | ストリームから文字を読みとる      |
    | fwrite() | ストリームにブロックデータを書き込む  |
    | fread()  | ストリームからブロックデータを読み込む |
    | fseek()  | ストリームの指定バイト位置に移動する  |
    | fprintf() | printf()のストリーム版         |
    | fscanf() | scanf()のストリーム版         |
    | feof()  | EOFに達するとtrueを返す        |
    | ferror() | エラーが発生すればtrueを返す      |
    | rewind() | ファイル位置インジケータを先頭へ戻す  |
    | remove() | ファイルを削除する           |
   └───────────────────────────────────┘
  
  ・ファイルへのストリームを結びつけるには、ファイルポインタを使用します。ファ
   イルポインタは、stdio.hで定義されたFILE型のポインタ変数です。
  
  ・先に述べた
     printf()関数 --- stdout(標準出力)ストリームへの出力
     scanf()関数 ---- stdin(標準入力ストリームからの出力
   ですが、stdin及びstdoutも実はFILE型ポインタのマクロとなっています。つまり
   Table4に示した関数群はストリーム指定を含んだ、より汎用的な入出力関数である
   と言えます。
  
  ・各関数の詳しい使用法はライブラリのマニュアルを参照して下さい。ここでは使用
   例を示すことにします。


 fopen(), fclose(), fgetc(), fputc()
 
  ・ファイル「in.dat」の内容をファイル「out.dat」へコピーします。改行カウントに
   より行数も表示します。

   [List6.ストリーム関数の使用例(1)]
   ┌───────────────────────────────────┐
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    int main()
    {
      FILE* fp_in;  //入力用ファイルポインタ
      FILE* fp_out;  //出力用ファイルポインタ
      char c_buf;   //データバッファ用キャラクタ変数
      int i_line = 0; //行数カウンタ
      
      if ((fp_in=fopen("in.dat","rt"))==NULL) { //入力ストリームオープン
        printf("[in.dat] File not found!!\n");
        return 1;
      }
      
      fp_out=fopen("out.dat","wt"); //出力ストリームオープン
      
      while((c_buf=fgetc(fp_in))!=EOF){ //読み込み
        fputc(c_buf, fp_out); //書き込み
        if (c_buf=='\n') i_line++; //改行ならカウントアップ
        printf("\r%d",i_line);
      }
      
      fclose(fp_out); //出力ストリームクローズ
      fclose(fp_in); //入力ストリームクローズ
      
      return 0;
    }
   └───────────────────────────────────┘


 fread(), fwrite(), fseek(), rewind(), feof()
 
  ・テキストデータをバイナリデータへ変換し、シーケンシャルアクセスとランダムア
   クセスを行います。
  
  ・List7の例では、main()関数において

   [Fig1.List7におけるmain()関数の流れ]
   ┌───────────────────────────────────┐
     ↓ テキストデータへのストリームをオープンする
     ↓ バイナリデータへのストリームをオープンする
     ↓*テキストファイルを読み込み、バイナリファイルを書き出す
     ↓ テキストデータへのストリームをクローズする
     ↓*バイナリファイルにシーケンシャルアクセスする
     ↓*バイナリファイルにランダムアクセス(3番目データへ)する
     ↓ バイナリファイルへのストリームをクローズする
   └───────────────────────────────────┘
   
   の順番で大まかな処理の流れを記述し、*の付いた処理は関数として構造化していま
   す。

   [List7.ストリーム関数の使用例(2)]
   ┌───────────────────────────────────┐
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    #define MAXNAMESIZE 20 //名前文字数の最大長マクロ
    
    // ===== プロトタイプ宣言部 =====
    void v_DataRead(FILE* fp_in, FILE* fp_out);
    void v_SequentialAccess(FILE* fp_out);
    void v_RandomAccess(int i_n, FILE* fp_out);
    
    
    /* -----------------------------------------------
      int main();
      ファイルのオープン/クローズと処理ブロックの記述
      ----------------------------------------------- */
    int main()
    {
      FILE* fp_in;   //テキストデータ入力
      FILE* fp_out;  //バイナリデータ入出力
      
      //テキストへのストリームオープン
      if ((fp_in=fopen("in.dat","rt"))==NULL) {
        printf("[in.dat] File not found!!\n");
        return 1;
      }
      fp_out=fopen("out.dat","w+b"); //バイナリへのストリームオープン
      
      v_DataRead(fp_in, fp_out); //データ読み込み
      
      fclose(fp_in); //テキストへのストリーム閉じる
      
      v_SequentialAccess(fp_out); //シーケンシャル処理
      v_RandomAccess(3, fp_out); //ランダム処理

      fclose(fp_out); //バイナリへのストリーム閉じる
      
      return 0;
    }

    
    /* --------------------------------------------------
      void v_DataRead(FILE* fp_in, FILE *fp_out)
      テキストデータを読み出し、バイナリデータへ書き出す
      -------------------------------------------------- */
    void v_DataRead(FILE* fp_in, FILE *fp_out)
    {
      int i_state;       //int 順位データ
      char c_name[MAXNAMESIZE]; //char MAXNAMESIZE文字で名前データ

      while((fscanf(fp_in,"%d%s",&i_state, c_name))!=EOF){
        //順位と名前の書き込み
        fwrite(&i_state, sizeof(int), 1, fp_out); //(*2)
        fwrite(c_name, MAXNAMESIZE*sizeof(char), 1, fp_out);
      }

      rewind(fp_out); //バイナリのストリームファイルポインタを先頭へ

      return;
    }

    /* ------------------------------------------------
      void v_SequentialAccess(FILE* fp_out)
      バイナリデータをシーケンシャル読み出し、表示する
      ------------------------------------------------ */
    void v_SequentialAccess(FILE* fp_out)
    {
      int i_state;       //int 順位データ
      char c_name[MAXNAMESIZE]; //char MAXNAMESIZE文字で名前データ

      do {
        //順位と名前の読み込み
        fread(&i_state, sizeof(int), 1, fp_out);
        fread(c_name, MAXNAMESIZE*sizeof(char), 1, fp_out);
        if (!feof(fp_out)) {
          printf("SEQ : 順位=%d, 名前=%s\n",i_state, c_name);
        }
      } while(!feof(fp_out));
      
      rewind(fp_out); //バイナリのストリームファイルポインタを先頭へ

      return;
    }

    /* ---------------------------------------------------
      void v_RandomAccess(int i_n, FILE* fp_out)
      バイナリデータのi_n番目をランダムアクセス、表示する
      --------------------------------------------------- */
    void v_RandomAccess(int i_n, FILE* fp_out)
    {
      int i_state;       //int 順位データ
      char c_name[MAXNAMESIZE]; //char MAXNAMESIZE文字で名前データ
      long int li_access;    //アクセスbyte位置計算
      
      li_access = (i_n-1) * (sizeof(int) + MAXNAMESIZE*sizeof(char));
      
      fseek(fp_out, li_access, SEEK_SET); //SEEK_SETは先頭を示すマクロ

      //順位と名前の読み込み
      fread(&i_state, sizeof(int), 1, fp_out);
      fread(c_name, MAXNAMESIZE*sizeof(char), 1, fp_out);
      printf("RND : 順位=%d, 名前=%s\n",i_state, c_name);
      
      return;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
    C:\Tmp>prog       | in.dat の中身
    SEQ : 順位=1, 名前=宮下 | 1  宮下
    SEQ : 順位=2, 名前=柏木 | 2  柏木
    SEQ : 順位=3, 名前=山田 | 3  山田
    SEQ : 順位=4, 名前=竹田 | 4  竹田
    SEQ : 順位=5, 名前=佐藤 | 5  佐藤
    SEQ : 順位=6, 名前=藤井 | 6  藤井
    SEQ : 順位=7, 名前=猪又 | 7  猪又
    RND : 順位=3, 名前=山田 |
   └───────────────────────────────────┘


(*1)組み込み以外のデータは読み込めないとも言えます。なので構造体のデータなどは要
  素毎にしか扱えません。

(*2)sizeofは型のサイズを示す演算子です。関数ではありません。詳しくは次回のテキス
  トで説明予定です。

[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2002-06-16 |初版
 |1.01   |2002-06-30 |語句修正
 |1.02   |2002-07-02 |語句修正
[end]
Copyright(C) 2002 Altmo