9

チェスのプログラムを作成しているとしましょう。私には機能があります

void foreachMove( void (*action)(chess_move*), chess_game* game); 

これは、有効な移動ごとに関数ポインター アクションを呼び出します。これで問題ありませんが、アクション関数にさらにパラメーターを渡す必要がある場合はどうすればよいでしょうか? 例えば:

chess_move getNextMove(chess_game* game, int depth){
  //for each valid move, determine how good the move is
  foreachMove(moveHandler, game);
}

void moveHandler(chess_move* move){
  //uh oh, now I need the variables "game" and "depth" from the above function
}

関数ポインターを再定義することは、最適な解決策ではありません。foreachMove 関数は用途が広く、コード内のさまざまな場所で参照されています。これらの参照のそれぞれが、必要のないパラメーターを含めるために関数を更新する必要があるのは意味がありません。

ポインターを介して呼び出している関数に追加のパラメーターを渡すにはどうすればよいですか?

4

9 に答える 9

13

ああ、C だけがクロージャをサポートしていれば...

アントニオは正しいです。追加のパラメーターを渡す必要がある場合は、追加の引数を受け入れるように関数ポインターを再定義する必要があります。必要なパラメーターが正確にわからない場合は、少なくとも 3 つの選択肢があります。

  1. プロトタイプの最後の引数を void* にします。これにより、他に必要なものを柔軟に渡すことができますが、型安全ではありません。
  2. 可変パラメータ (...) を使用します。C での可変引数の経験が不足しているため、関数ポインターでこれを使用できるかどうかはわかりませんが、これにより、最初のソリューションよりもさらに柔軟性が得られますが、型の安全性はまだありません。
  3. C++ にアップグレードし、関数オブジェクトを使用します。
于 2008-08-14T17:19:28.183 に答える
7

追加の引数を取るには、おそらく関数ポインターを再定義する必要があります。

void foreachMove( void (*action)(chess_move*, int), chess_game* game )
于 2008-08-14T16:58:47.600 に答える
4

C++ を使用する場合は、「関数オブジェクト」を使用できます。

struct MoveHandler {
    chess_game *game;
    int depth;

    MoveHandler(chess_game *g, int d): game(g), depth(d) {}

    void operator () (chess_move*) {
         // now you can use the game and the depth
    }
};

foreachMoveそしてあなたをテンプレートに変えます:

template <typename T>
void foreachMove(T action, chess_game* game);

次のように呼び出すことができます。

chess_move getNextMove(chess_game* game, int depth){
    //for each valid move, determine how good the move is
    foreachMove(MoveHandler(game, depth), game);
}

の他の用途を妨げることはありませんMoveHandler

于 2008-08-14T17:32:18.967 に答える
2

私がこれを正しく読んでいる場合、私が提案するのは、関数が構造体へのポインターを引数としてとるようにすることです。次に、必要なときに構造体に「ゲーム」と「深さ」を設定し、不要な場合は 0 または Null に設定したままにします。

その機能で何が起こっているのですか?次のような条件がありますか。

if (depth > -1) //some default
  {
  //do something
  }

関数は常に「ゲーム」と「深さ」を必要としますか? 次に、それらは常に引数である必要があり、それをプロトタイプに入れることができます。

その機能が「ゲーム」と「深さ」を時々必要とするだけであることを示していますか? たぶん、2 つの関数を作成し、必要なときにそれぞれを使用します。

しかし、引数として構造を持つことは、おそらく最も簡単なことです。

于 2008-08-14T17:24:57.423 に答える
1

void* の配列を使用することをお勧めします。最後のエントリは常に void です。これを行うことができる3つのパラメーターが必要だとします:

void MoveHandler (void** DataArray)
{
    // data1 is always chess_move
    chess_move data1 = DataArray[0]? (*(chess_move*)DataArray[0]) : NULL; 
    // data2 is always float
    float data1 = DataArray[1]? (*(float*)DataArray[1]) : NULL; 
    // data3 is always char
    char data1 = DataArray[2]? (*(char*)DataArray[2]) : NULL; 
    //etc
}

void foreachMove( void (*action)(void**), chess_game* game);

その後

chess_move getNextMove(chess_game* game, int depth){
    //for each valid move, determine how good the move is
    void* data[4];
    data[0] = &chess_move;
    float f1;
    char c1;
    data[1] = &f1;
    data[2] = &c1;
    data[3] = NULL;
    foreachMove(moveHandler, game);
}

すべてのパラメーターが同じ型の場合、void* 配列を使用せずに、必要な型の NULL で終わる配列を送信するだけです。

于 2008-08-14T18:15:26.447 に答える
0

パラメーターが変更された場合は、関数ポインター宣言を変更して、「...」手法を使用して可変数の引数を設定します。これにより、読みやすくなり、関数に渡すパラメーターごとに変更を加える必要がなくなります。void を渡すよりもはるかに安全です。

http://publications.gbdirect.co.uk/c_book/chapter9/stdarg.html

参考までに、リンクのサンプルコードについて: 一部の場所には「n args」があり、他の場所にはアンダースコア付きの「n_args」があります。それらはすべてアンダースコアを持つ必要があります。いくつかの場所でアンダースコアが削除されていることに気付くまで、構文が少しおかしいと思っていました。

于 2008-08-14T20:22:54.740 に答える
0

関数ポインターには typedef を使用します。この質問に対する私の回答を参照してください

于 2008-09-16T14:57:42.703 に答える
0

chess_move別のオプションは、関数プロトタイプの代わりに構造を変更することです。構造はおそらく、すでに 1 か所だけで定義されています。メンバーを構造体に追加し、それを使用する呼び出しの前に構造体に適切なデータを入力します。

于 2013-04-08T05:08:22.313 に答える
0

アントニオに+1。追加のパラメーターを受け入れるには、関数ポインター宣言を変更する必要があります。

また、void ポインターや (特に) void ポインターの配列を渡さないようにしてください。それはただトラブルを求めているだけです。void ポインターを渡し始めると、ポインターの型 (または型) を示す何らかのメッセージも渡す必要があります。この手法が適切であることはめったにありません。

パラメーターが常に同じである場合は、それらを関数ポインター引数に追加するだけです (または、多くのパラメーターがある場合は、構造体にパックしてそれを引数として使用することもできます)。パラメーターが変更された場合は、void ポインターを渡す代わりに、複数の呼び出しシナリオに対して複数の関数ポインターを使用することを検討してください。

于 2008-08-14T18:39:46.617 に答える