★ポインタ(メモリ領域の確保と解放4)★


続いては2次元配列を確保する方法を説明します。

正直に言って、2次元配列を確保する方法は面倒です。

例えば、

data[0][0]

のように2つの添え字で場所を指定出来るようにしようとした場合、非常に面倒な手続きが必要になります。

確保も面倒なら、解放も面倒なプログラムを書かなければなりません。

ジャグ配列のようなものを作るのであればやらなければなりませんが、ジャグ配列などそんなに使いません。

※ジャグ配列とは、2次元配列の各行の要素数が異なる配列です。

そこで、簡単に作れる代替案を書きます。


それは、1次元配列を確保し、2次元配列のように使う、という案です。

例えば、3×4の2次元配列が必要だとします。

data
      0  1  2  3
    +--+--+--+--+
  0 |  |  |  |  |
    +--+--+--+--+
  1 |  |  |  |  |
    +--+--+--+--+
  2 |  |  |  |  |
    +--+--+--+--+

要素数は12個ですから、1次元配列にすると↓のようになります。

data
      0  1  2  3  4  5  6  7  8  9 10 11
    +--+--+--+--+--+--+--+--+--+--+--+--+
    |  |  |  |  |  |  |  |  |  |  |  |  |
    +--+--+--+--+--+--+--+--+--+--+--+--+

要素数は同じですね。

ただ、2次元配列の先頭の要素は、

data[0][0]

ですが、1次元配列では、

data[0]

です。

全要素の対応を書くと↓になります。

  data[0][0]  data[0]
  data[0][1]  data[1]
  data[0][2]  data[2]
  data[0][3]  data[3]
  data[1][0]  data[4]
  data[1][1]  data[5]
  data[1][2]  data[6]
  data[1][3]  data[7]
  data[2][0]  data[8]
  data[2][1]  data[9]
  data[2][2]  data[10]
  data[2][3]  data[11]

2次元配列は縦と横の添え字で要素を指定します。

そこで、

  0 と 0 から 0 を
  0 と 1 から 1 を
  0 と 2 から 2 を
  0 と 3 から 3 を
  1 と 0 から 4 を
  1 と 1 から 5 を
  1 と 2 から 6 を
  1 と 3 から 7 を
  2 と 0 から 8 を
  2 と 1 から 9 を
  2 と 2 から 10 を
  2 と 3 から 11 を

作ることが出来れば、2次元配列の添え字から1次元配列にアクセス出来ます。

このような数値変換はプログラムでは良くやることです。

どのような計算をすれば、2次元配列の添え字から1次元配列の添え字を求める事が出来るでしょうか?

考えてみてください。

※ヒントは横方向の要素数(4)です。









































解答例です。

 縦の添え字 × 横の要素数(4) + 横の添え字

計算結果を全て書きます。

  0 × 4 + 0 = 0
  0 × 4 + 1 = 1
  0 × 4 + 2 = 2
  0 × 4 + 3 = 3
  1 × 4 + 0 = 4
  1 × 4 + 1 = 5
  1 × 4 + 2 = 6
  1 × 4 + 3 = 7
  2 × 4 + 0 = 8
  2 × 4 + 1 = 9
  2 × 4 + 2 = 10
  2 × 4 + 3 = 11

どうでしょう、出来ましたね。

これを使って、2次元配列の

data[i][j]

と書くところを

data[i * 4 + j]

と書けば、2つの添え字で1次元配列にアクセス出来ます。


長く説明しましたが、実際のプログラムを書きましょう。

<sample program 169-01>

#include <stdio.h>
#include <stdlib.h>

#define ROW 3
#define COL 4

int main(void)
{
    int *pData;

    int i;
    int j;

    pData = (int*)malloc(sizeof(int) * ROW * COL);

    if (!pData) {
        return 1;
    }

    for (i = 0; i < ROW; i++) {
        for (j = 0; j < COL; j++) {
            pData[i * COL + j] = i * COL + j;
        }
    }

    for (i = 0; i < ROW; i++) {
        for (j = 0; j < COL; j++) {
            printf("%3d", pData[i * COL + j]);
        }
        printf("\n");
    }

    free(pData);

    pData = NULL;

    return 0;
}

<実行結果>

  0  1  2  3
  4  5  6  7
  8  9 10 11
続行するには何かキーを押してください・・・

各要素に入れたデータは、そのまま1次元配列の添え字として使った数値です。


まず、確保のところですが、

pData = (int*)malloc(sizeof(int) * ROW * COL);

全要素数は、

  ROW * COL → 3 * 4 → 12

ですから、12個の要素を持った1次元配列を確保します。

そして、配列へのアクセスは、

pData[i * COL + j] = i * COL + j;

のように、iとjを使ってアクセスします。


以前、迷路を自動生成した事があります。

奇数×奇数の2次元配列を用意し、乱数を使って迷路を作りました。

この奇数の部分を乱数にして、毎回異なるサイズの迷路を作ってみましょう。

※関数分割はせず、main関数のみで作成します。


迷路のサイズですが、小さすぎても大きすぎてもいけませんので、次のように考えます。

  縦、横ともに7から15までの奇数とする

とすると生成したい乱数は、

 7,9,11,13,15

の5種類になります。

どのようにコードを書けば、これが出せるようになるでしょうか。

考えてみてください。









































解答例です。

7 + (rand() % 5) * 2

この式を使えば、

 rand() % 5

の結果が、

 0 の時 7
 1 の時 9
 2 の時 11
 3 の時 13
 4 の時 15

になります。

これを踏まえてサンプルプログラムを書きます。

※迷路の自動生成部分は以前やりましたので説明しません。

<sample program 169-02>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define DIRECTION 4

enum {
    FLOOR,
    WALL,
};

enum {
    UP,
    RIGHT,
    DOWN,
    LEFT,
};

int main(void)
{
    int row;
    int col;

    int *pMaze;

    int i;
    int j;

    srand((unsigned int)time(NULL));

    row = 7 + (rand() % 5) * 2;
    col = 7 + (rand() % 5) * 2;

    pMaze = (int*)malloc(sizeof(int) * row * col);

    if (!pMaze) {
        return 1;
    }

    for (i = 1; i < row - 1; i++) {
        for (j = 1; j < col - 1; j++) {
            pMaze[i * col + j] = FLOOR;
        }
    }

    for (i = 0; i < row; i++) {
        pMaze[i * col] = WALL;
        pMaze[i * col + (col - 1)] = WALL;
    }

    for (j = 1; j < col - 1; j++) {
        pMaze[j] = WALL;
        pMaze[(row - 1) * col + j] = WALL;
    }

    for (i = 2; i < row - 2; i += 2) {
        for (j = 2; j < col - 2; j += 2) {

            pMaze[i * col + j] = WALL;

            switch (rand() % DIRECTION) {
            case UP:
                pMaze[(i - 1) * col + j] = WALL;
                break;
            case RIGHT:
                pMaze[i * col + (j + 1)] = WALL;
                break;
            case DOWN:
                pMaze[(i + 1) * col + j] = WALL;
                break;
            case LEFT:
                pMaze[i * col + (j - 1)] = WALL;
                break;
            }
        }
    }

    for (i = 0; i < row; i++) {
        for (j = 0; j < col; j++) {
            if (pMaze[i * col + j] == FLOOR) {
                printf("□");
            }
            else {
                printf("■");
            }
        }
        printf("\n");
    }

    free(pMaze);

    pMaze = NULL;

    return 0;
}

<実行結果1>

■■■■■■■■■■■■■
■□□□□□□□■□■□■
■□■□■■■■■□■□■
■□■□■□□□□□■□■
■□■□■□■■■■■□■
■□■□□□■□■□□□■
■■■□■■■□■■■□■
■□□□□□□□□□□□■
■■■■■■■■■■■■■
続行するには何かキーを押してください・・・

<実行結果2>

■■■■■■■■■
■□□□■□■□■
■□■□■□■□■
■□■□■□□□■
■□■□■□■□■
■□■□□□■□■
■■■■■■■■■
続行するには何かキーを押してください・・・
何度か実行してみてください。

1次元配列を2次元配列風に使うことで、以前のプログラムとは少し異なる部分があります。

例えば、床(FLOOR)を配置する箇所は、

for (i = 1; i < ROW - 1; i++) {
    for (j = 1; j < COL - 1; j++) {
        maze[i][j] = FLOOR;
    }
}

だったのが、

for (i = 1; i < row - 1; i++) {
    for (j = 1; j < col - 1; j++) {
        pMaze[i * col + j] = FLOOR;
    }
}

こう変わっています。

しかし、やっていることは変わりません。

添え字iとjを使って配列にアクセスしているだけです。

縦方向の壁を作っている箇所も、

for (i = 0; i < ROW; i++) {
    maze[i][0] = WALL;
    maze[i][COL - 1] = WALL;
}

これが、↓のように変わっているだけです。

for (i = 0; i < row; i++) {
    pMaze[i * col] = WALL;
    pMaze[i * col + (col - 1)] = WALL;
}

基本は、

  縦の添え字 × 横の要素数(col) + 横の添え字

ですから、

maze[i][0]

pMaze[i * col + 0]

になり、

pMaze[i * col]

となっています。

maze[i][COL - 1]

pMaze[i * col + (col - 1)]

となっています。

前回と違うところは、「柱」をセットするのと迷路の自動生成を同時にしている事くらいでしょうか。


次回は、別の関数で領域を確保する方法を説明します。


次へ

戻る

目次へ