1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

c言語 汎用データ構造

Last updated at Posted at 2017-12-09

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ポインタでそういうことができる。

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_Subtypedefし、構造体のポインタはListtypedefしてある。このクラス(構造体とその操作関数群)を使うときは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などに渡す関数は、例のvprintPerson_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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?