C言語で配列に要素を追加(Push)する方法【固定長、可変長配列】

333, 2021-10-11

目次

C言語の配列の要素の追加(Push)方法

C言語の配列に要素を追加(Push)する方法を解説します。

配列に要素を加えたいシーンというのはプログラミングで多いものです。
しかしC言語には配列用のライブラリがないため、自前で実装するかライブラリを使う必要があります。

たとえばC言語のライブラリとして有名なGLibなどはArrayなどのライブラリがあります。
今回はこういったライブラリを使わずに、自前で配列に要素を追加する方法を解説します。

配列に要素を追加できるようになるとC言語のプログラミングがはかどるようになります。
では解説をしていきたいと思います。

関連記事
C言語の構造体の配列の使い方
C言語で配列に要素を追加(Push)する方法【固定長、可変長配列】
C言語の配列とポインタの使い方~この2つの関係性について~
C言語の文字列の使い方: 文字配列と文字列定数
C言語のchar型の配列(文字列)の使い方

固定長配列と可変長配列

C言語の配列には大きく分けて2種類の配列があります。
1つは固定長配列(静的配列)で、もう1つは可変長配列(動的配列)です。

これらの配列はシーンによって使い分けられます。
どちらもメリット・デメリットがあり、それらの特性をふまえて使う必要があります。

たとえば固定長配列は、配列の長さは固定ですが、メモリの確保・解放をしなくていいというメリットがあります。
いっぽう可変長配列は、メモリの確保・解放は必要ですが配列の長さを可変長にできるというメリットがあります。

この記事では↓を解説していきたいと思います。

  • 固定長配列(静的配列)に要素を追加する方法

  • 可変長配列(動的配列)に要素を追加する方法

固定長配列に要素を追加する方法

最初に固定長配列(静的配列)に要素をプッシュする方法を解説したいと思います。

固定長配列はメモリの動的確保が必要ないため、気軽に使うことができます。

メモリの解放漏れなども起こりにくい設計の配列になります。

では最初に固定長配列の設計から見ていきたいと思います。

ヘッダーファイルのインクルード

固定長配列の実装に必要なヘッダーをインクルードしておきます。

#include <stdio.h>
#include <stdint.h>

固定長配列の設計

固定長配列の設計には構造体を使います。
今回はint型の配列を扱います。
↓のような構造体を定義します。

typedef struct {
    int32_t array[100];  // 配列本体
    int32_t len;  // 現在の配列の長さ
} StaticIntArray;

今回は固定長配列はStaticIntArrayという構造体にします。
そして構造体に対する操作を行う関数を定義します。
配列へ要素をプッシュする関数も同様です。

構造体は配列本体を表すarrayと、その配列の現在の長さを表すlenをメンバに持ちます。
arrayには要素がプッシュされていきます。
プッシュされるごとにlenの値がインクリメントされます。
lenの値がarrayの要素数以上になったらプッシュに失敗する仕様にします。

固定長配列への要素のプッシュ

先ほどの固定長配列をあつかう構造体に要素をプッシュする関数を定義します。

StaticIntArray *
StaticIntArray_PushBack(StaticIntArray *self, int32_t elem) {
    int32_t capa = sizeof(self->array) / sizeof(self->array[0]);
    if (self->len >= capa) {
        return NULL;  // 配列の容量が足りない
    }

    self->array[self->len++] = elem;
}

↑の関数は第1引数に構造体のポインタを取ります。
そして第2引数に配列にプッシュする要素(elem)を取ります。
関数内では配列の現在の長さ(len)がarrayの要素数を超えていないかチェックします。
超えていなければarrayelemをプッシュしてlenをインクリメントします。

プッシュに失敗した場合はNULLを返すので、この関数の使用者はこの関数の返り値をチェックすれば、関数が失敗してないか確認できます。

固定長配列のテスト

さきほどの関数などがうまく機能しているかチェックします。

void
StaticIntArray_Show(const StaticIntArray *self) {
    for (int32_t i = 0; i < self->len; i += 1) {
        printf("array[%d] = %d\n", i, self->array[i]);
    }
}

int
main(void) {
    StaticIntArray ary = {0};

    StaticIntArray_PushBack(&ary, 10);
    StaticIntArray_PushBack(&ary, 20);
    StaticIntArray_PushBack(&ary, 30);

    StaticIntArray_Show(&ary);

    return 0;
}

配列の中身を出力するStaticIntArray_Show()を作り、main()関数内で簡易的な動作テストを行います。
コードをコンパイルして実行すると↓のような結果になります。

array[0] = 10
array[1] = 20
array[2] = 30

可変長配列に要素を追加する方法

可変長配列の実装にはcalloc()など動的メモリ確保関数を使います。

可変長配列の配列は可変なため、自由に伸縮できるようにする必要があります。
そのため静的配列ではなく、ポインタと動的メモリ確保関数を使います。

可変長配列は非常に使い勝手のいい便利なデータ構造です。
そのためさまざまな言語のライブラリとして実装されていることが多いです。
C++でもSTLにvectorがあります。

では実際に可変長配列を設計していきます。

ヘッダーファイルのインクルード

可変長配列の実装に必要なヘッダーをインクルードしておきます。

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

可変長配列の設計

可変長配列も構造体を使います。
こちらもint型の配列を実装します。
↓のような構造体を定義します。

typedef struct {
    int32_t *array;  // 配列本体のポインタ
    int32_t capa;  // 配列の要素数(上限数)
    int32_t len;  // 配列の現在の長さ
} DynamicIntArray;

DynamicIntArrayarrayメンバが配列本体のポインタです。
このポインタには動的に確保されたメモリのアドレスが保存されます。
それからcapaは配列の容量(総要素数)です。
lenは現在の配列の長さです。

可変長配列の実装にも、この構造体に対する関数を定義します。
構造体とそれらの関数の関係はクラスで言うところのクラスとメソッドになります。

可変長配列のメモリ確保

構造体と配列のメモリを確保する関数を定義します。

DynamicIntArray *
DynamicIntArray_New(void) {
    // 構造体自身のメモリを確保
    DynamicIntArray *self = calloc(1, sizeof(*self));
    if (!self) {
        return NULL;
    }

    // 配列のメモリを初期値で確保
    self->capa = 2;  // 初期容量(テストのため少なめに設定)
    self->array = calloc(self->capa, sizeof(int));
    if (!self->array) {
        free(self);
        return NULL;
    }

    return self;
}

今回の設計では構造体のメモリも動的確保しています。
この設計はメモリの確保回数が増えますが、その分モジュールの取り扱いが楽になるというメリットがあります。
構造体を表すselfのメモリを確保できたら、arrayのメモリを確保します。
このメモリの確保にはcalloc()を使います。
calloc()は第1引数に要素数、第2引数に要素1つのバイト数を指定します。
calloc()void *型のポインタを返してきますで、arrayに暗黙的にキャストします。

これで配列arrayのメモリが確保できました。
arrayの初期容量は2にしてありますが、これは少なめです。
今回は動作テストも行うので、このように少なめの容量にしてあります。

可変長配列のリアロケート

DynamicIntArrayの配列arrayをリサイズする関数を定義します。

DynamicIntArray *
DynamicIntArray_Resize(DynamicIntArray *self, int32_t capa) {
    int32_t byte = sizeof(int);  // 要素1つのサイズ(バイト)
    int32_t size = capa * byte;  // 配列全体のサイズ(バイト)
    int *tmp = realloc(self->array, size);  // メモリを再確保
    if (!tmp) {
        return NULL;
    }

    self->array = tmp;  // 再確保に成功したらポインタを保存する
    self->capa = capa;  // リサイズ後の容量に更新する

    return self;
}

配列のリサイズは、配列の容量が足りなくなった時に行われます。
リサイズにはrealloc()関数を使います。
realloc()はメモリの内容を維持したまま、メモリの再確保を行う関数です。
realloc()の第1引数には既存のメモリのポインタ、第2引数には再確保するメモリのバイト数を渡します。

今回はself->arrayをリサイズするため、第1引数にこのarrayを渡しています。
リサイズするサイズは関数の引数capaから計算します。
要素1つのサイズがsizeof(int)で求まり、これにcapaをかけるとリサイズ後のメモリのサイズになります。

realloc()はメモリの確保に成功するとポインタを返してきます。
これをtmpに保存し、確保に成功していたらarrayに代入します。
self->array = realloc(...)と書いてもいいですが、この書き方だとメモリの確保に失敗した場合、arrayNULLが入ります。
そうするとarrayのメモリを解放できなくなるので、今回は一時的にtmpに保存しています。

メモリの確保に成功したらself->arrayself->capaを更新します。
これで配列のリサイズ処理は終わりです。

可変長配列への要素のプッシュ

可変長配列に要素を追加(プッシュ)する関数を定義します。

DynamicIntArray *
DynamicIntArray_PushBack(DynamicIntArray *self, int32_t elem) {
    // プッシュ前に配列のリサイズを行う
    // 現在の配列の長さが容量以上だったらリサイズ
    if (self->len >= self->capa) {
        if (!DynamicIntArray_Resize(self, self->capa * 2)) {
            return NULL;
        }
    }

    self->array[self->len++] = elem;  // 要素を配列に保存

    return self;
}

要素を配列に保存する前に、配列の長さをチェックしています。
配列の長さが容量以上になっていたら、配列をリサイズします。
そして配列の現在のlenの位置に引数の要素を代入します。
代入したらlenをインクリメントして1増加させます。

プッシュ前に配列をリサイズしているので、この配列はメモリが許す限り配列を拡張できます。
リサイズに失敗した場合はNULLを返すので、この関数の使用者は関数が失敗したかどうか知りたい場合はこの関数の返り値をチェックします。

可変長配列のメモリ解放

可変長配列のメモリを解放する関数を定義します。

void
DynamicIntArray_Del(DynamicIntArray *self) {
    if (!self) {
        return;
    }

    free(self->array);  // 配列のメモリ解放
    free(self);  // 構造体のメモリ解放
}

この関数を呼び出すと構造体自身と構造体の配列のメモリが解放されます。
そのため、関数に渡した構造体のポインタは、この関数の呼び出し以降使えなくなります。
つまりこの関数は構造体の破棄関数です。

可変長配列のテスト

可変長配列の動作テストを行います。

void
DynamicIntArray_Show(const DynamicIntArray *self) {
    for (int32_t i = 0; i < self->len; i += 1) {
        printf("array[%d] = %d\n", i, self->array[i]);
    }
}

int
main(void) {
    DynamicIntArray *ary = DynamicIntArray_New();

    DynamicIntArray_PushBack(ary, 10);
    DynamicIntArray_PushBack(ary, 20);
    DynamicIntArray_PushBack(ary, 30);
    DynamicIntArray_PushBack(ary, 40);
    DynamicIntArray_PushBack(ary, 50);
    DynamicIntArray_PushBack(ary, 60);

    DynamicIntArray_Show(ary);

    DynamicIntArray_Del(ary);
    return 0;
}

DynamicIntArray_PushBack()を何回か呼び出し、DynamicIntArray_Show()で配列の中身を確認します。
コードをコンパイルして実行すると↓のようになります。

array[0] = 10
array[1] = 20
array[2] = 30
array[3] = 40
array[4] = 50
array[5] = 60

メモリチェックツールで確認するとメモリの解放漏れもなくメモリの再確保もうまくいっていました。

おわりに

今回はC言語の配列に要素を追加(プッシュ)する方法を詳しく見てみました。
固定長配列も可変長配列も一長一短があるデータ構造ですが、どちらも要素を追加できると便利に使えます。

配列に要素を追加できるようになると、配列に好きなデータを保存できるようになります。
そのためC言語でプログラムを作る場合、配列の取り扱いとしてマスターしておきたい操作です。

配列に要素をプッシュ!

倍プッシュだ!