見出し画像

続C言語教室 - 第4回 ファイル入力 その3

さて前回はキーボードやファイルからの「1行入力」というのを取り上げました。

続C言語教室 - 第3回 ファイル入力 その2

そこで、改行コードが合っていないと1行をうまく取り出せないので、いっそのこと1文字ずつ読み出すのがいいのかもというハナシで終わりました。今回は、その1文字ずつを説明します。

キーボードから1文字を読み込むのが getchar、ファイルから読むのが getcまたは fgetc です。さて先にファイルから読み込む getc から行きましょう。

#include <stdio.h>
void main() {
  int c;
  FILE *file = fopen(”sample.txt”, “r”);
  while (( c = getc(file)) ! = EOF) {
    printf(”%c”, c);
  }
  fclose(file);
}

基本的には1行ずつ読み込むときと同じです。違いは関数を呼び出すたびに1行ではなく1文字ずつ返ってくることです。この while 文の条件節に文字を入力するコードを書けてしまうのが、C言語らしいところですよね。getc の戻り値を c に代入して、その値がEOFと等しくないかを判定しているので、このループを抜けるのは ファイルが最後の位置に到達して、もう読むことが出来ないので getc が EOF を返したときです。

注意するところは戻り値の型が int であるところです。getcは1文字というか1バイトを読み込むので、charを返したのではファイル終端を示すEOFを表す値を上手に返す方法がありません。そこで char ではなく int を使って char には含まれない「はず」のEOFを返すことにしたようです。なんとなく歯がゆい説明をしているのは、実は理由があるのですが、今は 「EOF が戻るまで開いたファイルから1文字ずつ読むんだよ」と理解しておいてください。

getc

ところで1行ではなく1文字ずつ読むと遅くなるのではないかと思う方もいるでしょう。だいたいにおいてファイルからデータを読み込む時には、OSはディスク上のひと塊の領域を1度に読み込んで、そこから必要なデータだけを取り出しているので、その都度ディスクにアクセスしているわけではありません。それに「加えて」FILE構造体を使った入出力に関してはライブラリ側でもバッファを持っていて、関数を呼び出す毎にOSとのやりとりが発生することもありません。多くの処理系では getc は実際にはマクロで定義されていることが普通です。よって引き数に副作用のある式を書くのは厳禁です。普通はファイル構造体へのポインタをインクリメントとかはしないので大丈夫だとは思いますけどね。そこで関数で定義されていることがわかっている fgetc も用意されています。もっとも殆どの処理系では最適化により関数であってもインライン展開され関数呼び出しすら消滅してしまうので、いずれのせよパフォーマンスの心配はまったく要りません。

fgetc

ところでFILE構造体がバッファ処理をすることは、なかなか微妙な動作をすることがあります。ということで、次はキーボードからの入力もやってみましょう。

#include <stdio.h>
void main() {
  int c;
  while((c = getchar())  ! = EOF) {
    printf(”%c”, c);
  }
}

キーボードから入力してみると、少し不思議な動作をするかもしれません。このままのコードだと改行コードを入力されるまで入力待ちが続きます。どんどん文字は打てるのですが、改行コードを押すと、今まで打ち込んだ文字が一度に表示されます。これは入力がバッファされており、改行コードが打ち込まれたところで、ようやく処理が始まり、そこまでの入力に対して1文字ずつ処理が行われるのです。こんなところにも端末時代に作られた痕跡が残っているようで、便利なのか不便なのか悩ましいところです。

さて、キーボードからの入力には「終わり」というものは無いので、どんな時にEOFが返るのでしょうか。それぞれのOSには「テキストファイルの最後」というまさにEOF(End Of File)を意味する制御文字があって(UNIX系ならCTRL-D、WindowsならCTRL-Z)、この文字をキーボードからいれることで getchar は EOF を返します。ですからこの文字を入れれば先のプログラムは無事に終了するはずなのですが、ここでもバッファがチョッカイを出すので「EOF文字+改行」でようやく目的を果たせることになるわけです。ちなみに1文字を出力するには、printfではなく putchar も使えます(この場合は単に文字として1バイトを出力するだけで書式制御はありません)。

なお、1文字と言っていますが、これはあくまで1バイトで表現できる1文字であって、日本語などのマルチバイト文字を入力しても処理はあくまで1バイトずつです。

printf("%c ", c);

と1文字ごとに余分な空白を挟むか、

printf("%02x ", c);

と16進で表示すると実際の処理が見えてくると思います。マルチバイトの1文字ごとの処理は、これまた処理系や文字コード毎に微妙な違いがあるのですが、今のところ受け取ったバイト列をそのまま文字型配列に格納しておけば、大丈夫だと理解しておいてください。

getchar

https://www.orchid.co.jp/computer/cschool/CREF/getchar.html

なんかところどころ歯がゆい説明が残ってしまいましたが、これはFILE構造体を使った入出力がテキストファイルを相手にするように出来ているからです。その「テキスト」がOSや文字コードによって微妙に定義が異なるので、元々は便利だったはずの関数がうまく働かないケースが出てきてしまっているんですね。とはいえC言語での基本はこれらの関数を使うというのは変わっていません。

C言語 getchar関数の使い方【対話プログラムの作り方紹介】

https://monozukuri-c.com/langc-funclist-getchar/

さて、次回は出力側の1行出力と1文字出力を片付けてしまいましょう。


おまけ

そうそう1文字入力では ungetc というものがあって、getc で読み取る動作をキャンセルすることが出来ます。これは1文字しか出来なくて、まだ何も読んでいなかったり、何文字も戻そうとするとエラーになるのですが、これで読んでしまった文字を、もう一度 getc をすることで再び同じ文字を読み出すことが出来たりします。何が便利なのかすぐには思いつかないのですが、ファイルをパースするときなどに使えそうな気はします。

ungetc


さて、今回は研究課題を出しておきます。テキストファイルは必ず末尾にEOFが書き込まれている「はず」なのですが、これが無い場合に getc は正しくファイル終端を検出できるかと、異なるOSのファイル終端文字が書き込まれているとどうなるか。またファイル終端文字を超えてまだテキストが続いている場合に何が起こるのかなんていうのを調べてみると面白そうです。特に0xffというバイトがテキストに含まれている時に、それを getc で読み込むと何が起こるのでしょうね。他にも面白そうなネタはいろいろ落ちているのですが、それらもいずれ出してみようなかな。

参考:

C言語の標準入出力

scanf で1文字入力 - scanf で1文字ずつ読み取るという方法もありますが、ちょっと微妙なところもある

[迷信] scanfではバッファオーバーランを防げない

getchar関数と getche関数の違い(その1)- conio.h には便利そうな関数が用意されているのですが…

getc() getch() getche()の違いがわかりません。

ヘッダ画像は、以下のものを使わせていただきました。https://www.irasutoya.com/2016/09/blog-post_841.html

#C言語 #プログラミング講座 #キーボード入力 #ファイル入力 #getc #fgetc #getchar #EOF #バッファ処理 #改行

この記事が気に入ったらサポートをしてみませんか?