C言語/ファイル入出力

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

ファイルのオープン[編集]

まず、ファイルを作成したり読み書きしたりするための関数を使うには、

<stdio.h>をインクルードしないといけないい。

下記のような書式になる。

#include <stdio.h>

int main()
{
	FILE *ファイルポインタ; // ファイルポインタの作成

	ファイルポインタ = fopen("開きたいファイル名.拡張子", "モード");
	
        // ここにファイルの操作内容(作成、読み書きなど)を記述。

	fclose(ファイルポインタ);	
		
	return 0;
}

ファイルに読み書きなどの操作するためには、まず、そのファイルをオープン(開く)する必要がある。

オープンしていない状態(クローズ)の状態のファイルは、操作できない。


fopenがファイル操作のためにオープンする関数であるが、これだけではファイル操作をできず、あらかじめファイルポインタを宣言する必要がある。


ファイルポインタの宣言は、

FILE *ファイルポインタ;

の書式で行う。

例えば、 FILE *fp1; なら、fp1という名のファイルポインタが作成される。



なお Windowsでコードを作る場合、fopen関数が標準設定では禁じられている場合があるので、

その場合、使用を許可するために、下記のように#pragma warning(disable:4996)を下記のような位置に加える。

// Windowsでfopenが使えない場合

#include "stdafx.h"
#include <stdio.h>

#pragma warning(disable:4996)

int main()
{
	FILE *ファイルポインタ; // ファイルポインタの作成

	ファイルポインタ = fopen("開きたいファイル名.拡張子", "モード");
	
        // ここにファイルの操作内容(作成、読み書きなど)を記述。

	fclose(ファイルポインタ);	
		
	return 0;
}

#pragma warning(disable:4996) が、fopenなどの関数の使用許可の命令である。これは、stdafx など一連のWindows用ヘッダのインクルードの後で宣言する必要がある。


実際にWindows用のコードを書くと、下記のようになる。

// Windows用のファイル操作のコード例
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)

int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "w");
	if ( (fp1 = fopen("test1.txt", "w") ) == NULL) {
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}	

	fclose(fp1);	
		
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}


fopen は、モードを"w"で開いた場合、もし対象のファイルが存在しないときは、その名のファイルを新規作成する。

なお、fopenのモードは、

書き込みモードの"w"と、
読み込みモードの"r"と、
追記モードの"a"

がある。


なお、一般にファイル操作のプログラミングでは、安全のために、ファイルのオープンに失敗した場合を想定して、そのような処理を書く必要がある。

fclose は、ファイルを閉じる命令である。クローズしている最中のファイルは、プログラミングによる読み書きなどの操作をできない。


読者は、ソースファイルのあるフォルダを確認してみて、"test1.txt"というファイルが作られている事を確認しよう。


ソースファイルのあるフォルダの場所は、Windowsの場合、標準設定では

\ユーザー名\source\repos\プロジェクト名\プロジェクト名

のアドレスのフォルダにある。

VisualStudio 2019 をインストールしたとき、既に source というフォルダのある場合を除けば、普通は インストール時に source という visual studio のプログラミング用のフォルダが自動作成されているので、そこのフォルダを探せばいい。


さて、このtest1.txtをマウスでクリックして、中身を見ても、まだ何も文章は書き込まれていない事を確認しよう。


読み書き[編集]

ファイルへの書き込み[編集]

ファイルを作成したりオープンしただけでは、まだ何も読み込まれないし、何も書き込まれない。

ファイルに何かデータを書き込むには、 fprintf という関数を使う。


なお、windowsの設定では、ある設定の場合では fopen と同様に fprintf が初期状態では使えない場合があるので、

#pragma warning(disable:4996)

で宣言しておく必要の生じる場合もある。


インクルードすべきヘッダについては、標準出力のヘッダにあるので、

#include <stdio.h>

でインクルードすれば充分である。


さて fprintf で書き込むデータの形式は、変数でもいいし、書き込みたいテキストを直接指定することもできる。

また、もし 変数を書き込む場合なら、変数の型は、文字列型でもいいし、数値型でもいい。


ただし、書き込まれた先のテキストファイルでは、書かれたテキストの由来が何かは、もはや不明になる。つまり、書きこまれた先のテキストファイルでは、書かれた文字列は、すべて単なるテキスト文字の集まりとして扱われる。


さて、たとえばファイルに「test」とだけ書き込みたいなら、 fprintf(fp1, "test \n"); というふうに入力すればいい。


何か変数をファイルに書き込みたい場合、たとえば整数型の変数だとして、変数名が「hensuu」なら、それを書き込むには、あらかじめソースコードで

int hensuu;

のように、どこかで宣言しておく。

そのあと、プログラム中にて変数「hensuu」に目的の値を入力させるようにしておく。

そのあと、プログラムがコード内でのファイル書き込み実行場所にて

fprintf(fp1, "%d \n", hensuu);

のようなコードを実行するように、ソースコードを記述すればいい。



ファイル書き込むのできる変数の型は、数値型も文字列型でも可能である。


次のコード例として、文字列型の変数をファイルに書き込むコード例を示す。

コード例
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)


int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "w");
	if ( (fp1 = fopen("test1.txt", "w") ) == NULL) {
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	char mojiretu[50];
	printf("キーボードから文字列を入力してください。\n");
	scanf("%s", &mojiretu);
	printf("入力された文字列は %s です。 \n", mojiretu);

	printf("これをファイルに書き込みます。 \n");

	fprintf(fp1, "%s \n", mojiretu);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}

このようにして、ファイルへの出力が出来る。


ファイルの読み取り[編集]

次に、ファイルからの読み取りをする方法を学ぼう。

ファイルからの読み取りには、まずfopenの際にモードを"r"にする。

読み取りの際の関数には fgetsfscanf を使えばいい。

fscanffgetsは、ファイルから1行ずつ、読み取ることができる。

なお、 fscanffgets の違いは、

行末の改行を読み込むかどうか、
空白文字(半角スペース)があると読み込みを中断するかどうか、

です。fscanfのほうが、空白文字(半角スペース)があると読み込みを中断します。 くわしくは、あとの章で説明します。

とりあえず、ファイル読み取りの具体例を調べていきましょう。

fscanf の例

あらかじめ、テキストファイル test1.txt に、なんらかの文字列を書いておこう。

一例として、

aaaaaaaaaaa
ssssssss

dddddd

と書いたとする。


コード例を示す。

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)


int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ( (fp1 = fopen("test1.txt", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	
	char str1[150];
	printf("文字列を読み取っています。\n");
	
	fscanf(fp1, "%s", str1);

        printf("ファイルに書いてある文字列\n");
	printf("%s\n", str1);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}


実行結果
ファイルをオープンしました。
文字列を読み取っています。
ファイルに書いてある文字列
aaaaaaaaaaa
ファイルをクローズしました。
続行するには何かキーを押してください . . .


このように、最初の1行(aaaaaaaaaaa)だけが読み取りされている。



2行目以降も読み取りたい場合には、その行数だけ、下記のように繰り返しfscanfを使う必要がある。

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)


int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ((fp1 = fopen("test1.txt", "r")) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}


	char str1[150];
	printf("文字列を読み取っています。\n");

	fscanf(fp1, "%s", str1);
	printf("ファイル1行目に書いてある文字列\n");
	printf("%s\n", str1);


	fscanf(fp1, "%s", str1);
	printf("ファイル2行目に書いてある文字列\n");
	printf("%s\n", str1);


	fclose(fp1);
	printf("ファイルをクローズしました。\n");


	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}


実行結果
ファイルをオープンしました。
文字列を読み取っています。
ファイル1行目に書いてある文字列
aaaaaaaaaaa
ファイル2行目に書いてある文字列
ssssssss
ファイルをクローズしました。
続行するには何かキーを押してください . . .

fgetsとfscanfの違い[編集]

fscanffgets の違いは、大まかには

行末の改行を読み込むかどうか、
空白文字(半角スペース)があると読み込みを中断するかどうか、

です。fscanfのほうが、空白文字(半角スペース)があると読み込みを中断します。fgetsのほうが、改行を読み込みます。

論より証拠で、実際にコードを実行して比べてみましょう。

まず、読み込み対象のテキストファイル test2.textとして、

rt aaaaaaaaaaa
u ssssssss

dddddd

のように、空白文字(半角スペース)を入れてみましょう。rt と aaaaaaaaaaa のあいだに半角スペースが一文字あります。


そしてコードは、(Fedora30 の gccコンパイルで確認)

fgetsのほうは

#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。


int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ( (fp1 = fopen("test2.txt", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	
	char buffer1[150];
	printf("文字列を読み取っています。\n");
	
	fgets(buffer1,150,fp1);

	printf("1回目に読み取った文字列\n");
	printf("%s", buffer1);

	fgets(buffer1,150,fp1);
	printf("2回目に読み取った文字列\n");
	printf("%s", buffer1);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	return 0;
}


このコードでは、printf の%sの後ろには改行文字(\n)をつけていません。なぜなら、fgets ではファイル読み込み時に改行文字も一緒に読み込むからです。


実行結果
ファイルをオープンしました。
文字列を読み取っています。
1行目に書いてある文字列
rt aaaaaaaaaaa
2行目に書いてある文字列
u ssssssss
ファイルをクローズしました。


です。





いっぽう、fscanf のほうは

#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。


int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ( (fp1 = fopen("test2.txt", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	

	char buffer1[150];
	printf("文字列を読み取っています。\n");
	
	fscanf(fp1, "%s", buffer1);

        printf("1回目に読み取った文字列\n");
	printf("%s", buffer1);

	fscanf(fp1, "%s", buffer1);
        printf("2回目に読み取った文字列\n");
	printf("%s", buffer1);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	return 0;
}
実行結果
ファイルをオープンしました。
文字列を読み取っています。
1行目に書いてある文字列
rt2行目に書いてある文字列
aaaaaaaaaaaファイルをクローズしました。

です。


このように、fcanf のほうでは、空白文字があるので、最初の1回目のfscanfでは、「rt」までしか読み込みません。

しかも改行を読み込まないので、 「rt」と「2行目に書いてある文字列」が続いて

rt2行目に書いてある文字列

と表示されてしまっています。

そして、2回目の fscanf で「aaaaaaaaaaa」を読み込んでいます。

CSVファイルの入出力[編集]

fscanfによる入門的方法[編集]

CSVファイルという、カンマで区切った形式のファイルがある。

たとえば、 次のようなデータである。

ファイル名「sanpuru.csv」

名前,番号
山田,1
佐藤,2


なお、表計算ソフト( libreOffice の Calc など)で、表計算ファイルをCSVファイルに変換することができる。 表計算ソフトのそれぞれのセルにデータを入力したあと、表計算ファイルを「名前をつけて保存」するときに、ファイル形式を選択する欄があるので、その欄でCSV形式を選べばいい。


さて、CSVファイルをカンマで区切って一つずつ読み取りたい場合、

fscanf命令では %[^ ]という命令を使う。%[^ ]は、そのカッコの中にある文字をみつけるまで文字を読み込む。また、そのカッコ内の文字は保持しない。

たとえば %[^,]なら、カンマ記号(,)の手前まで文字を読み込み、カンマ記号じたいは除いて読み込む。

なので、%[^,]なら、結果的にカンマ文字を区切り文字として扱うことになる。


CSV読み取りファイルのプログラム例は、たとえば次のようなファイルになる。

(Fedora30 で確認)

#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。


int main()
{
	FILE *fp1;

	fp1 = fopen("sanpuru.csv", "r");
	if ( (fp1 = fopen("sanpuru.csv", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		//system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}


	char str1[150];
	char str2[150];

	printf("文字列を読み取っています。\n");
	
	fscanf(fp1, "%[^,],%s,%s", str1,str2);

	printf("str1として読み取った文字列\n");
	printf("%s\n", str1);

	printf("str2として読み取った文字列\n");
	printf("%s\n", str2);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	return 0;
}


実行結果 (Fedora30 で確認)

ファイルをオープンしました。
文字列を読み取っています。
str1として読み取った文字列
名前
str2として読み取った文字列
番号
ファイルをクローズしました。


fgetsによる方法[編集]

さきほどの章では、説明の都合上、fscanfを使ったが、じつは、fscanfでCSVファイルを作るのは、あまり実用的ではない。

なぜなら、まず空白文字(半角スペース)で読み込みを停止してしまうので、半角スペースを含む文字列の読み込みが出来無いからである。


なので、CSVの読み取りファイルをC言語で作る場合、fgets命令を作るほうが良い。


しかし、fgets には、%[^,] のようなカンマで読み込み中止命令をする都合いい機能が無い。


なので、いったんfgetsで、その行の全体を読み込んだ後に、 strtok という別の命令を使うのが良い。


strtok 命令とは、指定した文字列の直前までを読み込み機能のある命令なので、この命令でカンマ文字まで読み込むことを命令すればいい。 strtok では、指定した文字列じたいは読み込まない。

なお、 strtok 命令を使うためには #include <string.h> が必要である。

書式は

strtok(分解する文字列のある変数,"区切り文字にしたい文字")

のようになる。

CSVファイルを読み込ませたいなら、区切り文字にしたいのはカンマ記号なので、

strtok(分解する文字列のある変数,",")

となる。

なお、 strtok の第一引数を「NULL」にすると、前の文字の続きから読み込む。つまり

strtok(NULL,"区切り文字にしたい文字")

にすると、前の文字の続きから読み込む。


コードは下記のようになる。

(Fedora30 で確認)

#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
#include <string.h>

int main()
{
	FILE *fp1;

	fp1 = fopen("sanpuru.csv", "r");
	if ( (fp1 = fopen("sanpuru.csv", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	
	char buffer1[150];
	printf("文字列を読み取っています。\n");
	
	fgets(buffer1,150,fp1);

        printf("1行目の文字列\n");
	printf("%s", buffer1);


	char str1[150];
	char str2[150];

	strncpy(str1, strtok(buffer1,",") ,150); // str1 = strtok(buffer1,","); では代入できない。
	strncpy(str2, strtok(NULL,",") ,150);

        printf("str1として読み取った文字列\n");
	printf("%s", str1);
	printf("\n");

        printf("str2として読み取った文字列\n");
	printf("%s", str2);

	fgets(buffer1,150,fp1);
        printf("2行目の文字列\n");
	printf("%s", buffer1);


	strncpy(str1, strtok(buffer1,",") ,150);
	strncpy(str2, strtok(NULL,",") ,150);

        printf("str1として読み取った文字列\n");
	printf("%s", str1);
	printf("\n");

        printf("str2として読み取った文字列\n");
	printf("%s", str2);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	return 0;
}


実行結果
ファイルをオープンしました。
文字列を読み取っています。
1行目の文字列
名前,番号
str1として読み取った文字列
名前
str2として読み取った文字列
番号
2行目の文字列
山田,1
str1として読み取った文字列
山田
str2として読み取った文字列
1
ファイルをクローズしました。


上記のように、たしかに、読み取った文字列を分解している。


応用例[編集]

読み取りでオープンできなかったファイルは、クローズしてはいけない[編集]

Windows(Visual Studio)でもLinux(gcc)でも、

fp1 = fopen("test1.txt", "r"); のような命令で読み込みに失敗した場合、つまり、普通なら読み込み対象のファイルが無い場合は、

そもそも、命令fp1 = fopen("test1.txt", "r");の実行直後は、

まだファイル"test1.txt"は、まったくオープンされて無い状態です。

なので、もしも、読み込み対象の失敗時のときの操作を、If文などの記述ブロックのなかに fclose(fp1); を記述しても、オープンしてないファイルをクローズしようとしており、そのため、エラーになります。

Windowsの場合[編集]

いくつか前に示したコード、

コード例(まだ改造してない)
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)



int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ( (fp1 = fopen("test1.txt", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	
	char str1[150];
	printf("文字列を読み取っています。\n");
	
	fscanf(fp1, "%s", str1);

        printf("ファイルに書いてある文字列\n");
	printf("%s\n", str1);

	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}


は、次のようにも書き換えできる。


次のコードではelse文ブロックの中に、ファイルが存在していた時の処理をまとめている。

改造したコード例 (Visual Studio 2019 および Windows 7 で確認ずみ)
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。

#pragma warning(disable:4996)

int main()
{
	FILE* fp1;

	fp1 = fopen("test1.txt", "r");
	if ((fp1 = fopen("test1.txt", "r")) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		
		//fclose(fp1); // ここでクローズするとエラー
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");

		char str1[150];
		printf("文字列を読み取っています。\n");

		fscanf(fp1, "%s", str1);

		printf("ファイルに書いてある文字列\n");
		printf("%s\n", str1);

		fclose(fp1);
		printf("ファイルをクローズしました。\n");

	}

	system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
}

いっぽう、タイトルのとおり、下のようなコードがあると、エラーになります。

このエラーの起きるコードでは、if文の条件節にファイルポインタを使ったif文の中で、むりやりクローズしようとしています。

	fp1 = fopen("test1.txt", "r");
	if ((fp1 = fopen("test1.txt", "r")) == NULL) { 
		printf("ファイルを開けませんでした。\n");
		
		fclose(fp1); // コメントアウトしないとエラーになる
		system("pause");
		return 1;
	}


ついつい、ファイルを開けなかった場合などには、「この命令は用済みだから、すぐファイルを閉じよう(×)」って発想で、むりやりブロック中でクローズしようと思いたくなります。

しかし、上記のように、読み取りに失敗しあたとに、むりやりクローズしようとしても、実行時にエラーになります。


これはどういうことかというと、ファイルのオープンに失敗した場合、そもそも、そのファイルはオープンされてないのでクローズの必要もないです。

また、もしもオープンに失敗したファイルをクローズしようとした場合、実行時にエラーになります。


Linuxの場合[編集]

Linuxでも同様です。もしもファイルのオープンに失敗した場合、Linuxでも、そのファイルはオープンされてないのでクローズの必要もないです。

また、もしもオープンに失敗したファイルをクローズしようとした場合、Linuxでも実行時にエラーになります。

Linuxの場合、コンパイルできてしまうかもしれ無い場合もありますが、しかしビルドされた実行ファイルの実行時にエラーになり、「コアダンプ」などのメッセージが表示されます。


たとえば、次のコードは、読み取り対象のファイル"SettingFile.txt"の無い場合には、実行時にエラーになります

// Linuxでエラーになる例
#include <stdio.h>
#include <stdlib.h> // Windowsでは「続行するには何かキーを押してください . . .」を表示するのに必要だった。

// #pragma warning(disable:4996) // Linux なので不要だし、あるとエラーの原因になる

int main()
{
    FILE* fp1;
    {
	fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。

	if ( (fp1 = fopen("SettingFile.txt", "r") ) == NULL) {               // 読み込みが失敗(NULL)の場合のブロック
		printf("ファイルを開けませんでした。\n");
		fclose(fp1);
	}
	else {
		printf("ファイルを開けました。 \n");
		fclose(fp1);
	}
	//fclose(fp1);

	printf("ファイルを閉じました。 \n");	
	printf("終了しています。 \n");
	
	//system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
    }
}


いっぽう、次のように改善して、読み込み失敗後のクローズ命令を除去すれば、エラーにならず、実行できます。

(Fedora 31 で確認ずみ)

// Linuxでエラーにならない例
#include <stdio.h>
#include <stdlib.h> // Windowsでは「続行するには何かキーを押してください . . .」を表示するのに必要だった。

// #pragma warning(disable:4996) // Linux なので不要だし、あるとエラーの原因になる

int main()
{
    FILE* fp1;
    {
	fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。

	if ( (fp1 = fopen("SettingFile.txt", "r") ) == NULL) {               // 読み込みが失敗(NULL)の場合のブロック
		printf("ファイルを開けませんでした。\n");

	}
	else {
              printf("ファイルを開けました。 \n");
              fclose(fp1); // elseが実行される場合、このブロックの始めではファイルがオープン状態なので、すぐ閉じたいなら、ここで。
              printf("ファイルを閉じました。 \n");
	}
	//fclose(fp1); // ここではない。

	printf("終了しています。 \n");
	
	//system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
    }
}

もし、読み取り対象のファイルの無い場合に、

上のコードの

// ここではない

の場所で fclose(fp1); でクローズすると、ファイルの存在しない場合にエラーになります。なぜなら、オープンしてないファイルをクローズしようとしているからです。そのような動作は、エラーになり、認められません。


同様に、elseブロック内でクローズした場合でも、さらに「ここではない」の場所でもう一度クローズしてしまうと、これもエラーになります。


読み込もうとしたファイルが無い場合に、同名のファイルを作成したい場合[編集]

C言語では、 fopen で書き込みモードでオープンしようとしたとき、もしファイルがなければ、自動的にファイルを作成します。


しかし、読み込みモードでは、対象のファイルが無い場合には、けっしてC言語コンパイラは作成しません。


では、どのような場合に、読み込みモードの対象ファイルが無い場合に、作成したいと思うのでしょうか。

一例では、上書き可能なユーザーごとのカスタム設定ファイルなどが、もし無くなった場合に、標準設定の内容になっている設定ファイルを新規に自動作成すると、便利でしょう。


ともかく、もし読み込みモードで対象ファイルが無い場合に、同名のファイルを新規作成したい場合には、

Windowsなら、たとえば、次のようなコードで実装できます。

FILE* fp1;
{
	fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。

	if ( (fp1 = fopen("SettingFile.txt", "r") ) == NULL) {               // 読み込みが失敗(NULL)の場合のブロック
		fp1 = fopen("SettingFile.txt", "w");                         // 同名のファイルをそのまま fopen で開いてもいい
		{
		     // ここに、標準設定の書き込みの処理を書く。
		}
	}
fclose(fp1);
}

ここで重要なのは、 fopen で開いた回数は、上記のコードでは2箇所あるのに(if文の条件の記述での fopen は除く)、実際に開かれるファイル名は1個(例の場合ならテキストファイル "SettingFile.txt" の1個だけ)であるという事です。

また、 fopen は2箇所あるのに、いっぽうで fclose は1箇所だけであるという事です。


この理由は、おそらく、読み込みに失敗した場合は、そもそも何もオープンしてないと判断なされるので、なので、失敗したぶんの fopen の1回ぶんは、カウントされないからです。

ただし、書き込みは通常、対象ファイルの有無に関係なく成功するので(対象ファイルの無い場合には自動作成するので)、なので、書き込みの命令のさいにファイルがオープンされるので、その1回ぶんのクローズは必要になります。


なので、上記のコードの直後に、もし fgets などの読み込み命令をしても、エラーになって、読み込みできないです。

上記のコードのあとに読み込みをしたい場合、再度 fopen の読み込みモードで開いてからでないと、 fgets などの読み込み命令は実行できないのです。



Linux の場合で、ほぼ同様です。実際、次のソースコードを実行すると、わかります。

コード (Fedora 31 で確認ずみ)
// Linux用
#include <stdio.h>
#include <stdlib.h> // Windowsでは「続行するには何かキーを押してください . . .」を表示するのに必要だった。

// #pragma warning(disable:4996) // Linux なので不要だし、あるとエラーの原因になる

int main()
{
    FILE* fp1;
    {
	fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。

	if ( (fp1 = fopen("SettingFile.txt", "r") ) == NULL) {               // 読み込みが失敗(NULL)の場合のブロック
		fp1 = fopen("SettingFile.txt", "w");                         // 同名のファイルをそのまま fopen で開いてもいい
    	        printf("ファイルが存在しないので作成しています。ファイル名: SettingFile.txt  \n");
                fprintf(fp1, "書き込みテスト \n");
	}
	else {
              printf("ファイルはすでに存在しています。 \n");
	}
	fclose(fp1);

	printf("ファイルを閉じました。 \n");	
	printf("終了しています。 \n");
	
	//system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
	return 0;
    }
}


実行例 (ファイルのない場合)
ファイルが存在しないので作成しています。ファイル名: SettingFile.txt  
ファイルを閉じました。 
終了しています。 


実行例 (ファイルのある場合)
ファイルはすでに存在しています。 
ファイルを閉じました。 
終了しています。 
SettingFile.txtの中身
書き込みテスト 

のようになります。


  • エラー例

いっぽう、もし fclose(fp1); を2回使っても、コンパイルは失敗してエラーになり、下記のように途中でエラーメッセージが表示されてしまい中断します。

ファイルはすでに存在しています。 
free(): double free detected in tcache 2
Aborted (コアダンプ)

メモのあるファイルの数値の読取[編集]

たとえば

途中までの計算結果: 41

(ファイル名は totyuu.txt としよう。)

のような解説文「途中までの計算結果」のような文字列のあるテキストファイルから、数値の「41」だけを読取たい場合があるだろう。


このようなファイルの読取り方は、さきほど紹介した strtok や fgets などを応用するのが良い。

また、文字列型を整数型に変換するには atoi という関数を使います。

なお、浮動型の数に変換するには atof という関数になります。


とりあえず、コード例として、読み取ったファイルに数 1 を足すだけの簡単なプログラムを作ってみましょう。


コード例 (Fedora30 で確認ずみ)
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
#include <string.h>

int main()
{
	FILE *fp1;

	fp1 = fopen("totyuu.txt", "r");
	if ( (fp1 = fopen("totyuu.txt", "r") ) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		//system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else{
		printf("ファイルをオープンしました。\n");
	}

	
	char buffer1[150];
	printf("文字列を読み取っています。\n");
	
	fgets(buffer1,150,fp1);

        printf("1行目の文字列\n");
	printf("%s", buffer1);


	char str1[150];
	char str2[150];

	strncpy(str1, strtok(buffer1,":") ,150); // "," ではなく":" に変わってるのに注意。
	strncpy(str2, strtok(NULL,":") ,150);

	printf("str1として読み取った文字列\n");
	printf("%s", str1);
	printf("\n");

	printf("str2として読み取った文字列\n");
	printf("%s", str2);


	printf("中断してた計算の続きを開始します。\n");

	int henkan = atoi(str2);
	int keisan = henkan + 1;


	printf("%d に 1 を足すと %d です。\n", henkan, keisan );


	fclose(fp1);
	printf("ファイルをクローズしました。\n"); 
		
	
	return 0;
}

実行結果

ファイルをオープンしました。
文字列を読み取っています。
1行目の文字列
途中までの計算結果: 41
str1として読み取った文字列
途中までの計算結果
str2として読み取った文字列
 41
中断してた計算の続きを開始します。
41 に 1 を足すと 42 です。
ファイルをクローズしました。

文字コード[編集]

Windowsの場合、システム内部の文字コードには原則的にUnicodeを使っているのだが、しかし例外的にコンソール画面(DOSプロンプト画面)ではマイクロソフト独自規格の文字コードを使っている。

マイクロソフト社は、これをアメリカ標準規格のANSIと呼んでいるが、実は、中身は米国の行政府の定める本物の1バイト文字のANSIでなく、マイクロソフト独自規格の自称「ANSI」である。おそらく、マイクロソフト独自規格の自称「ANSI」は、日本語などの2バイト文字の表示機能を含むので、自称「ANSI」も2バイト以上の文字であろうと考えられている。

1980年代くらいの古い、マイクロソフト日本法人などの開発したShift-JISという比較的に低バイト文字(2バイト以上の文字)の独自規格を、マイクロソフト米国法人の古い低バイト文字(1バイト以上の文字)の文字コードとすりあわせた独自規格のことをマイクロソフト社は「ANSI」と称しているようだ。

いちおう、現在ではShift-JISは日本工業規格になっているが、実際のWindowsでの文字コードの実装では、Windows用に独自にアレンジされたものを使っており、厳密には区別のためCP932という文字コード名で、技術者は呼ぶ。(w:Microsoftコードページ932


アクセサリ「メモ帳」などのテキストエディタの文字コードでも、標準設定では、マイクロソフト独自規格の自称「ANSI」を使っている。



さて、上述のプログラム例で紹介したソースコードのように、特に文字コードの指定をする必要なく、日本語も表示できる。

マイクロソフト社が、そのように、コンソール(DOSプロンプト)の機能を調整しているからである。


コンソール画面で表示する場合、読み取るテキストファイルの文字コードは自称「ANSI」形式にしておけばいい。アクセサリ「メモ帳」の標準コードは「ANSI」(マイクロソフト自称)になっているので、特に変更する必要は無い。


もし、単に、あるテキストファイルを読み取ってコンソール画面で表示するだけの場合なら、むしろUnicodeは、使ってはならない(少なくとも初心者は)。 読み取り対象のテキストファイルをUnicodeなどに変換すると、コンソール表示の際には、文字化けをしてしまう。


ただし、コンソール画面以外のGUIアプリケーションの作成では(Windows API というのを使うと、GUIアプリを作れる)、文字表示の関数がANSI形式に対応しておらず Unicode 対応になっている場合もある。最終的に現代のプログラマが作るアプリケーションのほとんどはGUIアプリであるので、Windowsシステム内部の文字コードがANSIとUnicodeに不統一になっているのは、これは深刻な問題である。


ソースコード中にある文字については、コンパイラがコンパイル時に調節してくれるのが、しかしソースコード外部のテキストファイルについては、コンパイラが調整してくれないので、テキストファイルの読み取って表示などをした際に文字化けが起きることがある。


つまり、結論から言うと、GUIアプリとコンソールアプリで共通のテキストファイルを使うことは、あきらめるべきである。


ANSIコード(マイクロソフト自称)でも日本語は表示できるのだが、よそのパソコンなどに移動した場合に文字化けをする可能性がある。


なお、コンソール表示では不要だが、本来なら、自称「ANSI」コードや2バイト文字では、英語以外の文字を扱う際、

setlocale(LC_ALL, "japanese");

のように、英語以外の文字コードとして、どこの言語を使用するのかを指定しないといけない。2バイト文字の2バイトだけでは、65536文字までしか区別できないので、文字情報だけでは世界中の文字を網羅することは不可能であり、そのため、対象の文字を限定するために、言語を指定する必要があるからである。

(なお、日本語の常用漢字は、約2000個である。なので、2バイト文字なら、言語指定があれば、十分に常用漢字を表現できる。)


もっとも、Windowsのコンソール画面に表示する場合、そのような言語指定なく、表示できる。


Linux の場合[編集]

一例として、Fedora28でファイル入出力を実行する場合のコードと実行結果の例を下記にしめす。

テキストファイルの文字コードはUTF-8である。Fedoraの標準の文字コードのままである。テキストエディタにはGedit(ジーエディット)を用いている。GeditはFedora28で標準に付属してくるテキストエディタである。

#include <stdio.h>

int main()
{
	FILE *fp1;

	fp1 = fopen("test1.txt", "r");
	if ((fp1 = fopen("test1.txt", "r")) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		printf("ファイルを開けませんでした。\n");
		system("pause");// 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}


	char str1[150];
	printf("文字列を読み取っています。\n");

	fscanf(fp1, "%s", str1);
	printf("ファイル1行目に書いてある文字列\n");
	printf("%s\n", str1);


	fscanf(fp1, "%s", str1);
	printf("ファイル2行目に書いてある文字列\n");
	printf("%s\n", str1);

	fclose(fp1);
	printf("ファイルをクローズしました。\n");

	
	return 0;
}


実行結果
ファイルをオープンしました。
文字列を読み取っています。
ファイル1行目に書いてある文字列
aaaaaaaaaaa
ファイル2行目に書いてある文字列
ssssssss
ファイルをクローズしました。

Fedora28の場合、読み取り対象のテキストファイルの文字コードを何に変えても、コンソール画面で正しく表示される。

なお、Geditの変換可能な文字コードの一覧に「日本語 (CP932)」 というのがあるが、これがマイクロソフト Shift-JIS のことである(つまり マイクロソフト自称「ANSI」)。もし、マイクロソフト自称ANSIとLINUX用テキストファイルを共通化したい場合、 CP932 を選べばいい。

とはいえ、最近のWindows用テキストエディタでは(Linuxでは標準的に採用されている)UTF-8も表示できるのが一般的なので、特に文字化けなどの起きないかぎり、むりに文字コードを変える必要は無い。


ワイド文字のファイル入出力[編集]

Linuxの場合[編集]

Windowsでいう「Unicode」が、マイクロソフト社の自称にすぎず、実態は国際規格とは違う。

なので、説明の簡単のために、まず Linuxで説明する。


読み取りたいテキスト
これはテストです。

aaaaaaaaaaa


Linuxの場合のコード例
#include <stdio.h>
#include <locale.h>
#include <wchar.h>

int main(void)
{
	setlocale(LC_ALL, "ja_JP.UTF-8"); //ロケール(地域)を設定する。

	FILE* fp1;

	fp1 = fopen( "test1.txt", "r, ccs = UTF-8");
	if ((fp1 = fopen("test1.txt", "r")) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		wprintf(L"ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		wprintf(L"ファイルをオープンしました。\n");
	}


	wchar_t str1[150];
	wprintf(L"文字列を読み取っています。\n");

	fwscanf(fp1, L"%s", str1);

	wprintf(L"ファイルに書いてある文字列\n");
	wprintf(L"\n");
	wprintf(L"%s\n", str1);
	wprintf(L"\n");

	fclose(fp1);
	wprintf(L"ファイルをクローズしました。\n");
	
    return 0;
}


実行結果
ファイルをオープンしました。
文字列を読み取っています。
ファイルに書いてある文字列

これはテストです。

ファイルをクローズしました。


なお、テキストファイルの拡張子に「.txt」とあるので、もしかしたらLinuxの種類によってはWindowsのShift-JISコードだと判断してLinuxが自動的にそのテキストファイルのエンコードをShift-JISに書き換える可能性があるので、もしも、上記のコードで実行しても文字化けなどしてるなら、再度、テキストファイルを開いて、UTF-8にエンコードしなおす事。


なお、Linuxでよく標準的に付属してくるGCCコンパイラには、fopenのワイド文字バージョン( wfopen のような関数)は無い。

また、fopenで読み取るファイルに日本語を使うと、文字化けをしたり、ファイルを開くのに失敗したりする。

たとえば、「テスト1.txt」のような日本語のファイル名で作られたファイルは、上記のコードのfopenで読み取るファイル名を「テスト1.txt」に書き換えても、読み取りできない。


なお、Linuxでは、文字の変換には w:iconv(アイコンブ) などのAPIが利用されている。iconv については高度すぎる話題になるので、説明を省略する。


上記コードのようにLinuxでもwprintfなどのワイド文字型の組み込み関数が使えるが、しかし、printfなどでも日本語を表示できてしまうので、Linuxではワイド文字用の組み込み関数は、実用性は乏しい。

Windowsの場合[編集]

Unicodeなどのテキストファイルを読み取れるように、上記のようにワイド文字型の関数を使ってコードを書いても、日本語を含むファイルは文字化けをする。(いっぽう、テキストファイル中の英数字だけ、正しく表示されたりする。)

例えば、下記のようなコードを書いても、日本語の文字は、文字化けしてしまう。

// 文字化けしてしまう。
// Windowsを想定

#include <stdio.h>
#include <locale.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」

#pragma warning(disable:4996)

int main()
{
	_wsetlocale(LC_ALL, L""); //ロケール(地域)を設定する。

	FILE* fp1;

	fp1 = _wfopen( L"test1.txt", L"r, ccs = UNICODE");
	if ((fp1 = _wfopen(L"test1.txt", L"r")) == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		wprintf(L"ファイルを開けませんでした。\n");
		system("pause"); // 「続行するには何かキーを押してください . . .」の待機命令
		return 1;
	}
	else {
		wprintf(L"ファイルをオープンしました。\n");
	}


	wchar_t str1[150];
	wprintf(L"文字列を読み取っています。\n");

	fwscanf(fp1, L"%s", str1);

	wprintf(L"ファイルに書いてある文字列\n");
	wprintf(L"\n");
	wprintf(L"%s\n", str1);
	wprintf(L"\n");

	fclose(fp1);
	wprintf(L"ファイルをクローズしました。\n");


	system("pause"); // 「続行するには何かキーを押してください . . .」
	
    return 0;
}


いっぽう、テキストファイルにANSIエンコードを使い、ソースコードの文字表示にprintfなど非ワイド型の関数を使った場合では文字化けをしない。

よってWindowsでは、なるべくANSIエンコードを使うのが安全だろう。特別な理由がないかぎり、ワイド文字型を使う必要も無い。


また、このことから、Windowsの自称する「Unicode」が、実際の国際規格とは実態が違っていることが分かる。