情報処理Ⅱ 第10回 2004年12月14日(火)
本日学ぶこと① いくつかの型 なぜ「型」にこだわるか? 以下を読む上での注意 typedef 列挙型(enum型) プログラムを書きやすく 読みやすくする…定性的なメリット 実行時のコスト(メモリ使用量など)を見積もることができ,そこからプログラムの改善が期待できる…定量的なメリット いくつかの型 typedef 列挙型(enum型) なぜ「型」にこだわるか? 処理対象のデータを効果的・効率的に取り扱える 既存の機能をもとに,新たに便利な機能を創れる 以下を読む上での注意 この色とこの色は型名を表す. この色は変数名を表す.
typedef 独自に型(type)を定義(define)できる. 例 構造体の型名定義によく用いられる. typedef signed char schar; schar c; ⇒ signed char c; と同じ typedef unsigned char uchar; uchar c, *p; ⇒ unsigned char c, *p; と同じ typedef char *String; String s, t; ⇒ char *s, *t; と同じ typedef char c5[5]; c5 x, y, z; ⇒ char x[5], y[5], z[5]; と同じ 構造体の型名定義によく用いられる.
typedefの考え方 変数の定義 型の定義 char c5[5]; は,変数c5を定義し,その型はchar [5]型とする.
列挙型(enumeration)とは 「ラベル」を格納・参照・比較したいときに使う. 例 変数に格納される具体的な値には関心がない. 実際には,ラベルに整数値が割り当てられる.これを積極的に活用することもある. 例 enum Boolean {FALSE, TRUE}; enum Boolean p = TRUE, q = FALSE, r = p||q; enum month {Jan = 1, Feb, Mar, ..., Dec, MONTH_END} mloop; int rain[100][MONTH_END]; for (mloop = Jan; mloop < MONTH_END; mloop++) { rain[4][mloop] = ...; } 1 13 年月単位の降水量を格納する
汎整数型 整数型(char~long)と列挙型を合わせて,汎整数型(integral type)という. ただし,利用は代入,比較,範囲内の増分・減分にとどめておく.
独自の型はどこで定義する? 一般には,グローバル区間で定義し,複数の関数間で利用できるようにする. ブロックの中で定義してもよい. 有効範囲は,そのブロックの終わりまで.
まとめ typedefと列挙型を活用することで,各変数の型がより明確になる.
本日学ぶこと② 構造体 問題 「レジ(cache register)」ができる? 「500円,100円,100円,100円,50円,10円」で合計いくら? 「2,941円のお釣り」として,いくらの紙幣・硬貨を何枚出せばいい? 650円の勘定に,「1000円,100円,50円」を出してきたら,いくら返す? 650円の勘定に,「1000円,500円」を出してきたら?
構造体(structure) 複数のオブジェクトを取りまとめて一つのオブジェクトとして扱うための機構 構造体で表現するとよいもの (2次元,3次元などの)座標上の点 複素数 行列(行数と列数と各成分) 所持金 学生情報 (x,y) 500 50 100 10 学生番号:0001 氏名:あいうえお 情処Ⅰの点数:80 情処Ⅱの点数:75 修得単位数: 52
構造体の宣言と定義 構文: struct タグ名 { メンバ宣言子並び }; 例 「struct タグ名」型が定義される. struct point { double x; double y; }; struct point p; p.x = 1; p.y = -1; セミコロンを忘れないように この x と y を, point構造体の メンバという. p: x = 1 y = -1
構造体の宣言と定義の例(1) 構造体宣言と変数の定義を同時に行う. 構造体宣言と型の定義を同時に行う. struct point { double x; double y; } p; 構造体宣言と型の定義を同時に行う. typedef struct point { double x, y; } point; point p; struct point型と point型が定義される
構造体の宣言と定義の例(2) 行列(行数,列数とも10まで) 行列(成分はポインタ) struct matrix { row column int row, column; double element[10][10]; }; 行列(成分はポインタ) double **element; row column element … row column element
構造体の宣言と定義の例(3) 自己参照構造体 struct node { graph … value = 1 value = 99 next int value; struct node *next; }; struct node graph[100]; graph[0].value = 1; graph[0].next = graph + 1; graph[1].value = 99; graph[1].next = NULL; graph … value = 1 value = 99 next next = NULL すでに定義されている型で あれば,それを メンバの型に用いてもよい.
構造体の参照と代入(1) 前提 メンバへのアクセス方法 struct point { double x, y; }; struct point p, *pp = &p; メンバへのアクセス方法 構造体オブジェクトpのメンバxは p.x と書いて 参照・代入ができる. 構造体オブジェクトへのポインタppについては,(*pp).x もしくは pp->x と書けばよい. . と -> は演算子であり,最上位の優先順位を持つ. × *pp.x
構造体の参照と代入(2) 構造体オブジェクトの代入 構造体渡し struct matrix a, b; (aを初期化) b = a; とすると,bにはaのコピーが格納される. 構造体の中に配列があっても,コピーされる. 構造体の中のポインタは,ポインタ値がコピーされる. 構造体渡し 関数の引数や戻り値の授受でも,構造体オブジェクトが代入(コピー)される. しかし,構造体はしばしばポインタ渡しにされる. 関数処理で,構造体オブジェクトの中の値を変えたいとき 大きなサイズの構造体オブジェクトをスタックに置きたくないとき
sizeof(構造体) sizeof(構造体) は,構造体の各メンバのサイズの合計以上になる. 構造体オブジェクトの中で使用されない領域もあり得る.「パディング」という. s: c i パディング
所持金問題の仕様(1) 「所持金(pocket)」をひとつの実体として,構造体を定義する. 所持金に関する情報 一万円,五千円,千円の各紙幣(bill)の枚数 500円,100円,50円,10円,5円,1円の各硬貨(coin)の枚数 所持金の総額(amount) ⇒ 構造体のメンバ? 500 50 5 100 10 1
所持金問題の仕様(2) 所持金に関して知りたい情報,行いたい操作 紙幣や硬貨がそれぞれ何枚あるかを知る 紙幣や硬貨の所持枚数を指定する 「特定の紙幣や硬貨を0枚にする」を含む 支払ったときのお釣り(change)を求める 支払えないときは,何らかのエラー情報を返す 両替する ⇒ 授業では扱わない 100円玉45枚を,(可能な限り)千円札に 千円札1枚を,100円玉に
所持金問題のプログラミング(1) 所持金構造体 struct pocket { int bill_10000, bill_5000, bill_1000; int coin_500, coin_100, coin_50, coin_10, coin_5, coin_1; };
所持金問題のプログラミング(2) 定義した関数 紙幣や硬貨の枚数を指定する set_bill, set_coin, set_pocket replace_pocket, create_pocket 所持金の総額を求める calc_amount 紙幣や硬貨がそれぞれ何枚あるかを知る 個々の値は,メンバに直接アクセス 全体の出力は,print_pocket 支払ったときのお釣りを求める calc_change 「calc」は 「calculate」の略
関数calc_changeの仕様 calc_change 500 50 10 請求書 payment = 457 円也 poc_from 100 1 poc_return
関数定義の注意点 所持金を操作する各関数について,第1引数は所持金構造体のポインタとし,戻り値もその値としている. 戻り値をvoidにする(値を返さない)流儀もある. メンバ名と変数名は,別々に(異なる名前空間で)管理される識別子なので,重複してもよい. 関数set_bill, 関数set_coinを参照. calc_change関数の処理中に「お釣りが支払えない」とわかったら,その時点で処理を終えてNULLを返す. create_pocket関数の戻り値はポインタ ではなく構造体オブジェクトとして,構造体渡し としなければならない. 偽と評価される ポインタ値
構造体渡しでしてはいけないこと × 関数内のauto変数のポインタ値を戻り値とする. 例 struct pocket *create_pocket(int amount) { struct pocket poc_object; replace_pocket(&poc_object, amount); return &poc_object; } コンパイルエラーにはならないが,実行時に支障をきたす. 関数処理が終わると,poc_objectの領域が破棄される ⇒ *(&poc_object)が不定!
構造体とプログラミング さまざまな種類の情報を持つ「実体」を一つのオブジェクトとして処理したいとき, まず,「何に処理をしたいか?」を考える. そのオブジェクトの型を構造体で定義する. 座標上の点,学生情報,ノード(節点),など. 次に,「何の処理をしたいか?」を考える. 構造体を引数にとって処理する関数を定義する. 引数・戻り値ともに,型は構造体そのものにすべきか,構造体のポインタにすべきか,そのつど検討する.
まとめ 構造体により,複数のデータからなる「システム」を設計することができる. 構造体の中身を操作するには,ポインタ渡しの関数を定義し呼び出すとよい.
来週学ぶこと…プログラミングの例題3つ 行列式の計算 「ぷよ連結問題」の再帰的解法 「ぷよ連結問題」の非再帰的解法 学ぶこと:配列を含む構造体の利用,再帰呼び出し 「ぷよ連結問題」の再帰的解法 学ぶこと:再帰呼び出し,多次元配列を引数とする関数 「ぷよ連結問題」の非再帰的解法 学ぶこと:構造体の利用,配列の写像化 1 2 3 4 5 6 7 8 9 = 0