* 第3回 配列 [#c2dd887a]
- ''基本課題''は,必ず授業時間中に終わらせること。
- ''★印の付いている問題''は,担当教員に見せて確認の印(またはサイン)を得ること。
- ''応用課題''は,終わらなかったら次週(1週間後の午前11:00)までの宿題とする。
- ''他人のコピーと判断されたものは正当な提出と認めない。''その場合,2人のコピーなら2分の1の点数,3人のコピーなら3分の1の点数(以下同様)とする。
- プログラムのファイル名は,「問題番号.c」とすること。
-- 例: a1.c, ax3.c
- [[プログラムの提出のしかた>../提出]]をよく読むこと。
-- 提出URL http://vilab.org/upload/clab-upload.html
** 基本課題 [#b136922c]
*** 配列 [#v070f397]
''c1)'' 4つの要素を持つdouble型配列にキーボードから数値列を読み込み,合計と平均を求めるプログラムを作成しなさい.
>【ポイント】 たとえばint a[N];と宣言した配列は,a[0]からa[N-1]までのN個の要素を持つので,a[N]は存在しない! 型がintでなくても同じである。
int a[10];
a[10] = 3; /* 誤り!! */
上記は誤りだが,通常Cコンパイラはこれを検出しない。しかし,実行時に意味不明の挙動をするバグの原因となるので,エラーが出なくても決してしてはならない。
''c2)''★ キーボードから10個の整数を読み込み,最後に''入力とは逆の順番''に表示して終了するプログラムを作成しなさい。ループ(forまたはwhile)を使うこと。
''c3)''★ キーボードから10個の整数を読み込んだ後,まず平均以上の数値''だけ''を並べて表示し,次に平均未満の数値''だけ''を並べて表示するプログラムを作りなさい。
*** 文字列 [#l7bb42ea]
''c4)'' scanfを用いて,自分の出身地(都道府県など)をローマ字で読み込み,表示するプログラムを作成しなさい。
>【ポイント】 文字列の文字配列への読み込みには,%sを用いる。このとき,読み込める最大文字数(配列のサイズ−1)を指定するべきである。
char str[128];
/* 読み込める長さを指定しないやり方 */
scanf("%s", str);
/* 読み込める長さを指定したもっと好ましいやり方 */
scanf("%127s", str);
最大文字数を指定しないと,セキュリティホール(バッファオバーフロー攻撃の標的)になる。
''c5)''★ 文字型の配列を自分の名前のローマ字で初期化し(変数の宣言時に代入し),その配列中の文字の文字コード(文字番号)を縦に並べて1文字分ずつ画面に表示するプログラムを作成しなさい。
>【ポイント】 文字列の最後は,ヌル文字('\0': 終端文字)で終わる。よって,文字列を格納するためには,''その文字列の長さ+1文字''分の領域(charの配列)が必要である。
char str[] = "Tamagawa";
の場合,下記のように9文字分の領域を取る。
0 1 2 3 4 5 6 7 8
str[] ['T'|'a'|'m'|'a'|'g'|'a'|'w'|'a'|'\0']
>また,C言語における1文字の実態は整数の文字コード(要するに「文字番号」)である。通常使われるASCIIコードでは,A〜Z,a〜zに,それぞれ連続した番号が割り当てられている。例えば上記の例を文字コードで示すと以下のようになる。
0 1 2 3 4 5 6 7 8
str[] [ 84| 97|109| 97|103| 97|119| 97| 0 ]
** 応用課題 [#e3ee7314]
''cx1)'' キーボードから,double型の配列v[2]およびw[2]を読み込み,これらを2次元ベクトルと考えて,和と内積を表示するプログラムを作成しなさい。
- 参考(ベクトルの加算) http://shigihara.hp.infoseek.co.jp/vect12.htm
- 参考(ベクトルの内積) http://shigihara.hp.infoseek.co.jp/vect16.htm
*** 線形探索法 [#sdb54816]
''cx2)'' キーボードから,10個の整数を要素とする配列aと,整数xを読み込み,xがaに含まれているかどうか,aの先頭から順に調べて結果を表示するプログラムを作成しなさい。
i→ →| iを0から9まで変化
0 1 2 3 4 5 6 7 8 9
a[] [__|__|__|__|__|__|__|__|__|__]
a[i]とxを比較
''cx3)'' キーボードからアルファベットからなる文字列を読み込み,先頭文字と同じ文字の個数を数えるプログラムを作成しなさい。たとえば,「application」ならばaが2個あるので2が結果となる。
*** ROT13暗号 [#q2f2a90c]
''cx4)'' ROT13暗号とは,欧文に含まれる文字を1文字ずつ,下記の図に示すようにアルファベット順で13文字後ろの文字に“ずらす”ことによって,文章を暗号化するものである。
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G ...
A→→→→→→→→→→→→ N
H→→→→→→→→→→→→ U
N→→→→→→→→→→→→ A
Q→→→→→→→→→→→→ D
Aの13文字後はN,HはU,NはA,QはDへと変換され,例えば「HELLO」は「URYYB」と暗号化される。すると,「ABCDEFGHIJKLM」の各文字は「NOPQRSTUVWXYZ」の各文字と''交換''されることがわかる(小文字は小文字同士で交換)。
キーボードから読み込んだ文字列を,ROT13暗号によって暗号化するプログラムを作成しなさい。また,暗号化された文字列を同じプログラムでもう一度暗号化して,ちゃんと復号化(元に戻すこと)されることを確認しなさい。
>【ポイント】 スペースを含む文字列を1行読み込む関数にはgets()があるが,最大文字数を指定できないのでセキュリティホールになる。かわりに,scanf()の書式 %[^\n] を用いるとよい。
char str[128];
scanf("%127[^\n]", str);
*** スタックの実装 [#n41232f7]
''cx5)'' スタックとは,''先入れ後出し方式''のデータ構造である。データを上に積み重ねていくような形式のデータ構造であり,一番最後に積んだデータを一番最初に取り出すことができる。一番最初に積んだデータは,後から積んだデータを全部取り出さないと,取り出すことができない。
以下のような動作をするプログラムを作成しなさい。
+ スタックとスタックポインタを表す変数を用意する。
-- int stack[100]; 100個の整数を保存しておくための配列。
-- int sp = 0; スタックの中の''次の空き''を示すための変数(初期値はゼロ)。
+ キーボードから整数xを読み込む。
+ xの値を判定し:
-- x>0ならば,xをstackに積んで2.に戻る(push)。言い換えると,stack[sp]の位置にxを代入し,spを1増やす。
● xを積む(push)
↓
0 1 2 3 4 5 6 7 8 9
stack [●|●|●|●|●|__|__|__|__|__|....
↑
次の空き(sp)
0 1 2 3 4 5 6 7 8 9
stack [●|●|●|●|●|●|__|__|__|__|....
↑
次の空き(1増えたsp)
-- x<0ならば,stackに最後に積んだ値を表示して,それをstackから取り除き,2.に戻る(pop)。言い換えると,spを1減らし,stack[sp]の位置から値を取り出す。
次の空き(sp)
↓
0 1 2 3 4 5 6 7 8 9
stack [●|●|●|●|●|●|__|__|__|__|....
↓
取り出す(pop)
次の空き(1減ったsp)
↓
0 1 2 3 4 5 6 7 8 9
stack [●|●|●|●|●|__|__|__|__|__|....
↓
●
-- x=0ならば,stackの中身を逆順にすべて表示して,プログラムを終了する。言い換えると,stack[sp-1]からstack[0]までを表示する。
+ 2.に戻って繰り返す。
なお,スタックにデータをしまう操作をpushといい,データを取り出す操作をpopという。また,スタックの中の次の空き位置(または現在の最後の位置)を保存する変数spをスタックポインタという。
** 発展課題 [#d67e5cb8]
- 発展課題には''提出期限を設けない''ので,前回までの発展課題でやっていないものも含めて,どれでも自分の面白いと思うものをやってよい。
*** ブラックジャック [#md555ea1]
''cz1)'' トランプゲームのブラックジャック(簡略版)を作成したい。以下の手順で動作するプログラムを作成しなさい。
''準備''
+ 以下のような変数を用意しておくとよい。
int cards[52]; /* トランプのカード52枚を格納する配列 */
int player[10]; /* プレイヤーの手札 */
int dealer[10]; /* コンピュータの手札 */
int n_drawn; /* 既に束から引いたカードの枚数 */
int n_player; /* プレイヤーの手札の枚数 */
int n_dealer; /* コンピュータの手札の枚数 */
+ 配列cards[]に,スペードの「A」からクラブの「K」までの全カードを1枚ずつ格納させる。しかし,このブラックジャックではカードのスーツ(スペード・ハート等)は関係ないので,1から13までの整数を4個ずつ格納すればよい。
n = 0;
for (i = 1; i <= 13; i++) {
cards[n++] = i;
cards[n++] = i;
cards[n++] = i;
cards[n++] = i;
}
+ カードの束をよくシャッフルする。よくある方法では,配列card[]の要素からランダム2枚を選び交換する作業を適当な回数(以下の例では300回)繰り返す。なお,rand()を使う場合には''az1)''を参考にして発生前に乱数をかき混ぜておく。
/* rand()使用前に1度だけ実行 */
srand((unsigned) time(NULL))
for (i = 0; i < 300; i++) {
n = rand() % 52;
m = rand() % 52;
temp = cards[n];
cards[n] = cards[m];
cards[m] = temp;
}
''ゲーム''
+ 先頭のカードを2枚取り出し,コンピュータの手札とする。このうち1枚目だけを画面に表示する。
-- 配列cards[]の先頭の整数2つを,配列dealer[]にコピーすればよい。
-- 変数n_drawnとn_dealerも適切な値になるように。
+ 同様に,次のカードを2枚取り出し,プレイヤーの手札とする。これは両方とも画面に表示する。
-- 次の整数2つを,配列player[]にコピーすればよい。
-- 変数n_drawnとn_playerも適切な値になるように。
+ プレイヤーはカードの数字の合計が21に近くなるように,カードを1枚ずつ引くことができる。
-- 例えば「次のカードを引きますか?(Y/N)」という表示を出して,「Y」である間カードを引かせて配列player[]に追加していく。
-- 「J」「Q」「K」は,10として計算する。「A」は1または11の好きなほうで数えてよい。
-- カードの合計が21を超えてしまった場合は,その時点で負けである。
+ プレイヤーが終わったら,コンピュータの番である。
-- コンピュータは,カードの数字の合計が16以下だった場合には1枚ずつ引いて手札に加える。
-- もし,17以上になったらそのカードで引くのを終わりにする。
-- このとき「A」が含まれていたら,まず11として計算して17以上21以下ならそこで終わりとし,そうでなければ1として計算することにする。
-- プレイヤーと同様に21を超えてしまったら,その時点で負けになる。
+ 最後に,プレイヤーとコンピュータを比較して,21を超えずに21に近いほうが勝ちとなる。