ダブルポインタ(ポインタのポインタ)のメリットや使い道を紹介する
C言語では、ポインタを使うことで柔軟なデータ構造を表現できるが、今までダブルポインタ(ポインタのポインタ)についてイマイチ理解できていなかった。
色々調べていくと、ダブルポインタのメリットや使い道が分かってきたので、紹介していく。
ダブルポインタのメリットや使い道とは?
ダブルポインタのメリットとしては、様々なデータ型を格納できる動的配列を作れる、と言う点が挙げられる。
C++には、Vectorと呼ばれている便利なデータ構造が使える。このVectorはC言語の配列の強化版みたいなもので、好きなデータ型を自由に追加、削除ができるし、配列の大きさも自動で変更してくれる。(例えば、配列の0番目がint型、1番目が文字列型の様にもできる!)
一方で、C言語の配列は非常に機能が弱く、int array[5]
と宣言するとint
型しか要素が使えないし、6つ以上の要素を追加することができない。
int *array = (int *)malloc(sizeof(int) * 20);
array[0] = 32;
しかし、ダブルポインタを使えば、要素に様々な型のデータを入れることができるようになる。(ただし、ポインタに限る)
ダブルポインタを使った動的配列
では、ダブルポインタを使って動的配列を作るとどうなるかを見ていく。
まずは、動的配列のデータ構造は以下の通りになる。ポイントとしては、動的配列の中身を**body
のようにダブルポインタを使うことと、動的配列が確保しているメモリの数を記憶しておく変数size
を用意すること。
typedef struct _Vector VECTOR;
struct _Vector
{
int size; // 動的配列が確保しているメモリ数
int index; // 動的配列のindex
void **body; // 動的配列の中身
};
この動的配列を初期化するために、vec_make
関数を用意する。
#define INIT_SIZE 10
VECTOR vec_make(int size)
{
void *body = malloc(sizeof(void *) * INIT_SIZE);
VECTOR vec = {
.size = size, .alloc_num = INIT_SIZE, .index = 0, body = body};
return vec;
}
重要な点は、void *body = malloc(sizeof(void *) * INIT_SIZE);
のように、void
型のポインタの配列を作って、その配列のアドレスをbody
に格納すると言うこと。
void型のポインタにすることで、様々なデータ型のポインタを格納することができるようになる
次に、動的配列に要素を追加するための関数vec_push
を用意する。
void vec_push(VECTOR *vec, void *p)
{
vec->body[vec->index++] = p;
}
引数のvoid *p
には好きなデータ型のポインタを入れることができ、そのポインタを動的配列の要素に格納することができる。
本来は「vec->index
が動的配列の大きさよりも小さいか?」などの判定をすべきだが、サンプルコードと言う事で省略している。
また、動的配列の要素を取得するためのvec_get
を用意。
void *vec_get(VECTOR *vec, int index)
{
return vec->body[index];
}
そして、main関数で以下のように実行する。
int main()
{
VECTOR vec = vec_make(5);
char *str = "I am tarou";
int i = 15;
vec_push(&vec, str);
vec_push(&vec, &i);
char *str1 = vec_get(&vec, 0);
char *ip = vec_get(&vec, 1);
printf("str is %s\n", str1);
printf("int is %d\n", *ip);
return 1;
}
上記の動的配列vec
の0番目の要素にはchar *
型が格納され、1番目の要素にはint *
型が格納されるようになる。今回はchar *
とint *
型を配列に格納したが、構造体のポインタを格納するのが一般的だと思う。
リスト構造との違いは?
ダブルポインタを使った動的配列でなくとも、連結リストでデータを表現できることが多い。
typedef struct _List
{
int x;
struct _List *next;
} List;
しかし、連結リストの計算量はO(n)となっており、要素の数が多ければ検索の時間を大きくなっていく。一方、ダブルポインタを使った動的配列であれば計算量はO(1)なので、連結リストよりも高速であるメリットがある。
もちろん、連結リストも動的配列もそれぞれに良さがあるので、状況に応じて使い分けるのが良い。