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