おさえておきたいプログラミングの基本
複数のデータをひとまとまりにしたものが構造体です。
ここでは、C言語によるファイルの読み書きについて学ぶこととしましょう。その前に、まずはファイルの種類について簡単に説明しましょう。以下の表を見てください(表6-1)。この表から見てもわかる通り、ファイルには大きくわけて、テキストファイルとバイナリファイルが存在します。C言語でこれらを扱う方法は、基本的な部分に変わりはありませんが、ここではよりわかりやすく、汎用性の高いテキストファイルから説明することにします。(表6-1)
表6-1.ファイルの種類ファイルの種類 | 内容 | 例 |
---|---|---|
テキストファイル | 文字として読めるデータ。 | .txtファイル、.cファイル、.htmlファイルほか |
バイナリファイル | 文字として読めないデータ。(画像や音声など) | .pngファイル、.wavファイルほか |
では、実際に来れに代入したり、出力したりするサンプルを見てみましょう。
listex6-1:main.c#include <stdio.h>
#include <stdlib.h>
int main(int argc,char** argv){
FILE *file; // ファイルポインタ(出力用)
file = fopen("c:\\test\\sample.txt", "w"); // ファイルを書き込み用にオープン(開く)
if (file == NULL) { // オープンに失敗した場合
printf("ファイルが開けません。\n"); // エラーメッセージを出して
exit(1); // 異常終了
}
// ファイルにデータを書き込む
fprintf(file,"HelloWorld\r\n"); // ファイルに書く
fprintf(file,"ABCDEF\r\n");
fclose(file); // ファイルをクローズ(閉じる)
return 0;
}
プログラムを実行しても、画面上に特に大きな変化は現れません。しかし、testフォルダを開くと、「sample.txt」というファイルが存在し、中を見ると
sample.txtと記述されているはずです。
では、順を追って説明していきましょう。まず、2行目でファイルのポインタを宣言しています。これは、ファイルを操作するときには必ず必要になるポインタです。
ファイルポインタの宣言次に、3行目でファイルをオープンしています。通常、ファイル処理を行うときは、対象となるファイルを「オープン」する処理が必要となります。
ファイルのオープンファイルのオープンは、fopen関数を用いますが、書式は、以下の通りです。(表6-2)
表6-2.ファイルオープンの関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fopen() | fopen(ファイル名,モード); | ファイルを、指定したモードで開く。 戻り値が、ファイルのポインタ。 | FILE* fp = open("sample.txt","w"); |
このプログラムの場合、c:¥testフォルダ内にある、sample.txtというファイルを開くことになります。モードでで出ている"w"は、書き込みを意味します。なお、fopenのモードには、以下のものがあります。(表6-3)
表6-3.fopen()関数のモードモード | 機能 | ファイルが存在しないとき |
---|---|---|
r | テキストデータの読み込み | エラーになる |
w | テキストデータの書き込み | ファイルを新規作成する |
a | テキストデータへの追加書き込み | ファイルを新規作成する |
r+ | テキストデータを更新モードで開く(書き込み・読み込み共に可) | エラーになる |
w+ | テキストデータを更新モードで開く(書き込み・読み込み共に可) | ファイルを新規作成する |
a+ | テキストデータを更新モードで開く(書き込み・読み込み共に可) | ファイルを新規作成する |
rb | バイナリデータの読み込み | エラーになる |
wb | バイナリデータの書き込み | ファイルを新規作成する |
ab | バイナリデータへの追加書き込み | ファイルを新規作成する |
rb+ / r+b | バイナリデータを更新モードで開く(書き込み・読み込み共に可) | エラーになる |
wb+ / w+b | バイナリデータを更新モードで開く(書き込み・読み込み共に可) | ファイルを新規作成する |
ab+ / a+b | バイナリデータを更新モードで開く(書き込み・読み込み共に可) | ファイルを新規作成する |
この表から判る通り、ここでは、テキストファイルを書き込みモードで開いていることが判ります。このとき、ファイルのオープンが失敗すると、戻り値にNULLが返されます。そのときは、異常終了の処理に入ります。
プログラムの終了にはexit() 関数を用います。引数 status は 0 以上 255 以下の値を設定し、正常終了の場合には0を設定します。今回は異常終了として、引数に1を指定しています。
exit関数を利用したプログラムの異常終了また、ファイルのオープンに成功すると、fileにファイルポインタが渡されます。今後、開かれたファイルに対するアクセス(書き込み・読み込みなど)をするには、このポインタが必要になります。
このプログラムは、ファイルを書き込みのプログラムなので、次はファイルの書き込み処理となります。まず、書き込み処理の関数を見てみましょう。(表6-4)
表6-4.ファイルへの書き込み処理関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fprintf() | fprintf(ファイルポインタ,書き込み文字列,変数・・・); | 文字列をファイルに書き込む | fprintf(fp,"no=%d",i); |
見てわかるとおり、fprintf()は、画面に文字を出力するprintf()関数と似ています。ですので、最初にファイルのポインタを引数として渡す以外は基本的な文字列の操作はprinf関数とかわりません。
ただ、気を付けなくてはならないのが開業の場合です。これはOSによって異なります。
なお、OSがウィンドウズの場合、改行コードは「\n」ではなく、「\r\n」と2文字にります。改行を「\n」とすると、ファイルが改行されない可能性があるので気をつけましょう。linux系のOSでは、「\n」に書き変えなくてはなりません。
ファイル書き込みの一連の処理が終わったら、ファイルを閉じますファイルをとじるのはclose(クローズ)関数です。書式は以下の通りです。(表6-5)
表6-5.ファイルをクローズする関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fclose() | fclose(ファイルポインタ); | 指定したファイルポインタのファイルを閉じる | fclose(fp); |
これで、ファイルをオープンしてから、書き込み、クローズまでの一連の処理がわかりました。次は、書き込んだファイルを読み込んでみましょう。
次は、listex6-1で書き込んだファイルを読み込み、画面に表示するプログラムを作って見ましょう。以下のサンプルを実行してみてください。
listex6-2:main.c#include <stdio.h>
#include <stdlib.h>
#define SIZE 256
int main(int argc, char** argv) {
FILE *file; // ファイルポインタ(読み込み用)
char line[SIZE]; // 読み込む行
line[0] = '\0'; // 初期化(空文字列)
file = fopen("c:\\test\\sample.txt", "r"); // ファイルを読み込み用にオープン(開く)
if (file == NULL) { // オープンに失敗した場合
printf("ファイルが開けません。\n"); // エラーメッセージを出して
exit(1); // 異常終了
}
// ファイルのデータ読み込む
while ( fgets(line, SIZE, file) != NULL ) {
printf("%s", line);
}
fclose(file); // ファイルをクローズ(閉じる)
return 0;
}
実行結果
実行結果から判る通り、このプログラムによって、さきほど書き込まれたsample.txtが読み込まれ、その内容が表示されているのがわかります。ファイル読み込み時も、ファイルをオープンし、クローズするという一連の流れは変わりません。違いは、オープン時にモードが"r"になっていることぐらいです。
また、ファイルからデータを読み出す関数は複数ありますが、ここでは特に、テキストデータということで、テキストデータを一行読み出すfgets()関数を用いました。以下に、関数の説明を書きます。(表6-6)
表6-6.fgets()関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fgets() | fgets(文字列,文字列サイズ,ファイルポインタ); | 指定したサイズの文字列を ファイルから読み込む。 | fgets(s,10,fp); |
ここでいう文字列は、最初の引数として指定した文字列のサイズのことです。読み込みが成功すれば、これと同じ値が返ってきます。読み込みがうまくいかなかったり、最後まで読みきった場合は、NULLが帰ってきますので、このプログラムでは、NULLが帰ってくるまで読み込みを行い、表示するという処理をwhileループで行っています。
このプログラムで、文字列を入れる配列lineは、256という長さをとっています。気をつけなくてはならないのは、実際の文字列で一行の最大の文字列がこの値を超えてしまう場合もあるということです。なので、この配列のサイズは長めに取っておくことをおすすめします。このプログラムでも、実際はせいぜい10文字前後の文字列を読み込むのに、不必要なまでに大きなサイズの文字列を取っているのはそのためです。
しかし、それでもファイルの一行のサイズが、用意された文字列のファイルのサイズを超えてしまうことも想定されます。そこで、次に、1行ごとではなく、1文字ずつファイルを読み込み、表示する方法を紹介しましょう。
listex6-3:main.c#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
FILE *file; // ファイルポインタ(読み込み用)
int c; // 読み込む文字のコード
file = fopen("c:\\test\\sample.txt", "r"); // ファイルを読み込み用にオープン(開く)
if (file == NULL) { // オープンに失敗
printf("ファイルが開けません。\n"); // エラーメッセージ
exit(1); // 異常終了
}
// ファイルのデータ読み込む
while ((c=fgetc(file)) != EOF){
printf("%c",(char)c);
}
fclose(file); // ファイルをクローズ(閉じる)
return 0;
}
実行結果は、listex6-2と同じなので省略します。
このプログラムでは、fgetc()関数を用いて一字ずつ文字を取得しそれを表示しています。
変数cには、文字コードが入りますので、printf()関数では、文字列ではなく、文字の表示を表す'%c'が用いられています。fgetc()を実行すると、先頭から順次ファイルを読み込んでいきます。
戻り値が文字コードとなって いますが、ファイルの最後まで到達すると、EOFが返ってきます。EOFは「End of File」の略で、ファイルの終端を示す特別なマーカーであり、データの終わりを検出するために必要です。
したがって、このプログラムでは、EOFが出現するまでファイルを読み続け、表示することにより、読み込んだファイルが全て表示される仕組みになっています。
fgetc()とfgets()の違いのイメージは、以下のようになります。(図6-1)
図6-1.fgetc()とfgets()の違い続いて、バイナリファイルの書き込みと読み込みを行うプログラムを作ってみましょう。
listex6-4:main.c#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
FILE* file;
int i;
// 書き込むデータ
char wdata[] = { 0x10 , 0x1a , 0x1e , 0x1f };
char rdata[4];
//バイナリデータの書き込み
file = fopen("C:\\test\\test.bin","wb");
if(file == NULL){
printf("ファイルオープンに失敗しました。\n");
exit(1);
}
fwrite(wdata,sizeof(char),sizeof(wdata),file);
fclose(file); // ファイルをクローズ(閉じる)
//バイナリデータの書き込み
file = fopen("C:\\test\\test.bin","rb");
if(file == NULL){
printf("ファイルオープンに失敗しました。\n");
exit(1);
}
fread( rdata, sizeof(char), sizeof(rdata), file );
fclose(file); // ファイルをクローズ(閉じる)
// 結果を表示
for(i = 0; i < sizeof(rdata) ; i++){
printf("%x ",rdata[i]);
}
printf("\n");
return 0;
}
実行結果
このサンプルでは、前半でバイナリファイルの書き込み、後半でバイナリファイルの読み込みを行っています。
10行目から17行目は、ファイルへデータを書き込んでいます。fopen()でファイルを開くのは同じですが、モードを"wb"に指定していることから、バイナリモードでの書き込みとなります。
ここでは、fwrite()でデータを書き込みます。ファイル名は、cドライブ直下のtestフォルダー内にあるtest.binに書き込まれます。
データは、char型の配列wdataのデータを書き込んでいます。最後は、fclose()でファイルを閉じて、書き込みが終了します。
ここで出てきたfwrite()関数の仕様は以下の通りです。(表6-7)
表6-7.fwrite()関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fwrite() | fwrite(データ,データのバイト長, データの数,ファイルポインタ); | バイナリデータ の書き込み。 | fwrite(data,sizeof(int),sizeof(data),fp); |
16行目のfwrite()関数では、wdataのデータを書き込んでいます。wdataは、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、データの数に、配列wdataの大きさを指すsizeof(wdata)を指定します。
次に、18行目から25行目でデータを読み込みます。fopen()でファイルを開きますが、今度はバイナリファイルの読み込みなので、モードに"rb"を指定します。バイナリモードでの読み込みは、fread()関数を用います。ここでは、char型の配列rdataに値を読み込んでいます。最後はclose()でファイルを閉じて終了です。
最後に、rdataの値を16進数で表示すると、書き込んだ通りのデータが読み込まれていることがわかります。
ここで出てきたfread()関数の仕様は、以下の通りです。(表6-8)
表6-8.fread()関数関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fread() | fread(データ,データのバイト長, データの数,ファイルポインタ); | バイナリデータ の読み込み。 | fread(data,sizeof(int),sizeof(data),fp); |
24行目のfread()関数では、rdataのデータを書き込んでいます。rdataは、fwriteの場合と同様に、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、データの数に、配列rdataの大きさを指すsizeof(rdata)を指定します。
listex6-4で、バイナリファイルの基本的な読み書きの説明をしました。しかし、ここには大きな問題点があります。バイナリファイルに限った事ではありませんが、ファイルはき込むときは比較的簡単ですが、読み込むときには大きな問題があります。それは、読み込むべきファイルの大きさが分からないということです。
おそらく、ほとんどのプログラムは、サイズのわからないデータを読み込まなければならないはずです。そういった時にやるべきことは、ファイルの大きさを取得し、その分のメモリを確保してデータを読み込むことです。それには、fseek()関数、およびftell()関数が必要となります。まずは、以下のサンプルを見てください。
listex6-5:main.c#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
FILE* file;
int i,size;
// 書き込むデータ
char* rdata;
//バイナリデータの書き込み
file = fopen( "C:\\test\\test.bin", "rb" );
if( file == NULL ){
printf( "ファイルオープンに失敗しました。\n" );
exit( 1 );
}
// ファイルの最後までシーク
fseek(file, 0, SEEK_END);
// ファイルの大きさを取得
size = ftell(file);
// メモリのサイズだけ、配列を動的に生成
rdata = (char*)malloc(sizeof(char)*size);
// 再るの最後までポインタを戻す
fseek(file,0,SEEK_SET);
fread(rdata, sizeof(char), size, file);
fclose(file); // ファイルをクローズ(閉じる)
// 結果を表示
for(i = 0; i < size ; i++){
printf("%x ",rdata[i]);
}
printf("\n");
// メモリ解放
free(rdata);
return 0;
}
実行結果
このサンプルは、listex6-4で書き込まれた「test.bin」を読み出して表示するプログラムです。実行結果は同じなので省略します。
ここで用いられているfseek関数およびftell関数は、ファイルの位置を指定する関数です。仕様は以下の通りです。(表6-9)
表6-9.fseek(),ftell()関数の仕様関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fseek() | fseek(ファイルポインタ, 移動バイト数,開始位置); | ファイルを開始位置から、移動バイト数移動する | fseek(fp, 0L, SEEK_END); |
ftell() | ftell(ファイルポインタ); | 現在のファイル位置の値をバイト数で返す。 | ftell(fp); |
一般にこの二つの関数はセットで使用されるケースがほとんどです。なお、fseek関数で指定される開始位置は、以下の通りです。(表6-10)(図6-2)
表6-10.fseek関数で用いられる開始位置開始位置の定数 | 意味 |
---|---|
SEEK_SET | ファイルの先頭 |
SEEK_CUR | ファイルの現在位置 |
SEEK_END | ファイルの終端 |
では、これをもとにプログラムを解説していきましょう。ファイルオープンまでの流れはlistex6-4と同じなので省略します。まず、16行目のfseek()関数で、ファイルの読み出し位置を、ファイルの終端から0バイチ目、つまり、ファイルの終端にまで持っていきます。
次に、18行目でftell()関数を用いて、位置を取得しています。ftell()では、ファイルの位置を取得してます。ファイルの読み出しの現在位置は終端にあるので、この時得られる値はファイルの大きさのバイト数に相当するはずです。なので、この値を変数sizeに代入し、保存します。
次に、このsizeをもとに、データを格納するメモリを確保(20行目)し、再びfseek()関数を用いて、ファイルの読み出し位置を先頭に戻し(22行目)、そのバイト数だけのデータを読み出します。(23行目)
最後にファイルを閉じ(24行目)、読みだしたデータの結果を表示してから、生成したメモリを開放し(31行目)、プログラムを終了します。
画像データのように、大きさが一定しないデータを読み出すときはこの方法が便利です。尚、fseek()およびftell()はテキストデータの読み出しの時にも使えます。
練習問題 : 問題6.
一週間でわかるC言語・C++言語がオンライン講座になりました!動画と音声によってさらにわかりやすくなりました!! 1講座で2つの言語を学ぶことができる上に、練習問題の回答もダウンロードできます。
Read →本講座が「1週間でC言語の基礎が学べる本」として書籍化されました!サイトの内容プラスアルファでより学習しやすくなっています!Impressより発売中です!!
Read →