C言語で可変長配列ArrayListを実装する
皆さん、こんにちは。技術開発グループのn-ozawanです。
GWですね。この時期に観光客で大混雑する鎌倉では、徒歩で移動するように呼び掛ける実証実験をしています。
本題です。
C言語で実装しているとき、JavaのArrayListのような可変長配列が欲しいと思ったことはありませんか?今回はC言語でArrayListを実装してみたいと思います。
目次
C言語でArrayList
ゴール
C言語で可変長配列を実現するArrayListを実装します。今回はあくまでサンプル程度なので、要素の追加、要素の挿入、要素の取得、配列長の取得の計4メソッドを作成します。ArrayListが分からない方は、過去に投稿した「JavaのArrayListとLinkedListの違いを理解して使い分ける」を参照ください。
List インターフェース
まずはListインターフェースを実装します。「インターフェイス?オブジェクト指向言語じゃないのに何言ってんだ?」と思った方は、過去に投稿した、C言語でオブジェクト指向プログラミング(1回目、2回目)を参照ください。
// メンバ変数
typedef struct _List {
void (*List_add)(struct _List* list, void* element);
void (*List_insert)(struct _List* list, int index, void* element);
const void* (*List_get)(const struct _List* list, int index);
int (*List_size)(const struct _List* list);
} List;
// メソッド
void List_add(List* list, void* element) {
if (list->List_add) {
list->List_add(list, element);
} else {
printf("not implemented.\n");
}
}
// 以下略。
// List_insert(...), List_get(...), List_size(...) も、List_add(...)と同じように実装する。
コンストラクタとデストラクタ
ArrayListのコンストラクタとデストラクタを実装します。
// メンバ変数
typedef struct {
List parent; // 継承
void* array; // 配列の実体
int sizeOfElement; // 要素1個分のサイズ
int length; // 配列に格納した要素数
int maxLength; // メモリ確保した配列の数
} ArrayList;
// コンストラクタ
ArrayList* ArrayList_new(int sizeOfElement) {
ArrayList* list = (ArrayList*)malloc(sizeof(ArrayList));
// 初期配列の作成
list->length = 0;
list->maxLength = 10;
list->sizeOfElement = sizeOfElement;
list->array = (void*)malloc(sizeOfElement * list->maxLength);
// オーバーライド
list->parent.List_add = ArrayList_add;
list->parent.List_insert = ArrayList_insert;
list->parent.List_get = ArrayList_get;
list->parent.List_size = ArrayList_size;
return list;
}
// デストラクタ
void ArrayList_delete(ArrayList* arraylist) {
if (arraylist->array) free(arraylist->array);
free(arraylist);
}
array
に配列の実体を保持しています。初回は要素数10個分のメモリを確保します。そしてListインターフェースにArrayListの各関数ポインタを設定します。array
の型をvoid*
にしている理由は、構造体の配列も出来るようにするためです。
要素の取得
要素を取得する処理を実装します。
const void* ArrayList_get(const List* list, int index) {
ArrayList* arraylist = (ArrayList*)list;
return arraylist->array + (index * arraylist->sizeOfElement);
}
配列の実体array
の先頭から、指定されたindex
までのアドレス値を返却するだけです。
要素の追加
要素を追加する処理を実装します。
int ArrayList_ensureCapacity(ArrayList* arraylist, int minCapacity) {
void* newarray = realloc(arraylist->array, arraylist->sizeOfElement * minCapacity);
if (newarray == 0) {
printf("out of memory.");
return -1;
}
arraylist->maxLength = minCapacity;
arraylist->array = newarray;
return 0;
}
void ArrayList_add(List* list, void* element) {
ArrayList* arraylist = (ArrayList*)list;
if (arraylist->length == arraylist->maxLength) {
int result = ArrayList_ensureCapacity(arraylist, arraylist->maxLength * 1.4);
if (result != 0) return;
}
memcpy(arraylist->array + (arraylist->length * arraylist->sizeOfElement), element, arraylist->sizeOfElement);
arraylist->length += 1;
}
ArrayList_add
関数の第2引数に追加したい要素を指定します。もし確保した全ての配列要素を使用している場合は、ArrayList_ensureCapacity
関数で配列を拡張します。メモリの再確保にはrealloc
関数を使用しています。配列の拡張に成功したら、memcpy
関数で末尾に要素を書き込みます。

要素の挿入
要素を挿入する処理を実装します。
void ArrayList_insert(List* list, int index, void* element) {
ArrayList* arraylist = (ArrayList*)list;
if (arraylist->length == arraylist->maxLength) {
int result = ArrayList_ensureCapacity(arraylist, arraylist->maxLength * 1.4);
if (result != 0) return;
}
memcpy(arraylist->array + ((index + 1) * arraylist->sizeOfElement),
arraylist->array + (index * arraylist->sizeOfElement),
(arraylist->length - index) * arraylist->sizeOfElement);
memcpy(arraylist->array + (index * arraylist->sizeOfElement), element, arraylist->sizeOfElement);
arraylist->length += 1;
}
ArrayList_insert
関数の第2引数に挿入先のindexと、第3引数に挿入したい要素を指定します。ArrayList_insert
関数でも必要であればArrayList_ensureCapacity
関数で配列を拡張します。配列の拡張に成功したら、挿入したい要素より後ろの要素を、1つずらすようにコピーしてから、データを挿入します。

配列長の取得
配列長を取得する処理を実装します。
int ArrayList_size(const List* list) {
ArrayList* arraylist = (ArrayList*)list;
return arraylist->length;
}
内部で保持しているlenght
を返却しているだけです。
使い方
実装したコードを使ってみましょう。
void process(List* list) {
// 配列の末尾に追加
for (int i = 0; i < 20; i++) {
List_add(list, &i);
}
// 配列の途中に挿入
int value = 99;
List_insert(list, 10, &value);
// 結果を出力
for (int i = 0; i < List_size(list); i++) {
int* value = (int*)List_get(list, i);
printf("%d ", *value);
}
printf("\n");
}
int main() {
ArrayList* arraylist = ArrayList_new(sizeof(int));
process((List*)arraylist);
// Output: 0 1 2 3 4 5 6 7 8 9 99 10 11 12 13 14 15 16 17 18 19
ArrayList_delete(arraylist);
return 0;
}
main
関数はArrayListのインスタンスの生成と破棄を行います。本処理はprocess
関数になります。process
関数では数値を追加もしくは挿入して、その内容をprintf
関数で出力しているだけになります。また、process
関数ではListインターフェースで実装していますので、汎用性のあるコードになっていることが分かると思います。
おわりに
void*
で実装しているとジェネリクスが恋しくなります。今回調べていると、C11から_Generic
というマクロが追加されていることを知りました。私がC言語の開発から離れて10年以上経過するのですが、まだまだC言語も成長していると思うと、感慨深いものがあります。今年のGWは_Generic
で遊ぼうかな。
ではまた。