* 第6回 ポインタ [#cc472feb]

- ''基本課題''は,必ず授業時間中に終わらせること。

- ''★印の付いている問題''は,担当教員に見せて確認の印(またはサイン)を得ること。

- ''応用課題''は,終わらなかったら次週(1週間後の午前11:00)までの宿題とする。

- ''他人のコピーと判断されたものは正当な提出と認めない。''その場合,2人のコピーなら2分の1の点数,3人のコピーなら3分の1の点数(以下同様)とする。

- プログラムのファイル名は,「問題番号.c」とすること。
-- 例: a1.c, ax3.c

- [[プログラムの提出のしかた>../提出]]をよく読むこと。
-- 提出URL http://vilab.org/upload/clab-upload.html

** 基本課題 [#te71ccdf]

*** ポインタ [#aa0a1e27]

''f1)'' char型の配列 str[9] = "TAMAGAWA" を定義し,その各要素a[0], a[1], ..., a[8]の値と''アドレス''を表示するプログラムを作成しなさい。''※この問題ではポインタ変数は用いない。''

> 【ポイント】 変数の''アドレス(メモリ内での格納番地)''を求めるには「&」演算子を用い,それをprintfで表示するには%pを用いる。ポインタはこのような変数のアドレスを記憶するための変数である。※以下は整数の例である。
 int n;
 printf("%p\n", &n);

''f2)''★ まず,int型の変数a,bに適当な整数を代入しておく。次に,apをaへのポインタ,bpをbへのポインタにする。そして,apとbpを使って変数aとbの内容を交換するプログラムを作成しなさい。''交換のときaやbへの代入(「a=...」や「b=...」)を使ってはいけない。'' 結果を確認するため,交換前後のa,bの値を表示させなさい。

> 【ポイント】 ポインタ(アドレス値)の頭に「*」演算子をつけると,そのアドレスにあるデータを普通の変数と同じように操作することができる。
 int n, m;
 int *p;
 p = &n;
 *p = 3; /* n = 3 と書くのと同じ意味になる */
 m = *p; /* m = n と書くのと同じ意味になる */

''f3)''★ int型の配列array[10]とint型へのポインタptrを定義し,ptrを用いてarrayの各要素にキーボードから整数を入力するプログラムを作成しなさい(最後に全要素を表示して確認する)。※直接array[i]に値を入力せず,ptrを使ってarrayの値を変えること。
 int array[10];  /* int型の配列 */
 int *ptr; /* int型へのポインタ */
以下は''ダメな例''である。
 scanf("%d", &array[i]);  /* ← ダメ! */
 array[i] = ...なんか...; /* ← ダメ! */

> 【ポイント】 ポインタは必ず変数などのある有効なメモリ領域を指していなければならない。初期化していないポインタは非常に危険である。

''f4)'' まず,char型の配列strとポインタptrを定義し,strにキーボードから文字列を読み込む。次に,文字列の先頭から末尾まで順に str[i] と *(str + i) を表示する。最後に,ptrにstrを代入(ptr = str)してから,同様に ptr[i] と *(ptr + i) を表示するプログラムを作成しなさい。

> 【ポイント】 C言語ではほとんどの場合,配列の名前はその先頭要素のアドレスとして扱われる。つまり,配列名は値を変更できないポインタだと考えてよい((ただし,sizeofの結果を除く。))。また,xが配列でもポインタでも必ず以下の公式が成り立つ。
 *(x + i) == x[i]
このとき,iは整数ならば正でも負でもよい。よって,ptr[-1] という書き方も有効で,*(ptr - 1) という意味になる。

''f5)''★ 2つの整数x,yを引数に取り,商と余りを同時にもとめる関数divideを作成しなさい。なお,関数の戻り値は,y=0なら0を返し,それ以外なら1を返すようにしなさい。
 int divide(int x, int y, int *sho, int *amari);
使用例
 int a, b, ok;
 ok = divide(5, 3, &a, &b);
 if (ok)
     printf("商 %d  余り %d\n", a, b);
 else
     printf("0では割れません"); 

> 【ポイント】 C言語の関数呼び出しは値渡し(call by value)なので,呼び出された関数の中で変数の値を変えても,呼び出し元の変数にはまったく影響がない。しかし,関数の変数のアドレス(格納場所)を渡せば,呼び出された関数の中で呼び出し元の変数の値を変更できる((scanfが変数に値を読み込めるのはこれを使っているからである。))。
- 参考 http://wisdom.sakura.ne.jp/programming/c/c31.html

** 応用課題 [#va9899f1]

''fx1)'' キーボードから文字列strを読み込み,strの中を指すポインタptrを用いて printf("%s", ptr) という処理をするとどうなるか試してみなさい。それを利用して,str 内の1文字目から末尾までの文字列,2文字目から末尾までの文字列,3文字目から末尾までの文字列,…を順に出力するプログラムを作成しなさい。以下に出力例を示す。
 Tamagawa
 amagawa
 magawa
 agawa
 gawa
 awa
 wa
 a

''fx2)'' 長さ10文字のchar型の配列fromとtoを定義する。キーボードからfromに文字列を読み込んだ後,fromの中の要素を指すポインタとtoの中の要素を指すポインタを用いて,fromからtoに文字列をコピーするプログラムを作成しなさい。

> 【ポイント】 ポインタによる連続コピーでは,よく以下のような書き方が用いられる。
 *p++ = *q++;

''fx3)'' 整数型の変数へのポインタを2つ取り,それらの指す整数値を交換する関数swapを作成しなさい(swap関数の動作を調べるmain関数もつけること)。
 void swap(int *p, int *q);
使用例
 int a = 3, b = 5;
 printf("a=%d b=%d\n", a, b);
 swap(&a, &b);  /* aとbの交換 */
 printf("a=%d b=%d\n", a, b);

''fx4)'' 2つの文字列を比較し,同じなら1,異なる場合は0を返す関数を作成しなさい(関数の動作を調べるmain関数もつけること)。※ただし,標準ライブラリ関数strcmpは''用いない''こと。
 int strequal(char *str1, char *str2);

''fx5)'' キーボードから文字列patternとstringを読み込み,patternがstringのなかに含まれる場合には「成功」と表示して終了し,含まれない場合には「失敗」表示としてから、成功するまでキーボードからstringを読み込み続けるプログラムを作成しなさい。※標準ライブラリ関数strstrを使ってよいので調べてみよ。

** 発展課題 [#qff2277e]

- 発展課題には''提出期限を設けない''ので,前回までの発展課題でやっていないものも含めて,どれでも自分の面白いと思うものをやってよい。

*** ダンジョン探索 [#f1a8b80c]

''fz1)'' RPGシリーズということで,今度はダンジョン(地下迷宮)の地図を作り,プレイヤーがその中を探検できるようにしてみよう。今回はやることが多いので説明はかなり簡略されている。不明な点は自分で考えて適切に設計しながらチャレンジしてほしい。

主人公たちは村外れにたつ小さな古城にたどり着いた。言い伝えでは,この地下は昔牢獄として使われ,今では恐ろしい吸血鬼や悪霊が住み着く迷宮となっているという。村人のためにも,吸血鬼を退治しなければ…。

ダンジョンの地図は2次元配列(グローバル変数)として用意し,主人公の現在位置は変数(x, y)で表すとよい。この例では,初期位置は上り階段(map[1][1])のある(1,1)とする。
 int map[7][7] = {
     { 9, 9, 9, 9, 9, 9, 9 },
     { 9, 1, 9, 0, 9, 5, 9 },
     { 9, 0, 9, 0, 2, 0, 9 },
     { 9, 0, 9, 0, 9, 9, 9 },
     { 9, 0, 0, 0, 2, 0, 9 },
     { 9, 0, 9, 0, 9, 7, 9 },
     { 9, 9, 9, 9, 9, 9, 9 } };
ここで,0〜9はそれぞれ次のものを表す(ここらへんは自由にアレンジしてよい)。
-「0」通路
-「1」上り階段(外に脱出できる)
-「2」扉
-「5」十字架(吸血鬼を倒すためのアイテム)
-「7」吸血鬼(ボスキャラ)
-「9」壁(進めない)

また,主人公が地図上のそれぞれの記号を訪れた場合の関数を作成する。壁は移動することができないので作らなくてよい。
 void passage() /* 通路 */
 {
     /* 何も起きない */
 }
 
 void stairway() /* 階段 */
 {
     /* 地上に脱出するかどうか質問する */
     /* 吸血鬼を倒してから脱出すれば,ハッピーエンド */
 }
 
 void door() /* 扉 */
 {
     /* 何かメッセージを表示させるといいだろう */
 }
 
 void cross() /* 十字架 */
 {
     /* 宝箱があり,中に十字架が入っている */
     /* 紙に書かれた謎々を解くと箱を開けられる */
 }
 
 void vampire() /* 吸血鬼 */
 {
      /* 吸血鬼に仲間にならないか誘われるが,断ると対決 */
      /* 十字架を持っていれば勝つことができる */
 }

プログラムは,ユーザに,主人公を東西南北(上下左右)どちらの方向に進ませるか尋ね,入力された方向に壁がなければ,xとyを適切に変化させてその場所の記号に対応する関数を呼び出す。
 printf("どちらの方向に進みますか?(1:東 2:西 3:南 4:北)");
 scanf("%d", &dir);
 switch (dir) {
 case 1:
     if (map[y][x+1] != 9) x++;
     break;
 case 2:
     if (map[y][x-1] != 9) x--;
     break;
 case 3:
     if (map[y+1][x] != 9) y++;
     break;
 case 4:
     if (map[y-1][x] != 9) y--;
     break;
 default
     printf("その方向には進めません。\n");
     /* エラー処理 */
 }
 
 switch (map[y][x]) {
 case 0:
     passage();
     break;
 case 1:
     stairway();
     break;
 case 2:
     door();
     break;
 case 5:
     cross();
     break;
 case 7:
     vampire();
     break;
 }
 
画面には,主人公の周囲の状況を表示するとよい。一例としては,最初階段にいるときに以下のよう表示させる。どんな文字や表示方法を使うか,どこまでの範囲を表示させるかは自由である。
- 文字表示の例
 あなたは,いま上り階段にいます。
 東は壁です。西は壁です。北は壁です。南は通路です。
 どちらの方向に進みますか(東西南北)?
- マップ表示の例
 ■■■
 ■?■
 ■ ■ どちらの方向に進みますか(東西南北)?

吸血鬼に負けてしまった場合は,主人公も吸血鬼になってゲームオーバーである。自分のような冒険者の生き血を求めて,このダンジョンに住み続けることになる…。

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS