C言語の関数ポインタを引数に渡す方法

381, 2022-01-01

目次

C言語の関数ポインタを引数に渡す方法

C言語の関数ポインタ、関数は関数の引数に渡すことができます
たとえば↓のような関数があったとします。

void func(void) {
    printf("I am func!\n");
}

この関数を保存できる関数ポインタは↓になります。

void (*func_ptr)(void);

これを関数の引数に書くことで、関数の引数で関数ポインタおよび関数を受け取ることが出来ます。
↓のコードをご覧ください。

#include <stdio.h>

// test関数に渡すfunc関数
void func(void) {
    printf("I am func!\n");
} 

// 引数に関数ポインタを取るtest関数
void test(void (*func_ptr)(void)) {
    // 引数で渡された関数を呼び出す
    func_ptr();  // I am func!
}

int main(void) {
    // test関数にfunc関数を渡す
    test(func);
    return 0;
}

関数test()は引数に関数ポインタを取る関数です。
引数の型はvoid (*func_ptr)(void)になっています。
これは関数で言うとvoid func(void) { ... }という関数を受け取れる関数ポインタになります。

main()関数内ではtest()を呼び出してますが、引数にはfunc()関数を渡しています。
func()は内部で「I am func!」と出力します。
test()内では引数のfunc_ptrを関数として呼び出しています。
上のコードをコンパイルして実行すると「I am func!」と表示されます。

typedefで関数ポインタを扱いやすくする

関数ポインタの定義は非常に複雑な文法になっています。
最初これを見たときは誰しも「なんじゃこりゃ?」と思うでしょう。

しかしこの関数ポインタもtypedefを使うと扱いやすい「関数ポインタ型」にすることができます
たとえば先ほどの関数ポインタですが、

void (*func_ptr)(void);

この関数ポインタをtypedefして新しい型にすると↓のようになります。

typedef void (*MyFunc)(void);

MyFuncという識別子が型名です。
この型を使うと先ほどのコードは↓のように書き換えることができます。

#include <stdio.h>

// 関数ポインタ型MyFuncの定義
typedef void (*MyFunc)(void);

// test関数に渡すfunc関数
void func(void) {
    printf("I am func!\n");
} 

// 引数に関数ポインタを取るtest関数
void test(MyFunc func_ptr) {
    // 引数で渡された関数を呼び出す
    func_ptr();  // I am func!
}

int main(void) {
    // test関数にfunc関数を渡す
    test(func);
    return 0;
}

test()関数の引数がMyFunc func_ptrになっています。
MyFunctypedef void (*MyFunc)(void);で新しい型として定義されています。
MyFuncという型を使うとこのように関数ポインタは見慣れた形で関数の引数に出来ます。

関数ポインタが使いづらいと感じた場合はtypedefを使って関数ポインタ型を定義するのが吉です。

いろいろな関数ポインタの引数

以下に関数ポインタを関数の引数にする例を書きます。

#include <stdio.h>
#include <string.h>

// xとyを加算する関数
int add(int x, int y) {
    return x + y;
}

// keyに応じたvalueを文字列で返す関数
const char *get_value(const char *key) {
    if (!strcmp(key, "hige")) {
        return "bosabosa";
    } else {
        return "unknown";
    }
}

void f1(int (*callback)(int, int)) {
    int result = callback(1, 2);
    printf("result[%d]\n", result);  // result[3]
}

void f2(const char *(*callback)(const char *)) {
    const char *value = callback("hige");
    printf("value[%s]\n", value);  // value[bosabosa]
}

int main(void) {
    f1(add);
    f2(get_value);
    return 0;
}

実行結果↓。

result[3]
value[bosabosa]

typedefを使わないとなかなかカオスになりますね。
関数ポインタに慣れると読み解けるようになります。
慣れないうちはtypedefを多用すると良いと思います。
またtypedefを使ったほうが可読性が上がるので、保守性の意味でもそっちのほうがいいです。

関数ポインタを引数に取るライブラリ関数

stdlib.hをインクルードすると使えるqsort()関数はクイックソートを実行する関数です。
クイックソートとはソートのアルゴリズムの一種で、データを整列させるアルゴリズムのことです。
qsort()を実行するとデータを整列させることができます。

qsort()の関数の引数は↓のようになっています。

void qsort(
    void *base,  // データへのポインタ(配列など)
    size_t nmemb,  // データの件数(配列の長さなど)
    size_t size,  // データ1件ののバイト数
    int (*compar)(const void *, const void *)  // 比較関数
);

最後の引数のcomparというのは関数ポインタです。
qsort()ではデータを並べ替えるときに隣り合うデータ同士を比較する必要があるのですが、この関数ポインタにはそのための比較関数を設定します。

たとえばint型の配列をソートするときは↓のようなコードを書きます。

#include <stdio.h>
#include <stdlib.h>

// qsortに渡す比較関数
int compar(const void *lhs, const void *rhs) {
    int a = *(int *) lhs;  // void型のポインタを整数に変換
    int b = *(int *) rhs;  // 同上
    return a - b;  // 比較
}

int main(void) {
    // ソート対象の配列
    int ary[4] = { 3, 1, 4, 2 };

    // クイックソートの実行
    qsort(ary, 4, sizeof(int), compar);

    // 結果を出力
    for (int i = 0; i < 4; i += 1) {
        printf("%d\n", ary[i]);
    }

    return 0;
}

実行結果↓。

1
2
3
4

おわりに

今回はC言語の関数ポインタを関数の引数に渡す方法について解説しました。
関数ポインタは書き方が独特ですが、慣れると便利に使えます。

(^ _ ^)

関数ポインタを使って無双しよう

(・ v ・)

関数ポインタで武装しよう

この記事のアンケートを送信する