🇯🇵 日本語 | 🇺🇸 English
広告スペース

C言語 関数ポインタ入門

関数をデータとして扱う。qsort やコールバックなど、実用で必ず出会う高度なトピック。

💪 初心者は1回で分からなくて普通
関数ポインタは宣言構文がCで最も読みにくい部類。1回で覚える必要はなく、必要になった時に戻るのが正攻法です。
再挑戦のステップ
  1. 関数の基本ポインタ を先にマスター
  2. typedef で名前を付ける書き方だけまず覚える
  3. qsort の使い方だけ覚えておけば実務では十分
  4. 「関数もアドレスを持つ」という1点だけ納得する
💡 コツ: 宣言の読み方: 変数名から出発して内側から読む。int (*fp)(int) は「fpはポインタで、int を受けて int を返す関数を指す」。

関数ポインタとは

関数もメモリ上のアドレスを持っています。そのアドレスを保持するのが関数ポインタです。関数をデータのように変数に入れ、引数として渡し、配列に並べることができます。
#include <stdio.h>

int square(int x) { return x * x; }

int main(void) {
    // fp は「int を受け取り int を返す関数」へのポインタ
    int (*fp)(int) = square;  // 関数名は自動的にアドレスに

    printf("%d\n", fp(5));   // → 25
    printf("%d\n", (*fp)(5)); // → 25 (同等、古い書き方)
    return 0;
}
重要な規則: 関数名 square は式中では自動的にアドレス &square になります。つまり fp = squarefp = &square は同じ意味。呼び出しも fp(5)(*fp)(5) は同義。

宣言の読み方と typedef

関数ポインタの宣言はC言語で最も読みにくい構文のひとつです。規則を押さえれば怖くありません。

宣言の読み方

int (*fp)(int, int);
① 変数名は fp
(*fp) — fp はポインタ
(int, int) — 2個のintを受ける関数
④ 先頭の int — その関数は int を返す
→ 「int 2つを受け取り int を返す関数へのポインタ」

括弧の位置に注意

int *fp(int);       // ❌ これは関数ポインタではない!
                    // 「int*を返す関数fpのプロトタイプ」

int (*fp)(int);     // ✅ 関数ポインタ

typedef で読みやすく

// 比較関数の型を CmpFn という名前に
typedef int (*CmpFn)(const void *, const void *);

// 以後は普通の型のように使える
void sort(int *a, int n, CmpFn cmp) {
    /* ... */
}
読み方のコツ:typedef を付けると変数名の位置に型名が入る」と覚えると楽。つまり CmpFn x; とすれば x が関数ポインタになる。

コールバック(引数として関数を渡す)

「配列の各要素に対して何かを実行する」処理を、実行する内容を関数として渡せるようにした例です。
#include <stdio.h>

// int 配列の各要素に対して fn を適用する汎用関数
void for_each(int *a, int n, void (*fn)(int)) {
    for (int i = 0; i < n; i++) {
        fn(a[i]);
    }
}

// コールバック例1: 表示
void print_int(int v) { printf("%d ", v); }

// コールバック例2: 2乗して表示
void print_square(int v) { printf("%d ", v * v); }

int main(void) {
    int a[] = {1, 2, 3, 4, 5};
    for_each(a, 5, print_int);    // 1 2 3 4 5
    printf("\n");
    for_each(a, 5, print_square); // 1 4 9 16 25
    printf("\n");
    return 0;
}
コールバックの利点: 汎用処理(for_each)と具体処理(print_int/print_square)を分離できる。イベント駆動、GUIコールバック、非同期完了通知、ソート比較などで頻出。

qsort で任意の型をソート

標準ライブラリの qsort は比較関数を関数ポインタで受け取ることで、int でも文字列でも構造体でもソートできる汎用関数になっています。

qsort のシグネチャ

void qsort(void *base,
           size_t nmemb,
           size_t size,
           int (*compar)(const void *, const void *));
base=先頭アドレス、nmemb=要素数、size=1要素のバイト数、compar=比較関数。

int の昇順

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

int cmp_int_asc(const void *p1, const void *p2) {
    int a = *(const int *)p1;
    int b = *(const int *)p2;
    // 負なら a が前、0なら同じ、正なら a が後
    if (a < b) return -1;
    if (a > b) return 1;
    return 0;
}

int main(void) {
    int a[] = {5, 2, 8, 1, 9, 3};
    qsort(a, 6, sizeof(int), cmp_int_asc);
    for (int i = 0; i < 6; i++) printf("%d ", a[i]);
    // → 1 2 3 5 8 9
}
罠: return a - b; は一見動くが、a や b が大きい値や負だとオーバーフローして誤った符号になる。必ず大小比較で -1 / 1 を返すのが安全。

構造体を2つのキーでソート

struct Student {
    char name[32];
    int score;
};

// 得点が高い順、同点なら名前の辞書順
int cmp_student(const void *p1, const void *p2) {
    const struct Student *a = p1;
    const struct Student *b = p2;
    if (a->score != b->score)
        return (b->score > a->score) ? 1 : -1;  // 降順
    return strcmp(a->name, b->name);                  // 昇順
}

// 呼び出し:
qsort(students, n, sizeof(struct Student), cmp_student);

関数ポインタ配列(ディスパッチテーブル)

コマンドや演算子ごとに処理を切り替えるときに switch の代わりに使える、洗練されたパターンです。
#include <stdio.h>

double op_add(double a, double b) { return a + b; }
double op_sub(double a, double b) { return a - b; }
double op_mul(double a, double b) { return a * b; }
double op_div(double a, double b) { return b != 0 ? a / b : 0; }

// 演算子 → 関数ポインタ の対応表
struct OpEntry {
    char sym;
    double (*fn)(double, double);
};

static const struct OpEntry ops[] = {
    {'+', op_add},
    {'-', op_sub},
    {'*', op_mul},
    {'/', op_div},
};

double calc(char op, double a, double b) {
    for (int i = 0; i < sizeof(ops) / sizeof(ops[0]); i++) {
        if (ops[i].sym == op) return ops[i].fn(a, b);
    }
    return 0;
}

int main(void) {
    printf("%.2f\n", calc('+', 3, 4)); // 7.00
    printf("%.2f\n", calc('*', 6, 7)); // 42.00
    return 0;
}
switch との比較: 演算子を増やすときにテーブルに1行追加するだけで済む。switch だと case を追加しさらにロジックの場所を探す必要がある。データ駆動な設計が可能になる。

チャレンジ課題

課題1: 降順ソート
qsort で int 配列を降順に並べる比較関数を書け。
課題2: 文字列配列のソート
char *words[] = {"banana","apple","cherry"}; を辞書順にソートせよ。比較関数の引数型がポイント(const char **)。
課題3: filter 関数
int filter(int *a, int n, int *out, int (*pred)(int)) を実装せよ。pred が真を返す要素だけ out にコピーし、個数を返す。偶数抽出で動作確認。
課題4: 状態機械をテーブルで書く
信号機(赤→青→黄→赤…)の各状態で実行する関数を用意し、関数ポインタ配列で状態遷移を実装せよ。