LoginSignup
6
2

二次元配列を動的に割り当てる(実行時に大きさが決まる二次元配列,構造体二次元配列)

Last updated at Posted at 2021-01-26

動的な二次元配列を実行時に確保

実行時に大きさが決まる配列をC言語で書いてみます。これは動的な二次元配列の確保と表現されます。calloc関数を使います。

calloc関数

Cell Allocation(メモリセルの配置)関数です。空きメモリから必要なメモリブロックを確保します。こちらなどに情報があります。

実行時に確保と開放を行うサンプルプログラム(一次元配列)

一次元配列はよくあるパターンです。メモリブロックを確保して,その先頭アドレスをポインタ変数に代入します。callocは確保したメモリブロックの先頭アドレスを返すだけで,何に使われるか分からないので,(int *)としてキャストしています。int型の要素が代入される配列を指すポインタ(先頭アドレス)として,aに代入しています。使用し終わったらfree関数を使ってメモリブロックを開放しています。

一次元配列の確保
#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *a;
    unsigned h=10;
    unsigned i;

    a = (int *)calloc(h,sizeof(int));

    for(i=0;i<h;i++){
        a[i]=i;
        printf("%d ",a[i]);
    }
    printf("\n");

    free(a);

    return 0;
}

画像1.png

実行時に確保と開放を行うサンプルプログラム(二次元配列)

int型の要素がh個代入できる大きさの一次元配列をv個用意しています。使用し終わったらfree関数を使ってメモリブロックを開放しています。開放する順番は逆順になりますので,注意してください。callocは確保したメモリブロックの先頭アドレスを返すだけで,何に使われるか分からないので,int **int *としてキャストしています。なお,int **aint** aは同じです。前者の方が古い書き方です。

二次元配列の確保
#include<stdio.h>
#include<stdlib.h>

int main()
{
    int **a;
    unsigned v=5,h=10;
    unsigned i,j;

    a = (int **)calloc(v,sizeof(int *));
    for(i=0;i<v;i++){
        a[i] = (int *)calloc(h,sizeof(int));
    }
    
    for(i=0;i<v;i++){
        for(j=0;j<h;j++){
            a[i][j] = i*j;
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }

    for(i=0;i<v;i++){
        free(a[i]);
    }
    free(a);

    return 0;
}

画像2.png

image.png

配列の確保と開放を行う専用関数を呼ぶサンプルプログラム

main関数でmakeArray関数とfreeArray関数を呼び出しています。その際の引数ですが,ポインタを指すポインタのアドレス&a・・・つまりポインタのポインタを「指すもの」なので,ポインタのポインタのポインタとなり,引数を受け取る側では***となります。そしてaですが,上のサンプルプログラムでは,一次元配列(長さh個)の先頭アドレスをv個だけ格納する一次元配列の先頭アドレスそのものでした。今回は,さらにその先頭を指すアドレスが格納されているのが'a'となりますので,そのaに代入されているアドレス先にある実態を表現する*aに割りつけます。つまり*a = (int**)calloc(v, sizeof(int*));と書きます。for文内でのメモリの割り当てですが,*a[i]と書きたくなりますが,*より[の方が結びつきの方が強いので,*(a[i])と同じになってしまい,エラーになります。そのため (*a)[i]と書きます。

二次元配列の確保(メモリ確保と開放をする関数)
#include<stdio.h>
#include<stdlib.h>

void makeArray(int*** a, unsigned v, unsigned h)
{
    unsigned i;
    *a = (int**)calloc(v, sizeof(int*));
    for (i = 0; i < v; i++) {
        (*a)[i] = (int*)calloc(h, sizeof(int));
        //*a[i] = (int*)calloc(h,sizeof(int)); // *(a[i])と同じなのでエラー
    }
}

void freeArray(int*** a, unsigned v)
{
    unsigned i;
    for (i = 0; i < v; i++) {
        free((*a)[i]);
    }
    free(*a);
}

int main()
{
    int** a;
    unsigned v = 5, h = 10;
    unsigned i, j;

    makeArray(&a, v, h);

    for (i = 0; i < v; i++) {
        for (j = 0; j < h; j++) {
            a[i][j] = i * j;
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }

    freeArray(&a,v);
    return 0;
}

構造体の二次元配列のサンプルプログラム

これまでに書いた二つのサンプルプログラムのintを構造体の型に変えるだけなので,省略します。

構造体のメンバとして動的な二次元配列を割り当てる

二次元配列のサイズとint** a;をメンバに持つ構造体について,aに二次元配列を割り当てるmakeArray関数とfreeArray関数を書いてみます。上の二つのサンプルプログラムの応用になります。これは後述のように矢印演算子(アロー演算子)を使った方が綺麗に書けます。

構造体のメンバに動的に二次元配列を割り当てる
#include<stdio.h>
#include<stdlib.h>

typedef struct{
    unsigned v,h;
    int** a;
}stType;

void makeArray(stType* st)
{// 「.」より「*」の方が結合が弱いので()でくくる
    unsigned i;
    (*st).a = (int**)calloc((*st).v,sizeof(int*));
    for(i=0;i<(*st).v;i++){
        (*st).a[i] = (int*)calloc((*st).h,sizeof(int));
    }
}

void freeArray(stType* st)
{
    unsigned i;
    for(i=0;i<(*st).v;i++){
        free((*st).a[i]);
    }
    free((*st).a);
}

int main()
{
    stType st;
    st.v=5;
    st.h=10;
    unsigned i,j;

    makeArray(&st);
    
    for(i=0;i<st.v;i++){
        for(j=0;j<st.h;j++){
            st.a[i][j]=i*j;
            printf("%d ",st.a[i][j]);
        }
        printf("\n");
    }
    
    freeArray(&st);
    return 0;
}

矢印演算子を使って書くと次のようになります。

構造体のメンバに動的に二次元配列を割り当てる(アロー演算子)
#include<stdio.h>
#include<stdlib.h>

typedef struct {
    unsigned v, h;
    int** a;
}stType;

void makeArray(stType* st)
{// 矢印演算子を使った例
    unsigned i;
    st->a = (int**)calloc(st->v, sizeof(int*));
    for (i = 0; i < st->v; i++) {
        st->a[i] = (int*)calloc(st->h, sizeof(int));
    }
}

void freeArray(stType* st)
{
    unsigned i;
    for (i = 0; i < st->v; i++) {
        free(st->a[i]);
    }
    free(st->a);
}

int main()
{
    stType st;
    st.v = 5;
    st.h = 10;
    unsigned i, j;

    makeArray(&st);

    for (i = 0; i < st.v; i++) {
        for (j = 0; j < st.h; j++) {
            st.a[i][j] = i * j;
            printf("%d ", st.a[i][j]);
        }
        printf("\n");
    }

    freeArray(&st);
    return 0;
}

長方形でない(2番目の要素番号が一定でない)配列(構造体配列と構造体のメンバーとしての配列の両方を動的に割り当てる二次元配列)

下記のように構造体のポインタ変数を用意し,そのポインタ変数にメモリを割り当て,構造体のメンバにメモリを割り当てていくことで,長方形でない二次元配列を作ることができます。またv[i].h[j]の様にして各要素にわかりやすくアクセスできます。

構造体とメンバを使った二次元配列の動的な割り当て
#include<stdio.h>
#include<stdlib.h>

typedef struct {
    unsigned unitNum;//正方形の二次元配列ならこのメンバは不要
    int *unit;
}nnType;

int main()
{
    nnType *layer;
    unsigned layerNum=5;//要素数は適当に代入
    unsigned i, j;

    layer = (nnType*)calloc(layerNum, sizeof(nnType));//メモリ割り当て
    for (i = 0; i < layerNum; i++) {
        layer[i].unitNum = 3;//要素数は適当に代入,layer毎に異なる要素数でもok
    }
    for (i = 0; i < layerNum; i++) {
        layer[i].unit = (int*)calloc(layer[i].unitNum, sizeof(int));//メモリ割り当て
    }
    for (i = 0; i < layerNum; i++) {
        for (j = 0; j < layer[i].unitNum; j++) {
            layer[i].unit[j] = i * j;//動作チェックのため適当に代入
        }
    }
    for (i = 0; i < layerNum; i++) {
        for (j = 0; j < layer[i].unitNum; j++) {
            printf("%3d ",layer[i].unit[j]);//要素の表示
        }
        printf("\n");
    }

    for (i = 0; i < layerNum; i++) {
        free(layer[i].unit);//メモリ開放
    }
    free(layer);//メモリ開放

    return 0;
}

上のプログラムで,メモリ割り当てと開放を関数化するプログラムをもうすぐ掲載

6
2
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2