ファイル入出力と標準ライブラリ

C言語勉強会 第十二回

kumar
October 30, 2013
引用 :

今回の内容

ファイル入出力は簡単

C言語も当然、ファイル入出力ができる。

ファイル入出力があなたのプログラムのレベルを大幅に上げることは想像に難くない。少し複雑なプログラムなら当然、設定の保存をしたり、主人公のHPをセーブしたりするはずだ。少し頑張れば、簡単なテキストエディタだって作れるだろうし、ゲームのワールドエディタだってありだ。

それに、C言語のファイル入出力は、初期化やお片づけのためのたった2つの関数を除けば、今まで使ってきたprintfscanfに、一本の毛が生えただけの関数しか基本的に必要としない。ここまで来たあなたなら、ここから先の内容は楽勝だ。

3ステップでできるC言語におけるファイル入出力

①ファイルを開く  
     ↓
②ファイルに出力する or ファイルから入力を受ける  
     ↓ 
③ファイルを閉じる
  - お わ り -

開いているファイルは、ファイルポインターと呼ばれる変数を利用して操作する。

また、標準ライブラリstdio.hをインクルードしている必要がある。

ファイルに出力する擬似コード

簡単のため、実際のコードを見せる前に擬似コードを見せる。
ファイルポインターは長いのでfpと書く。

fp = ファイルオープン("ファイルパス");

ファイルに出力(fp, "hogehogehoge~~~~");

ファイルクローズ(fp);

ファイルオープン関数はそのファイルを指し示すファイルポインターを返している。
なぜファイルポインターを出力(入力)関数に渡しているのかは、C言語を作った人の気持ちになって考えればすぐわかるはずだ。
ファイルクローズは、これ以上このファイルを操作しませんよ、とOSに伝えている。

擬似コードとC言語の関数の対応表は、以下のとおりだ。

擬似 C言語
ファイルオープン fopen
ファイルクローズ fclose
ファイルに出力 fprintf
ファイルから入力 fscanf

そしてファイルポインターは、FILE *が担当する。

さあ、この表を参考にCのコードに書き換えてみよう!

ファイルに出力するCのコード

あっという間にファイル出力が出来ました。

FILE * handle = fopen("unko.txt", "w");

fprintf(handle, "hogehogehoge~~~~~\n");

fclose(handle);

コンパイルして実行すると、unko.txtが出来上がっているはずだ。

ただし、このコードは簡単のため、万が一ファイルを開けなかった場合のエラー処理を怠っている。そして、関数fopenの2個めの引数の意味もまだわからないだろう。

それらも含め、これより各部分を解説する。

ファイル入出力

ストリーム

データの入力または出力の機能を提供するもの。

ここで言うデータとは、ファイル、メモリ、ネットワーク通信等、様々である。

FILEポインタ

C言語における、ファイルのストリーム

FILE *

FILE構造体へのポインタである。 C言語ではこれを介して、ファイル(等)の入出力を行う。

ハンドルや、ストリームポインタとも呼ばれる。

これを含め、ファイル入出力のための構造体や関数などの定義は、標準ライブラリ stdio.h で定義されている。

fopen

FILE *fopen(const char *path, const char *mode);

path で指定された名前のファイルを開き、ストリームと結びつける。

引数 mode は、以下の文字をひとつ以上組み合わせた文字列へのポインタである。

それには以下がある。(次のページへ)

文字 モード 意味(ていねい)
r read テキストファイルを読み出すために開く。
w write ファイルを書き込みのために開く。(なければ新規作成、あれば上書き)
a add 追加(ファイルの最後に書き込む)のために開く。(なければ新規作成)

加えて、末尾にbをつけるとバイナリモードで開く。

戻り値は開いたファイルを示すFILEポインタ。失敗するとNULL。

簡単のためかなり省略している。完全な説明は

Man page of FOPEN

はまだ載せずに、開いたファイルの閉じ方を紹介することにした。次のページヘ。

fclose

int fclose(FILE *fp);

fpが指すストリームを閉じる。

正常に終了すると 0 が返される。そうではない場合 EOF が返される。

また、ストリームを閉じる前に、バッファリングされていた全ての出力データをフラッシュする。

実際のところ、自分でfcloseしなくても、プログラムが終了するとき、OSが勝手にfcloseしてくれるのだが、強制終了した時はその限りでないので、忘れず書いたほうがいいと思う。(これは諸説あるので自信がない)

ファイルを開いて閉じるだけのプログラム

理解の確認のため、全く入出力せず、ファイルを開いて閉じるだけの、プログラムの例を載せる。

FILE * fp;

/* ファイルを開く */
fp = fopen("test.txt", "w");    /* 書き込みモードでtest.txtを開く */

/* ファイルを閉じる */
fclose(fp);

ファイルを開いて閉じるだけのプログラム + エラー処理

面倒かもしれないがエラー処理は絶対にやってほしい。

FILE * fp;

fp = fopen("test.txt", "w");

/* エラー処理 */
if (NULL == fp) {
    printf("ファイルを開けなかった¥n");
    exit(EXIT_FAILURE);
}

fclose(fp);

fprintf

int fprintf(FILE *stream, const char *format, ...);

出力をstreamに書き出す。

お察しの通り、printfに一つ引数が増えただけで、以下のように使う。

fprintf(yourstream, "%d個のうんこがある¥n", 10);

このほかにfputcや、fputsなどがある。

fscanf

int fscanf(FILE *stream, const char *format, ...);

streamからの入力を読み込む。

お(ry、scanfに一つ引数が増えただけで、以下のように使う。

int x;
fscanf(yourstream, "%d", &x);

このほかにfgetcや、fgetsなどがある。

ファイルを書き出すプログラム

ファイルに文字列を書き出す簡単なプログラム

FILE * fp;

fp = fopen("test.txt", "w");    /* 書き込みモード */

if (NULL == fp) {
    fprintf(stderr, "ファイルを開けなかった¥n");
    exit(EXIT_FAILURE);
}

/* fprintfで、fpに出力 */
fprintf(fp, "やったぜ¥n");

fclose(fp);

ファイルを読み込むプログラム

ファイルから空白が来るまで文字列を読み込む簡単なプログラム

FILE * fp;

fp = fopen("test.txt", "r");    /* 読み込みモード */

if (NULL == fp) {
    fprintf(stderr, "ファイルを開けなかった¥n");
    exit(EXIT_FAILURE);
}

/* fscanfで文字列を読み込んで、printfする */
char str[100];
fscanf(fp, "%99s", str);
printf("%s¥n", str);

fclose(fp); 

コラム|標準ストリーム

ストリームの中でも、標準で用意されているもの

それらは全て FILE * 型で、以下の通りである。

変数名 名前
stdout 標準出力ストリーム
stderr 標準エラー出力ストリーム
stdin 標準入力ストリーム

printf等の関数は、stdoutへ出力を行う。そしてscanf等の関数は、stdinから入力を受ける。

プログラムを起動した時点でオープンされている。だから、fopenを呼ぶ必要はない。

よって、printfはfprintfにより、こう書ける

fprintf(stdout, "フォーマット", ...);

そして、scanfはfscanfにより、こう書ける

fscanf(stdin, "フォーマット", ...);

エラーメッセージは、標準エラー出力へ出力すべきだ。

fprintf(stderr, "エラーメッセージ¥n");

標準Cライブラリ

標準Cライブラリ

C言語の標準規格で定められた、型・マクロ・関数の集合からなるライブラリ

今まで使ってきた、 stdio.hstdlib.h などのことである。

例えば、
stdio.hをインクルードすれば、printfなどの標準入出力や、今までやっていたファイル入出力が扱え、
math.hをインクルードすれば、sinやcos等の数学関数が使え、
time.hをインクルードすれば、現在時刻が得られる・・・などだ。

Wikipediaによると、最新の規格、C11では29種類ある。

ググれば出てくるので自分で調べて欲しい。Wikipediaで見やすくまとめられているのでおすすめだ。

標準Cライブラリ - Wikipedia

プリプロセッサ

プリプロセッサ

ソースコードをコンパイルする前に、ソースコードに対して行われる前処理をプリプロセス(preprocess)と呼ぶ(直訳)。そのプリプロセスを行うプログラムのことをプリプロセッサと呼ぶ。

C プリプロセッサは、以下の4つの機能を提供する。

プリプロセッサのための命令を、プリプロセッサディレクティブという。ディレクティブは命令という意味である。

gccで、プリプロセスのみの実行を行うには、-Eオプションを使う。標準出力に結果が出力される。

ヘッダファイルの読み込み

#includeにより行う。

#include

指定したファイルの中身をまるごと挿入する

#include <h-char-sequence>
#include "q-char-sequence"

今まで使ってきたinclude文であるが実はこういう単純な意味であった。

上の角括弧の方のinclude文は、標準CライブラリやOSのAPIライブラリなどの「パスの通った(特定の場所に置かれた)」ヘッダーをサーチし、
下のダブルクォートの方のinclude文は、このinclude文が書かれたファイルからの相対パスでサーチする。

どちらも、コンパイルオプションでサーチ先を追加することが出来る。

ヘッダーを自分で書いてみよう

ヘッダーの仕組みが単なる挿入であることがわかったところで、自分でヘッダーを書いてみよう。ちなみに、今まで使ってきた標準Cライブラリも、誰か人間が書いたものだ。

add.hを以下のとおりに書いて保存して欲しい。

int add(int a, int b) {
    return a + b;
}

同じディレクトリにadd.hがあることを確認して、そこで適当な名前でいいのでtest.cを書く。

#include <stdio.h>
#include "add.h" /* 今書いたヘッダー */

int main(void) {

    int sum = add(1, 10)

    printf("%d\n", sum);

    return 0;
}

add.hに書かれているadd関数を呼び出せた。これがヘッダーの基本である。だが、ヘッダー等を使った複数のファイルを使ったCプログラミングの話をすると、インクルードガードやexternの話がちょっと長くなりすぎるので、自分で調べて欲しい。

ヘッダファイルの読み込みの話はこれでやめにする。

マクロによる置換

プリプロセッサの中でも、文字列の置換を行うプログラムのことをマクロと呼ぶ。

本当に単なる置換である。

マクロの定義は、 #define で行う。

#define

#define にはいくつか文法がある。ここではメジャーなもの2つを紹介する。それぞれ、マクロ定数を定義するものと、マクロ関数を定義するものである。

マクロ定数を定義する

#define identifier replacement-token-list

指定したidentifierをソースコード内で見つけたら、replacement-token-listに置き換える

#define UNKO 1

int func(void) {
    return UNKO;
}

関数funcは 1 を返す。

マクロ定数の定義を書かないこともできる

#define identifier

後に出る#ifdef等と組み合わせて使う

マクロ関数を定義する

#define identifier(parameter-list) replacement-token-list

マクロ定数の機能に加え、引数をとる事ができる。

とった引数は、replacement-token-listの中で使うことが出来る。

#define ADD(A, B) A + B

int func(void) {
    const int a = 1, b = 3;

    return ADD(a, b);
}

ADD(a, b)a + b に置換され、関数funcは 4 を返す。

マクロ関数の注意点

replacement-token-listを()で囲まなかったせいでヤバい例
#define ADD(A, B) A + B

int x = 5 * ADD(2, 3);

5 * (2 + 3) = 25になることを期待したと思うが、マクロ関数による単なる置換が行われ、5 * 2 + 3 = 13になってしまう。

なので、マクロ関数を書く場合、replacement-token-listを必ず() で囲むべきだ。

直した例
#define ADD(A, B)   (A + B)

int x = 5 * ADD(2, 3);  // 5 * (2 + 3) = 25

条件分岐

条件を満たしたらプログラムを含め、そうでなければ除外する

#if constant-expression
    …
#endif

constant-expressionが真のとき(0以外のとき)、...をプログラムに含む。偽のとき(0のとき)、...はプログラムから除外される。

constant-expressionは定数で無くてはならない。プリプロセッサはコンパイル前に実行されるので、当然である。

(この場合、定数とはconst変数のことではない。)

マクロの命名規則

言語的には、基本的に、__なんとかじゃなければ何でも問題ない。

だが、慣習があるので、それに従うといいだろう。私が従っている慣習は、大文字のスネークケース(単語をアンダーバーでつなぐこと)

LIKE_THIS

not_like_this
__NOT_LIKE_THIS
NOTLIKETHIS

である。

条件分岐ディレクティブは幾つかある。

ディレクティブ 意味
#if … if …
#ifdef … if defined …
#ifndef … if not defined ...
#elif ... else if ...
#else else
#endif endif

おわりに

やってみませんか

C言語よりも楽しい言語でプログラミングしませんか

ここまでずっとC言語の話をしてきてなんですが、C言語より楽しい言語がたくさんあります

他のもっとナウい言語を勉強してみませんか。

どの他の言語でも、どこかC言語に似ているので、このC言語勉強会で学んだことは、必ず役に立つはずです。

どの言語が良いかはあなたのやりたいこと次第で決まってきます。先輩方に相談してみよう!

皆さん、お疲れ様でした。

終わり

Special thanks...