第3回 配列

基本課題

配列

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個の整数を読み込んだ後,まず平均以上の数値だけを並べて表示し,次に平均未満の数値だけを並べて表示するプログラムを作りなさい。

文字列

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 ]

応用課題

cx1) キーボードから,double型の配列v[2]およびw[2]を読み込み,これらを2次元ベクトルと考えて,和と内積を表示するプログラムを作成しなさい。

線形探索法

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暗号

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);

スタックの実装

cx5) スタックとは,先入れ後出し方式のデータ構造である。データを上に積み重ねていくような形式のデータ構造であり,一番最後に積んだデータを一番最初に取り出すことができる。一番最初に積んだデータは,後から積んだデータを全部取り出さないと,取り出すことができない。 以下のような動作をするプログラムを作成しなさい。

  1. スタックとスタックポインタを表す変数を用意する。
    • int stack[100]; 100個の整数を保存しておくための配列。
    • int sp = 0; スタックの中の次の空きを示すための変数(初期値はゼロ)。
  2. キーボードから整数xを読み込む。
  3. 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]までを表示する。
  4. 2.に戻って繰り返す。

なお,スタックにデータをしまう操作をpushといい,データを取り出す操作をpopという。また,スタックの中の次の空き位置(または現在の最後の位置)を保存する変数spをスタックポインタという。

発展課題

ブラックジャック

cz1) トランプゲームのブラックジャック(簡略版)を作成したい。以下の手順で動作するプログラムを作成しなさい。

準備

  1. 以下のような変数を用意しておくとよい。
    int cards[52];   /* トランプのカード52枚を格納する配列 */
    int player[10];  /* プレイヤーの手札 */
    int dealer[10];  /* コンピュータの手札 */
    int n_drawn;     /* 既に束から引いたカードの枚数 */
    int n_player;    /* プレイヤーの手札の枚数 */
    int n_dealer;    /* コンピュータの手札の枚数 */
  2. 配列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;
    }
  3. カードの束をよくシャッフルする。よくある方法では,配列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;
    }

ゲーム

  1. 先頭のカードを2枚取り出し,コンピュータの手札とする。このうち1枚目だけを画面に表示する。
    • 配列cards[]の先頭の整数2つを,配列dealer[]にコピーすればよい。
    • 変数n_drawnとn_dealerも適切な値になるように。
  2. 同様に,次のカードを2枚取り出し,プレイヤーの手札とする。これは両方とも画面に表示する。
    • 次の整数2つを,配列player[]にコピーすればよい。
    • 変数n_drawnとn_playerも適切な値になるように。
  3. プレイヤーはカードの数字の合計が21に近くなるように,カードを1枚ずつ引くことができる。
    • 例えば「次のカードを引きますか?(Y/N)」という表示を出して,「Y」である間カードを引かせて配列player[]に追加していく。
    • 「J」「Q」「K」は,10として計算する。「A」は1または11の好きなほうで数えてよい。
    • カードの合計が21を超えてしまった場合は,その時点で負けである。
  4. プレイヤーが終わったら,コンピュータの番である。
    • コンピュータは,カードの数字の合計が16以下だった場合には1枚ずつ引いて手札に加える。
    • もし,17以上になったらそのカードで引くのを終わりにする。
    • このとき「A」が含まれていたら,まず11として計算して17以上21以下ならそこで終わりとし,そうでなければ1として計算することにする。
    • プレイヤーと同様に21を超えてしまったら,その時点で負けになる。
  5. 最後に,プレイヤーとコンピュータを比較して,21を超えずに21に近いほうが勝ちとなる。

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-10-30 (火) 15:00:16