
C言語のきほん|文字列のファイル入出力
1行ずつていねいに扱うからわかりやすい。C言語で学ぶ文字列ファイル入出力の基本。
ここまでC言語では、画面に文字を表示したり、キーボードから文字列を入力したりする方法を学んできました。けれど、実際のプログラムでは、その場で表示するだけではなく、文字列をファイルに保存したい場面や、ファイルに書かれた文章を読み込みたい場面がたくさんあります。
たとえば、日記のようなメモを保存したり、設定ファイルを読み込んだり、文章データを1行ずつ処理したりするときには、文字列のファイル入出力がとても役立ちます。
C言語では、このような文字列の読み書きに、主に fgets 関数と fputs 関数を使います。
fgets は、ファイルから1行分の文字列を読み込むときに便利な関数です。
一方で fputs は、文字列をそのままファイルへ書き込むときに使います。
この2つの関数は、数字を扱う fscanf や fprintf と比べて、文章やメッセージをそのまま扱いやすいのが特徴です。
とくに、複数行のテキストを順番に読み込んだり、そのまま別のファイルへ書き出したりするときには、とてもわかりやすく使えます。
ここでは、文字列をファイルに入出力する基本を、流れとしくみがしっかり見えるように、やさしく整理しながら見ていきましょう。
文字列をファイルに入出力するとは
文字列のファイル入出力とは、文字や文章の並びを、ファイルに書き込んだり、ファイルから読み込んだりすることです。
たとえば、次のような処理が考えられます。
| したいこと | 使い方の例 |
|---|---|
| メッセージを保存したい | 学習メモや日報をテキストファイルに書く |
| 保存済みの文章を読みたい | メモ帳で作った文章を読み込む |
| 1行ずつ処理したい | ログファイルや文章ファイルを順番に読む |
| 内容を別ファイルへ移したい | 元ファイルの文章を新しいファイルへコピーする |
文字列のファイル入出力では、数値を扱うときよりも、行という単位がとても大事になります。
なぜなら、文章ファイルでは1行ずつ意味が区切られていることが多いからです。
そのため、fgets で1行読み込み、fputs でその1行を書き出す、という流れはとても基本的で大切です。
fgets関数とは
fgets は、ファイルから文字列を読み込むための関数です。
もともと標準入力からの文字列入力でも使えましたが、本来はストリームから1行分の文字列を読むための関数です。
関数宣言は次のとおりです。
#include <stdio.h>
char *fgets(char *restrict s, int n, FILE *restrict stream);fgetsの役割
fgets は、stream が指すファイルから最大 n - 1 文字を読み込み、配列 s に格納します。
読み込みは、次のどれかに達した時点で止まります。
- 改行文字に達したとき
- ファイルの終端に達したとき
- 最大文字数まで読み込んだとき
そして、最後には自動で文字列の終端を表すナル文字が付きます。
ここで大切なのは、改行文字も読み込まれることがあるという点です。
これは初心者が最初に少し戸惑いやすいポイントです。
たとえば、ファイルに次の1行があったとします。
こんにちはこの行の末尾に改行が入っていれば、fgets で読み込んだ配列の中には、次のようなイメージで文字が入ります。
こ ん に ち は \n \0つまり、見た目にはわかりにくくても、改行文字も配列の中に入っていることがあります。
fgetsの返却値
fgets の返却値も大事です。
| 返却値 | 意味 |
|---|---|
| s | 読み込み成功 |
| NULL | ファイル終端に達して何も読めなかった、またはエラー |
このため、while 文と組み合わせるととても便利です。
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
/* 読み込めた行を処理する */
}この形は、文字列ファイル入出力の基本パターンのひとつです。
1行読めるあいだ処理を続けて、読み込めなくなったら終わります。
fputs関数とは
fputs は、文字列をファイルへ書き込むための関数です。
関数宣言は次のとおりです。
#include <stdio.h>
int fputs(const char *restrict s, FILE *restrict stream);fputsの役割
fputs は、文字列 s を stream が指すファイルへ書き込みます。
このとき、文字列の終端を表すナル文字は書き込みません。
また、もうひとつとても重要なのが、自動で改行を入れないことです。
たとえば、次のように書いたとします。
fputs("おはようございます。", fp);
fputs("今日も学習を進めます。", fp);この場合、ファイルの中では2つの文字列が続けて書かれるので、結果はこうなります。
おはようございます。今日も学習を進めます。行を分けたいなら、自分で改行文字を入れる必要があります。
fputs("おはようございます。\n", fp);
fputs("今日も学習を進めます。\n", fp);このように書けば、ファイルの中で2行に分かれます。
fputsの返却値
fputs の返却値は次のように考えれば十分です。
| 返却値 | 意味 |
|---|---|
| 非負の値 | 書き込み成功 |
| EOF | エラー |
学習の最初の段階では、まずは「成功なら非負、失敗なら EOF」と押さえておけば大丈夫です。
fgetsとfputsの違いを整理しよう
この2つの関数は、名前が似ているので混同しやすいですが、役割ははっきり分かれています。
| 関数 | 役割 | 特徴 |
|---|---|---|
| fgets | ファイルから文字列を読む | 改行文字も読み込むことがある |
| fputs | ファイルへ文字列を書く | 改行は自動では付かない |
この違いはとても重要です。
読む側では改行を含んで受け取ることがあり、書く側では改行を自分で付ける必要があります。
つまり、読み込みでは改行が入ることに注意し、書き込みでは改行を必要に応じて自分で付ける、という意識を持つと整理しやすいです。
文字列ファイル入出力の流れ
文字列をファイルで扱うときも、基本の流れは変わりません。
- ファイルを開く
- 文字列を読み込む、または書き込む
- ファイルを閉じる
これを図にすると、次のようなイメージになります。

この図では、入力ファイルから fgets で1行を取り出し、その内容をバッファに入れ、その後 fputs で出力ファイルへ書き出す流れを表しています。
ここでのポイントは、直接ファイルからファイルへ移しているのではなく、いったん文字配列を経由していることです。
この文字配列は、よく buffer という名前で使われます。
buffer の中に1行分の文字列が入り、その内容を使って表示したり、加工したり、別のファイルへ保存したりできます。
つまり、fgets と fputs を使えるようになると、ただのコピーだけでなく、文章の一部を変更してから保存するといった処理にもつなげやすくなります。
サンプルプログラム
この例では、message.txt というファイルを読み込み、その内容を backup_message.txt にコピーします。
ファイル名:17_2_1.c
#include <stdio.h>
int main(void)
{
/* 読み込み用ファイルを開く */
FILE *fin = fopen("message.txt", "r");
if (fin == NULL) {
printf("読み込み用ファイルを開けませんでした。\n");
return 1;
}
/* 書き込み用ファイルを開く */
FILE *fout = fopen("backup_message.txt", "w");
if (fout == NULL) {
printf("書き込み用ファイルを開けませんでした。\n");
fclose(fin);
return 1;
}
/* 1行分の文字列を入れる配列 */
char buffer[256];
/* ファイルの終わりまで1行ずつ読み込み、書き込む */
while (fgets(buffer, sizeof(buffer), fin) != NULL) {
fputs(buffer, fout);
}
/* ファイルを閉じる */
fclose(fin);
fclose(fout);
printf("メッセージファイルのコピーが完了しました。\n");
return 0;
}
このプログラムで使っているファイルのイメージ
たとえば、message.txt に次のような内容が入っているとします。
おはようございます。
今日はC言語の復習をします。
文字列のファイル入出力を練習中です。このプログラムを実行すると、backup_message.txt が作られ、同じ内容が書き込まれます。
画面には次のように表示されます。
メッセージファイルのコピーが完了しました。この例のよいところは、文字列を1行ずつ読み書きする意味が目で見てわかりやすいことです。
ソースコードのコピーでも仕組みは同じですが、文章ファイルのほうが fgets と fputs の役割を素直に理解しやすいです。
読み込み用ファイルを開く処理を見てみよう
最初の部分では、読み込み用ファイルを r モードで開いています。
FILE *fin = fopen("message.txt", "r");r モードは、既存のファイルを読むためのモードです。
そのため、message.txt が存在しない場合は開けません。
だからこそ、すぐあとでエラーチェックをしています。
if (fin == NULL) {
printf("読み込み用ファイルを開けませんでした。\n");
return 1;
}この確認を入れておくことで、存在しないファイルを無理に読もうとして問題が起きるのを防げます。
書き込み用ファイルを開く処理を見てみよう
次に、書き込み用ファイルを w モードで開いています。
FILE *fout = fopen("backup_message.txt", "w");w モードは、書き込み用のモードです。
ファイルがなければ新しく作られ、すでに存在していれば中身を空にして開きます。
ここはとても大事な注意点です。
つまり、backup_message.txt がすでにあった場合、その中身は消えて新しく書き直されます。
そのため、以前の内容を残したいときには w ではなく a を使うことがありますが、この例では「コピー先を新しく作る」目的なので w が自然です。
もしここで fout のオープンに失敗したら、先に開いていた fin を閉じてから終了しています。
if (fout == NULL) {
printf("書き込み用ファイルを開けませんでした。\n");
fclose(fin);
return 1;
}このように、途中で失敗したときも、先に開いたファイルをきちんと閉じるのは大切な書き方です。
buffer配列の役割を理解しよう
このプログラムでは、次の配列を用意しています。
char buffer[256];これは、1行分の文字列を一時的に入れておくための箱です。
fgets は、ここにファイルから読んだ文字列を入れます。
256 という大きさにしているので、1回で読めるのは最大255文字分です。
最後の1文字は、文字列の終端を表すナル文字のために使われます。
つまり、fgets の第2引数に sizeof(buffer) を書くと、
- buffer 全体の大きさを渡せる
- 書き間違いしにくい
- 配列サイズをあとで変えても対応しやすい
というメリットがあります。
while文とfgetsの組み合わせがとても大切
この部分が、このプログラムの中心です。
while (fgets(buffer, sizeof(buffer), fin) != NULL) {
fputs(buffer, fout);
}ここでは、まず fgets が1行読み込みます。
読み込みに成功すると buffer が返されるので、条件式は真になります。
その結果、ループの中で fputs が実行され、その1行が出力ファイルに書き込まれます。
この流れをくり返して、ファイルの終端に達すると fgets は NULL を返します。
すると条件式が偽になって、while 文が終了します。
この書き方はとてもきれいで、文字列ファイル処理の定番です。
流れを順番に書くと、こうなります。
- 1行読む
- 読めたらその行を書く
- また次の1行を読む
- 読めなくなるまで続ける
このように、fgets と while は非常に相性がよいです。
fgetsで読み込むと改行も入ることに注意しよう
fgets を使うときに、とくに気をつけたいのが改行文字です。
ファイルに次のような内容があるとします。
りんご
みかん
ぶどうこのとき、通常は各行の末尾に改行があります。
そのため、fgets で1行目を読むと、buffer の中には次のような内容が入ることがあります。
りんご\nつまり、改行も含んだ形で読み込まれます。
この性質のおかげで、今回のようにそのまま fputs で書き出すと、元のファイルと同じ改行位置を保ったままコピーできます。
これはファイルコピーのような処理ではとても都合がよいです。
一方で、画面表示のときや文字列比較のときには、末尾の改行が邪魔になることもあります。
その場合は、必要に応じて改行を取り除く処理を入れます。
つまり、fgets の改行は「便利なときもあるし、注意が必要なときもある」と考えるとよいです。
fputsは改行を自動で付けない
fputs は、受け取った文字列をそのまま書き込みます。
ここで大切なのは、行を分けたければ、文字列の中に改行が必要だということです。
今回のプログラムでは、fgets が改行を含んだ文字列を buffer に入れてくれるため、そのまま fputs しても問題ありません。
けれど、もし自分で文字列を書き込む場合は注意が必要です。
たとえば次のようなコードでは、
fputs("1行目のメモ", fp);
fputs("2行目のメモ", fp);ファイルの中身はこうなります。
1行目のメモ2行目のメモ行を分けるには、こう書きます。
fputs("1行目のメモ\n", fp);
fputs("2行目のメモ\n", fp);この違いはとてもよく出てくるので、早めに慣れておくと安心です。
文字列コピーの処理は実はとても応用が利く
今回のプログラムは単純に見えるかもしれませんが、実はこの形はさまざまな応用の土台になります。
たとえば、while 文の中で buffer をそのまま書くだけでなく、少し加工してから書くこともできます。
| 応用例 | 内容 |
|---|---|
| 行番号を付ける | 読み込んだ各行の先頭に番号を付ける |
| 特定の語句を置き換える | 文章中の単語を別の単語に変える |
| 空行を飛ばす | 空の行だけは出力しない |
| 画面にも表示する | ファイルに書くだけでなく printf でも確認する |
つまり、fgets で1行取り出せるようになると、その1行に対して自由に処理を加えられるようになります。
ここが文字列入出力のおもしろいところです。
行単位で扱うことのよさ
文字列ファイル入出力では、1文字ずつ扱う方法もありますが、最初の学習では行単位のほうが理解しやすいです。
| 扱い方 | 特徴 |
|---|---|
| 1文字ずつ | 細かい制御ができるが、処理の流れが少し見えにくい |
| 1行ずつ | 文章としてまとまりがあり、処理内容を把握しやすい |
文章ファイルは、多くの場合「1行ごとに意味がある」ので、fgets と fputs の組み合わせはとても自然です。
とくに、ログ、メモ、設定ファイル、文章データなどでは、この方法がよく使われます。
このプログラムから学んでおきたいこと
今回の内容では、単に fgets と fputs の名前を覚えるだけでなく、次の点をしっかり意識しておくと理解が深まります。
| 学んでおきたい点 | 内容 |
|---|---|
| fgets は1行読む | 改行を含むことがある |
| fputs はそのまま書く | 自動改行はしない |
| while と相性がよい | 読める限り処理を続けられる |
| バッファが必要 | いったん配列に入れて扱う |
| 開いたファイルは閉じる | fclose を忘れない |
この5点が頭の中でつながると、文字列のファイル入出力がかなり整理されます。
文字列のファイル入出力は実用的なプログラムへの入口
画面に文字を出すだけのプログラムから一歩進むと、文字列をファイルに保存したり、あとで読み込んだりしたくなります。
そこで活躍するのが fgets と fputs です。
この2つを使えるようになると、文章データを扱うプログラム、ログを保存するプログラム、メモを読み込むプログラムなど、実用的な処理にぐっと近づきます。
最初は小さなコピーの例からで十分ですが、その中にファイルを開く、1行ずつ読む、1行ずつ書く、最後に閉じるという大切な流れがしっかり詰まっています。
文字列のファイル入出力は、見た目は地味でも、とても実践的で応用の広いテーマです。
ぜひ実際に短いテキストファイルを用意して、自分の手で動かしながら感覚をつかんでみてください。
