c言語　汎用データ構造

c言語で数値をデータとしてもつ連結リストを使う場合、例えば次のようにして、構造体とその操作関数を定義する。

```c
typedef struct st_list_number{
 int value;
 struct st_list_number *next;
}List_number;

List_number *List_number_create(int value);
etc..
```
ここでデータ型として文字列をとりたいと思ったとき

```c
typedef struct st_list_string{
 char *string;
 struct st_list_string *next;
}List_string;

List_string *List_string_create(char *string);
etc..
```
このように新たに構造体とその操作関数を作ることになると面倒である。そこで、いろいろなデータ型を使いまわせる汎用データ構造が欲しくなる。実は、voidポインタでそういうことができる。

```List.h
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`型を使用する。
実装と使用例：

```List.c
#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);
 }
}
```

```List_test.c
#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

```List_test2.c
#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変数を使う方法を紹介する。

```c
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