c言語で数値をデータとしてもつ連結リストを使う場合、例えば次のようにして、構造体とその操作関数を定義する。
typedef struct st_list_number{
int value;
struct st_list_number *next;
}List_number;
List_number *List_number_create(int value);
etc..
ここでデータ型として文字列をとりたいと思ったとき
typedef struct st_list_string{
char *string;
struct st_list_string *next;
}List_string;
List_string *List_string_create(char *string);
etc..
このように新たに構造体とその操作関数を作ることになると面倒である。そこで、いろいろなデータ型を使いまわせる汎用データ構造が欲しくなる。実は、voidポインタでそういうことができる。
typedef struct st_list{
void *content;
struct st_list *next;
}List_Sub;
typedef List_Sub *List;
List List_create(void *content);
void List_delete(List list, void (*delete)(void*));
void List_deleteAll(List list, void (*delete)(void*));
List List_append(List list, void *content);
void List_print(List list, void (*print)(void*));
この連結リストはデータとしてどのような型のポインタも入れることができる。List_print List_deleteでは内部データを操作する関数を関数ポインタとして受けとることで、データ型ごとに異なる処理をその関数にやってもらうようにしている。
ちなみに構造体の実体はList_Subとtypedefし、構造体のポインタはListとtypedefしてある。このクラス(構造体とその操作関数群)を使うときはList型を使用する。
実装と使用例:
# include <List.h>
# include <stdio.h>
# include <stdlib.h>
List List_create(void *content){
List list = malloc(sizeof(List_Sub));
list->content = content;
list->next = NULL;
}
void List_delete(List list, void (*delete)(void* content)){
if(delete){delete(list->content);}
free(list);
}
void List_deleteAll(List list, void (*delete)(void*)){
if(list){
if(list->next){List_deleteAll(list->next,delete);}
if(delete){delete(list->content);}
free(list);
}
}
List List_append(List root, void *content){
List list = root;
if(!list){return List_create(content);}
while(list->next){list=list->next;}
list->next = List_create(content);
return root;
}
void List_print(List list, void (*print)(void*)){
for(;list;list=list->next){
print(list->content);
}
}
# include <List.h>
# include <stdio.h>
# include <stdlib.h>
void print(char *content){
printf("(%s)->",content);
}
void vprint(void *content){print(content);}
int main(void){
List list=NULL;
list = List_append(list,"a");
list = List_append(list,"b");
list = List_append(list,"c");
List_print(list,vprint);
printf("\n");
List_deleteAll(list,NULL);
}
実行結果
(a)->(b)->(c)->
例2
# include <List.h>
# include <stdio.h>
# include <stdlib.h>
typedef struct st_person{
char *name;
int age;
}Person_Sub;
typedef Person_Sub *Person;
Person Person_create(char *name, int age){
Person person = malloc(sizeof(Person_Sub));
person->name = name;
person->age = age;
}
void Person_delete(Person person){
printf("%s is dead\n",person->name);
free(person);
}
void Person_vdelete(void *person){Person_delete(person);}
void Person_print(Person person){
printf("name : %s,age : %d\n",person->name,person->age);
}
void Person_vprint(void *person){Person_print(person);}
int main(void){
List list=NULL;
list = List_append(list,Person_create("Bob",12));
list = List_append(list,Person_create("Suzuki",24));
list = List_append(list,Person_create("Yamada",50));
List_print(list,Person_vprint);
List_deleteAll(list,Person_vdelete);
}
実行結果
name : Bob,age : 12
name : Suzuki,age : 24
name : Yamada,age : 50
Yamada is dead
Suzuki is dead
Bob is dead
List_printなどに渡す関数は、例のvprint、Person_vprint,Person_vdeleteのようにvoidポインタ型を引数としてもつ関数を定義しておかなければならない。
このようにどんなデータ型にも使えて便利だが、中に入っているデータ型の情報はどこにもないのでプログラマが想定しないデータ型が入ってしまったり、実際と異なるデータ型として読み取ってしまわないように注意する必要がある。
暗黙の引数を持つ関数
上の例でList_print関数はvoid print(void*){}という型の関数のポインタを引数として受け取るが、これではprint関数の引数の数が固定されてしまう。すると例えばインデントの数を別の引数として受け取るprint関数はList_print関数に渡すことができない。この問題を解決するにはグローバル変数を使う手もあるが、ここではstatic変数を使う方法を紹介する。
enum print_mode{mode_set,mode_print}
void print_internal(char *string, int indent, int mode){
int i;
static int indent_static;
switch(mode){
case mode_set:
indent_static = indent;
break;
case mode_print:
for(i=0;i<indent_static;i++){printf("\t");}
printf("%s\n",string);
break;
}
}
void print_set_indent(int indent){print_internal(NULL,indent,mode_set);}
void print(char *string){print_internal(string,0,mode_print);}
void vprint(void *content){print(content)};
このようにすると、List_print関数にばれずにprint関数に値を渡して振る舞いを変えることができる。
参考にしたページ
http://d.hatena.ne.jp/pknight/20090722/1248247345
http://d.hatena.ne.jp/Einherjar/20071003/p2
http://www.sage-p.com/process/cool.htm