関数ポインタ

前回の簡単なリストの続きです。List_display_int と List_display_string という似通った関数を、関数ポインタを使ってまとめよう、というのが今回のお題です。

まず関数ポインタの説明から入ります。無味乾燥な例です:

/* coding: cp932
 * ファイル名: function_pointer.c
 */
#include <stdio.h>

typedef int (*op) (int a, int b);

int calc (op func, int a, int b);
int add (int a, int b);
int fact (int n);

int
main (int argc, char *argv[])
{
  printf ("%d\n", calc (add, 3, 5));
  printf ("%d\n", calc ((op) fact, 5, 0));
  return 0;
}

int
calc (op func, int a, int b)
{
  return func (a, b);
}

int
add (int a, int b)
{
  return a + b;
}

int
fact (int n)
{
  return n ? n * fact (n - 1) : 1;
}

まず、calc という関数があります。これは 3 つの引数をとります。1 つは op 型の関数ポインタ func、2 つは int 値 a, bです。calc 自体は大した仕事をせず、func に a と b を渡すだけの怠け者です。

この op 型というのは typedef で定義されます。2 つの int 値を受け取り、int 値を返す関数、というポインタ型です。一見意味不明な typedef ですが、op という型を定義している、という点に注意して下さい。なお、calc のシグネチャを、typedef を使わずに下のようにすることもできます:

int calc (int (*func) (int a, int b), int a, int b)

とはいえ、typedef を使わないとキャストが大変なので、typedef を活用するのがよさそうです。

関数 add と fact は calc に渡すためのものです。add は名前の通り、2 つの int 値を受け取り、それを足した値を返します。calc (add, 3, 5) の呼び出しは以下のようなイメージです:

calc (add, 3, 5)
add (3, 5)
3 + 5

関数 fact は名前の通り、1 つの int 値を受け取り、階乗を計算して返します。calc が受け取る op 型は 2 つの引数を受け取りますが、fact は 1 つだけです。なので、fact は calc に渡す時に op 型にキャストします。calc ((op) fact, 5, 0) の呼び出しは以下のようなイメージです:

calc ((op) fact, 5, 0)
fact (5, 0) <- ここで 0 がどうなるのかは謎です
5 * fact (4)
(中略)
5 * 4 * 3 * 2 * 1 * 1

関数をキャストすることで、柔軟性が生まれます。

では前回のリストに関数ポインタのテクニックを組み込んでみましょう:

/* coding: cp932
 * ファイル名: list4.c
 */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

struct List
{
  void *data;
  struct List *next;
};
typedef struct List List;
typedef void (*each) (void *data, void *user_data);

List *List_prepend (List *list, void *data);
void List_each (List *list, each func, void *user_data);
void display_int (void *data, void *user_data);

int
main (int argc, char *argv[])
{
  List *int_list, *string_list;
  int counter = 0;

  int_list = List_prepend (NULL, (void *) 1);
  int_list = List_prepend (int_list, (void *) 2);
  int_list = List_prepend (int_list, (void *) 3);
  List_each (int_list, display_int, &counter);

  string_list = List_prepend (NULL, (void *) "!");
  string_list = List_prepend (string_list, (void *) "world");
  string_list = List_prepend (string_list, (void *) "Hello ");
  List_each (string_list, (each) printf, NULL);
  printf ("\n");
  return 0;
}

List *
List_prepend (List *list, void *data)
{
  List *new_list = (List *) malloc (sizeof (List));
  new_list->data = data;
  new_list->next = list;
  return new_list;
}

void
List_each (List *list, each func, void *user_data)
{
  List *current;
  for (current = list; current; current = current->next)
    {
      func (current->data, user_data);
    }
}

void
display_int (void *data, void *user_data)
{
  printf ("%d つめの値: %d\n", ++*(int *) user_data, (int) data);
}

以下は実行例です:

$ gcc -Wall -pedantic list4.c
$ ./a
1 つめの値: 3
2 つめの値: 2
3 つめの値: 1
Hello world!

リストの全てのデータにアクセスするための List_each という関数を定義しました。この関数は、リストと each 型の関数ポインタと任意のデータを受け取ります。リストの各データを使ってどのような処理をするのかは each 型関数で定義します。なお、user_data という変数名も GLib から拝借しました。

display_int は、リストの要素数を数えつつデータである int 値を表示するための each 型関数です。リストの要素ごとに display_int が呼ばれるので、カウンタはこの関数の外部に用意しておかないといけません。

リストに文字列を保存し、各要素を表示したい場合、pritnf を each 型にキャストするだけで OK です。簡単です。

リストの data に動的に確保したメモリ領域へのアドレスを保存する場合、各要素 (List 構造体) を free する前に data を free しておかないとメモリリークになってしまいます。各データの free も List_each (list, (each) free, NULL) で手軽に実行できるので、なかなか便利だと思いますが、どうでしょう。

(コウヅ)



キャッシング

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中