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

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

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

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

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

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

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

** 基本課題 [#i341647d]

*** ポインタ [#v1805475]

''★f1)'' int型の変数aとint型へのポインタpを定義し,pを用いてaに好きな値(キーボードから入力した整数)を代入するプログラムを作成しなさい。※直接aに値を代入せず,pを使ってaの値を変える。
 int a;
 int *p;
''★f1)'' int型の変数numとint型へのポインタptrを定義し,ptrを用いてnumに好きな値(キーボードから入力した整数)を代入するプログラムを作成しなさい。※直接numに値を代入せず,ptrを使ってnumの値を変える。
 int num;
 int *ptr;

> 【ポイント】 ポインタは必ず変数などのある有効なメモリ領域を指していなければならない。初期化していないポインタは非常に危険である。※下記は課題とは関係ない。
> 【ポイント】 ポインタは必ず変数などのある有効なメモリ領域を指していなければならない。初期化していないポインタは非常に危険である。''※下記の例は課題とは関係ない。''
 char str[20];
 char *ptr;
 ptr = &str[3];
 ptr = &str[3];  /* 必ず有効な変数を指すように */

''f2)'' まず,int型の変数a,bに適当な整数を代入しておく。次に,pをaへのポインタ,qをbへのポインタとする。そして,pとqを使って(a,bを使わずに)変数a,bの内容を入れ替えなさい。結果を確認するため,入れ替える前と後の変数a,bの値を表示しなさい。
''f2)'' まず,int型の変数a,bに適当な整数を代入しておく。次に,paをaへのポインタ,pbをbへのポインタとする。そして,paとpbを使って(a,bを使わずに)変数a,bの内容を入れ替えなさい。結果を確認するため,入れ替える前と後の変数a,bの値を表示しなさい。

''★f3)'' 整数型の配列a[10]と,整数型へのポインタpを定義し,配列の要素a[3]に適当な値を代入し,pにa[3]のアドレスを代入する。このときのa[3],&a[3], *p, pを表示すること。その後,*pに別の値を代入するとどうなるかも同様に表示しなさい。

> 【ポイント】 ポインタの値(アドレス)を表示するには%pを用いる。
 printf("%p\n", ptr);
 printf("%p\n", p);

''f4)'' キーボードから入力された文字列を char 型への配列 str に読み込み,char型へのポインタ ptr を用いて,str 内の1文字目から最後までの文字列,2文字目から最後までの文字列,3文字目から最後までの文字列,…を順に出力するプログラムを作成せよ。以下に出力例を示す。
''f4)'' キーボードから入力された文字列をchar型への配列str[]に読み込み,char型へのポインタptrを用いて,str内の1文字目から最後までの文字列,2文字目から最後までの文字列,3文字目から最後までの文字列,…を順に出力するプログラムを作成しなさい。以下に出力例を示す。
 Tamagawa
 amagawa
 magawa
 agawa
 gawa
 awa
 wa
 a

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

** 応用課題 [#v5d40004]

''fx1)'' 長さ5のint型,char型の配列を作り,各要素に適当な値を代入しなさい。それぞれの配列について,ポインタを使って配列の要素のアドレスと中身を1つずつ表示しなさい。
''fx1)'' 長さ5のint型,char型の配列をそれぞれ作り,各要素に適当な値を代入しなさい。それぞれの配列について,ポインタを使って配列の要素の値とその''アドレス''を1つずつ表示しなさい。

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

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

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

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

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

** 発展課題 [#f2f615a6]

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

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

''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("どちらの方向に進みますか?(6:東 4:西 2:南 8:北)");
 scanf("%d", &dir);
 switch (dir) {
 case 6:
     if (map[y][x+1] != 9) x++;
     break;
 case 4:
     if (map[y][x-1] != 9) x--;
     break;
 case 2:
     if (map[y+1][x] != 9) y++;
     break;
 case 8:
     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