ファイル入出力と標準ライブラリ
C言語勉強会 第十二回
kumar
October 30, 2013
引用 :
C言語勉強会 第十二回
kumar
October 30, 2013
引用 :
C言語も当然、ファイル入出力ができる。
ファイル入出力があなたのプログラムのレベルを大幅に上げることは想像に難くない。少し複雑なプログラムなら当然、設定の保存をしたり、主人公のHPをセーブしたりするはずだ。少し頑張れば、簡単なテキストエディタだって作れるだろうし、ゲームのワールドエディタだってありだ。
それに、C言語のファイル入出力は、初期化やお片づけのためのたった2つの関数を除けば、今まで使ってきたprintfやscanfに、一本の毛が生えただけの関数しか基本的に必要としない。ここまで来たあなたなら、ここから先の内容は楽勝だ。
①ファイルを開く
↓
②ファイルに出力する or ファイルから入力を受ける
↓
③ファイルを閉じる
- お わ り -
開いているファイルは、ファイルポインターと呼ばれる変数を利用して操作する。
また、標準ライブラリstdio.hをインクルードしている必要がある。
簡単のため、実際のコードを見せる前に擬似コードを見せる。
ファイルポインターは長いのでfpと書く。
fp = ファイルオープン("ファイルパス");
ファイルに出力(fp, "hogehogehoge~~~~");
ファイルクローズ(fp);
ファイルオープン関数はそのファイルを指し示すファイルポインターを返している。
なぜファイルポインターを出力(入力)関数に渡しているのかは、C言語を作った人の気持ちになって考えればすぐわかるはずだ。
ファイルクローズは、これ以上このファイルを操作しませんよ、とOSに伝えている。
擬似コードとC言語の関数の対応表は、以下のとおりだ。
| 擬似 | C言語 |
|---|---|
| ファイルオープン | fopen |
| ファイルクローズ | fclose |
| ファイルに出力 | fprintf |
| ファイルから入力 | fscanf |
そしてファイルポインターは、FILE *が担当する。
さあ、この表を参考にCのコードに書き換えてみよう!
あっという間にファイル出力が出来ました。
FILE * handle = fopen("unko.txt", "w");
fprintf(handle, "hogehogehoge~~~~~\n");
fclose(handle);
コンパイルして実行すると、unko.txtが出来上がっているはずだ。
ただし、このコードは簡単のため、万が一ファイルを開けなかった場合のエラー処理を怠っている。そして、関数fopenの2個めの引数の意味もまだわからないだろう。
それらも含め、これより各部分を解説する。
データの入力または出力の機能を提供するもの。
ここで言うデータとは、ファイル、メモリ、ネットワーク通信等、様々である。
C言語における、ファイルのストリーム
FILE *
FILE構造体へのポインタである。 C言語ではこれを介して、ファイル(等)の入出力を行う。
ハンドルや、ストリームポインタとも呼ばれる。
これを含め、ファイル入出力のための構造体や関数などの定義は、標準ライブラリ stdio.h で定義されている。
FILE *fopen(const char *path, const char *mode);
path で指定された名前のファイルを開き、ストリームと結びつける。
引数 mode は、以下の文字をひとつ以上組み合わせた文字列へのポインタである。
それには以下がある。(次のページへ)
| 文字 | モード | 意味(ていねい) |
|---|---|---|
| r | read | テキストファイルを読み出すために開く。 |
| w | write | ファイルを書き込みのために開く。(なければ新規作成、あれば上書き) |
| a | add | 追加(ファイルの最後に書き込む)のために開く。(なければ新規作成) |
加えて、末尾にbをつけるとバイナリモードで開く。
戻り値は開いたファイルを示すFILEポインタ。失敗するとNULL。
簡単のためかなり省略している。完全な説明は
はまだ載せずに、開いたファイルの閉じ方を紹介することにした。次のページヘ。
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);
int fprintf(FILE *stream, const char *format, ...);
出力をstreamに書き出す。
お察しの通り、printfに一つ引数が増えただけで、以下のように使う。
fprintf(yourstream, "%d個のうんこがある¥n", 10);
このほかにfputcや、fputsなどがある。
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言語の標準規格で定められた、型・マクロ・関数の集合からなるライブラリ
今まで使ってきた、 stdio.h や stdlib.h などのことである。
例えば、
stdio.hをインクルードすれば、printfなどの標準入出力や、今までやっていたファイル入出力が扱え、
math.hをインクルードすれば、sinやcos等の数学関数が使え、
time.hをインクルードすれば、現在時刻が得られる・・・などだ。
Wikipediaによると、最新の規格、C11では29種類ある。
ググれば出てくるので自分で調べて欲しい。Wikipediaで見やすくまとめられているのでおすすめだ。
ソースコードをコンパイルする前に、ソースコードに対して行われる前処理をプリプロセス(preprocess)と呼ぶ(直訳)。そのプリプロセスを行うプログラムのことをプリプロセッサと呼ぶ。
C プリプロセッサは、以下の4つの機能を提供する。
プリプロセッサのための命令を、プリプロセッサディレクティブという。ディレクティブは命令という意味である。
#が付いている。gccで、プリプロセスのみの実行を行うには、-Eオプションを使う。標準出力に結果が出力される。
#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 にはいくつか文法がある。ここではメジャーなもの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 を返す。
()で囲まないとヤバい()で囲まなかったせいでヤバい例#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言語勉強会で学んだことは、必ず役に立つはずです。
どの言語が良いかはあなたのやりたいこと次第で決まってきます。先輩方に相談してみよう!
皆さん、お疲れ様でした。