ポインタ
C言語

C言語の関数ポインタを使ってみた話

More than 1 year has passed since last update.

この記事は香川大学工学部サークルSLPのアドベントカレンダー(SLP KBIT Advent Calendar 2017)の8日目の記事です。

アウトプットをするのは今回が初めての挑戦となります。

いろいろとガバガバなのは見逃してください。

はじめに

新しく得た知識ってやたら使いたくなりますよね!

私も最近知った関数ポインタというものを使ってみたくなったので勉強ついでに使ってみました。

何かの開発として使っていけたらよかったのですが、なかなかそうもいかず、、、

にわか仕込みの知識しかないですが、とりあえず今回はとある講義の課題に無理やり使ってみました。課題としては使う必要はなかった。

関数ポインタとは

関数ポインタとは、簡単にいうとポインタの関数版です。

ポインタの参照先を関数にすると、そのポインタが指し示す関数を呼び出すことができます。

ポインタに格納する関数のアドレスを変えることで、呼び出す関数を動的に変更することが可能です。

使い方

関数ポインタは、アドレスを格納する関数と同じ戻り値の型を使って宣言します。

関数ポインタの仮引数の型と、ポインタに格納する関数の型は一致する必要があります。

関数ポインタを使うときには、ポインタ名に()を付けて使いましょう。

型 (*変数名)(仮引数)

sample1.c
#include <stdio.h>

int addition(int x, int y);       // 足し算
int subtraction(int x, int y);    // 引き算

int main(void){
    int a = 3;
    int b = 5;
    int answer;
    int (*funcp)(int, int);

    funcp = addition;
    answer = (*funcp)(a, b);
    printf("a + b = %d\n", answer);

    funcp = subtraction;
    answer = (*funcp)(a, b);
    printf("a - b = %d\n", answer);

    return 0;
}

int addition(int x, int y) {
    return (x + y);
}

int subtraction(int x, int y) {
    return (x - y);
}

一回目の関数呼び出し時は、funcpに関数additionのアドレスが格納されていて、二回目の呼び出し時には関数subtractionのアドレスが格納されています。

このようにしてポインタに関数のアドレスを格納することで、呼び出す関数を変更することができます。

関数ポインタの仮引数を入れる部分は、仮引数の型だけを入れて宣言しておけば大丈夫なようです。

上記のコードの実行結果は、

a + b = 8
a - b = -3

となります。

ちなみに、以下のように宣言して配列として使うこともできます。

sample2.c
int main(void){
    int a = 3;
    int b = 5;
    int answer;
    int (*funcp[])(int, int) = { addition, subtraction };

    answer = (*funcp[0])(a, b);
    printf("a + b = %d\n", answer);

    answer = (*funcp[1])(a, b);
    printf("a - b = %d\n", answer);

    return 0;
}

実行結果は上のコードと同じになります。

実践してみた

使ったのが講義の課題なので実際のコードを載せるのは避けますが、以下のような感じで関数の引数に関数ポインタを利用しました。

sample3.c
#include<stdio.h>

typedef void (* FUNCPT)(char *); 

void printText1(char *s);        // 文字列をそのまま出力
void printText2(char *s);        // 文字列を""で囲んで出力
void exec(char *s, FUNCPT p);    // 関数を呼び出す

int main(void) {
    FUNCPT p;

    p = printText1;
    exec("ぷりんとてきすと1", p);

    p = printText2;
    exec("ぷりんとてきすと2", p);

    return 0;
}

void printText1(char *s){
    printf("%s\n", s);
}

void printText2(char *s){
    printf("\"");
    printf("%s", s);
    printf("\"\n");
}

void exec(char *s, FUNCPT p){
    p(s);
}

実行結果

ぷりんとてきすと1
"ぷりんとてきすと2"

関数ポインタを関数の引数に利用する際には、typedefでプロトタイプ宣言しておくと使いやすくなります。

余談

個人的には、main文をこのように書けるのがスマートでかっこいいと思いました。(小並)

int main(void) {

    exec("ぷりんとてきすと1", printText1);
    exec("ぷりんとてきすと2", printText2);

    return 0;
}

おわりに

実際に取り組んだ課題は、関数を使った処理を複数回繰り返したときの実行時間を計測する内容でした。

ばかみたいに時間はかかりましたが、時間取得などの処理を1つにまとめることができたのは個人的に良かったと思っています。

ポインタに関してはまだまだよくわからないことも多いですが、今後少しずつ活かしていけたらいいかなって思ってます。

追記

調べてて分かったんですけど、関数の引数として渡す関数のことをコールバック関数って言うようですね。

どこかで聞いた覚えがあるようなないような。

参考にしたサイト

以下、今回の学習で参考にしたサイトです。

関数ポインタを利用して呼び出す関数を動的に変更する
(https://www.kishiro.com/programming/c/function_pointer.html)
関数ポインタ
(http://wisdom.sakura.ne.jp/programming/c/c54.html)
関数の引数として関数ポインタを渡す
(http://blog.panicblanket.com/archives/2920)