関数へのポインタ(関数を指すポインタ)

Copyright(C) 14May2003 coskx

関数を指すポインタを紹介する。これは,関数の先頭アドレスを保存する事のできるポインタ変数である。


1.配列の先頭アドレスと,関数の先頭アドレス


配列名は,そのまま配列の先頭番地を意味していた。これと同じように,関数名は機械語に変換された関数の先頭番地を表している。
ゆえに,どちらもprintfでは%pで表示することが出来る。

List.1 配列の先頭アドレスと,関数の先頭アドレス
#include <stdio.h>

void printarray(int ary[], int num)
{
    int i;
    for (i=0; i<num; i++) {
        printf("%d : %d\n",i+1,ary[i]);
    }
}

int main()
{
    int array[]={3,5,7,9};
    printarray(array,4);
    printf("address of array      = %p\n",array);
    printf("address of printarray = %p\n",printarray);
    return 0;
}

実行結果
1 : 3
2 : 5
3 : 7
4 : 9
address of array      = 01B3F768
address of printarray = 00A61260



2.関数へのポインタ(関数の先頭アドレスを指すポインタ変数)


関数名は,(機械語に変換された)関数の先頭アドレスを意味していることが確認できた。
関数の先頭アドレスを保存できる関数へのポインタ変数がある。

int型変数を表すポインタ変数pnthensuの宣言は int *pnthensu,あるいは int* pnthensuであった。
関数の場合は宣言表現が少々複雑になる。
int型の値ataiを与え,int型の値を返す関数kansuなら int kansu(int atai) であるが,
int型の値ataiを与え,int型の値を返す関数を指すポインタ変数pntkansuなら
関数名のところが(*pntkansu)になって,引数名は定まっていないので(int)だけになるので
 int (*pntkansu)(int) になる。
(かっこを付けずに int *pntkansu(int) では演算子の優先順位のため (int *)pntkansu(int) の用に解釈されてしまうため,コンパイルエラーになってしまう。) 

関数へのポインタ変数に値(関数の先頭アドレス)が保存されてしまえば,このポインタ変数は関数と同じように使うことが出来る。

List.2 関数へのポインタの利用
#include <stdio.h>

void printarray(int ary[], int num)
{
    int i;
    for (i=0; i<num; i++) {
        printf("%d : %d\n",i+1,ary[i]);
    }
}

int main()
{
    int array[]={3,5,7,9};
    void (*ptrtofunc)(int[],int);
    printarray(array,4);
    printf("address of array      = %p\n",array);
    printf("address of printarray = %p\n",printarray);
    ptrtofunc=printarray;
    ptrtofunc(array,4);
    return 0;
}

実行結果
1 : 3
2 : 5
3 : 7
4 : 9
address of array      = 0216FDE8
address of printarray = 01061260
1 : 3
2 : 5
3 : 7
4 : 9




3.関数へのポインタ(関数の先頭アドレスを指すポインタ)の配列


Lint.2のような例では,関数へのポインタのありがたみがわからない。
関数へのポインタは,「関数へのポインタの配列」として,場合分けをスマートに記述するのに使われることがある。
ある変数の値に応じて呼び出す関数を変更するには,if-elseif構文,あるいはswtch-case構文で記述する場合が多いが,
これをスマートに表現するプログラムを書いてみよう。

通常使われる例
if (i==1) func1();
else if (i==2) func2();
else func3();

List.3 関数へのポインタの配列
#include <stdio.h>

void printarray1(int ary[], int num)
{
    int i;
    printf("printarray1\n");
    for (i=0; i<num; i++) {
        printf("%d : %d\n",i+1,ary[i]);
    }
    printf("\n");
}

void printarray2(int ary[], int num)
{
    int i;
    printf("printarray2\n");
    for (i=num-1; 0<=i; i--) {
        printf("%d : %d\n",i+1,ary[i]);
    }
    printf("\n");
}

void printarray3(int ary[], int num)
{
    int i;
    printf("printarray3\n");
    for (i=0; i<num; i++) {
        printf("%d : %c\n",i+1,(char)(ary[i]+0x40));
    }
    printf("\n");
}

int main()
{
    int array[]={3,5,7,9};
    void (*ptrtofunc[])(int[],int)={
        printarray1,printarray2,printarray3
    };
    int select;
    printf("select 1,2,3    0:quit  ==> ");
    scanf("%d",&select);
    while(0<select && select<4) {
        ptrtofunc[select-1](array,4);
        printf("select 1,2,3    0:quit  ==> ");
        scanf("%d",&select);
    }
    printf("quit\n");
    return 0;
}


実行結果
select 1,2,3    0:quit  ==> 1
printarray1
1 : 3
2 : 5
3 : 7
4 : 9

select 1,2,3    0:quit  ==> 2
printarray2
4 : 9
3 : 7
2 : 5
1 : 3

select 1,2,3    0:quit  ==> 3
printarray3
1 : C
2 : E
3 : G
4 : I

select 1,2,3    0:quit  ==> 0
quit



次の4,5の項目「ソートアルゴリズム」はクイックソートのところで再掲される。


4.C標準ライブラリのクイックソート(1)


ソートアルゴリズムの1つクイックソート使えるようにするため,C言語では標準ライブラリとしてクイックソートが提供されている。
昇順にソートしようとする時も降順にソートしようとする時も同じクイックソート関数を使うことができるように,2つのデータの比較方法を与える関数を別に定義し,標準ライブラリのクイックソート関数に,この比較関数を使ってほしいとお願いするプログラミングとなる。
このとき,比較関数を与えるのに,関数へのポインタが使われる。
クイックソート関数を使う場合は,「stdlib.h」をインクルードする。

qsort()はstdlib.h中で次のようにプロトタイプ宣言されている

void qsort(void *base, size_t num, size_t size, int (*compare)(const void*, const void*))

ここで「int (*compare)(const void*, const void*)」は「2つのデータの比較方法を与える」関数へのポインタであり,その返す値はint型である。2つのデータの大きさの状態によって,正の値・0・負の値を返すように作っている。
引数は2つあり,比較すべき2つのデータへのポインタとなっている。
なおこの比較関数の名前はcompareである必要はなく,なんでもよい。

List.4 標準ライブラリのクイックソート関数を使うプログラム例

#include "stdio.h"
#include "stdlib.h"

#define SIZE 20

void makedata(int x[], int num)
{
    int i;
    for (i=0; i<num; i++) x[i]=rand()%(5*SIZE);
}

void printdata(int x[], int num)
{
    int i;
    for (i=0; i<num; i++) printf("%2d ",x[i]);
    printf("\n");
}

/*小さい順ソート済みデータのチェック
  返す値0:正常
     1:異常あり
*/
int checkSortedData(int x[], int num)
{
    int i;
    int ng=0;
    for (i=0; i<num-1; i++) {
        if (x[i+1]<x[i]) {
            printf("%2d %2d\n",x[i],x[i+1]);
            ng=1;
        }
    }
    return ng;
}

/*比較関数     昇順ソートの場合 */
/*第一引数が小さい :負の値を返す  */
/*第一引数が大きい :正の値を返す  */
/*等しい      :0を返す    */
int mycompfunc(const void *a, const void *b)
{
    return *(int *)a - *(int *)b;
    /*それぞれint型のポインタにキャストしてから,そのポインタ変数が指す値を使っている*/

}

int main()
{
    int x[SIZE];
    int ng;
    makedata(x,SIZE);
    printdata(x,SIZE);
    qsort(x,SIZE,sizeof(int),mycompfunc);
    /*    ソート対象配列                  */
    /*      ソート対象配列のデータ 数                */
    /*           ソート対象配列の1データのバイト数 */
    /*                       比較関 数               */
    printdata(x,SIZE);

    ng=checkSortedData(x,SIZE);
    if (ng) printf("no good\n");
    else printf("good\n");
}

実行例
41 67 34  0 69 24 78 58 62 64  5 45 81 27 61 91 95 42 27 36
 0  5 24 27 27 34 36 41 42 45 58 61 62 64 67 69 78 81 91 95
good

 


5.C標準ライブラリのクイックソート(2)


C標準ライブラリのクイックソートは比較関数仕様が柔軟にできているため(扱いにくいが),構造体のソートも比較関数をスイッチするだけでできる。

List.5 標準ライブラリのクイックソート関数で構造体を扱うプログラム例

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

typedef struct {
    char name[32];  /*島の名前*/
    int population; /*島の人口*/
    double area;    /*島の面積[km2]*/
} island_t;

island_t Island[]={
    {"ぽこぽこ島", 1234,  5.3},
    {"ぷろげん島",  345,  1.2},
    {"ろんかい島", 5678,  4.5},
    {"いーらー島",  123,  3.4},
    {"それいけ島", 9874,  4.2},
    {"ぴっころ島",  874,  8.2}
};

void printData(island_t data[], int num, char *title)
{
    int i;
    puts(title);
    for (i=0; i<num; i++) {
        printf("%14s %6d %8.2lf\n",
         data[i].name, data[i].population, data[i].area);
    }
    printf("\n");
}

int compfuncName(const void *a, const void *b)
{
    return strcmp(((island_t*)a)->name,((island_t*)b)->name);
    /*island_t型のポインタにキャストしてからメンバnameで比較*/
}

int compfuncPopulation(const void *a, const void *b)
{
    return ((island_t*)a)->population - ((island_t*)b)->population;
    /*island_t型のポインタにキャストしてからメンバpopulationで比較*/
}

int compfuncArea(const void *a, const void *b)
{
    double comp;
    comp = ((island_t*)a)->area - ((island_t*)b)->area;
    /*island_t型のポインタにキャストしてからメンバareaで比較*/
    if (comp<0.) return -1;
    else if (0.<comp) return 1;
    else return 0;
}

int main()
{
    int DataSize=sizeof(island_t);
    int NumberOfDatas=sizeof(Island)/DataSize;
    printData(Island, NumberOfDatas, "初期状態");

    qsort(Island, NumberOfDatas, DataSize, compfuncName);
    printData(Island, NumberOfDatas, "名前順");

    qsort(Island, NumberOfDatas, DataSize, compfuncPopulation);
    printData(Island, NumberOfDatas, "人口順");

    qsort(Island, NumberOfDatas, DataSize, compfuncArea);
    printData(Island, NumberOfDatas, "面積順");

    return 0;
}

実行例

初期状態
    ぽこぽこ島   1234     5.30
    ぷろげん島    345     1.20
    ろんかい島   5678     4.50
    いーらー島    123     3.40
    それいけ島   9874     4.20
    ぴっころ島    874     8.20

名前順
    いーらー島    123     3.40
    それいけ島   9874     4.20
    ぴっころ島    874     8.20
    ぷろげん島    345     1.20
    ぽこぽこ島   1234     5.30
    ろんかい島   5678     4.50

人口順
    いーらー島    123     3.40
    ぷろげん島    345     1.20
    ぴっころ島    874     8.20
    ぽこぽこ島   1234     5.30
    ろんかい島   5678     4.50
    それいけ島   9874     4.20

面積順
    ぷろげん島    345     1.20
    いーらー島    123     3.40
    それいけ島   9874     4.20
    ろんかい島   5678     4.50
    ぽこぽこ島   1234     5.30
    ぴっころ島    874     8.20