見出し画像

C言語教室 第24回 - 関数ポインタとは

リストなどのデータ構造について、ポインタを使いながら説明をしてきました。ポインタを使うことで複雑な構造を能率よく記述できることが感じられるようになってきたのではと思います。よりモダンなプログラミング言語であれば、リストは予め用意されているものですが、結局のところ CPU は、C言語で書かれているような操作を延々とすることで、リスト構造の処理をしているわけです。

さて、データではなくプログラムの方に構造を持たせたい、例えば変数の値に応じて処理を切り替えるには、どうすればよいのでしょう。

FORTRANやBASICでは、条件によって呼び出すサブルーチンを切り替える際には、IF で分岐して呼び出すか、FORTRANであれば計算型GOTOまたは行番号変数によるGOTO、BASICであれば ON GOTO/GOSUB といった方法で、変数の値によって処理を移す方法しかありません。

FORTRAN の例 - IF文

I=1
IF I=1 THEN
  CALL SUBA
ELSE IF I=2 THEN
  CALL SUBB
ENDIF

FORTRAN の例 - 計算型GOTO(今は推奨されない)

I=1
GO TO (100,200),I

FORTRAN の例 - 割当型GOTO(今は推奨されない)

ASSIGN 100 TO LINENUM
GO TO LINENUM

BASICの例 - IF文

100 IF A=1 THEN GOSUB 100 ELSE IF A=2 THEN GOSUB 200

BASICの例 - ON GOSUB

100 A=2
110 ON A GOSUB 200,300

C言語でも同じように if で条件を書いて関数を呼び出すことはもちろん、以下のように switch case を使うことも出来ます。

C言語の例 - if文

if (a == 1)
  func_a();
else
  func_b();

C言語の例 - switch/case文

switch (a) {
case 1:
  proc_a();
  break;
case 2:
  proc_b();
  break;
}

このような方法に加えてC言語では変数に関数へのポインタを入れ、これを呼び出すことで呼び出す関数を切り替える方法があります。

C言語の例 - 関数ポインタを使う

void func_a();
void func_b();

void main() {
  int a;
  void (* pfunc)();

  a = 1;

  if (a == 1)
    pfunc = func_a;
  else
    pfunc = func_b;  

  (* pfunc)(); 
}

※新しいCでは (* pfunc)() ではなく、pfunc() と書くことも出来る。

まず、関数ポインタ型の変数を宣言します。宣言を書く順序が難しいのですが、以下のような順で書きます。変数名が真ん中になるのが注意。

[関数の戻り値の型] ( * [関数ポインタの変数名] ) ( [関数の引き数の型を引き数の数だけ] );

この変数を使って関数を呼び出す時は、

(* [関数ポインタの変数名] ) ( [関数の引き数を必要な数だけ書く] );

どうして謎の括弧が必要になるのかというと、C言語の演算子の優先順位の都合で、変数の'*'を先に解釈してもらう必要があるためです。新しい規格ではこの括弧が不要になったようですが、そうすると名前で呼んでいるのか、変数名で呼んでいるのかちょっと区別が難しくなりそうです。

このように関数(へのポインタ)を変数にしまっておけるということは、配列や構造体に入れることが出来たり、関数呼び出しの引き数で渡すこともできるということです。ちょっとわかりにくかもしれませんが、都度、条件分岐を書く必要がなくなり、柔軟なプログラミングをするためにとても便利なんです。もっとも慣れていないとコードを読む時に?が連発するかもしれませんが。

なお、関数ポインタの宣言が読みにくいので、typedef を使って型を作ることもできます。

#include <stdio.h>

typedef int(*FUNC)(int, int);

int sub(int i, int j) {
  return i + j;
}

void main() {
  int i;
  FUNC pfunc = sub;

  i = (*pfunc)(2, 3);
  printf("%d\n", i);
}

残念ながら、関数ポインタのコードはブラウザ環境では動作してくれません。そろそろお手元にC言語の開発環境を作って頂く必要がありそうです。

ということで、具体的な使い方を課題にして今回は終わります。次回は、この関数ポインタを関数呼び出しの引き数に使うコールバック関数を説明します。

課題
1. まず整数の引き数を2つ持ち、この引き数の和を返す関数と、差を返す関数を書きなさい。
2. 整数を2つと先に作った和と差を求める関数のポインタ型(整数の引き数が2つ、戻り値が整数の関数)を持つ構造体を定義し、この構造体の配列を作って、それぞれの要素の2つの整数型のメンバ変数には適当な値を入れてください。
3. 構造体配列の最初の整数型のメンバ変数の値が奇数だった場合は、構造体の関数ポインタ型のメンバ変数に和を求める関数、偶数だった場合は、差を求める関数を代入してください。
4. 構造体配列の要素を順に取り出し、整数型の2つのメンバ変数を引数として、関数ポインタ型のメンバ変数の関数を呼び出し、引き数と戻り値を表示してください。

typedef は使っても使わなくても構いません。

ヘッダ画像は、いらすとや さんより

https://www.irasutoya.com/2014/01/blog-post_7220.html


この記事が気に入ったらサポートをしてみませんか?