* 第4回 関数 [#xf6b4c3a]

- ''基本課題''は授業時間中に終わらせること。
- ★印の付いている問題は,当教員に見せて確認の印(またはサイン)を得ること。
- 授業時間中に終わらなかった''応用課題''は,次回(2週間後)までの宿題とする。
- ''基本課題''は,必ず授業時間中に終わらせること。

- [[C言語のコンパイルと実行のしかた>../C言語環境]]
-- プログラムのファイル名は,「問題番号.c」としてください。例:a1.c, ax3.c
- [[プログラムの提出のしかた>../提出]]
- ''★印の付いている問題''は,担当教員に見せて確認の印(またはサイン)を得ること。

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

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

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

- [[プログラムの提出のしかた>../提出]]をよく読むこと。
-- 提出URL http://vilab.org/upload/clab-upload.html
- [[C言語おすすめ情報>../C言語参考資料]]
-- おすすめのWebページや書籍の紹介。

** 基本課題 [#af7d7608]

*** 関数 [#ic2c7b90]

''★d1)'' 半径rを引数として,円周の長さを表示する関数を作成しなさい。その関数の動作を確認するプログラムを作成しなさい。以下にプロトタイプ宣言の例を示す。
 void print_circle(double r);
''★d1)'' 整数を関数の引数として,1000の位のあとに「,」(カンマ)を入れて表示する関数を作成しなさい(例:「2056」→「2,056」)。さらにその関数の動作を確認するプログラムを作成しなさい。以下に関数のプロトタイプ宣言の例を示す。(ヒント:「56」を「056」と表示させるには,printfで「%03d」とすればよい)
 void comma1000(int n);

>【ポイント】 関数プロトタイプ宣言は,関数の型(使い方)をコンパイラに教えるためのもので,関数の定義(関数の中身を実際に書くこと)とは別である。関数を定義より前に使用する場合には,プロトタイプ宣言が必要である。
 /* プロトタイプ宣言:行末にセミコロンが必要 */
 void print_circle(double r);
 void comma1000(int n);
 
 int main(void)
 {
     /* 関数の使用例 */
     print_circle(10.0);
     comma1000(2056);
 }
 
 /* 関数の中身の定義:{}の中に実行内容を書く */
 void print_circle(double r)
 void comma1000(int n)
 {
     /* 関数の実行内容 */
 }

''d2)'' 関数f(x)=x+1をC言語で表し、整数nをキーボードから読み込み、f(0)+f(1)+f(2)+...+f(n)の値を表示するプログラムを作成しなさい。
''d2)'' 関数f(x)=x+1をC言語で表し,整数nをキーボードから読み込み,f(0)+f(1)+f(2)+...+f(n)の値を表示するプログラムを作成しなさい。※関数名は「f」とし,''f(x)=x+1の定義は変更しないこと''。

''★d3)'' 2つの整数を引数として,値の小さい方を返す関数min2を作成しなさい。それを''そのまま''呼び出して,10個の整数を読み込み,その最小値を求めるmainプログラムを作成しなさい。
>【ポイント】 関数の値を返すにはreturn文を用いる。例えば,円の半径を引数としてその面積を返す関数は以下のようになる。
 double circle(double r)
 {
     return r * r * 3.14;
 }

''★d3)'' 2つの整数を引数として,値の小さい方を返す関数min2を作成しなさい。さらに,min2を呼び出して,10個の整数を読み込み,その最小値を求めるmainプログラムを作成しなさい。''※なお,1度作ったmin2を変更してはいけない''。min2の中で10個の整数を比較したり,min2の中で10回ループをまわしたり,画面表示をしてはいけない。min2は,2つの整数を引数として値の小さい方を返す関数のままである。
 int min2(int n, int m);

*** ライブラリ関数 [#j947a04d]
*** 標準ライブラリ関数 [#j947a04d]

以下の問題''d4)'',''d5)''は,関数を自分で作るのではなく,C言語に既に用意されている関数(標準ライブラリ関数)を使う問題である。''関数を自作する必要はない。''
- 参考 http://www9.plala.or.jp/sgwr-t/c/sec07.html 

''★d4)'' 座標平面上の点 (x, y) から原点までの距離を計算するC言語のプログラムを作成し,コンパイル・実行させたい.キーボードからdouble型の数値xおよびyを読み込み,距離を計算して画面に表示するプログラムを作成しなさい.平方根を計算するライブラリ関数sqrtのマニュアルには、以下のように記述されている。
''★d4)'' 座標平面上の点 (x, y) から原点までの距離を計算するC言語のプログラムを作成し,コンパイル・実行させたい.キーボードからdouble型の数値xおよびyを読み込み,距離を計算して画面に表示するプログラムを作成しなさい.平方根を計算するライブラリ関数sqrtのマニュアルには,以下のように記述されている。
 #include <math.h>
 double sqrt(double x);

>【ポイント】 C言語の処理系(OS)によっては,数学関係の標準ライブラリを使ったプログラムをコンパイルするために数学ライブラリをリンクする必要がある。たとえば,gcc(UNIX)ではコンパイル時のオプションで-lmと指定する。
>【ポイント】 ライブラリ関数とは,C言語にあらかじめ用意されている関数である。ライブラリ関数を使うときには,その関数のプロトタイプ宣言などが書かれた''ヘッダファイル''(インクルードファイル)を,ソースファイルの先頭で''#include''しなければならない。

>使う関数に対応するヘッダファイルはマニュアルに載っている。今までほとんど説明なく書いてきた #include <stdio.h> も,printf()やscanf()を使うためのヘッダファイルstdio.hをインクルードするものである。
 /* printfを使うためにstdio.hをインクルード */
 #include <stdio.h>
 
 int main(void)
 {
     /* stdio.hで宣言されているprintf()を使用 */
     printf("Hello, world!);
     return 0;
 }

>【参考】 C言語の処理系(OS)によっては,数学関係の標準ライブラリを使ったプログラムをコンパイルするために数学ライブラリをリンクする必要がある。たとえば,gcc(UNIX)ではコンパイル時のオプションで-lmと指定する。
 gcc -lm ファイル名.c

''d5)'' 自分の名前(ローマ字)をキーボードから読み込み、標準ライブラリ関数toupperを用いて1文字ずつ大文字に変換した後、画面に表示するプログラムを作成しなさい。
''d5)'' 自分の名前(ローマ字)をキーボードから読み込み,標準ライブラリ関数toupperを用いて1文字ずつ大文字に変換した後,画面に表示するプログラムを作成しなさい。
 #include <ctype.h>
 int toupper(int c);

** 応用課題 [#fc66e8e7]

''dx1)'' 西暦を引数とし,その年の1月1日の元号が平成(0)・昭和(1)・大正(2)・明治(3)・それ以外(-1)のいずれであるかを返す関数を作成しなさい。その関数の動作を確認するプログラムを作成しなさい。以下にプロトタイプ宣言の例を示す。
 int gengo(int seireki);

''dx2)'' キーボードから読み込みこんだ整数n (ただしn≧2)を1辺の長さとするアスタリスク(*)の四角形を描画するC言語の関数を作成し,その関数の動作を確認するプログラムを作成しなさい。たとえば、n=2,3,4のときは、それぞれ以下のような図形が出力される。
''dx2)'' キーボードから読み込みこんだ整数n (ただしn≧2)を1辺の長さとするアスタリスク(*)の四角形を描画するC言語の関数を作成し,その関数の動作を確認するプログラムを作成しなさい。たとえば,n=2,3,4のときは,それぞれ以下のような図形が出力される。
 n=2 **    n=3 ***    n=4 ****
     **        * *        *  *
               ***        *  *
     **        ***        ****
               ***        ****
                          ****

''dx3)'' 標準ライブラリ関数rand()は,0〜RAND_MAXの範囲の擬似乱数(でたらめな数)を返す関数である。この関数を用いて,0以上10未満の乱数を100個発生させ,画面に表示するプログラムを作成しなさい。
 #include <stdlib.h>
 int rand(void);
- なお,randは起動するたびに同じ数列を返してしまうので,実際の使用ではさらに工夫が必要である。たとえば,発展課題 ''az1)'' のようにsrand()と時間を使って初期値をかき混ぜる。

''dx4)'' 三角形の2辺の長さとそれらの挟む角を引数とし、面積を返す関数を作成しなさい。ただし、入力する角度の単位は“度”とする。 ※関数sinを利用するが、度からラジアンへの変換が必要である。
''dx4)'' 三角形の2辺の長さとそれらの挟む角を引数とし,面積を返す関数を作成しなさい。ただし,入力する角度の単位は“度”とする。 ※関数sinを利用するが,度からラジアンへの変換が必要である。
- 参考 http://shigihara.hp.infoseek.co.jp/sin53.htm
- 参考 http://www2.ocn.ne.jp/~atel.a/emath/sugakuc.html

''dx5)'' 前回の線形探索のプログラム ''bx2)'' を改造して,整数配列a,その要素数n,探したい整数xを引数に取り,''最初に''見つかった場所(添字)を返す関数linear_searchを作成しなさい。ただし,見つからなかった場合には,-1を返すようにしなさい。
''dx5)'' 前回の線形探索のプログラム ''cx2)'' を改造して,整数配列a,その要素数n,探したい整数xを引数に取り,''最初に''見つかった場所(添字)を返す関数linear_searchを作成しなさい。ただし,見つからなかった場合には,-1を返すようにしなさい。
 int linear_search(int a[], int n, int x);
- 値を「''返す''」というのは「''returnする''」ということで,画面に表示させることではありません。
- 値を「''返す''」というのは「''returnする''」ということで,画面に表示させることではない。関数linear_searchの中で画面に表示してはいけない。

** 発展課題 [#tc5df070]

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

*** ダンジョン探索 [#pca3cc96]
*** ○×ゲーム [#l4e4b271]

''dz1)'' RPGシリーズということで,今度はダンジョン(地下迷宮)の地図を作り,プレイヤーがその中を探検できるようにしてみよう。今回はやることが多いので説明はかなり簡略されている。不明な点は自分で考えて適切に設計しながらチャレンジしてほしい。
''dz1)'' ○×ゲーム(Tic-Tac-Toe)を作る。以下がプログラムの行う処理の説明である。なるべく処理をまとめて関数を使うようにして書いてみなさい。

主人公たちは村外れにたつ小さな古城にたどり着いた。言い伝えでは,この地下は昔牢獄として使われ,今では恐ろしい吸血鬼や悪霊が住み着く迷宮となっているという。村人のためにも,吸血鬼を退治しなければ…。
''準備''
+2次元配列を用いて,3×3の盤面を定義し,全部のマスを空に初期化する。※ たとえば,整数配列int board[3][3]で定義する。

ダンジョンの地図は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」壁(進めない)
''入力''
+次の手が,先手の番か後手の番かを表示する。※ もちろんだが,先手と後手は交互にくる。
+キーボードから,手(座標)を読み込む。※ たとえば,「1 2」とか「1b」とか…
+盤面に,その手を保存する。※ たとえば,空なら「0」,○なら「1」,×なら「2」のように決めておく。
+盤面の状態を表示する。※ 2重ループを使う。

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

プログラムは,ユーザに,主人公を東西南北(上下左右)どちらの方向に進ませるか尋ね,入力された方向に壁がなければ,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;
 }
 
画面には,主人公の周囲の状況を表示するとよい。一例としては,最初階段にいるときに以下のよう表示させる。どんな文字や表示方法を使うか,どこまでの範囲を表示させるかは自由である。
- 文字表示の例
 あなたは,いま上り階段にいます。
 東は壁です。西は壁です。北は壁です。南は通路です。
 どちらの方向に進みますか(東西南北)?
- マップ表示の例
 ■■■
 ■?■
 ■ ■ どちらの方向に進みますか(東西南北)?
''反復''
+以上2.〜7.の手順を,先手と後手を交互にしながら繰り返す。

吸血鬼に負けてしまった場合は,主人公も吸血鬼になってゲームオーバーである。自分のような冒険者の生き血を求めて,このダンジョンに住み続けることになる…。
''dz2)'' ○×ゲームをコンピュータとの対戦型にしなさい。上級レベルでは,コンピュータは必勝の手を打ってくるようにしなさい。


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS