【C言語入門】part8 : 配列
前回はfor文と乱数の生成についてやりました.
今回は配列についてやっていきます.
配列(1次元)
今回は新しく配列という概念を導入します.たとえば主人公が攻撃ではなく,アイテムを使用して回復するなんてときもありますよね.そんなとき所持しているアイテムの数を変数に保存したいと思って,いちいち変数を作って,item1,item2,item3…って作るのは大変です.このような同じ種類(型)の連続した変数を使うとき,1パックにまとめたものを配列といいます.使い方は以下のような感じです.
・test8-1.c
#include <stdio.h> int main(void){ int heroHP; int choose; int item[3] = {1, 2, 1}; //アイテムの個数を入れる配列の定義(1個,2個,1個) int i; do{ printf("主人公のHPを入力してね:"); scanf("%d",&heroHP); if(heroHP <= 0){ printf("HPは0より大きい値で入力してね\n"); } }while(heroHP <= 0); printf("***かばんの中身***\n"); printf("0:回復薬, 1:大回復薬, 2:毒薬(自分用)\n個数 "); for(i = 0; i < 3; i++){ printf("%d:%dコ ",i,item[i]); } printf("\n"); do{ printf("使うアイテムの番号を指定してね:"); scanf("%d",&choose); if(choose < 0 || choose >= 3){ printf("0~2で入力してね\n"); } }while(choose < 0 || choose >= 3); switch(choose){ case 0: printf("主人公は3回復した!\n"); heroHP = heroHP + 3; break; case 1: printf("主人公は6回復した!\n"); heroHP = heroHP + 6; break; case 2: printf("主人公は毒のダメージを受けた!\n"); heroHP = heroHP - 3; } item[choose] = item[choose] - 1; printf("主人公のHP:%d\n",heroHP); printf("***かばんの中身***\n"); printf("0:回復薬, 1:大回復薬, 2:毒薬(自分用)\n個数 "); for(i = 0; i < 3; i++){ printf("%d:%d ",i,item[i]); } printf("\n"); return 0; }
・実行結果
C:\Users\ユーザ名\Desktop>test8-1 主人公のHPを入力してね:10 ***かばんの中身*** 0:回復薬, 1:大回復薬, 2:毒薬(自分用) 個数 0:1コ 1:2コ 2:1コ 使うアイテムの番号を指定してね:0 主人公は3回復した! 主人公のHP:13 ***かばんの中身*** 0:回復薬, 1:大回復薬, 2:毒薬(自分用) 個数 0:0 1:2 2:1
配列はitem[数値]のように書き,printfなどで出力するときはitem[1]などと書きます.今回はfor文をつかって配列の中身を全部表示したいのでitem[i](iは0~2)になっています.ここで注目してほしいのは配列の通し番号は0から始まっていることです.なので1番目の値が欲しいときはitem[0]と入力します.このitem配列をイメージ図にすると以下のようになります.変数は箱のようなイメージなので,配列は箱が連続しているイメージです.
配列の定義の仕方は以下のような形です.
今回は初期値を最初に指定しましたが,forを用いてキーボード入力から受け付けることも可能です.例えば以下のような感じです.
・test8-2.c
#include <stdio.h> int main(void){ int item[3]; int i; printf("***かばんの中身***\n"); printf("0:回復薬, 1:大回復薬, 2:毒薬(自分用)\n"); printf("それぞれ何個欲しいか入力してね\n"); for(i = 0; i < 3; i++){ printf("%d:",i); scanf("%d",&item[i]); } printf("***かばんの中身***\n"); printf("0:回復薬, 1:大回復薬, 2:毒薬(自分用)\n個数 "); for(i = 0; i < 3; i++){ printf("%d:%dコ ",i,item[i]); } printf("\n"); return 0; }
・実行結果
C:\Users\ユーザ名\Desktop>test8-2 ***かばんの中身*** 0:回復薬, 1:大回復薬, 2:毒薬(自分用) それぞれ何個欲しいか入力してね 0:4 1:5 2:0 ***かばんの中身*** 0:回復薬, 1:大回復薬, 2:毒薬(自分用) 個数 0:4コ 1:5コ 2:0コ
配列はたくさん変数を作らずに,まとめることができる便利なもの!っていう解釈でOKなのでいろんな場面で使って慣れていきましょう.
2次元配列
2次元配列….なんか難しそうです.でもすごく単純な話で,普通の(1次元)配列の各要素に配列を入れるイメージです.1次元の配列を縦に並べてさらにその配列の1つの要素の中に配列を入れると横に広がり,配列が縦横に広がるマップのようなものができます.縦に3マス,横に4マスあるマップを2次元配列で表すと以下の図のようになります.縦に何個,横に何個?っていう考え方をするとわかりやすいですね.2次元配列の書き方は以下のような感じです.
それでは2次元配列の具体例を見てみましょう.RPGゲームのマップをイメージしてみてください.
(ドット絵世界)
縦横6マスで移動できるフィールドを0,端は壁(1)で囲われているものとしたとき以下のように書けます.
・test8-3.c
#include <stdio.h> int main(void){ int map[6][6]; //マップの定義 int i,j; //マップの初期化(0:移動可能,1:壁(移動不可)) for(i = 0 ;i < 6 ;i++){ for(j = 0 ;j < 6 ;j++){ if(i == 0 || i == 5 || j == 0 || j == 5){ map[i][j] = 1; } else{ map[i][j] = 0; } } } for(i = 0 ;i < 6 ;i++){ for(j = 0 ;j < 6 ;j++){ printf("%d",map[i][j]); } printf("\n"); } return 0; }
・実行結果
C:\Users\ユーザ名\Desktop>test8-3 111111 100001 100001 100001 100001 111111
マップの初期化では,for文を2重にすることで,縦と横の全要素にアクセスできるようにしています.途中のif文はiかj(縦か横)が0か5であれば画面端なので1(壁)を入れるという意味です.
ちなみにマップの定義は後からforで作るのではなく,1次元配列のときのように以下のように初期値として代入することも可能です.マップを定義するときはこのほうがビジュアル的にわかりやすそうですね.
//マップの定義と初期化(0:移動可能,1:壁(移動不可)) int map[6][6] = { {1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1}};
これだけではマップを生成しただけで操作ができないので,主人公(@)を操作して移動できるようにしてみましょう.ただし,壁(1)は移動できないのでその処理に注意して,処理を見てみましょう.
・test8-4.c
#include <stdio.h> int main(void){ int map[6][6]; //マップの定義 int i,j; int choose; int hero[2] = {1, 1}; //主人公の初期位置 int end = 0; //終了条件 //マップの初期化(0:移動可能,1:壁(移動不可)) for(i = 0 ;i < 6 ;i++){ for(j = 0 ;j < 6 ;j++){ if(i == 0 || i == 5 || j == 0 || j == 5) map[i][j] = 1; else map[i][j] = 0; } } /*RPGゲームメインループ処理*/ while(end == 0){ for(i = 0 ;i < 6 ;i++){ for(j = 0 ;j < 6 ;j++){ //主人公の居る位置は0でなく@を描画 if(hero[0] == i && hero[1] == j) printf("@"); else printf("%d",map[i][j]); } printf("\n"); } printf("(↑:1,→:2,↓:3,←:4,5:END)>"); scanf("%d",&choose); switch(choose){ case 1: //次移動するところが壁でなければ移動する if(map[hero[0]-1][hero[1]] != 1) hero[0] -= 1; break; case 2: if(map[hero[0]][hero[1]+1] != 1) hero[1] += 1; break; case 3: if(map[hero[0]+1][hero[1]] != 1) hero[0] += 1; break; case 4: if(map[hero[0]][hero[1]-1] != 1) hero[1] -= 1; break; case 5: end = 1; break; default: printf("不正な入力です\n"); } } return 0; }
・実行結果
C:\Users\ユーザ名\Desktop>test8-4 111111 1@0001 100001 100001 100001 111111 (↑:1,→:2,↓:3,←:4,5:END)>2 111111 10@001 100001 100001 100001 111111 (↑:1,→:2,↓:3,←:4,5:END)>1 111111 10@001 100001 100001 100001 111111 (↑:1,→:2,↓:3,←:4,5:END)>2 111111 100@01 100001 100001 100001 111111 (↑:1,→:2,↓:3,←:4,5:END)>3 111111 100001 100@01 100001 100001 111111 (↑:1,→:2,↓:3,←:4,5:END)>5
実際主人公(@)が動いているのがわかると思います.主人公の現在地座標をhero[2]に保存しています.hero[0]がy座標,hero[1]がx座標のイメージですね.次移動するところが壁なら移動しないといった処理がすごくRPGっぽいですね.アイテムを使ったら壁を貫通できる…とかも面白いかもしれません.RPGゲームをもし原始的にC言語で作るならこんな感じになるハズです.さらに今回if文の{}を省略しましたが,if文などは中身の処理の内容が1行だけであるとき{}を省略して書くことができます.便利なので使ってみましょう.
今回マップを例にしましたが,この1つの要素を画素とすると,縦横6画素の6×6の画像とも言えます.つまりみなさんが見ている画像も2次元配列みたいなものってことですね.
配列は1次元,2次元とあるからには3次元もあります.配列の中の配列の中の配列…といった感じに説明すると大変なので省略しますが,例えば3次元は3DCGのようなものを配列で作れるといったイメージになりますね.
まずは配列という概念を理解して書けるようにしていきましょう.練習問題もぜひやってみてください.
お疲れさまでした.
練習問題
・問題1-1テストの集計プログラムを作成する.まず10人分のテストの点数(0~100)をキーボードから入力し,整数型の配列score(長さ10)に代入し,表示するプログラムを書いてみよう(test8-5.c).
・実行結果例
C:\Users\ユーザ名\Desktop>test8-5 ***テスト集計プログラム*** 0番目の人のテストの点数:55 1番目の人のテストの点数:63 2番目の人のテストの点数:46 3番目の人のテストの点数:50 4番目の人のテストの点数:77 5番目の人のテストの点数:69 6番目の人のテストの点数:89 7番目の人のテストの点数:400 7番目の人のテストの点数:40 8番目の人のテストの点数:5 9番目の人のテストの点数:53 0番目の人:55点 1番目の人:63点 2番目の人:46点 3番目の人:50点 4番目の人:77点 5番目の人:69点 6番目の人:89点 7番目の人:40点 8番目の人:5点 9番目の人:53点
・問題1-2
test8-5.cで作成したプログラムを改良する.点数の分布を示す以下のような結果を得られるプログラムを作成してみよう(test8-6.c).ただし点数分布を保存する整数型配列agg(長さ11),平均点を求めるために合計値を保存する整数型変数totalを定義する.なお,点数分布を表示するにあたって点数の高い人から低い人へ表示するときにfor文を用いるときi++と同様に,大きい値から小さい値へ1ずつ減らすものにi--(i = i - 1と同じ意味)があるので使ってみよう.
[100点]:○人
[90~99点]:○人
[80~89点]:○人
[70~79点]:○人
[60~69点]:○人
[50~59点]:○人
[40~49点]:○人
[30~39点]:○人
[20~29点]:○人
[10~19点]:○人
[0~9点]:○人
平均点:○点(小数点以下2桁まで)
C:\Users\ユーザ名\Desktop>test8-6 ***テスト集計プログラム*** 0番目の人のテストの点数:55 1番目の人のテストの点数:63 2番目の人のテストの点数:46 3番目の人のテストの点数:50 4番目の人のテストの点数:77 5番目の人のテストの点数:69 6番目の人のテストの点数:89 7番目の人のテストの点数:100 8番目の人のテストの点数:5 9番目の人のテストの点数:53 [100点]:1人 [90~99点]:0人 [80~89点]:1人 [70~79点]:1人 [60~69点]:2人 [50~59点]:3人 [40~49点]:1人 [30~39点]:0人 [20~29点]:0人 [10~19点]:0人 [0~9点]:1人 平均点:60.70点
・問題2
test8-4.cを改良する.まずマップの大きさを7×7(縦横7マス)に拡張する.そこへ任意の地点(例えばi=3,j=3)にゴール(G)を作り,そこへたどり着いたら"GOAL!"と表示し終了するプログラム(test8-7.c)を作成してみよう.ただしゴールの座標は整数型配列goal[2]で定義する.
・実行結果例
C:\Users\ユーザ名\Desktop>test8-7 1111111 1@00001 1000001 100G001 1000001 1000001 1111111 (↑:1,→:2,↓:3,←:4)>2 1111111 10@0001 1000001 100G001 1000001 1000001 1111111 (↑:1,→:2,↓:3,←:4)>2 1111111 100@001 1000001 100G001 1000001 1000001 1111111 (↑:1,→:2,↓:3,←:4)>1 1111111 100@001 1000001 100G001 1000001 1000001 1111111 (↑:1,→:2,↓:3,←:4)>3 1111111 1000001 100@001 100G001 1000001 1000001 1111111 (↑:1,→:2,↓:3,←:4)>3 1111111 1000001 1000001 100@001 1000001 1000001 1111111 GOAL!
練習問題の解答例
・二次元配列の?の答えmap[2][1]
・問題1-1
・test8-5.c
#include <stdio.h> int main(void){ int score[10]; int i; printf("***テスト集計プログラム***\n"); for(i = 0; i < 10; i++){ do{ printf("%d番目の人のテストの点数:",i); scanf("%d",&score[i]); }while(score[i] < 0 || score[i] > 100); } printf("\n"); for(i = 0; i < 10; i++){ printf("%d番目の人:%d点\n",i,score[i]); } printf("\n"); return 0; }
・問題1-2
・test8-6.c
#include <stdio.h> int main(void){ int score[10]; int i; int agg[11]; int total; //agg配列の初期化 for(i = 0; i < 11; i++){ agg[i] = 0; } printf("***テスト集計プログラム***\n"); for(i = 0; i < 10; i++){ do{ printf("%d番目の人のテストの点数:",i); scanf("%d",&score[i]); }while(score[i] < 0 || score[i] > 100); } printf("\n"); //i番目の人の点数の分布場所のaggを++する //同時に平均点を求めるためのtotalを作る for(i = 0; i < 10; i++){ agg[score[i]/10]++; total = total + score[i]; } printf("[100点]:%d人\n",agg[10]); for(i = 9; i >= 0; i--){ printf("[%d~%d点]:%d人\n",i*10,i*10+9,agg[i]); } printf("平均点:%.2f点\n",total/10.0); return 0; }
・問題2
・test8-7.c
#include <stdio.h> int main(void){ int map[7][7]; //マップの定義 int i,j; int choose; int hero[2] = {1, 1}; //主人公の初期位置 int goal[2] = {3, 3}; //ゴールの位置 //マップの初期化(0:移動可能,1:壁(移動不可)) for(i = 0 ;i < 7 ;i++){ for(j = 0 ;j < 7 ;j++){ if(i == 0 || i == 6 || j == 0 || j == 6) map[i][j] = 1; else map[i][j] = 0; } } /*RPGゲームメインループ処理*/ while(1){ for(i = 0 ;i < 7 ;i++){ for(j = 0 ;j < 7 ;j++){ //主人公の居る位置は0でなく@を描画 if(hero[0] == i && hero[1] == j) printf("@"); //ゴールの場所は0ではなくGを描画 else if(goal[0] == i && goal[1] == j) printf("G"); else printf("%d",map[i][j]); } printf("\n"); } //終了判定 if(goal[0] == hero[0] && goal[1] == hero[1]) break; printf("(↑:1,→:2,↓:3,←:4)>"); scanf("%d",&choose); switch(choose){ case 1: //次移動するところが壁でなければ移動する if(map[hero[0]-1][hero[1]] != 1) hero[0] -= 1; break; case 2: if(map[hero[0]][hero[1]+1] != 1) hero[1] += 1; break; case 3: if(map[hero[0]+1][hero[1]] != 1) hero[0] += 1; break; case 4: if(map[hero[0]][hero[1]-1] != 1) hero[1] -= 1; break; default: printf("不正な入力です\n"); } } printf("GOAL!\n"); return 0; }