先頭へ戻る

ポインタ⑧(高度な使用法) 解答ページ | Programming Place Plus C言語編 第38章

Programming Place Plus トップページC言語編第38章

先頭へ戻る

問題①

問題① 関数ポインタのところで登場した、難易度に応じて処理を分岐させるサンプルプログラムに、結果を出力するような関数を追加してください。 この関数は、引数で得点を受け取り、難易度ごとに異なる方法で合格・不合格を決定するものとします。 たとえば、簡単なモードなら 60点で合格ですが、難しいと 80点必要という感じです。 得点は、mainProcXXXX関数が戻り値で返すようにするなど、元のプログラムを一部修正して構いません。
なお、関数ポインタを使う方法と、使わない方法の両方で、プログラムを作成してください。


まず、関数ポインタを使わない方法の解答例です。

#include <stdio.h>
#include <assert.h>

int mainProcForEasy(void);
int mainProcForNormal(void);
int mainProcForHard(void);
void resultProcForEasy(int score);
void resultProcForNormal(int score);
void resultProcForHard(int score);

int main(void)
{
    puts( "難易度を選んでください。" );
    puts( "0~2 で大きいほど難しくなります。" );

    char str[40];
    int level;
    fgets( str, sizeof(str), stdin );
    sscanf( str, "%d", &level );

    int score;

    // 難易度に応じたメインの処理を実行する
    switch( level ){
    case 0:
        score = mainProcForEasy();
        break;
    case 1:
        score = mainProcForNormal();
        break;
    case 2:
        score = mainProcForHard();
        break;
    default:
        puts( "入力が正しくありません。" );
        return 0;
    }

    // 難易度に応じた合否判定
    switch( level ){
    case 0:
        resultProcForEasy( score );
        break;
    case 1:
        resultProcForNormal( score );
        break;
    case 2:
        resultProcForHard( score );
        break;
    default:
        assert( 0 );
        return 0;
    }
}

int mainProcForEasy(void)
{
    puts( "簡単なモードで実行。" );
    return 65;
}

int mainProcForNormal(void)
{
    puts( "標準的な難易度のモードで実行。" );
    return 65;
}

int mainProcForHard(void)
{
    puts( "難しいモードで実行。" );
    return 65;
}

void resultProcForEasy(int score)
{
    if( score >= 60 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

void resultProcForNormal(int score)
{
    if( score >= 70 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

void resultProcForHard(int score)
{
    if( score >= 80 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

実行結果:

難易度を選んでください。
0~2 で大きいほど難しくなります。
1
標準的な難易度のモードで実行。
不合格

mainProcXXXX関数の戻り値を int型に変更し、得点を返すようにしてあります。 返す得点は定数にしていますが、もちろん本当のプログラムなら、正しい得点を返すようにします。

関数ポインタを使わないこの方法の場合、分岐構造が2か所に必要になります。 このように、どんどん同じ趣旨の分岐構造がプログラム中にばら撒かれていく傾向があります。

次に、関数ポインタを使った方法です。

#include <stdio.h>

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

int mainProcForEasy(void);
int mainProcForNormal(void);
int mainProcForHard(void);
void resultProcForEasy(int score);
void resultProcForNormal(int score);
void resultProcForHard(int score);

int main(void)
{
    typedef int (*mainProc_t)(void);  // メイン処理の関数ポインタ型
    typedef void (*resultProc_t)(int);  // 結果処理の関数ポインタ型

    const mainProc_t mainProcArray[] = {  // メイン処理の関数テーブル
        mainProcForEasy,
        mainProcForNormal,
        mainProcForHard,
    };
    const resultProc_t resultProcArray[] = {  // 結果処理の関数テーブル
        resultProcForEasy,
        resultProcForNormal,
        resultProcForHard,
    };


    puts( "難易度を選んでください。" );
    puts( "0~2 で大きいほど難しくなります。" );

    char str[40];
    int level;
    fgets( str, sizeof(str), stdin );
    sscanf( str, "%d", &level );

    if( level < 0 || SIZE_OF_ARRAY(mainProcArray) <= level ){
        puts( "入力が正しくありません。" );
        return 0;
    }

    // 難易度に応じたメインの処理を実行する
    int score = mainProcArray[level]();

    // 難易度に応じた合否判定
    resultProcArray[level]( score );
}

int mainProcForEasy(void)
{
    puts( "簡単なモードで実行。" );
    return 65;
}

int mainProcForNormal(void)
{
    puts( "標準的な難易度のモードで実行。" );
    return 65;
}

int mainProcForHard(void)
{
    puts( "難しいモードで実行。" );
    return 65;
}

void resultProcForEasy(int score)
{
    if( score >= 60 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

void resultProcForNormal(int score)
{
    if( score >= 70 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

void resultProcForHard(int score)
{
    if( score >= 80 ){
        puts( "合格" );
    }
    else{
        puts( "不合格" );
    }
}

実行結果:

難易度を選んでください。
0~2 で大きいほど難しくなります。
1
標準的な難易度のモードで実行。
不合格

この方法だと、分岐構造は増えません。新たな関数ポインタテーブルを用意し、添字を変えることで処理を分岐できます。

問題②

問題② qsort関数や bsearch関数に渡す関数ポインタにおいて、指し示す先の関数の引数が void*型ではなく、const void*型である理由はなぜでしょうか。


const void*型であれば、渡されたポインタを経由して、その先にある値を書き換えることができなくなります。この関数が呼び出されるのは、qsort関数や bsearch関数が、ソートやサーチの作業を行っている「途中」ですから、突然、値の一部が書き換えられると、正しく作業できなくなってしまいます。

const修飾子のうまい使い方はこういうものです。コメントやドキュメントで「書き換えないでください」と書いても何の強制力も働きませんが、const修飾子を明示すれば、強制力が働きます。


参考リンク


更新履歴

’2018/6/1 第37章の内容をそのままの形で移動して上書き。

’2018/3/13 全面的に文章を見直し、修正を行った。
章のサブタイトルを変更(高度な使用法 -> 関数ポインタ)

≪さらに古い更新履歴を展開する≫



第38章のメインページへ

C言語編のトップページへ

Programming Place Plus のトップページへ