第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
© Copyright 2024 ExpyDoc