計算機プログラミング
第13 回 ファイル処理
担当:知能ソフトウェア研究室
重要:中間試験
2
について
I 実施日:7 月 16 日 I 試験場所:M 棟計算機室(直接計算機室に来ること) I 学外からの投稿は認めない(出席を要する) I 試験時間帯:9:00~11:00 の 2 時間(終了次第退出可) I 実施内容:通常の課題と同様の形式で、与えられた問題を解くプログラムを 作成する I 問題数:3 問 I 出題範囲:第11 回以前の内容(関数 1、関数 2、配列、文字列) I 第12回以降のプリプロセッサ、ファイル処理は含まない I 講義スライド、書籍、自分のノートやメモは参照してよいが、Web 上の情 報は参照不可 I 通常の課題と同様に自動採点による合否が付くが、それとは別に人手によ る採点を行う I 不合格の場合も、多くの場合部分点が与えられる I この試験の主目的は、その結果に基づいて後程補足説明を行うこと I 中間試験の成績への影響は、期末試験に比べると小さい今日の内容
講義の内容
I ファイル処理関数(fopen, fclose, fprintf, fscanf)
I ファイル形式の例:PGM 形式の画像ファイル
演習の内容
I 第13 回の問題セット
I 問題1(必修)
ファイル処理の基本
I これまではprintf, scanf 関数により「標準出力」「標準入力」に対する入出力 を行ってきたが、ほぼ同様のやり方で「ファイル」に対する入出力を行える I 入出力に先立ち、対象のファイルのファイルポインタを取得する必要がある I この演習では「テキストファイル」の読み書きのみを扱う(文字コードの列 が保存されたファイル) I ファイルからデータを読み込む場合 1. fopen関数を用いてファイルを読み込みモードで開き、ファイルポインタを取 得する 2. ファイルポインタとfscanf関数を用いて、ファイルからデータを読み込む 3. fclose関数を用いてファイルを閉じる I ファイルにデータを書き込む場合 1. fopen関数を用いてファイルを書き込みモードで開き、ファイルポインタを取 得する 2. ファイルポインタとfprintf関数を用いて、ファイルにデータを書き込む 3. fclose関数を用いてファイルを閉じるファイルへの書き込みの例
# include < stdio .h > main (){ FILE * fp ; // フ ァ イ ル ポ イ ン タ を 保 存 す る 変 数 fp の 宣 言 // ( 変 数 名 の 前 の * に 注 意 ) // 書 き 込 み モ ー ド " w " で フ ァ イ ル test . txt を 開 き ( 新 規 作 成 ) // そ の フ ァ イ ル ポ イ ン タ を fp に 取 得 fp = fopen (" test . txt ", " w "); // フ ァ イ ル fp に 123 と い う 文 字 列 ( 3 文 字 ) を 書 き 込 む fprintf ( fp , " 123 "); // フ ァ イ ル fp を 閉 じ る // ( こ れ を 忘 れ る と 、 デ ー タ が 正 し く 書 き 込 ま れ な い こ と が あ る ) fclose ( fp ); } 実行結果の確認$ cat test . txt // test . txt の 内 容 を 表 示 123
ファイルからの読み込みの例
# include < stdio .h > main (){ FILE * fp ; // フ ァ イ ル ポ イ ン タ を 保 存 す る 変 数 fp の 宣 言 int n ; // 読 み 込 み モ ー ド " r " で フ ァ イ ル test . txt を 開 き // そ の フ ァ イ ル ポ イ ン タ を fp に 取 得 fp = fopen (" test . txt ", " r "); // フ ァ イ ル fp か ら 整 数 1 つ を 読 み 込 み 、 変 数 n に 代 入 fscanf ( fp , " % d ", & n ); // フ ァ イ ル fp を 閉 じ る fclose ( fp ); printf (" % d \ n ", n ); } 実行結果(ファイルtest.txt の内容が 123 である場合) 123ファイル処理に関する関数
I fopen 関数:ファイルを読み込みモードまたは書き込みモードで開き、その ファイルを示すファイルポインタを返す I 書式1(読み込みモード):fopen(ファイルの相対パスを表す文字列, "r") I 書式2(書き込みモード):fopen(ファイルの相対パスを表す文字列, "w") I fclose 関数:指定されたファイルポインタが示すファイルを閉じる I 書式:fclose(ファイルポインタ) I fprintf 関数:ファイルにデータを書き込む I 書式:fprintf(ファイルポインタ, 書式指定子をn個含む文字列, 式1, 式2, …, 式n) I fscanf 関数:ファイルにデータを書き込む I 書式:fscanf(ファイルポインタ, 書式指定子をn個含む文字列, &変数1, &変数2, …, &変数n)I fprintf, fscanf 関数は第 1 引数にファイルポインタを渡す点以外は printf, scanf 関数と同じ I scanf と同様、fscanf でも %s に対応する変数については直前の &は不要
ファイル処理における注意点
I 読み込みモードで開こうとしたファイルが存在しない場合、fopen 関数は ファイルポインタとして定数NULL を返す(無効なファイルポインタ) I 安全のため、通常は下記のような「ファイルが見つからなかった場合(例外 的な場合)」を考慮した分岐処理(例外処理)を行う I 簡単のため、今回の課題においては行わなくてもよいものとする fp = fopen (" test . txt ", " r "); if( fp == NULL ){printf (" ERROR : ␣ test . txt ␣ does ␣ not ␣ exist "); // 例 外 処 理 }else{ // 通 常 の ( 本 来 実 行 し た か っ た ) 処 理 } I 書き込みモードで既に存在するファイルを開いた場合、そのファイルの内 容は削除される(書き込みが末尾に行われるのではなく、まず白紙にしてか ら行われる)点に注意する
scanf, fscanf
を用いた入力についての補足
I 書式指定子 %d %lf %c %s を用いて「1 つの値・文字列」を受け取る 際、空白・タブ・改行は区切り文字として扱われ、無視される I 例:次のプログラム fscanf ( fp , " % d ", & a ); fscanf ( fp , " % d ", & b ); fscanf ( fp , " % d ", & c );// 上 記 3 行 は fscanf ( fp , "% d % d % d " , &a , &b , & c ); と 同 じ において、fp が示すファイルの内容が、下のいずれであっても a = 75, b = 10, c = 30 となる 75 10 30 75 10 30 75 10 30 75 10 30
ファイル形式の例:
PGM
形式による画像の表現
I PGM 形式(portable graymap format):グレースケール画像(各ピクセルの色が黒〜白の中間色)をテキストファイルで表現する形式の1 つ I 形式: P2 幅 高さ 最大値 値1 値 2 ... 値n I 画像の幅と高さ(ピクセル)が幅、高さであり、画像全体で幅×高さピクセル I 最大値は各ピクセルの色を表す値が0以上最大値以下であることを表し、0が 黒、最大値が白を表す I 値1から値nが各ピクセルの色を表し、nは幅×高さである。値は1行目 (上)から順に並んでおり、1行分のデータは1列目(左)から順に並んでいる I 画像を確認するには端末で gnome-open PGM ファイル名 を実行 例:small.pgm P2 6 5 255 0 0 0 0 0 0 0 255 255 255 255 0 0 255 0 0 255 0 0 255 255 255 255 0 small.pgm が表す画像
第
13
回の課題
問題1(必修) PGM 形式のグレースケール画像 lena.pgm(補足ページからダウンロード)を読 み込み、画像の各ピクセルについて2 値化(完全な黒か完全な白の 2 色のいずれ かに変換)を行い、ファイルout.pgm に出力するプログラムを作成する。 2 値化については、入力画像の各ピクセルの最大値が m であるとき、m/2 以下 のピクセルを0 に、そうでないピクセルを 255 に変換して出力するものとする。 従って、出力するPGM ファイルの各ピクセルの最大値は 255 とする。void read_pgm (char path [] , int info [] , int pixel [1000][1000])
相対パスがpathのPGMファイルについて、その幅、高さ、最大値をそれぞれinfo[0],
info[1], info[2]に読み込み、x列y 行のピクセルの値をpixel[x][y]に読み込む。ここ で x とy は0 から開始するものとする(画像左上のピクセルがpixel[0][0]、右下が
pixel[info[0]-1][info[1]-1])。ここで画像の幅と高さは1000以下であるものと仮定する。
void write_pgm (char path [] , int info [] , int pixel [1000][1000]) read_pgmと同様に、画像の幅、高さ、最大値と各ピクセルの値が配列infoと二次元配列
pixelで与えられたとき、その内容を相対パスがpathのファイルにPGM形式で書き込 む。ここで、出力する各値は改行で区切る(1行に1つの値を出力する)ものとする。
問題1(必修)の続き
void read_pgm (char path [] , int info [] , int pixel [1000][1000]){
char s [100]; // 変 数 は 適 宜 追 加 す る FILE * fp ; fp = fopen ( path , " r "); fscanf ( fp , " % s ", s ); // " P2 " を s に 読 み 込 む ( が 使 わ ず に 捨 て る ) // フ ァ イ ル の 内 容 を 読 み 込 み 、 info , pixel に 代 入 fclose ( fp ); }
void write_pgm (char path [] , int info [] , int pixel [1000][1000]){ FILE * fp ; // 変 数 は 適 宜 追 加 す る fp = fopen ( path , " w "); fprintf ( fp , " P2 \ n "); // " P2 " を 出 力 // info , pixel の 内 容 を フ ァ イ ル に 書 き 込 む fclose ( fp ); }
問題1(必修)の続き main (){
char input [] = " lena . pgm ";
char output [] = " out . pgm ";
int info [3];
int pixel [1000][1000]; // 変 数 は 適 宜 追 加 read_pgm ( input , info , pixel );
// 各 ピ ク セ ル を 0 か 2 5 5 の 値 に 二 値 化
info [2] = 255; // 二 値 化 後 の 各 ピ ク セ ル の 最 大 値 は 2 5 5 write_pgm ( output , info , pixel );
第
13
回の課題
問題2(発展)
問題1 における 2 値化の代わりに、画像の各ピクセルの値をその周囲のピクセル
の平均値とする処理(平滑化、ぼかし)を行うプログラムを作成する。以下の2
つの関数を作成・利用すること。
int average_point (int x , int y , int pixel [1000][1000])
x列y行のピクセルの、自身を含めた周囲9ピクセルの平均値を返す。ここで平均値の 小数部は切り捨て、int型で返す。なおx列y行の周囲の位置は配列pixelの添字の範囲 内であること(0 <= x - 1 && x + 1 < 1000 && 0 <= y - 1 && y + 1 < 1000)を仮 定してよい。
void average_filter (int w , int h , int pixel [1000][1000] , int new_pixel [1000][1000])
幅wピクセル、高さhピクセル、各ピクセルの値がpixelの画像について、各ピクセ ルについて周囲9ピクセルの平均を取って得られる画像をnew_pixelに代入する。つま り、new_pixel[x][y]にaverage_point(x, y, pixel)を代入する。ただし、画像の端 に存在するピクセルについては平均値を求められないため、元の値をそのまま用いるも のとし new_pixel[x][y] = pixel[x][y]とする。ここで列番号x や 行番号yは0か ら開始するため、右端の列の番号はw - 1であり、最下行の番号はh - 1であることに 注意する。
第
13
回の課題
問題2(発展)の続き 入力データ(small.pgm) P2 6 5 255 0 0 0 0 0 0 0 255 255 255 255 0 0 255 0 0 255 0 0 255 255 255 255 0 0 0 0 0 0 0 出力データ(out.pgm) P2 6 5 255 0 0 0 0 0 0 0 85 113 113 85 0 0 141 198 198 141 0 0 85 113 113 85 0 0 0 0 0 0 0反復処理についての注意点
I while 文の書き方によっては、プログラムがずっと実行し続けて止まらない 無限ループに陥ることがある I 実行中のプログラムは、端末上で「Ctrl-C」(Ctrl キーを押しながら C キー)を押すと強制的に終了させることができる I 無限ループに陥っているように見える場合は、強制終了させる I ある程度時間が経過しても、何も出力が得られない時など I 無限ループの原因は概ね「条件式の間違い」か「反復の進度を表す変数が適 切に更新されていない」ことのいずれか I 反復の進度を表す変数の内容を(反復中に)出力し、想定通りの内容か確認する課題の投稿についての注意点
I 問題に併記しているすべての実行例について、正しく動作することを確認 する I 正しい結果が得られない実行例が1つでもある場合は、大体不合格になる I 問題によっては、併記した実行例以外の例を採点時に用いていることがある。 このため、併記した全ての実行例について正しく動作したからといって合格す るとは限らない I 現実の問題では、実行例が与えられることは少なく、自分で実行例(ある入力 に対する正しい出力は何か)を考える必要があることに注意 I 出力する(文字列の)内容は正確に I 空白、ピリオドなどの記号を含めた、計算結果以外の内容 I 数を出力する場合は、整数と実数のどちらを選ぶか I 指定された内容以外のものを出力しない I データを標準入力から入力させる場合、「...を入力してください」といったガ イドを出力すること自体は良いことだが、課題として提出する際はその出力は 行わないようにする(コメントアウトしてから出す)適切なインデントによる、構造の明瞭化
インデントされていない例 if( type == 0){ if( alc > 37 ){ tax = ...;} else{ t a x = ...;}} else{if( alc <= 20 ){ tax = ...;} else{ tax = ..;}} printf (...); インデントされている例 if( type == 0){ if( alc > 37 ){ tax = ...; } else{ tax = ...; } } else{ if( alc <= 20 ){ tax = ...; } else{ tax = ...; } } printf (...); 1. 文の終わり ; と波括弧 { } の後に改行を入れる 2. 全体を選択し、TAB キーを押す(選択範囲を自動インデント) Edit Select All C-x hソースコード作成中の、積極的なインデント
I 特に何も選択せずにTAB キーを押すと、現在の行(カーソルがある行)が 適切にインデントされる I セミコロン ; 、丸括弧 ( 、改行文字などの特別な文字が入力された時も 現在の行がインデントされる I このため、インデントはあまり積極的に行わなくてもある程度行われる形 になるが、基本的には「新しく行を書き始めるときはまず最初にTAB キー を押し、それから書き始める」のがよい I 既に書いた行の一部を後で修正した場合も、それによってインデントが崩 れることがあるので、その場合は再度TAB キーを押してインデントし直す I 既に書いた部分を、後からブロック { } の中身にする場合は、その中身は 自動的にインデントされないので、手動でインデントし直すようにする I ブロックの中身の部分(を含むような広い範囲)を選択し、TABキーを押す if( type == 0 ){ /* 後 か ら 追 加 */ area = l * l ; volume = area * h ; } /* 後 か ら 追 加 */ 中央2 行を 選択しTAB if( type == 0 ){ area = l * l ; volume = area * h ; }コンパイル時のエラーメッセージ
I 問題のあるソースコードをgcc でコンパイルすると、エラーメッセージが出 力される I エラーメッセージが出力された場合は実行ファイルも生成されていないの で、問題の原因を見つけて修正する必要がある I エラーメッセージは、大抵の場合問題が生じた行番号および問題の内容を 含んでいるので、それを手掛かりに原因を考える I その行番号の行そのものには問題がなく、別の場所に問題があることも 例:文末のセミコロンが無い(5 行目の文の実行に支障が出ているので、その前の行を見直す)hello . c :5: error : expected ‘; ’ before ‘p r i n t f ’
例:変数名の間違い(定義されていない変数の利用)
hello . c :7: error : ‘a r a ’ undeclared ( first use in this function )
例:関数名の間違い(定義されていない関数の呼び出し)
hello . c :(. text +0 x11 ): undefined reference to ` print'
例:include 文で指定するファイル名の間違い