在C函数指针中传递更多参数

时间:2020-03-05 18:39:41  来源:igfitidea点击:

假设我正在创建一个国际象棋程序。我有一个功能

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函数具有多种功能,并且代码中的许多不同地方都引用了该函数。这些引用中的每个引用都不必更新其功能以包含它们不需要的参数,这是没有意义的。

如何通过指针将额外的参数传递给正在调用的函数?

解决方案

回答

我们可能需要重新定义函数指针以采用其他参数。

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

回答

啊,如果只有C支持闭包...

安东尼奥是对的。如果需要传递其他参数,则需要重新定义函数指针以接受其他参数。如果我们不确定所需的参数,那么至少有三个选择:

  • 原型中的最后一个参数为void *。这使我们可以灵活地传递我们需要的其他任何内容,但这绝对不是类型安全的。
  • 使用可变参数(...)。考虑到我缺乏C语言中可变参数的经验,我不确定是否可以将其与函数指针一起使用,但这比第一种解决方案具有更大的灵活性,尽管仍然缺乏类型安全性。
  • 升级到C ++并使用函数对象。

回答

如果我没看错,我的建议是使函数将指向结构的指针作为参数。然后,结构在需要它们时可以具有"游戏"和"深度",并且在不需要它们时将它们设置为0或者Null。

该功能发生了什么?我们是否有条件说:

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

该功能是否总是需要"游戏"和"深度"?然后,它们应该始终是参数,并且可以放入原型中。

我们是否表示该功能有时仅需要"游戏"和"深度"?好吧,也许要做两个功能,并在需要时使用每个功能。

但是,将结构作为参数可能是最简单的事情。

回答

如果我们愿意使用某些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的其他使用。

回答

我建议使用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终止数组。

回答

+1安东尼奥。我们需要更改函数指针声明以接受其他参数。

另外,请不要开始传递空指针或者(尤其是)空指针数组。那只是自找麻烦。如果我们开始传递void指针,则还必须传递某种消息以指示指针类型是(或者多个)指针类型。这种技术很少适合。

如果参数始终相同,则只需将它们添加到函数指针参数中即可(或者,如果有很多参数,则可以将它们打包到一个struct中并用作参数)。如果参数更改,则考虑在多个调用方案中使用多个函数指针,而不要传递空指针。

回答

如果参数更改,我将更改函数指针声明以使用" ..."技术来设置可变数量的参数。它可以节省可读性,并且还必须为要传递给该函数的每个参数进行更改。绝对比绕过虚空要安全得多。

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

仅供参考,关于链接中的示例代码:某些地方有n个args,其他地方是n_args,带下划线。他们都应该有下划线。我以为语法看起来有些可笑,直到我意识到他们在某些地方删除了下划线。

回答

使用typedef作为函数指针。看到我对这个问题的回答