Transcript Document

第7章 配列
オセロを作るとします。
各マスの状態を保存するために64個の変数が必要です。
でも64個も変数を作るのはめんどくさいですよね。
第7章 配列
1
配列とは(1)
 配列とは、一列に並んだ変数の集合体
配列
変数
メモリ
メモリ
確保される場所は
バラバラ
必ず一列に並んで
確保
配列の宣言
文法 データ型 配列名[ 領域を確保する個数 ];
個数は定数や数字でなければならない。変数や式は不可能!
第7章 配列
2
配列とは(2)
 配列を宣言すると指定した個数分、連続してメモリに
領域が確保される
 配列名をそのまま書くと、配列の先頭のアドレスを
取得できる
#include <stdio.h>
int main(void){
int a[5];
printf("配列aの先頭のアドレスは%pです\n", a);
return 0;
}
アドレス + 1で
指定したアドレスの
#include <stdio.h>
int main(void){
隣のアドレスが取得できる
int a[5];
printf(“配列aの2つめのアドレスは%pです\n", a + 1);
return 0;
}
第7章 配列
3
配列の代入と参照(1)
配列の実体がポインタだと分かれば代入も参照もできますよね?
#include <stdio.h>
int main(void){
int a[5];
*(a + 3) = 7;
printf(“配列aの4番目の値は%dです\n", *(a + 3));
return 0;
}
※*(a + 3)は配列の先頭の3つ隣なので、4番目になります
メモリ
*a
1番目
*(a+1) *(a+2) *(a+3) *(a+4)
2番目
3番目
第7章 配列
4番目
5番目
4
配列の代入と参照(2)
ゲームになると配列をたくさん使う
その度にいちいち *(a + 5)って書くのは面倒
添字演算子で省略して書くことができる
演算子 意味
優先順位
種類
[n]
配列の参照・代入
2
単項
*(a + n)とa[n]は同じ意味 (nは整数)
#include <stdio.h>
int main(void){
int a[5];
a[3] = 7;
printf(“配列aの4番目の値は%dです\n", a[3]);
return 0;
}
第7章 配列
5
配列の代入と参照(3)
 添字演算子の方が見やすい
 添字演算子のがよくつかわれている
 添字演算子を積極的に使うべき
 むしろ*(a + n)なんて書くべきではない
(今回はポインタとの関係性について説明するために使っただけ)
特に後でやる2次元配列を
間接参照演算子を使った表し方で表すと
無茶苦茶見にくくなる
(説明のために書くけどね)
第7章 配列
6
配列の代入と参照(4)
aを配列、nを整数として、a[n]とあるとき、
nを添字
a[n]の値を、添字nの要素
n+1番目の要素
又は単に要素
配列の要素の個数のことを要素数
と言います
用語も覚えよう
第7章 配列
7
配列と引数
関数の引数として配列を渡したいこともあるでしょう
次のようにすると、引数に配列を指定できます
void func(int arr[],int arrCount){
・・・・
}
配列と一緒に配列の要素数を
渡す時が多い
配列はただのアドレスだということを用いれば、
以下のようにも書けます
void func(int* arr,int arrCount){
・・・・
}
第7章 配列
8
配列とループ処理(1)
配列とループ処理(特にfor)は非常に相性が良いです。
二つを合わせて使う場合がほとんどです
#include <stdio.h>
#define COUNT 5
int main(void){
int scores[COUNT];
for(int i = 0; i < COUNT; i++){
printf("%d人目の点数 : ", i + 1);
scanf("%d", &scores[i]);
}
for(int i = 0; i < COUNT; i++){
printf("%d人目の点数は%dです\n", i + 1, scores[i]);
}
return 0;
}
配列scoresに生徒5人分の点数を保存することができました
第7章 配列
9
配列とループ処理(2)
ここに平均点を求める処理を追加しましょう
#include <stdio.h>
#define COUNT 5
int getTotal(int arr[],int arrCount){
int total = 0;
for(int i = 0;i < arrCount;i++){
total += arr[i];
}
return total;
}
int main(void){
・・・先ほどと同じなので略・・・
printf("平均点は%lfです\n",
(double)getTotal(scores, COUNT) / COUNT);
return 0;
}
第7章 配列
10
配列の初期化
#include <stdio.h>
int main(void){
int a[3];
a[0] = 1;
a[1] = 6;
a[2] = -4;
return 0;
}
配列は左のように初期化できます。
ただ、要素数が増えたら
めんどくさいですよね
右のように省略して
書くことができます
少しは楽になりましたね
この場合は、
要素数は省略できます
#include <stdio.h>
int main(void){
int a[] = { 1, 6, -4};
return 0;
}
第7章 配列
11
配列のコピー(1)
次のプログラムで配列をコピーできないでしょうか?
#include <stdio.h>
int main(void){
int a[5];
int b[5];
b = a;
return 0;
}
コンパイルエラーが出ますね。
配列の要素に値を代入できても
配列そのものには代入できません。
第7章 配列
12
配列のコピー(2)
配列に値が代入できないなら、ポインタならどうでしょう
#include <stdio.h>
int main(void){
int a[5];
int* b;
b = a;
return 0;
}
このプログラムはコンパイルエラーは
出ませんが、重大な問題があります
これはあくまでアドレスの
コピーであって、
配列の要素そのものは
コピーされません。
よって、bを編集すれば
aも編集されます。
下のプログラムを実行しよう
#include <stdio.h>
int main(void){
int a[5];
int* b;
b = a;
b[2] = 3;
printf("a[2]=%d\n",a[2]);
return 0;
}
第7章 配列
13
配列のコピー(3)
配列のコピーは要素を一つ一つコピーするしかありません
#include <stdio.h>
#define COUNT 5
int main(void){
int a[COUNT];
int b[COUNT];
for(int i = 0;i < COUNT;i++){
b[i] = a[i];
}
return 0;
}
第7章 配列
14
二次元配列(1)
 配列を要素に持つ配列のことを二次元配列と言います
配列の宣言
文法 データ型 配列名[要素数][要素内の配列の要素数];
int a[2][3]と宣言すると・・・
 要素数が3の配列が2つできる
 要素数が2の配列ができ、その要素に要素数3の
配列のアドレスが入る
第7章 配列
15
二次元配列(2)
int a[2][3]を図式化するとこんな感じ・・・
矢印は、終点のアドレスが始点に代入されていることを表す
メモリ
要素数2の配列ができて、
要素数3の配列のアドレスが代入される
a
*a
*(a+1)
**(a+1)
**a
変数に配列の
アドレス代入
要素数3の配列が2つ出来る
*(*(a+1)+1) *(*(a+1)+2)
*(*a+1)
**(a+2)
2×3の配列と考えることができる
第7章 配列
16
二次元配列(3)
二次元配列も普通の配列と同じように添字演算子を使用できる
*(*(a + n) + m) と a[n][m]は同じ意味
(n,mは整数)
#include <stdio.h>
int main(void){
int a[2][3];
*(*(a + 1) + 2)= 5;
printf("a[1][2]=%d\n", a[1][2]);
return 0;
}
第7章 配列
17
二次元配列とループ処理(1)
一次元配列はループ処理と相性が良かったですが、
二次元配列もループ処理、特に二重ループと相性が良いです
次ページからその例を出します
次のページの例は、
5人の生徒の5教科の点数をそれぞれ入力させ、
各生徒の合計点数を表示するプログラムです
第7章 配列
18
二次元配列とループ処理(2)
#include <stdio.h>
#define PEOPLE_COUNT 5
#define SUBJECT_COUNT 5
・・・・・getTotal関数はスライド10ページと同じ・・・・
int main(void){
int scores[PEOPLE_COUNT][SUBJECT_COUNT];
for(int i = 0;i < PEOPLE_COUNT;i++){
for(int j = 0;j < SUBJECT_COUNT;j++){
printf("%d人目の%d教科目の点数 : ",i + 1, j + 1);
scanf("%d",&scores[i][j]);
}
}
for(int i = 0;i < PEOPLE_COUNT;i++){
printf("%d人目の合計点%d\n",
i + 1, getTotal(scores[i], SUBJECT_COUNT));
}
return 0;
}
第7章 配列
19
二次元配列とループ処理(3)
2次元配列とループ処理はさまざまなところで
用いられる
オセロの右からn、上からmマス目には何
がある?
将棋
テトリスも
マインスイーパー
二次元上で自由に動けるRPGのマップ
第7章 配列
20
二次元配列とループ処理(4)
#include <stdio.h>
#define COUNT 8
#define WHITE 1
#define BLACK 2
int cells[COUNT][COUNT];
void showColLine(){ //横罫線を表示
for(int j = 0;j < COUNT;j++){
printf("┼─");
}
printf("┼\n");
}
void show(){ //画面を表示
printf("---------------\n");
for(int i = 0;i < COUNT;i++){
showColLine();
for(int j = 0;j < COUNT;j++){
if(cells[j][i]== WHITE ){
printf("│○");
}else if(cells[j][i]==BLACK){
printf("│●");
}else{
printf("│ ");
}
}
printf("│\n");
}
showColLine();
}
int main(void){
int player = 1;
for(int i = 0;i < COUNT;i++){
for(int j = 0;j < COUNT;j++){
cells[j][i]=0;
}
}
cells[3][3]=WHITE;
cells[3][4]=BLACK;
cells[4][3]=BLACK;
cells[4][4]=WHITE;
while(true){
int x,y;
show();
printf("x : "); scanf("%d",&x);
printf("y : "); scanf("%d",&y);
if(x >= COUNT || x < 0 || y >= COUNT || y < 0)
break;
cells[x][y]=player;
if(player == BLACK) player = WHITE;
else player = BLACK;
}
return 0;
}
小さいので
家に帰って
wikiなどで
確認してください
第7章 配列
21
二次元配列とループ処理(5)
配列をマスターすれば
オセロも作れる
さっきのスライドは
オセロっぽい何か未完成の物
だけどね
第7章 配列
22
二次元配列とループ処理(6)
#include <stdio.h>
#define WIDTH 10
#define HEIGHT 20
bool cells[WIDTH][HEIGHT];
void show(){
printf("---------------\n");
for(int i = 0;i < HEIGHT;i++){
for(int j = 0;j < WIDTH;j++){
if(cells[j][i] )
printf("■");
else
printf(" ");
}
printf("\n");
}
}
void drop(){
for(int i=0;i < WIDTH;i++){
if(cells[i][HEIGHT - 1]) return;
}
for(int i = HEIGHT - 2;i >= 0;i--){
for(int j = 0;j < WIDTH;j++)
cells[j][i+1]=cells[j][i];
}
for(int i = 0;i < WIDTH;i++)
cells[i][0]=false;
}
int main(void){
for(int i = 0;i < HEIGHT;i++){
for(int j = 0;j < WIDTH;j++)
cells[j][i]=false;
}
cells[4][0]=true;
cells[4][1]=true;
cells[5][1]=true;
cells[5][2]=true;
while(true){
int num;
show();
printf("[0:継続 それ以外:終了]");
scanf("%d",&num);
if(num != 0) break;
drop();
}
return 0;
}
小さいので
家に帰って
wikiなどで
確認してください
第7章 配列
23
二次元配列とループ処理(7)
なんか落ちてきたー!!
第7章 配列
24
二次元配列とループ処理(8)
このように、二次元配列とループ処理を組み合わせると
すごく強力な武器になります
皆さんもぜひ、これらのプログラムは
実際に打ち込んで、実行して、理解してください
あと、改造して、実際にゲームとして動くようにしてもいいかも
第7章 配列
25
静的配列の寿命
静的配列とは、今まで扱ってきた配列です。
これらの寿命は、変数と同じです。
よって以下のプログラムは正常にする保証はありません。
寿命を迎えてすぐなら
使えるかも
時間がたつと使えない
#include <stdio.h>
#include <windows.h>
・・・・
int main(void){
int* arr;
init(&arr);
Sleep(1000);//1秒待つ
printf("arr[2]=%d\n", arr[2]);
return 0;
}
#include <stdio.h>
void init(int* arr[]){
int a[5];
a[2] = 3;
*arr = a;
}
int main(void){
int* arr;
init(&arr);
printf("arr[2]=%d\n", arr[2]); このような運任せプログラムは
return 0;
禁物です
第7章 配列
26
}
sizeof演算子
sizeof演算子は、
データ型、変数のメモリ上サイズを取得できる演算子
演算子
意味
sizeof サイズ取得
優先順位
3
種類
単項
#include <stdio.h>
int main(void){
printf("int型変数は%dバイトメモリを消費します\n",sizeof(int));
return 0;
}
第3章でint型は4バイトと教えましたがあってますね。
sizeof演算子の後は、直接データ型を指定してもよいですし、
あるデータ型の変数を指定してもかまいません
第7章 配列
27
動的配列(1)
 静的配列では要素数は必ず整数でなければならない。
 要素数がどれぐらいになるかが分からないこともあるよね
malloc関数を使えば、要素数を変数で宣言できる
malloc関数(stdlib.hで定義)
引数で指定したByte数分だけメモリを確保します
引数
何Byte連続してメモリを確保するか
戻り値
確保したメモリの先頭のアドレス
int型ポインタ変数に代入する際は
戻り値を(int*)にキャストする必要があります
第7章 配列
28
動的配列(2)
ポインタ変数arrは
#include <stdlib.h>
要素数4の配列
int main(void){
int *arr;
arr = (int*)malloc(sizeof(int) * 4);
arr[0] = 5;
return 0;
}
実はこのプログラムには問題あり!!
要注意
動的配列は寿命がない
つまり、メモリの確保をするとその領域が永遠に残ってしまう
メモリ不足の原因
これを防ぐために、強制的に寿命を迎えさせる処理が必要になる
第7章 配列
29
動的配列(3)
強制的に寿命を迎えさせることを、メモリの解放と呼びます
free関数でメモリの解放を行えます
free関数(stdlib.hで定義)
引数で指定したアドレスのメモリを開放します
引数
解放するメモリの先頭のアドレス
戻り値
なし
第7章 配列
30
動的配列(4)
#include <stdio.h>
#include <windows.h>
寿命がないので、
void init(int* arr[]){
これなら正常動作が保証される
int *a;
a = (int*)malloc(sizeof(int)*5);
a[2] = 3;
*arr = a;
}
int main(void){
int* arr;
メモリの解放は忘れやすいので
init(&arr);
極力静的配列を使うべき
Sleep(1000);//1秒待つ
printf("arr[2]=%d\n", arr[2]);
free(arr);//使ったら絶対解放
return 0;
}
第7章 配列
31
動的配列(5)
配列の要素に、変数も指定可能です
#include <stdio.h>
int main(void){
int* scores;
int count;
printf("生徒は何人いますか?"); scanf("%d", &count);
scores = (int*)malloc(sizeof(int) * count);
for(int i = 0;i < count;i++){
printf("%d人目の点数 : ", i + 1);
scanf("%d", &scores[i]);
}
for(int i = 0;i < count;i++){
printf("%d人目の点数は%dです\n", i + 1, scores[i]);
}
free(scores);
return 0;
}
第7章 配列
32
動的配列(6)
二次元配列の動的確保はどのようにやるのでしょう。
残念ながらこのようなことをする文法はありません。
メモリ
a
*a
*(a+1)
**(a+1)
**a
*(*(a+1)+1) *(*(a+1)+2)
*(*a+1)
**(a+2)
二次元配列のこの表を用いれば作れます
intポインタ型の配列をつくる
そこに、int型配列を入れていく
第7章 配列
33
動的配列(7)
#include <stdlib.h>
#define WIDTH 3
メモリの確保、
#define HEIGHT 5
解放だけで大変ですね
int main(void){
int **arr;
//メモリの確保
arr = (int**)malloc(sizeof(int) * WIDTH);
for(int i = 0;i < WIDTH;i++){
arr[i] = (int*)malloc(sizeof(int) * HEIGHT);
}
//メモリの解放
for(int i = 0;i < WIDTH;i++){
free(arr[i]);
}
free(arr);
return 0;
この方法を用いると
m×nではない二次元配列も
作成可能です
}
第7章 配列
34
動的配列(8)
#include <stdlib.h>
#define WIDTH 3
int main(void){
int **arr;
//メモリの確保
arr = (int**)malloc(sizeof(int) * WIDTH);
for(int i = 0;i < WIDTH;i++){
arr[i] = (int**)malloc(sizeof(int) * (i + 1));
}
メモリ
//メモリの解放
for(int i = 0;i < WIDTH;i++){
free(arr[i]);
}
free(arr);
return 0;
}
こんな配列ができる
第7章 配列
35
ここまでのまとめ
配列とは、連続して確保されたメモリの領域
配列の実体はポインタ
配列とループ処理は相性抜群
配列のコピーは一つ一つの要素を見るしかない
二次元配列はn×mの配列だと思えばいい
第7章 配列
36
練習問題
2×2の行列を2×2の二次元配列で表す。
このページの行列とは全て2×2の行列である。
問1
問2
2つの行列の積を計算する関数を作れ
ユーザーにx,y,θ,scaleをdouble型で入力させ、
点(x,y)を原点周りにθ回転させた点を
さらに、原点中心にscale倍に拡大した点(X,Y)
を問1の関数を使って求めるプログラムを作れ
0  cosθ  sinθ x 
 X   scale
   

 
scale sinθ cosθ  y 
Y   0
第7章 配列
37
char型
char型は-128~127の範囲を扱える整数型でした。
このデータ型はなぜ存在するのでしょうか。
コンピュータでは文字も数字として扱います。
半角文字1文字文が、ちょうどchar型変数1つ分!
次のようなプログラムで、char型変数に値を代入できます
#include <stdio.h>
int main(void){
char a;
a = 'A';
printf("%c\n",a);
return 0;
}
シングルクォーテーション(')で
文字を囲む
フォーマット指定子は%c
第7章 配列
38
char型配列(1)
では、複数の文字を代入したい場合はどうしましょうか?
もうわかりますね!配列です!!
注意
文字の配列の最後には、
必ず'\0'(ヌル文字)を指定します
また、char型配列のフォーマット指定子は%sです
#include <stdio.h>
int main(void){
char a[] = {'A', 'B', 'C', '\0'};
printf("%s\n",a);
return 0;
}
第7章 配列
39
char型配列(2)
配列の実体はポインタなのでscanfの第二引数に指定した場合、
アドレス演算子は不必要です
#include <stdio.h>
int main(void){
char name[100];//適当な分だけメモリ確保
printf("名前を入力してください ");
scanf("%s", name);
printf("あなたの名前は%sです\n" ,name);
return 0;
}
第7章 配列
40
char型配列(3)
char型配列{'A', 'B', 'C', '\0'}を
"ABC"と書くことができます
最後の\0は自動挿入される
何かに気づきましたよね?
今まで文字をダブルクォーテーションでくくってたのは
全てchar型配列だったのです!
#include <stdio.h>
int main(void){
char name[100];
char text[] = "あなたの名前は";
printf("名前を入力してください "); scanf("%s", name);
printf("%s", text); //フォーマット指定子を使ってもいいし
printf(name); //使わなくてもchar型配列は表示できる
printf("%s","です\n"); //こんな書き方もできる
return 0;
第7章 配列
}
41
夏休み前の講習はここまで!
(の予定)
皆さんお疲れ様でした
楽しい夏休みを過ごしてください!!
そして、夏休みはプログラミングに
最適な時期なので、
たくさんプログラミングしよう!!
次の講習は「第8章 構造体」です
第7章 配列
42