16.3 関数と構造体 構造体ポインタ 地底探査ゲーム 第16章 構造体 16.3 関数と構造体 構造体ポインタ 地底探査ゲーム
今日のポイント 構造体、タグ名、メンバーとそれらの 使い方を思い出す! 構造体のデータを関数間でやりとり する場合の仮引数・実引数の使い方 使い方を思い出す! 構造体のデータを関数間でやりとり する場合の仮引数・実引数の使い方 関数値を構造体で戻す方法
中点を求めて出力する関数 center_point のプロトタイプ宣言 16.3 関数と構造体 構造体、タグ名、メンバーとは何だったか? x 座標 y 座標 point プログラム例 16.3.2 から タグ名 struct point { double x; double y; }; 「2つのdouble型から成る構造体に point という名前のブランドタグをつけよう」 という感じ メンバーの宣言 void center_point(struct point p1, struct point p2); 中点を求めて出力する関数 center_point のプロトタイプ宣言 p1, p2 は point というタグがついた構造体変数
構造体とメンバーのアドレス p1 p1.x → 1310568 3.8 p1.y → 1310576 5.6 #include <stdio.h> struct point {double x,y;}; int main(void) { struct point p1; p1.x = 3.8; p1.y = 5.6; printf("%g, %g, (%g, %g)\n", p1.x, p1.y, p1); printf("%u, %u, %u\n", &p1.x, &p1.y, &p1); return 0; } 同じ型ならまとめて書いても良い p1 p1.x → 1310568 3.8 p1.y → 1310576 5.6 メンバーを省略すると 先頭から順にメンバーの 数だけ出力しようとする (VC++の場合)
構造体の配列の構造 #include <stdio.h> struct point {double x,y;}; int main(void) { struct point p[2]; p[0].x = 3.8; p[0].y = 5.6; p[1].x = 7.4; p[1].y = 9.2; printf("(%g, %g), (%g, %g)\n", p[0].x, p[0].y, p[1].x, p[1].y); printf("%u, %u, %u\n", &p[0].x, &p[0].y, &p[0]); printf("%u, %u, %u\n", &p[1].x, &p[1].y, &p[1]); return 0; } p[0] p[0].x → 1310560 3.8 p[0].y → 1310568 5.6 p[1] p[1].x → 1310576 8.4 p[1].y → 1310584 18.2
16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し #include <stdio.h> 16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し #include <stdio.h> struct point {double x, y;}; void center_point(struct point p1, struct point p2); int main(void) { struct point p1, p2; p1.x = 3.8; p1.y = 5.6; p2.x = 8.4; p2.y = 18.2; center_point(p1, p2); return 0; } 構造体の実引数を関数に引き渡す
16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し 16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し 仮引数が構造体である関数の定義 void center_point(struct point p1, struct point p2) { double xm, ym; xm = (p1.x + p2.x) / 2.; ym = (p1.y + p2.y) / 2.; printf("(%g, %g) と (%g, %g) の中点の座標は (%g, %g)\n", p1.x, p1.y, p2.x, p2.y, xm, ym); } メンバーを用いて計算 出力はメンバーで
構造体変数 p3 のアドレスを関数に引き渡して結果を受け取る 16.3 関数と構造体 関数から結果を構造体で 受け取る → 構造体ポインタ プログラム例 16.3.2改 #include <stdio.h> struct point {double x, y;}; void center_point(struct point p1, struct point p2, struct point *p3); int main(void) { struct point p1, p2, p3; p1.x = 3.8; p1.y = 5.6; p2.x = 8.4; p2.y = 18.2; center_point(p1, p2, &p3); printf("(%g, %g) と (%g, %g) の中点の座標は (%g, %g)\n", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); return 0; } 結果を受け取る構造体 p3 を用意 構造体変数 p3 のアドレスを関数に引き渡して結果を受け取る 出力はメンバーで
16.3 関数と構造体 プログラム例 16.3.2改 関数から結果を構造体で 受け取る → 構造体ポインタ 16.3 関数と構造体 関数から結果を構造体で 受け取る → 構造体ポインタ プログラム例 16.3.2改 void center_point(struct point p1, struct point p2, struct point *p3) { (*p3).x = (p1.x + p2.x) / 2.; (*p3).y = (p1.y + p2.y) / 2.; } 仮引数が構造体ポインタである関数の定義 メンバーを用いて計算 構造体ポインタの場合、選択演算子(アロー演算子) "->" でメンバーを指定 (*p3).x p3 -> x (*p3).y p3 -> y p3 -> x = (p1.x + p2.x) / 2.; p3 -> y = (p1.y + p2.y) / 2.; は に書き換えて
16.3 関数と構造体 プログラム例 16.3.2改 関数から結果を構造体で 受け取る → 構造体ポインタ 16.3 関数と構造体 関数から結果を構造体で 受け取る → 構造体ポインタ プログラム例 16.3.2改 void center_point(struct point p1, struct point p2, struct point *p3) { p3 -> x = (p1.x + p2.x) / 2.; p3 -> y = (p1.y + p2.y) / 2.; printf("p3 -> x = %g, p3 -> y = %g\n", p3 -> x, p3 -> y); printf("(*p3).x = %g, (*p3).y = %g\n", (*p3).x, (*p3).y); printf("&p3 -> x = %u, &p3 -> y = %u\n", &p3 -> x, &p3 -> y); printf("&(*p3).x = %u, &(*p3).y = %u\n", &(*p3).x, &(*p3).y); } 仮引数が構造体ポインタである関数の定義 メンバーを用いて計算 優先順位はポインタ演算子よりもメンバー演算子のほうが高いことに注意 *p3.x は *(p3.x) と解釈されるので、p3 がポインタの場合は間違いとなる。
16.3 関数と構造体 プログラム例 16.3.2改2 関数から結果を構造体で 受け取るもう1つの方法 16.3 関数と構造体 関数から結果を構造体で 受け取るもう1つの方法 プログラム例 16.3.2改2 #include <stdio.h> struct point {double x, y;}; struct point center_point(struct point p1, struct point p2); int main(void) { struct point p1, p2, p3; p1.x = 3.8; p1.y = 5.6; p2.x = 8.4; p2.y = 18.2; p3 = center_point(p1, p2); printf("(%g, %g) と (%g, %g) の"中点の座標は (%g, %g)\n", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); return 0; } 戻り値が構造体であることを宣言 結果を受け取る構造体 p3 を用意 結果の構造体を関数から戻り値で受け取る 出力はメンバーで
16.3 関数と構造体 プログラム例 16.3.2改2 関数から結果を構造体で 受け取るもう1つの方法 16.3 関数と構造体 関数から結果を構造体で 受け取るもう1つの方法 プログラム例 16.3.2改2 戻り値が構造体である関数の定義 struct point center_point(struct point p1, struct point p2) { struct point p; p.x = (p1.x + p2.x) / 2.; p.y = (p1.y + p2.y) / 2.; return p; } 結果を入れる構造体を用意 メンバーを用いて計算 結果を構造体で返す
地底探検ゲーム(undergnd.c) ■■ ■ ■ ■ ■ ■ ■ ■☆ ■ ■ ■ 距離 0 m: 4←→6 ↓=2 ギブアップ=0: 10 ■■ ■ ■ ■ ■ ■ ■ ■☆ ■ ■ ■ 距離 0 m: 4←→6 ↓=2 ギブアップ=0: 現在地 4 ブロック もぐった距離 テンキーで入力 左右にブロックを避けながら下にすすんでいき、進めなくなったところまでの距離(スコア)を競うゲーム
地底探検ゲーム(undergnd.c) 講義資料のWebページからダウンロード #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> /* 地面を格納する構造体の宣言 */ struct ground { int block[10]; // 地下情報を格納する配列の宣言 char blockStr[21]; // 地下ブロックパターン }; 乱数発生に必要 乱数初期化に必要 g[0].block = {1, 0, 0, 1, 0, 0, 0, 1, 0, 1} g[0].blockstr ■__■___■_■ 2バイト文字なので倍のサイズが必要 + \0 → 21バイト
地底探検ゲーム(undergnd.c) /* 地面を作成する関数 */ void makeGround(struct ground *p) { int i; char *wall[] = { " ", "■" }; /* 左の壁 */ p??block[0] = 1; strcpy(p??blockStr, wall[1]); /* 中央のブロックパターン */ for (i = 1; i <= 8; i++) { p??block[i] = rand() % 4 ? 0 : 1; // 確率1/4でブロック発生 strcat(p??blockStr, wall[p??block[i]]); } /* 右の壁 */ p??block[9] = 1; strcat(p??blockStr, wall[1]); 3バイトの文字列2つの配列 "■" 4で割り切れたら0:偽→1, 割り切れなければ1~3:真→0 対応する位置にブロックを置く "■"
地底探検ゲーム(undergnd.c) 現在地記憶用変数 int main(void) { ギブアップチェック用フラグ int i, score = 0, x = 4, key = 1; struct ground g[4]; // 地面の構造体配列(4層分) /* 最初の地面の作成 */ srand(time(NULL) % 100); // 乱数シード:現在時刻の100の剰余 for (i = 0; i < 4; i++) makeGround(g + i); /* メインループ */ while (key) { /* 画面表示 */ strncpy(g[2]?blockStr + x * 2, "☆", 2); for (i = 0; i < 4; i++) printf("%s\n", g[i]?blockStr); strncpy(g[2]?blockStr + x * 2, " ", 2); ギブアップチェック用フラグ 現在地マーク の埋め込み 構造体配列の ポインタで引渡し 4層分の地面を描画 次回の印刷時に前回の☆印が残らないよ うに2バイト分の空白で事前に消しておく
地底探検ゲーム(undergnd.c) /* キー入力 */ printf("距離 %d m: ", score); scanf("%d", &key); /* 移動 */ if (key == 4 && g[2]?block[x - 1] == 0) x--; else if (key == 6 && g[2]?block[x + 1] == 0) x++; else if (key == 2 && g[3]?block[x] == 0) { for (i = 0; i < 3; i++) g[i] = g[i + 1]; makeGround(g + 3); score++; } /* 終了処理 */ printf("スコア: %d メートル!\n", score); return 0; 左に移動 右に移動 下に移動 1段下から 順にコピー 最下層の新規作成
スキルアップタイム1 地底探検ゲーム(undergnd.c)を完成させ、コンパイル・実行して動作を確認せよ 地面の深さや幅を変えるにはどこを変更すればよいか、検討せよ 地面の深さや幅を変えやすいようにソースを改良し(undergnd1.c)、深さを1、幅を2、増やせ(4→5, 10→12)。 ただし、現在地は常に下から2層目とする
スキルアップタイム2 ファイル入出力関数の利用により、「ハイスコアの表示・保存機能」を付け足せ(保存ファイル名はhighscr.txt) ソース名は"undergnd2.c"とせよ
スキルアップタイム2のヒント int highscore=0; FILE *fp; ... if ((fp = fopen("highscr.txt", "r")) != NULL) { fscanf(fp, "%d", &highscore); fclose(fp); } if (score > highscore) { printf("ハイスコアです!\n"); fp = fopen("highscr.txt", "w"); fprintf(fp, "%d\n", score); } else printf("ハイスコア: %d メートル\n", highscore); ハイスコアの初期値 ハイスコアファイルがある場合に読み込む ハイスコアの場合にファイルに書き込む
第4回レポート(任意) 課題: スキルアップタイム1または2 提出期限: 2010年2月4日(金) 12:50 提出場所: ネットワーク実験室(1)入口の提出箱 以下の項目を入れること(表紙は不要) 学籍番号、氏 名 "undergnd1.c"または"undergnd2.c"のソースリスト 3.実行結果の例(変更したことがわかる部分だけ残し、後は省略してよい) 4.感 想
本日のパズル このプログラムは何を出力するか #include <stdio.h> #define PR(value) printf(#value"=%d ",(value)) #define NL putchar('\n') #define PRINT3(x1,x2,x3) PR(x1),PR(x2),PR(x3),NL int a[2][3] = {{1,2,3},{4,5,6}}; int main(void) { int i; for (i=0; i<2; i++) PRINT3(a[i][2-i], *a[i], *(*(a+i)+i)); return 0; }