f1) char型の配列str[10]を定義し,その各要素a[0], a[1], ..., a[9]のアドレス(メモリ内での格納番地)を表示するプログラムを作成しなさい。※この問題ではポインタは用いないが,ポインタはこのような変数のアドレス(格納番地)を記憶するための変数である。
【ポイント】 変数のアドレスを求めるには「&」演算子を用いる。また,printfでアドレス(番地)を表示するには%pを用いる。下記はint型の変数の例である。
int n; printf("%p\n", &n);
★f2) int型の変数numとint型へのポインタptrを定義し,ptrを用いてnumに好きな値(キーボードから入力した整数)を代入するプログラムを作成しなさい。※直接numに値を代入せず,ptrを使ってnumの値を変える。
int num; /* int型の変数 */ int *ptr; /* int型へのポインタ */
【ポイント】 ポインタは必ず変数などのある有効なメモリ領域を指していなければならない。そうでないポインタは非常に危険である。※下記の例は課題とは関係ない。
char str[20]; char *ptr; ptr = &str[3]; /* 必ず有効な変数を指すように */
★f3) まず,int型の変数a,bに適当な整数を代入しておく。次に,apをaへのポインタ,bpをbへのポインタにする。そして,apとbpを使って(a,bを使わずに)変数a,bの内容を入れ替えるプログラムを作成しなさい。結果を確認するため,入れ替える前と後の変数a,bの値を表示させなさい。
【ポイント】 ポインタの頭に「*」演算子をつけると,そのポインタの指すアドレスにあるデータを普通の変数と同じように操作することができる。
int n; int *p; p = &n; *p = 3; /* n = 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を返すようにしなさい。
int divide(int x, int y, int *sho, int *amari);
使用例
int sho, amari, ok; ok = divide(5, 3, &a, &b); if (ok) printf("商 %d 余り %d\n", sho, amari); else printf("0では割れません");
【ポイント】 C言語の関数呼び出しは値渡し(call by value)なので,呼び出された関数の中で変数の値を変えても,呼び出し元の変数にはまったく影響がない。しかし,関数の変数のアドレス(格納場所)を渡せば,呼び出された関数の中で呼び出し元の変数の値を変更できる。
fx1) 長さ5個のint型,char型の配列をそれぞれ作り,各要素に適当な値を代入しなさい。それぞれの配列について,ポインタを使って配列の要素の値(内容)とそのアドレス(番地)を1つずつ表示しなさい。
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; 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を使ってよいので調べてみよ。
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はそれぞれ次のものを表す(ここらへんは自由にアレンジしてよい)。
また,主人公が地図上のそれぞれの記号を訪れた場合の関数を作成する。壁は移動することができないので作らなくてよい。
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; }
画面には,主人公の周囲の状況を表示するとよい。一例としては,最初階段にいるときに以下のよう表示させる。どんな文字や表示方法を使うか,どこまでの範囲を表示させるかは自由である。
あなたは,いま上り階段にいます。 東は壁です。西は壁です。北は壁です。南は通路です。 どちらの方向に進みますか(東西南北)?
■■■ ■?■ ■ ■ どちらの方向に進みますか(東西南北)?
吸血鬼に負けてしまった場合は,主人公も吸血鬼になってゲームオーバーである。自分のような冒険者の生き血を求めて,このダンジョンに住み続けることになる…。