* 第4回 前半の応用 [#r8a4c1e6]
* 第4回 関数 [#xf6b4c3a]

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

- [[C言語のコンパイルと実行のしかた>../C言語環境]]
-- プログラムのファイル名は,「問題番号.c」としてください。例:a1.c, ax3.c
- [[プログラムの提出のしかた>../提出]]
-- 提出URL http://vilab.org/upload/clab-upload.html
- [[C言語おすすめ情報>../C言語参考資料]]
-- おすすめのWebページや書籍の紹介。
- ''★印の付いている問題''は,担当教員に見せて確認の印(またはサイン)を得ること。

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

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

''d1)'' a(1) = a(2) = 1, a(n) = a(n-1) + a(n-2) で表される数列をa(1)からa(10)まで表示するプログラムを作成しなさい。''ただし、配列は使ってはならない。''
- 再帰は使わないほうがよい。
- プログラムのファイル名は,「問題番号.c」とすること。
-- 例: a1.c, ax3.c

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

''★d2)'' まず,ともに要素数5の整数配列aとbを定義し,キーボードから数値を読み込む.そして,aとbに共通の要素があるかどうか調べ,ある場合にはそれらをすべて表示するプログラムを作成しなさい。
** 基本課題 [#af7d7608]

''★d3)'' 下記のような勝ち点表(の数値部分)を表す2次元配列int point[4][5]を定義し,各チームの勝ち点を集計して表示しなさい。
||''ブラジル''|''オーストラリア''|''クロアチア''| ''日本'' |''勝ち点''|
|''ブラジル''|CENTER:0|CENTER:3|CENTER:3|CENTER:3||
|''オーストラリア''|CENTER:0|CENTER:0|CENTER:1|CENTER:3||
|''クロアチア''|CENTER:0|CENTER:1|CENTER:0|CENTER:1||
|''日本''|CENTER:0|CENTER:0|CENTER:1|CENTER:0||
*** 関数 [#ic2c7b90]

> 【ポイント】 上の表と2次元配列point[][]の要素との対応は以下のようにする。
||''ブラジル''|''オーストラリア''|''クロアチア''| ''日本'' |''勝ち点''|
|''ブラジル''|CENTER:[0][0]|CENTER:[0][1]|CENTER:[0][2]|CENTER:[0][3]|CENTER:[0][4]|
|''オーストラリア''|CENTER:[1][0]|CENTER:[1][1]|CENTER:[1][2]|CENTER:[1][3]|CENTER:[1][4]|
|''クロアチア''|CENTER:[2][0]|CENTER:[2][1]|CENTER:[2][2]|CENTER:[2][3]|CENTER:[2][4]|
|''日本''|CENTER:[3][0]|CENTER:[3][1]|CENTER:[3][2]|CENTER:[3][3]|CENTER:[3][4]|
''★d1)'' 整数を関数の引数として,1000の位のあとに「,」(カンマ)を入れて表示する関数を作成しなさい(例:「2056」→「2,056」)。さらにその関数の動作を確認するプログラムを作成しなさい。以下に関数のプロトタイプ宣言の例を示す。(ヒント:「56」を「056」と表示させるには,printfで「%03d」とすればよい)
 void comma1000(int n);

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

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

''★d4)'' double型の引数を取り、その絶対値を返す関数absoluteを定義し、さらにその関数を利用して関数 f(x) = |-x + 1|を定義しなさい。関数f(x)の動作を確認するプログラムを作成しなさい。
- 関数呼び出しの関係は,main → f → absolute となる。

''d5)'' 自然数nに関して,以下の定義で表される再帰関数factをC言語のプログラムで作成しなさい。
>n≧1のとき  fact(n) = n・fact(n-1) &br;
n=0のとき  fact(0) = 1 

>【ポイント】 関数の中では自分自身の関数を呼ぶことも可能であり,これを「再帰」という。再帰関数を作るときには,必ず処理が終わるようにすることを注意する。
 int fact(int n)
>【ポイント】 関数の値を返すにはreturn文を用いる。例えば,円の半径を引数としてその面積を返す関数は以下のようになる。
 double circle(double r)
 {
     /* この中で fact(n-1) を使って計算 */
     return r * r * 3.14;
 }

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

''dx1)'' キーボードから0以上100未満の整数を複数読み込み、1ケタ、10台、20台、30台…90台の整数がそれぞれ何個ずつあるか数えるプログラムを作成しなさい。負数が入力された時点で入力の終了とする。
-個数を数えるカウンタを配列にする。たとえば1ケタの整数の個数をcount[0],10台をcount[1],20台をcount[2],30台をcount[3],以下同様…で数えるようにする。
*** 標準ライブラリ関数 [#j947a04d]

''dx2)'' 2次元配列を用いて2×2の行列double A[2][2]とdouble B[2][2]を定義し,AとBの各要素に数値を読み込ませてから,その積double C[2][2]を計算するプログラムを作成しなさい。
以下の問題''d4)'',''d5)''は,関数を自分で作るのではなく,C言語に既に用意されている関数(標準ライブラリ関数)を使う問題である。''関数を自作する必要はない。''
- 参考 http://www9.plala.or.jp/sgwr-t/c/sec07.html 

''dx3)'' 半径を引数として円の面積を返す関数circleを作成しなさい。次に、そのcircleを使って、半径と高さを引数として円柱の体積を求める関数cylinderを作成しなさい。最後に,cylinderを使って円柱の体積を表示するプログラムにまとめなさい。
- 〔円柱の体積〕=〔底面の円の面積〕×〔高さ〕
''★d4)'' 座標平面上の点 (x, y) から原点までの距離を計算するC言語のプログラムを作成し,コンパイル・実行させたい.キーボードからdouble型の数値xおよびyを読み込み,距離を計算して画面に表示するプログラムを作成しなさい.平方根を計算するライブラリ関数sqrtのマニュアルには,以下のように記述されている。
 #include <math.h>
 double sqrt(double x);

*** バブルソート [#q96001ae]
>【ポイント】 ライブラリ関数とは,C言語にあらかじめ用意されている関数である。ライブラリ関数を使うときには,その関数のプロトタイプ宣言などが書かれた''ヘッダファイル''(インクルードファイル)を,ソースファイルの先頭で''#include''しなければならない。

配列の中身を順番に(小さい順や大きい順に)並べ替えることを考えてみよう。もっとも古くからある方法は、バブルソートと呼ばれている。この方法では、配列を調べて、隣同士が逆順に並んでいるところがあれば交換する。これを何回も繰り返して、交換する必要がなくなるまで続ける。交換の必要がなくなれば、配列の要素はすべて順番に並んだことになる。

''準備)'' キーボードから10個の整数を要素とする配列aを読み込み、配列の先頭から順に隣同士の要素を比較して、前の要素が後の要素よりも大きいところをすべて表示するプログラムを作成しなさい。
    i→                   →|       iの変化は8まで
    0  1  2  3  4  5  6  7  8  9
 a [__|__|__|__|__|__|__|__|__|__]
>使う関数に対応するヘッダファイルはマニュアルに載っている。今までほとんど説明なく書いてきた #include <stdio.h> も,printf()やscanf()を使うためのヘッダファイルstdio.hをインクルードするものである。
 /* printfを使うためにstdio.hをインクルード */
 #include <stdio.h>
 
         a[i]とa[i+1]を比較
 int main(void)
 {
     /* stdio.hで宣言されているprintf()を使用 */
     printf("Hello, world!);
     return 0;
 }

''dx4)'' 以下に示すような動作をするプログラムを作成しなさい。
+ キーボードから10個の整数を読み込み、配列aに格納する。
+ 配列の先頭から順に隣同士の要素を比較して、前の要素が後の要素よりも大きければ交換する。
+ 配列の最後の要素まで到達したら:
-- 交換回数が0回より多ければ、0にセットしなおして2.に戻って繰り返す。
-- 交換回数が0回ならば、配列全体を表示して終了する。
>【参考】 C言語の処理系(OS)によっては,数学関係の標準ライブラリを使ったプログラムをコンパイルするために数学ライブラリをリンクする必要がある。たとえば,gcc(UNIX)ではコンパイル時のオプションで-lmと指定する。
 gcc -lm ファイル名.c

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

*** 2分探索法 [#l2e679a8]
** 応用課題 [#fc66e8e7]

''準備)'' 以下に示すソースコードは,小さい順(昇順)に並んだ整数配列a[]のa[n]〜a[m]の要素に整数xが含まれるかどうか探す関数 binary_search と,その動作を確かめるプログラムである。【???】の部分を適切に埋めて関数を完成させ,正しく動くかどうか確かめなさい。
 int binary_search(int a[], int n, int m, int x)
 {
     int c;
 
     if (【???】) 
         return -1;  /* 探索範囲がない→探索失敗 */
 
     c = (n + m) / 2;  /* 真ん中の位置の計算 */
     if (x < a[c]) { 
         /* xが真ん中の要素より小さい場合 */
         return binary_search(a, n, c - 1, x);
     } else if (【???】) {
         /* xが真ん中の要素より大きい場合 */
         return 【???】;
     } else {
         /* x == a[c] なので発見→発見位置を返す */
         return c;
     }
 }
 
 int main(void)
 {
     int a[] = {  1,  2,  4,  6,  8, 
                  9, 12, 14, 17, 20,
                 25, 26, 28, 30, 32, 
                 34, 35, 36, 39, 40 };
     int x, index;
 
     scanf("%d", &x);    
     index = binary_search(a, 0, 19, x);
     if (index != -1)
         printf("a[%d]=%d\n", index, a[index]);
     else
         printf("Not found.\n");
     return 0;    
 }
    
''dx1)'' 西暦を引数とし,その年の1月1日の元号が平成(0)・昭和(1)・大正(2)・明治(3)・それ以外(-1)のいずれであるかを返す関数を作成しなさい。その関数の動作を確認するプログラムを作成しなさい。以下にプロトタイプ宣言の例を示す。
 int gengo(int seireki);

''dx5)'' 上記の関数binary_searchを改造して,''大きい順(降順)''に並んでいる配列から整数を探索する関数を作り,キーボードから入力された数値を使って動作を確かめるプログラムを作成しなさい。
''dx2)'' キーボードから読み込みこんだ整数n (ただしn≧2)を1辺の長さとするアスタリスク(*)の四角形を描画するC言語の関数を作成し,その関数の動作を確認するプログラムを作成しなさい。たとえば,n=2,3,4のときは,それぞれ以下のような図形が出力される。
 n=2 **    n=3 ***    n=4 ****
     **        ***        ****
               ***        ****
                          ****

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

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

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

** 発展課題 [#tc5df070]

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

*** マインスイーパ [#e764b8c4]
*** ○×ゲーム [#l4e4b271]

''dz1)'' Windowsにも標準でついてくるゲーム「マインスイーパ」を作成しなさい。詳しいルールが分からない人は,Windowsでゲームを起動し,ヘルプを読むとよい。
''dz1)'' ○×ゲーム(Tic-Tac-Toe)を作る。以下がプログラムの行う処理の説明である。なるべく処理をまとめて関数を使うようにして書いてみなさい。

''準備''
+ 2次元配列で9×9の盤面を用意する。たとえば,各マスの値に下記のような意味を持たせる。
 int board[9][9];
-- -1: まだ開けていなくて,何もない。
-- -2: まだ開けていなくて,地雷が隠されている。
-- 0〜8: 既に開いていて,周囲に地雷が0〜8個あることが分かっている。 
+ まず,すべてのマス目をまだ開けていない状態にする。
 for (x = 0; x < 9; x++) {
     for (y = 0; y < 9; y++) { 
         board[x][y] = -1;
     }
 }
+ そして,ランダムな位置に地雷を10個埋設する。下記の例では同じ位置に地雷を重なって埋設してしまう可能性があるので,さらに改良したほうがよい。
 for (i = 0; i < 10; i++) {
     x = rand() % 9;
     y = rand() % 9;
     board[x][y] = -2;
 }
+2次元配列を用いて,3×3の盤面を定義し,全部のマスを空に初期化する。※ たとえば,整数配列int board[3][3]で定義する。

''ゲーム''
+ 盤面を表示する。下記は数字で表示する例。
 printf("   ");
 for (x = 0; x < 9; x++) {
     printf("%d ", x);
 }
 for (y = 0; y < 9; y++) {
     printf("%d: ", y);
     for (x = 0; x < 9; x++) {
         if (board[x][y] < 0)
             printf("- ");
         else
             printf("%d ", board[x][y]);
     }
     printf("\n");
  }
+ キーボードから座標 (x, y) を読み込む。
+ 座標位置が地雷だったらゲームオーバー。
 if (board[x][y] == -2) { ...
+ 座標位置が地雷でなく,まだ開いていなかったら,周りの8つのマス目を調べて,周りにある地雷の個数をそのマス目の値にする。※ここがポイント。
 else if (board[x][y] == -1) { ...
+ 地雷以外のすべてのマス目が開いたら攻略成功。
''入力''
+次の手が,先手の番か後手の番かを表示する。※ もちろんだが,先手と後手は交互にくる。
+キーボードから,手(座標)を読み込む。※ たとえば,「1 2」とか「1b」とか…
+盤面に,その手を保存する。※ たとえば,空なら「0」,○なら「1」,×なら「2」のように決めておく。
+盤面の状態を表示する。※ 2重ループを使う。

盤面の大きさを変えたり,地雷の目印に旗を立てて,旗と地雷の位置が完全に一致したら攻略成功にするともっとよい。
''判定''
+タテ・ヨコ・ナナメに1列そろっているかどうか判定し,そろっていればその手番の勝利とする。
+すべてのマスが○か×で埋め尽くされても勝負がつかなかったら,そこまでで終わりとする。

''反復''
+以上2.〜7.の手順を,先手と後手を交互にしながら繰り返す。

''dz2)'' ○×ゲームをコンピュータとの対戦型にしなさい。上級レベルでは,コンピュータは必勝の手を打ってくるようにしなさい。


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