プログラミングC
第3回 演習問題


最初に

/home/course/prog1/public_html/2024/ex/ex03/より、以下のファイルをコピーしておくこと。
input1.txt, input2.txt, output1b_sample.txt, output3_sample.txt

A問題(50点)

演習問題1

以下のプログラムは、テキストファイルinput1.txt を開き、その内容を1文字づつ読み込んで標準出力に表示するとともに、ファイルoutput1.txtに書き込むことでテキストファイルの内容をコピーする。また、読み取った文字数と行数を記録して最後に標準エラー出力に表示を行う。

さらに、stdlib.hをインクルードしてexit()関数を使用して、ファイル操作時にエラーが発生した際にはメッセージを標準エラー出力に表示した上でプログラムを強制終了するようにしている(exit()関数についてはプログラミング入門ハンドアウトのLec12-13 (p.67)を参照のこと)。

(提出ファイル名: prog01a.c)
#include <stdio.h>
#include <stdlib.h>
/* <stdlib.h> はexit関数の利用のために必要 */
/* プログラミング入門ハンドアウトのLec12-13 (p.67)を参照のこと */

int main() {
    char c; /* 文字読み取り用の変数 */
    int count = 0; /* 読み取り文字数記録用の変数 */
	int lcount = 0; /* 読み取り行数記録用の変数 */
    
    /* 入力用と出力用の二つのファイルポインタの宣言 */
    /* 複数のファイルポインタを一度に宣言する場合の書式は */
    /* ハンドアウトLec03-14 (p.96) を参照 */
    FILE ____________;
 
    /* 入力用ファイルinput1.txtを読み出し指定でオープン */
    /* エラーが生じた場合は標準エラー出力にメッセージを表示して終了 */
    if((fpin = ____________) == ____________){
        ____________(____________, "Failed to open: input1.txt\n");
        exit(1);
    }
 
    /* 出力用ファイルoutput1.txtを書き込み指定でオープン */
    /* エラーが生じた場合は標準エラー出力にメッセージを表示 */
    /* 既に開いている入力用ファイルはクローズしてから終了 */
    if((fpout = ____________) == ____________){
        ____________(____________, "Failed to open: output1.txt\n");
        ____________;
        exit(2);
    }
 
    /* 入力用ファイルから1文字づつ読み取るのをループで繰り返し */
    /* 読み取った文字をprintfで表示するとともに出力用ファイルに書き出し */
    /* 読み取った文字数と行数も記録する */
    /* ファイル終端まで到達したらループ終了 */
    /* 文字の読み取りとファイル終端到達判断の書き方は */
    /* ハンドアウトLec03-8 (p.95)、Lec03-9, -11, -12 (p.96) などを参照し */
    /* 文字の読み取りと結果の代入、条件判断を同時に行うこと */
    while(____________ != ____________){
        printf("%c", c);
        ____________(____________, "%c", c);
        count++;
		if(c == '\n') lcount++;
    }
    
    /* 読み取った文字数を含むメッセージを標準エラー出力に表示 */
    ____________(____________, "%d characters (%d lines) are read and copied.\n", count, lcount);
 
    /* 入力用と出力用の二つのファイルをクローズ */
    ____________;
    ____________;

    return 0;
 
}

このプログラムの空欄を埋めて、正しく動作するようにせよ。

さらに以下のテストを行い、ファイル操作時のエラー処理の動作確認をするとともに標準出力・標準エラー出力の違いを把握すること。

  1. 基本的な動作の確認

    テキストファイルの内容の確認はcatコマンドで行える。また、2つのファイルの内容の一致・不一致はdiffコマンドを用いて確認できる。

    % cat input1.txt
    C is an imperative, procedural language in the ALGOL tradition. It has a static type system. In C, all executable code is contained within subroutines (also called "functions", though not in the sense of functional programming). 
    "C (programming language)" from Wikipedia (https://en.wikipedia.org/wiki/C_(programming_language)). (Access date: 2024/10/03)
    % ./a.out 
    C is an imperative, procedural language in the ALGOL tradition. It has a static type system. In C, all executable code is contained within subroutines (also called "functions", though not in the sense of functional programming). 
    "C (programming language)" from Wikipedia (https://en.wikipedia.org/wiki/C_(programming_language)). (Access date: 2024/10/03)
    356 characters (2 lines) are read and copied.
    % cat output1.txt
    (input1.txtと同じなので略)
    % diff input1.txt output1.txt
    %
    (ファイルが完全に一致した場合は、このように何も表示されない)
    
  2. 出力先

    標準出力と標準エラー出力へのそれぞれの表示を確認する(ハンドアウトLec03-14, -15 (p.96)参照)。

    % ./a.out > prog1_stdout.txt
    356 characters (2 lines) are read and copied.
    (文字数の表示は標準エラー出力に行なっているので、標準出力をリダイレクトしても画面表示される)
    % cat prog1_stdout.txt
    (input1.txtと同じなので略)
    
  3. 入力ファイルのエラー処理

    規定の入力ファイルが存在しない場合の動作を確認する。

    % mv input1.txt input1_.txt
    (ファイルinput1.txtの名称を変更し、input1.txtが存在しない状態にする)
    % ./a.out
    Failed to open: input1.txt
    %
    (ファイルinput1.txtが開けなかった旨のメッセージが表示される)
    % mv input1_.txt input1.txt
    (ファイルinput1.txtの名称を元に戻す)
    
  4. 出力ファイルのエラー処理

    規定の出力ファイルを作成できない場合の動作を確認する。

    % chmod a-w output1.txt
    (ファイルoutput1.txtの書き込み許可を取り除き、上書きできない状態にする)
    % ./a.out
    Failed to open: output1.txt
    %
    (ファイルoutput1.txtが開けなかった旨のメッセージが表示される)
    % rm output1.txt
    rm: remove write-protected regular file 'output1.txt'? y
    (ファイルoutput1.txtを削除する。書き込み許可がないファイルを削除する場合確認のメッセージが表示されるのでyと答えること)
    

次にこのプログラムを変更して、以下のような動作を行うプログラムを作成せよ。

(提出ファイル名: prog01b.c)
  1. input1.txtを開き、その内容を1文字づつ読み込んで標準出力に表示するとともに、ファイルoutput1b.txtに書き込む(prog01a.cと同様に標準出力するが、出力ファイル名が異なる)
  2. input1.txtを閉じる
  3. 次に、ファイルoutput1b.txtに改行を書き込み、1行空ける
  4. さらに、再びinput1.txtを開き、その内容を1文字づつ読み込んで、今度は小文字を大文字に変換し、 それ以外はそのままファイルoutput1b.txtに書き込む
  5. 文字数と行数のカウント、及び出力は不要

結果として得られる出力ファイルoutput1b.txtの内容は以下の通りになる。

C is an imperative, procedural language in the ALGOL tradition. It has a static type system. In C, all executable code is contained within subroutines (also called "functions", though not in the sense of functional programming). 
"C (programming language)" from Wikipedia (https://en.wikipedia.org/wiki/C_(programming_language)). (Access date: 2024/10/03)

C IS AN IMPERATIVE, PROCEDURAL LANGUAGE IN THE ALGOL TRADITION. IT HAS A STATIC TYPE SYSTEM. IN C, ALL EXECUTABLE CODE IS CONTAINED WITHIN SUBROUTINES (ALSO CALLED "FUNCTIONS", THOUGH NOT IN THE SENSE OF FUNCTIONAL PROGRAMMING). 
"C (PROGRAMMING LANGUAGE)" FROM WIKIPEDIA (HTTPS://EN.WIKIPEDIA.ORG/WIKI/C_(PROGRAMMING_LANGUAGE)). (ACCESS DATE: 2024/10/03)

このプログラムでは、ファイルinput1.txtの内容を2回読み込む必要がある。このため、最初の読み込みが完了したあと一度ファイルを閉じ、再度開くようにすること(ハンドアウトLec03-20 (p.97)で紹介されている文字配列などを利用すれば一度のファイル内容読み込みでも同じ動作をさせることができるが、この問題ではそのようにする必要はない)。

小文字を大文字に変換するにはハンドアウトLec01-20 (p.85)のサンプルプログラムlec01-8.cの中に含まれている関数cnvtoupperか、Lec03-16 (p.96)のサンプルプログラムで使われている関数toupperのいずれかを用いればよい。前者を使う場合は関数本体とプロトタイプ宣言はそのまま使用し、関数の利用は今回のプログラムに合わせて適切に行うこと。後者を使う場合はctype.hincludeする必要がある。また、必要に応じて変数を追加してよい。

output1b.txt が指示通りになっているかを確認するために、 output1b.txtと、 サンプルファイル output1b_sample.txt を diff コマンドを用いて比較するとよい。

B問題(50点)

演習問題2

input2.txtは1行に1つの自然数が記載された数値データである。 この数値データの中から、その最大値と最小値を発見するとともに、平均値を計算するプログラムを作成する。作成するプログラムは、 ものとする。これを以下の手順で作成する。
  1. まず、読み込む数値、最大値、最小値、読み取った数値の合計、読み取った数値の数を格納する int 型の 5 つの変数を用意する。
  2. 最大値および最小値の初期値は、それぞれ数値データの取りうる最小値および最大値に定める。 与えられた数値データは自然数であることが分かっているので、最大値の初期値は 0 とする。
  3. 最小値の初期値は int 型の最大値を表すマクロとしてヘッダファイル limits.h で定義済みの INT_MAX を用いる。 (INT_MAX を用いる際にはヘッダファイル limits.h をインクルードすること)
  4. ファイルから数値を1つ読み込む。これを最大値と比較し、大きければ最大値を更新する。 最小値についても同様に比較・更新を行う。 また、読み取った数値の合計と読み取った数値の数の更新も行う。
  5. 次の数値を読み込むことができなくなるまで 4. に戻り、処理を繰り返す。
  6. 発見した最大値、最小値、及び平均値の数値を、ファイル result2.txt に出力し、プログラムを終了する。
(提出ファイル名: prog02.c)

input2.txt の内容

68
80
42
95
84
67
49
64
28
39
63
47
62
15
52
12
11
21
10
88

result2.txt の内容例

max = 95, min = 10, average = 49.850000
また、input2.txtの最終行に自然数1と999を追記したファイルを読み込ませた場合に、 同じプログラムできちんと題意を満たすかどうかを確認すること。

追記した場合のinput2.txt の内容

68
80
42
(中略)
21
10
88
1
999

追記した場合のresult2.txt の内容

max = 999, min = 1, average = 90.772727

演習問題3

まず、以下のような動作をするプログラムを作成せよ。

(提出ファイル名: prog03a.c)

結果として得られる出力ファイルoutput3.txtの内容は以下の通りになる。

C is an imperative, procedural language in the ALGOL tradition. It has a static type system. In C, all executable code is contained within subroutines (also called "functions", though not in the sense of functional programming). 
"C (programming language)" from Wikipedia (https://en.wikipedia.org/wiki/C_(programming_language)). (Access date: 2024/10/03)

c is an imperative, procedural language in the algol tradition. it has a static type system. in c, all executable code is contained within subroutines (also called "functions", though not in the sense of functional programming). 
"c (programming language)" from wikipedia (https://en.wikipedia.org/wiki/c_(programming_language)). (access date: 2024/10/03)

)30/01/4202 :etad sseccA( .))egaugnal_gnimmargorp(_C/ikiw/gro.aidepikiw.ne//:sptth( aidepikiW morf ")egaugnal gnimmargorp( C"
 .)gnimmargorp lanoitcnuf fo esnes eht ni ton hguoht ,"snoitcnuf" dellac osla( senituorbus nihtiw deniatnoc si edoc elbatucexe lla ,C nI .metsys epyt citats a sah tI .noitidart LOGLA eht ni egaugnal larudecorp ,evitarepmi na si C

output3.txt が指示通りになっているかを確認するために、 output3.txtと、 サンプルファイル output3_sample.txtdiff コマンドを用いて比較するとよい。

次に、自分で作成したプログラムの出力output3.txtが、サンプル (output3_sample.txt) と一致しているかをdiffコマンドと同様に調べるプログラムを作成せよ。

(提出ファイル名: prog03b.c)

このプログラムは以下のような動作をするものとする。

prog03b.cでの実行例(prog03a.cが正しく動作している場合)

% ./a.out 
Two files are identical.
(output3.txtとoutput3_sample.txtが一致した場合)

prog03a.cが題意通り正しく動作してoutput3.txtがサンプル (output3_sample.txt) 通りになっていると、ファイル不一致の場合の動作確認ができない。ファイルoutput3.txtの一部を書き換えてサンプルとは一致しないようにしてからファイル不一致の場合の動作確認を行うこと。

% echo hoge >> output3.txt
(追記のリダイレクトで、ファイルoutput3.txtの末尾に余分な文字を付け加える)
% ./a.out
Two files are different at 1071 byte.
(ファイル不一致の場合は、どこで相違が見つかったのかを表示する)

Extra問題

/home/course/prog1/public_html/2024/ex/ex03/extra/ より、以下のファイルをコピーしておくこと。 vss_*.pbm(演習問題4)、data05-*.txt(演習問題5)
(全ファイルをzipでまとめたものを、Zipファイルとして用意したので、必要であればここからダウンロードできる。)

演習問題4

複数の同じサイズの白黒画像を重ね合わせてできる画像を出力するプログラムを作成する。 ここで、画像の重ね合わせとは、白黒画像を透明なシートに印刷し、 位置を合わせてそれらを重ね合わせた時にできる画像と同様のものと考えることとする。 したがって、重ね合わせてできる画像の各画素値は、重ね合わせる画像の同じ位置の画素値が全て白であれば白、 それ以外(一つでもその位置の画素値が黒の画像がある場合)は黒となる。

以下の仕様を満たすように関数 read, write, superpose の中身を埋めて、プログラムを完成させなさい。 ただし、扱う画像ファイルのフォーマットはすべて Plain PBM(プログラミング入門第10回ハンドアウト参照)とする。

「関数 read の仕様」 「関数 write の仕様」 「関数 superpose の仕様」 (提出ファイル名: prog04.c)

補足)視覚復号型秘密分散法と呼ばれる暗号技術は、画像を複数の画像(分散画像)に分けて暗号化するものである。 この演習問題で扱っている画像の重ね合わせは、この方法で暗号化された情報の復号演算(暗号化された情報を復元して取り出す処理)に相当している。 実行例で使用しているサンプル画像ファイル vss_*.pbm は、視覚復号型秘密分散法で作成された分散画像になっているので、 このプログラムによって秘密画像が復元できることになる。

#include <stdio.h>
#include <stdlib.h>

#define MAX_W 256       /* 画像の幅の最大 */
#define MAX_H 256       /* 画像の高さの最大 */
#define BUF 256         /* バッファサイズ */

/* 画像の幅,高さ,画素値を格納する外部変数 */
int w, h, i_img[MAX_W * MAX_H], o_img[MAX_W * MAX_H];

int read();
void write();
void superpose();

int main(){
	superpose();
	write();
	return 0;
}
 
/*
 * 入力されたファイル名の Plain PBM 形式画像ファイルを読み込み,
 * その幅,高さ,画素値をそれぞれ外部変数 w, h, i_img に格納する
 */
int read(){
	FILE *fp;
	char filename[BUF];
	int r;
	
	/* 必要に応じて変数宣言を追加 */
	
	printf("ファイル名を入力してください: ");
	
	if((r = scanf("%s", filename)) == EOF)
		return EOF;
		
	if((fp = fopen(filename, "r")) == NULL){
		fprintf(stderr, "ファイル %s を開けません!\n", filename);
		exit(1);
	}
	
	/* 仕様にしたがって関数を作成しなさい */

}
 
/*
 * 外部変数 w(幅),h(高さ),o_img(画素値) で与えられる画像を,
 * Plain PBM 形式でファイル out.pbm に書き出す
 */
void write(){
	/* 仕様にしたがって関数を作成しなさい */
}

/*
 * 画像ファイルを read 関数を用いて読み込み,
 * それらを重ね合わせてできる画像の画素値を
 * 外部変数 o_img に格納する
 */
void superpose(){
	/* 仕様にしたがって関数を作成しなさい */
}

用意した画像は以下の通り

注意: displayコマンドによる画像の表示は、以下の環境で行ってください。
1. 演習室環境
2. VPNによる仮想ネットワーク接続を経由した、VNCによるGUIでのリモートログイン環境

% display vss_panda_1.pbm

% display vss_panda_2.pbm

% display vss_eye_1.pbm

% display vss_eye_2.pbm

実行例

% ./a.out
ファイル名を入力してください: vss_panda_1.pbm
width = 256, height = 256
ファイル名を入力してください: vss_panda_2.pbm
width = 256, height = 256
ファイル名を入力してください: ^D(これは Ctrl-D を意味する)
% display out.pbm


% ./a.out
ファイル名を入力してください: vss_eye_1.pbm
width = 256, height = 128
ファイル名を入力してください: vss_eye_2.pbm
width = 256, height = 128
ファイル名を入力してください: ^D
% display out.pbm


%

演習問題5

昇順(小さい順)に並んだ複数の数値(int型に格納可能とする)が入っているファイルが 2 つある。 この 2 つのファイルに入っている数値をすべてまとめて全体を昇順で標準出力するプログラムを作成せよ。 2つのファイルそれぞれに値がいくつ入っているかわからない。また、重複する数値は重複した回数分出力すること。

2つのファイルのファイル名は、コマンド名の後に与えることとする。

% ./a.out data05-1.txt data05-2.txt

この実行例のように、コマンドラインオプションでファイル名などを読み込む方法は、 今後第6回で学ぶ(Lec06-11, -12, -13を見よ)ので参照すること。 以下に例を示す。これを元に解答を作成してもよい。

(提出ファイル名: prog05.c )
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv){

	FILE *fpin1, *fpin2;
	
	/* 入力ファイルその1を開く */
	if ((fpin1 = fopen(argv[1], "r")) == NULL){
		printf("Failed to open: %s\n", argv[1]);
		exit(1);
	}
	
	/* 入力ファイルその2を開く */
	if ((fpin2 = fopen(argv[2], "r")) == NULL){
		printf("Failed to open: %s\n", argv[2]);
		fclose(fpin1);
		exit(2);
	}
	
	/*
	 * 数値の読み込みと処理,および結果を出力する
	 */
	
	/* 開いた入力ファイルをそれぞれ忘れずに閉じて終了する */
	fclose(fpin1);
	fclose(fpin2);
	return 0;
}

実行例

% ./a.out data05-1.txt data05-2.txt
-25
-24
-23
-22
-22
-22
-21
-21
-21
-19
-19
-18
-18
-18
-17
-14
-14
-13
-13
-12
-12
-11
-10
-9
-8
-8
-8
-7
-6
-5
-4
-3
-3
0
2
4
4
5
6
6
6
6
6
8
10
12
14
14
15
15
17
17
18
19
19
19
20
23
23
24
%

プログラムのテストは data05-1.txtdata05-2.txtdata05-3.txt を組み合せて行うこと。組合せは 3 通りある。

解答の一例

  1. 2つのファイルは昇順に並んでいるので、まず、両方のファイルから一つずつ数字を読み込む。
  2. 読み込んだ2つの数字の小さいほうを出力するとともに、 出力した数字を持っていたファイルからもう一つ数字を読み込む。 ただし、両者の数値が一致した場合はどちらの数値から出力してもよい。
  3. ステップ 2 を繰り返すと、ファイルの終端に到達したときに数値を読むことができなくなる。 fscanfは読み込めない場合にEOFを返すので、このことを利用して、残ったファイルからの読み込みと出力を続行する。
  4. 両方のファイルの終端に到達したときが処理の終了を表す。

結果の確認の方法について

出力結果は大きくなるので、リダイレクトを用いると、結果を確認しやすい。

./a.out data05-1.txt data05-2.txt > data05.out

この例ではファイル「data05.out」に結果が出力される。

Unixコマンドで、作成するプログラムと同じ結果を得るためには、

cat data05-1.txt data05-2.txt | sort -n > cmdresult.out
とするとファイル「cmdresult.out」に結果が入る。 この結果と自分の結果を演習問題1で紹介した diff コマンドを用いて確認せよ。

diff data05.out cmdresult.out