C言語/ファイル入出力

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

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

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

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

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

#include <stdio.h>

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

	fclose(fp);	
		
	return 0;
}

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

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


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


FILE型へのポインタの宣言は、

FILE *fp;

の書式で行う。

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


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

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

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

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

#pragma warning(disable:4996)

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

	fclose(fp);	
		
	return 0;
}

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

Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合

Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合は、

[ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自動的にコンソールを閉じる]

を無効にします。


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

// Windows用のファイル操作のコード例
#include "stdafx.h"
#include <stdio.h>

#pragma warning(disable:4996)

int main()
{
	FILE *fp1 = fopen("test1.txt", "w");
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		return 1;
	}	

	fclose(fp1);	
}


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

なお、fopenのモードは、

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

がある。

なお、英語で、文字や文章を書くことを write (ライト)という。 wとは、その write の 頭文字w のこと。
英語で、本や文章を読むことを read (リード)という。 rとは、その read の 頭文字r のこと。
英語で、追加することを アペンド append という。 aとは、その append の 頭文字a のこと。


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

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


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


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

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

のパスのフォルダにある。

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


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

"stdafx.h"って何?
プログラムの冒頭で#include "stdafx.h""stdafx.h" なるヘッダーファイルをインクルードしています。

これはC言語標準にはなく、Windows(Visual Studio)固有のプリコンパイルヘッダーです。stdafx は Standard Application Frameworks の略で、MFC(Microsoft Foundation Class)の使用を想定しているものでした。 VS2019からが、"stdafx.h" から "pch.h"(Pre compile header)と機能と一致した名前になりました。

いずれにしても、Windows/Visual Studio でプリコンパイルヘッダーを使う状況でしか意味がありませんし、この本で扱うサイズのプリグラムではプリコンパイルヘッダーを使うメリットはありません。


読み書き[編集]

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

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

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

fprintf の冒頭のfは、ファイル関係であることをあらわしている。最後のfは、もともとC言語には「printf」という関数があり、(最後のfは)Format の f。


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

#pragma warning(disable:4996)

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


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

#include <stdio.h>

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


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

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


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


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


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

int var;

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

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

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

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

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



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


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

コード例
#include "stdafx.h"
#include <stdio.h>

#pragma warning(disable:4996)


int main()
{
	FILE *fp1 = fopen("test1.txt", "w");
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}

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

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

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

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

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


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

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

ファイルからの読み取りには、まずfopenの際にモードを"r"にする。(英語で、本や文章などを読むことを read (リード)という。その read リードの頭文字 r のこと。)

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

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

なお、 fscanffgets の違いは、

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

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

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

fscanf の例

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

一例として、

aaaaaaaaaaa
ssssssss

dddddd

と書いたとする。


コード例を示す。

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

#pragma warning(disable:4996)


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

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

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

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


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


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


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

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

#pragma warning(disable:4996)


int main()
{
	FILE *fp1 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1  == NULL) {
		perror("ファイルを開けませんでした。\n");
		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");
}


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

fgetsとfscanfの違い[編集]

fscanffgets の違いは、大まかには

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

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

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

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

rt aaaaaaaaaaa
u ssssssss

dddddd

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


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

fgetsのほうは

#include <stdio.h>

int main()
{
	FILE *fp1 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		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");
}


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


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


です。





いっぽう、fscanf のほうは

#include <stdio.h>

int main()
{
	FILE *fp1 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		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");
}
実行結果
ファイルをオープンしました。
文字列を読み取っています。
1行目に書いてある文字列
rt2行目に書いてある文字列
aaaaaaaaaaaファイルをクローズしました。

です。


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

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

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

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

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

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

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

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

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

ファイル名「sample.csv」

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


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


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

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

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

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


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

(Fedora30 で確認)

#include <stdio.h>

int main()
{
	FILE *fp1 = fopen("sample.csv", "r");
	if (fp1  == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		perror("ファイルを開けませんでした。\n");
		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");
}


実行結果 (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 <string.h>

int main()
{
	FILE *fp1 = fopen("sample.csv", "r"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		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");
}


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


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


要素数の多い場合の例[編集]

さきほどの例では、背つめいの単純化のため、たったの各行あたり、要素数はたった2個だった。

しかし、実際の場合は、もっと要素数が多い。

たとえば、

名前,番号,国語の得点,数学の得点
山田,1,80,90
佐藤,2,70,100

のように、各行あたり4個のデータを読み込む場合を考えよう。


コード例
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)

int main()
{
	FILE* fp1 = fopen("sample.csv", "r");
	if (fp1 == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		perror("ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
		printf("\n");
	}


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

	struct seisekihyou {
		char str[150];
	};

	struct seisekihyou row[20]; // 構造体配列の宣言

        // 下記のline は読み取りたい行数
	for (int line = 1; line <= 2; line = line + 1) {
		fgets(buffer1, 150, fp1);

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


		strncpy(row[0].str, strtok(buffer1, ","), 150); // str1 = strtok(buffer1,","); では代入できない。

		printf("row[%d].strとして読み取った文字列\n", 0);
		printf("%s\n", row[0].str);

                // 下記のtempは1行あたりの読み取りたい項目数
		for (int temp = 1; temp <= 3; ++temp) {
			strncpy(row[temp].str, strtok(NULL, ","), 150);

			printf("row[%d].strとして読み取った文字列\n", temp);
			printf("%s\n", row[temp].str);
		}
	
		printf("\n");
	}


	fclose(fp1);

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

	return 0;
}


実行例
ファイルをオープンしました。

文字列を読み取っています。
1行目の文字列
名前,番号,国語の得点,数学の得点
row[0].strとして読み取った文字列
名前
row[1].strとして読み取った文字列
番号
row[2].strとして読み取った文字列
国語の得点
row[3].strとして読み取った文字列
数学の得点


2行目の文字列
山田,1,80,90
row[0].strとして読み取った文字列
山田
row[1].strとして読み取った文字列
1
row[2].strとして読み取った文字列
80
row[3].strとして読み取った文字列
90


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

このウィンドウを閉じるには、任意のキーを押してください...
解説

前の節では、単純化のために配列や構造体やfor文は用いなかったが、実務では用いたほうがイイだろう。


各行の最初の項目だけ strncpy(row[0].str, strtok(buffer1, ","), 150); のようにstrtokの第一引数を指定する必要がある。


いっぽう、strncpy(row[temp].str, strtok(NULL, ","), 150); のようにstrtokの第一引数がnullなら、strtokは以前の区切り文字から次の区切り文字までを抜き取るだけなので、2番目の項目からはfor文で使いまわしができる。

if文との組み合わせ[編集]

fgets関数やstrtok関数などで読み取った文字列をもとに、if文などの条件分岐関数と組み合わせる際、標準C言語のif文では、文字列にはイコール記号== など算術的な記号での比較ができないです。

これはつまり、Linux標準のGCCでは、文字列の比較にイコール記号が使えないという事です。

標準C言語では、文字列どうしの比較をする際、 strcmp という関数を使います。

strcmpについて詳しくは『C言語/制御文』をお読みください。


文字コード[編集]

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

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

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

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


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



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

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


コマンドプロンプトで表示する場合、読み取るテキストファイルの文字コードは自称「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 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		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 = fopen( "test1.txt", "r, ccs = UTF-8"); // ここを読み取りモード"r"にするのを忘れないように
	if (fp1 == NULL) {
		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でよく標準的に付属してくる glibc には、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>

#pragma warning(disable:4996)

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

	FILE* fp1 = _wfopen( L"test1.txt", L"r, ccs = UNICODE");
	if (fp1 == NULL) { // ここを読み取りモード"r"にするのを忘れないように
		wprintf(L"ファイルを開けませんでした。\n");
        perror(0);
		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");
}


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

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


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

バイナリーファイルの読み書き[編集]

バイナリーファイルとしての書き込み[編集]

C言語のプログラムで、バイナリーファイルの読み書きをしたい場合、

fopen の引数で「r」(読み込み)とか「w」(書き込み)とか編集モードの指定がありますが、それに「b」をつけます。つまり「rb」や「wb」などの引数になります。

また、書き込む関数には fwrite を使う必要があります。 いっぽう、 fprintf では、テキストファイルに自動的に変換してしまいます。

たとえば下記のようになります。

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

#pragma warning(disable:4996)

int main()
{
	FILE *fp1 = fopen("test1.bin", "wb");
		
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}

	printf("バイナリファイルに書き込んでいます。 \n");
	
    char buf[5] = {0x42,0x4d,3,4,5};
    fwrite(buf, 1, 5, fp1);
         
	fclose(fp1);
	printf("ファイルをクローズしました。\n");
}

書き込みできたか否かを確認するには、バイナリエディタ(あるいは「16進エディタ」などと言われる)で確認してください。

0x42 などの冒頭の 0x は16進数であることを表す。バイナリーファイルの読み書きに限らず、一般にC言語で16進数をあつかう場合は、16進数である数に接頭辞 0x をつけて区別する。数値 0~9 までは0x をつけなくても十進数と同じなので省略できる。

バイナリエディタで読み込み

42 4D 03 04 05

と書き込まれていることが確認できれば成功である。


読み取り[編集]

バイナリファイルの読み取りには fread を使うことがある。

いきなりバイナリファイルを読み取るのは初心者には難しいので、まずテキストファイルを読み取る実験をしてみよう。

以前に作った test1.txt ファイルを読み取るとしよう。

test1.txt

aaaaaaaaaaa
ssssssss

dddddd

コード例

#include <stdio.h>

#pragma warning(disable:4996)

int main()
{
	FILE *fp1 = fopen("test.txt", "r");
		
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}


    char str1[150];
	printf("文字列を読み取っています。\n");
	    
    fread(str1, sizeof(unsigned char), sizeof(str1) / sizeof(str1[0]), fp1);
        
    printf("ファイルに書いてある文字列\n");
	printf("%s\n", str1);

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


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

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


では、バイナリーデーターを読み取ろう。

コード例

#include <stdio.h>

#pragma warning(disable:4996)

int main()
{
	FILE *fp1 = fopen("test1.txt", "rb");
		
	if (fp1 == NULL) {
		perror("ファイルを開けませんでした。\n");
		return 1;
	}
	else {
		printf("ファイルをオープンしました。\n");
	}

        char str1[70]; // 表示結果を短くするために数値を微妙に小さくした
	printf("バイナリーデーターを読み取っています。\n"); // 「文字列」ではなくバイナリーデーター
	
    fread(str1, sizeof(char), 50, fp1);
            
    printf("ファイルに書いてあるバイナリーデーター\n");
	
	for (int i=0 ; i < sizeof(str1) / sizeof(str1[0]); i = i+1)
	{
		printf("%02x ", str1[i]); // 最低でも2桁を表示、の意味
	}
	
	fclose(fp1);
	printf("\nファイルをクローズしました。\n");

}


実行結果
ファイルをオープンしました。
バイナリーデーターを読み取っています。
ファイルに書いてあるバイナリーデーター
61 61 61 61 61 61 61 61 61 61 61 0d 0a 73 73 73 73 73 73 73 73 0d 0a 0d 0a 64 64 64 64 64 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
ファイルをクローズしました。
※ ファイル作成時・保存時の文字コードの種類によっては改行文字 0d 0a などの制御文字の内容が若干違うかもしれません。

脚註[編集]

  1. ^ マイクロソフトは、CP932を「ANSI」とは自称しておらず、ANSI/OEM Code Page Support (SNANLS)の一環として、ANSI/OEM - Japanese Shift-JISの名称でサポートしている。この節は、この誤解に基づいて編集されていることに注意する必要がある。