Передача большего количества параметров в указатели на функции C - PullRequest
9 голосов
/ 14 августа 2008

Допустим, я создаю шахматную программу. У меня есть функция

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 является универсальной, и на нее ссылается множество различных мест в коде. Для каждой из этих ссылок не имеет смысла обновлять свою функцию для включения параметров, которые им не нужны.

Как передать дополнительные параметры в функцию, которую я вызываю через указатель?

Ответы [ 9 ]

11 голосов
/ 14 августа 2008

Ах, если бы только C поддерживал замыкания ...

Антонио прав; если вам нужно передать дополнительные параметры, вам нужно переопределить указатель на функцию, чтобы принять дополнительные аргументы. Если вы точно не знаете, какие параметры вам понадобятся, у вас есть как минимум три варианта:

  1. Пусть последний аргумент в вашем прототипе будет пустым *. Это дает вам гибкость при передаче всего, что вам нужно, но это определенно не безопасно для типов.
  2. Использовать переменные параметры (...). Учитывая мой недостаток опыта работы с переменными параметрами в C, я не уверен, что вы можете использовать это с указателем на функцию, но это дает еще большую гибкость, чем первое решение, хотя все еще с отсутствием безопасности типов.
  3. Обновление до C ++ и использование функциональных объектов .
8 голосов
/ 14 августа 2008

Возможно, вам потребуется переопределить указатель функции, чтобы получить дополнительные аргументы.

void foreachMove( void (*action)(chess_move*, int), chess_game* game )
5 голосов
/ 14 августа 2008

Если вы хотите использовать 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.

3 голосов
/ 14 августа 2008

Если я читаю это правильно, то я бы предложил, чтобы ваша функция взяла указатель на структуру в качестве аргумента. Тогда ваша структура может иметь «игру» и «глубину», когда она им нужна, и просто оставить для них значение 0 или ноль, когда они вам не нужны.

Что происходит в этой функции? У вас есть условие, которое говорит:

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

Всегда ли функция ТРЕБУЕТ "игры" и "глубины"? Тогда они всегда должны быть аргументами, и это может войти в ваши прототипы.

Вы указываете, что функция требует иногда только "игры" и "глубины"? Ну, может сделать две функции и использовать каждую, когда вам нужно.

Но иметь структуру в качестве аргумента, наверное, проще всего.

1 голос
/ 14 августа 2008

Я бы предложил использовать массив 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 * и просто отправить массив с нулевым символом в конце любого необходимого вам типа.

0 голосов
/ 08 апреля 2013

Другой вариант - изменить структуру chess_move вместо прототипа функции. Структура предположительно определена только в одном месте. Добавьте членов в структуру и заполните структуру соответствующими данными перед любым вызовом, который ее использует.

0 голосов
/ 16 сентября 2008

Используйте typedef для указателя функции. См. Мой ответ на этот вопрос

0 голосов
/ 15 августа 2008

Если ваши параметры изменятся, я бы изменил объявление указателя функции, чтобы использовать технику "..." для установки переменного числа аргументов. Это может спасти вас в удобочитаемости, а также внести изменения для каждого параметра, который вы хотите передать в функцию. Это определенно намного безопаснее, чем пропускать пустоту вокруг.

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

Просто к сведению, пример кода в ссылке: в некоторых местах они имеют «n args», а в других это «n_args» с подчеркиванием. Все они должны иметь подчеркивание. Я думал, что синтаксис выглядит немного забавно, пока не понял, что в некоторых местах они опустили подчеркивание.

0 голосов
/ 14 августа 2008

+ 1 к Антонио. Вам нужно изменить объявление указателя на функцию, чтобы принимать дополнительные параметры.

Кроме того, пожалуйста, не начинайте разыгрывать пустые указатели или (особенно) массивы пустых указателей. Это просто напрашивается на неприятности. Если вы начнете передавать пустые указатели, вам также придется передать какое-то сообщение, чтобы указать, какой тип указателя (или типы). Этот метод редко уместен.

Если ваши параметры всегда одинаковы, просто добавьте их в аргументы указателя функции (или, возможно, упакуйте их в структуру и используйте в качестве аргумента, если параметров много). Если ваши параметры изменяются, рассмотрите возможность использования нескольких указателей функций для сценариев множественных вызовов вместо передачи пустых указателей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...