情報処理Ⅱ 2005年12月16日(火)
本日学ぶこと いくつかの型 なぜ「型」にこだわるか? 以下を読む上での注意 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,int,long など)と列挙型を合わせて, 汎整数型(integral type)という. ただし,利用は代入,比較,範囲内の増分・減分にとどめておく.
独自の型はどこで定義する? 一般には,グローバル区間で定義し,複数の関数間で利用できるようにする. ブロックの中で定義してもよい. 有効範囲は,そのブロックの終わりまで.
レジ問題 構造体を用いるとすっきり表現できる例 「レジ(cache register)」をシミュレートできる? 「模擬」ともいう 構造体を用いるとすっきり表現できる例 「レジ(cache register)」をシミュレートできる? 「500円,100円,100円,50円,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 = {1, -1}; セミコロンを忘れないように この x と y を, point構造体の メンバという. x = 1 y = -1 p: 配列と同じ書式で 初期化可能
構造体の宣言と定義の例(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 パディング struct s { char c; int i; } sizeof(char) == 1 sizeof(int) == 4 でも sizeof(struct s) != 5
レジ問題の仕様(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」は 「calculate」の略
レジ問題のプログラミング(3) 定義した関数(続き) レジに入金・出金する add_pocket, sub_pocket 支払ったときのお釣りを求める get_change 「sub」は 「subtract」の略
関数定義の注意点 所持金を操作する各関数について,第1引数は所持金構造体のポインタとし,戻り値もその値としている. 戻り値をvoidにする(値を返さない)流儀もある. メンバ名と変数名は,別々に(異なる名前空間で)管理される識別子なので,重複してもよい. 関数set_bill, 関数set_coinを参照. get_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)が不定!
構造体とプログラミング さまざまな種類の情報を持つ「実体」を一つのオブジェクトとして処理したいとき, まず,「何に処理をしたいか?」を考える. そのオブジェクトの型を構造体で定義する. 座標上の点,学生情報,ノード(節点),など. 次に,「何の処理をしたいか?」を考える. 構造体を引数にとって処理する関数を定義する. 引数・戻り値ともに,型は構造体そのものにすべきか, 構造体のポインタにすべきか,そのつど検討する.
まとめ typedefと列挙型を活用することで,各変数の型がより明確になる. 構造体により,複数のデータからなる「システム」を創ることができる. 構造体の中身を操作するには,ポインタ渡しの関数を定義し呼び出すとよい.
スケジュール 第2回レポート掲示:本日の授業終了後すぐ 第11回:12月22日(木) 16:30~18:00 Webページ参照 第11回:12月22日(木) 16:30~18:00 (12月23日(金)は祝日) JABEEアンケート締切:12月22日(木) 情報通信の学生は全員回答してください 第12回:1月13日(金) 第2回レポート提出を忘れずに