[C言語][初心者]関数へのポインタとコールバック関数の違いと使い方

C言語/C++

C言語を使っていると何が違うのか?と考えることがあります。
タイトルに含まれている以下の2つです。

  • 関数へのポインタ
  • コールバック関

この記事では、上記2つについて使い方とメモ書きです。
関数へのポインタはC言語を利用している人以外はあまり聞き馴染みのない言葉かもしれません。
もう1つのコールバック関数については聞いたことがある、知っているという方も多いはずです。
でも実際にはいつ使うの?どうやって使うの?となる人も多いハズです。
まだC言語を書き始めたころは、そう思っていました。
そのために自分自身が忘れてしまわないようにするためにメモ書きです。

動作確認を行うための環境はubuntuのgccを使います。

スポンサーリンク

関数へのポインタ

まずは、関数へのポインタについてです。
これは、C言語を使う人以外はあまり聞き馴染みがなかとおもいます。

補足

関数へのポインタ・・・関数ポインタとも呼ばれています。※本記事では同意義とします。

例えば、以下のような関数があるとします。

int func (int number)

上記のような関数がある時、関数へのポインタを格納するポインタ変数は以下のように宣言できます。

int (*func_p) (int);

これが関数へのポインタです。
非常にわかりにく構文ですよね。と文句を言いたくなりますが、とりあえず関数へのポインタを使用したサンプルです。

#include <stdio.h>

/*引数に1を足して返却する関数*/
int func_add(int number) {
    return number + 1; 
}
/*引数に1を引いて返却する関数*/
int func_sub(int number) {
    return number - 1;
}

int main() {
    int (*func_p) (int);
    
    //func_addをポインタ変数にセット、実行
    func_p = func_add;
    int numA = func_p(2);
    printf("func_add : %d\n", numA);

    //func_subをポインタ変数にセット、実行
    func_p = func_sub;
    int numB = func_p(2);
    printf("func_sub : %d\n", numB);
}

コールバック関数

コールバック関数とは、関数を呼び出し時に引数として渡される関数のことをいいます。

コールバック関数とは - IT用語辞典
コールバック関数とは、コンピュータプログラム中で、ある関数を呼び出す際に引数などとして引き渡される別の関数のこと。呼び出し側の用意した関数を、呼び出し先のコードが「呼び出し返す」(callback)ように実行される。プログラムにおける関数は...
#include <stdio.h>
#include <string.h>

/*引数に1を足して返却するコールバック関数*/
int cb_add(int number) {
    return number + 1; 
}
/*引数に1を引いて返却するコールバック関数*/
int cb_sub(int number) {
    return number - 1;
}

//コールバック関数を呼び出す関数
void func_cb(int num, int (*cb)(int)) {
    printf("result:%d\n", cb(num));
}
int main() {
    int (*func_p) (int);
    char* s = "sub";
    
    func_p = NULL;
    if (strcmp("add", s) == 0) {
        func_p = cb_add;
        func_cb(5, func_p);
    }
    else if (strcmp("sub", s) == 0) {
        func_p = cb_sub;
        func_cb(5, func_p);
    }
    else {
        printf("unknown command\n");
    }
}

関数へのポインタとコールバック関数の違いは?

これについては私的には大きな違いはなくほぼ同意義として使われていると思います。
※あくまで私的な意見です。
私のこれまでの経験から明確に2つの言葉が使い分けられている印象はありません。

それでも分けるというのであれば、
C言語の場合には、関数のポインタをどの場面で使うかによって関数へのポインタなのかコールバック関数なのか分けることは出来ると思います。

関数へのポインタ

ポインタ変数に関数のアドレスがセットされて以下のように使用されている場合には、関数へのポインタと呼ぶ

int (*func_p) (int);
//func_addをポインタ変数にセット、実行
func_p = func_add;
int numA = func_p(2);
コールバック関数

関数へのポインタがラップされて使われいるようなイメージ
関数へのポインタがある関数の引数として利用されているような場合は、コールバック関数と呼ぶ

void func_cb(int num, int (*cb)(int)) {
    int ret = cb(num));
}

おまけ:実践的な使い方

以前にmbedというものを使っているときにコマンドラインから実行されるコマンドを書いているコードを見たことがありました。
そこでは、関数へのポインタが使われていました。

mbed - Wikipedia

それと似たものを最近見て(なんとなく)思い出したので以下に書いてみます。
ぼんやりとしか覚えていないため、以下のものがそれかは定かではありませんが、、、

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

//
//typedef
//
//科目一覧
typedef enum {
    Jap = 3,
    Mat = 4,
    Eng = 5,
} Subject;

//生徒一覧
struct Student {
	int no;
	char* name;
	int total;
	int jap;
	int mat;
	int eng;
};

// グローバル変数
//科目
static Subject sub;
//点数
static int score;
//生徒
static struct Student student[] = {
    {	1, "yamada"	, 0, 0, 0, 0 },
    {	2, "fijita"	, 0, 0, 0, 0 },
    {	3, "tanaka"	, 0, 0, 0, 0 },
    {	4, "suzuki"	, 0, 0, 0, 0 },
};

//初期化メソッド
static void initScore_cb(struct Student* std) {
	std->jap = 0;
	std->mat = 0;
	std->eng = 0;
    std->total = 0;
    printf("No:%d name:%s init\n", std->no, std->name);
}

//セットメソッド
static void setScore_cb(struct Student* std) {
    switch(sub){
        case Jap:
            std->jap = score;
            break;
        case Mat:
            std->mat = score;
            break;
        case Eng:
            std->eng = score;
            break;
        default:
            return;
    }
    std->total = std->jap + std->mat + std->eng;
}

//結果メソッド
static void resultScore_cb(struct Student* std) {
    printf("-----------------------------------\n");
    printf("No:%d Name:%s TotalScore:%d\n", std->no, std->name, std->total);
}

//関数へのポインタ
typedef void (* command_func_t)(struct Student *std);

//コマンド一覧
static struct {
	char* cmd;
	command_func_t func;
	char *doc;
} command[] = {
	{ "score-init" 	, initScore_cb	,  "init set score test"    },
    { "score-set"	, setScore_cb	,  "set score"			    },
    { "score-result", resultScore_cb,  "result score"           },
	{ NULL          , NULL          , NULL                      }
};
// read command
static void readCommand(char* cmd, int no) {
	for (int i = 0; command[i].cmd; i++) {
		if (strcmp(command[i].cmd, cmd) == 0) {
            command[i].func(&student[no]);
			return;
		}
	}
}
// main
int main(int argc, char* argv[]) {
    //初期化
	readCommand("score-init", 0);
    readCommand("score-init", 1);
    readCommand("score-init", 2);
    readCommand("score-init", 3);

    //値をセット
    sub     = Jap;
    score   = 100; 
    readCommand("score-set", 3);
    sub     = Mat;
    score   = 88; 
    readCommand("score-set", 3);
    sub     = Eng;
    score   = 49; 
    readCommand("score-set", 3);
    sub     = Jap;
    score   = 27; 
    readCommand("score-set", 0);

    //結果
    readCommand("score-result", 0);
    readCommand("score-result", 1);
    readCommand("score-result", 2);
    readCommand("score-result", 3);
}
解説ポイント1

typedefを使用して「関数へのポインタ」の宣言する方法

void (*func_p) (struct Student* std);
void(*func_p)(&std[0]);

typedefを使用することで、以下のように宣言して使用することが可能です。

typedef void (* command_func_t)(struct Student *std);
command_func(&std[0]);
解説ポイント2

3つの関数で関数へのポインタを使用

  • static void initScore_cb(struct Student* std);
  • static void setScore_cb(struct Student* std);
  • static void resultScore_cb(struct Student* std);

上記をcommand配列の第2引数で宣言した関数へのポインタ(command_func_t func)で使用
readCommand()の第1引数の文字列で実行コマンドを分岐して第2引数にセットされている関数へのポインタを実行

タイトルとURLをコピーしました