Как вы передаете функцию в качестве параметра в C?

616

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

andrewrk
источник
12
Если вы используете функции / массивы в качестве переменных, ВСЕГДА используйте typedef.
Mooing Duck
3
Мы называем это указателем на функцию
AminM
Имя функции должно быть знаком с функцией. Большинство людей, изучающих C, рано или поздно изучают qsort, что делает именно это?
Маккензм
5
@ MooingDuck Я не согласен с вашим предложением, и в вашем предложении нет аргументов в поддержку этого вывода. Лично я предпочитаю никогда не использовать typedef для указателей на функции, и я думаю, что это делает код более понятным и легким для чтения.
andrewrk
2
@andrewrk: Вы предпочитаете void funcA(void(*funcB)(int))и void (*funcA())()к typedef void funcB(); void funcA(funcB)и funcB funcA()? Я не вижу преимущества.
Mooing Duck

Ответы:

747

декларация

Прототип функции, которая принимает параметр функции, выглядит следующим образом:

void func ( void (*f)(int) );

Это говорит о том, что параметр fбудет указателем на функцию, которая имеет voidтип возвращаемого значения и которая принимает один intпараметр. Следующая функция ( print) является примером функции, которую можно передать funcкак параметр, потому что это правильный тип:

void print ( int x ) {
  printf("%d\n", x);
}

Вызов функции

При вызове функции с параметром функции передаваемое значение должно быть указателем на функцию. Используйте имя функции (без скобок) для этого:

func(print);

будет вызывать func, передавая ему функцию печати.

Тело функции

Как и любой параметр, funcтеперь можно использовать имя параметра в теле функции для доступа к значению параметра. Допустим, что funcк функции будет применено переданное ей число 0-4. Сначала рассмотрим, как будет выглядеть цикл для непосредственного вызова print:

for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
  print(ctr);
}

Так funcкак объявление параметра говорит, что fэто имя для указателя на желаемую функцию, мы сначала напомним, что если fэто указатель, то *fэто то, на что fуказывает (то есть функция printв данном случае). В результате просто замените все вхождения print в цикле выше на *f:

void func ( void (*f)(int) ) {
  for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
    (*f)(ctr);
  }
}

Источник

Нияз
источник
40
В вашем первом и последнем примерах кода * не является обязательным. Как определение параметра функции, так и fвызов функции могут быть fтакими же, как и без *. Возможно, было бы неплохо сделать это так, как вы, чтобы было очевидно, что параметр f является указателем на функцию. Но это часто ухудшает читабельность.
Готье
5
См. [C99, 6.9.1§14] для примеров. И то и другое верно, я просто хотел упомянуть альтернативу.
Готье
6
В самом деле? Ответ с наивысшим рейтингом не содержит ни одной ссылки на использование typedefуказателей для функции? Извините, надо понизить голосование.
Джонатон Рейнхарт
3
@JonathonReinhart, какие преимущества дает apprach typedef? Хотя эта версия выглядит намного чище и лишена дополнительных утверждений. здесь довольно много.
Абхинав Гауниал
3
@JonathonReinhart хорошо заметили; Следует четко отметить, что указатели typedefs запутывают код и поэтому не должны использоваться.
ММ
128

На этот вопрос уже есть ответ для определения указателей на функции, однако они могут стать очень запутанными, особенно если вы собираетесь передавать их по своему приложению. Чтобы избежать этой неприятности, я бы порекомендовал вам ввести указатель функции во что-то более читабельное. Например.

typedef void (*functiontype)();

Объявляет функцию, которая возвращает void и не принимает аргументов. Чтобы создать указатель на функцию этого типа, теперь вы можете сделать:

void dosomething() { }

functiontype func = &dosomething;
func();

Для функции, которая возвращает int и принимает символ, вы бы сделали

typedef int (*functiontype2)(char);

и использовать это

int dosomethingwithchar(char a) { return 1; }

functiontype2 func2 = &dosomethingwithchar
int result = func2('a');

Существуют библиотеки, которые могут помочь превратить указатели функций в удобные для чтения типы. Библиотека функций буста великолепна и стоит затраченных усилий!

boost::function<int (char a)> functiontype2;

это намного приятнее, чем выше.

РОО
источник
4
Если вы хотите «превратить указатель функции в тип», вам не нужна библиотека boost. Просто используйте typedef; это проще и не требует дополнительных библиотек.
wizzwizz4
73

Начиная с C ++ 11 вы можете использовать функциональную библиотеку, чтобы сделать это кратко и обобщенно. Синтаксис, например,

std::function<bool (int)>

где boolздесь возвращаемый тип функции с одним аргументом, первый аргумент которой имеет тип int.

Я включил пример программы ниже:

// g++ test.cpp --std=c++11
#include <functional>

double Combiner(double a, double b, std::function<double (double,double)> func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}

Однако иногда удобнее использовать функцию шаблона:

// g++ test.cpp --std=c++11

template<class T>
double Combiner(double a, double b, T func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}
Ричард
источник
43
Вопрос о С; C ++ здесь не применяется.
Супер кот
16
Вопрос для C ++ ( stackoverflow.com/questions/6339970/… ) упоминается здесь, поэтому я думаю, что этот ответ на месте.
mr_T
8
Может быть, вопрос о C ++ здесь не стоит упоминать, потому что этот вопрос относится к C.
Майкл Фултон,
5
Этот вопрос возник в первую очередь, когда я искал «вызов функции c ++ как параметр», поэтому этот ответ, в любом случае, служит своей цели.
ufoxDan
25

Передать адрес функции в качестве параметра другой функции, как показано ниже

#include <stdio.h>

void print();
void execute(void());

int main()
{
    execute(print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void f()) // receive address of print
{
    f();
}

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

#include <stdio.h>

void print();
void execute(void (*f)());

int main()
{
    execute(&print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void (*f)()) // receive address of print
{
    f();
}
Йогиш ХТ
источник
1
Очень хороший ответ, с целыми блоками кода (вместо того, чтобы нарезать все в непонятный беспорядок). Не могли бы вы рассказать о различиях обеих техник?
Рафаэль Эйнг
5

Функции могут быть «переданы» как указатели на функции, в соответствии с ISO C11 6.7.6.3p8: « Объявление параметра как« функции, возвращающего тип »должно быть настроено на« указатель на функцию, возвращающую тип » , как в 6.3 .2.1. " Например, это:

void foo(int bar(int, int));

эквивалентно этому:

void foo(int (*bar)(int, int));
doppelheathen
источник
Из какого документа вы цитируете? Любая ссылка на это?
Рафаэль Эйнг
1
Я цитирую из стандарта ISO C11.
doppelheathen
4

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

saint_groceon
источник
-2

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

#include <stdio.h>

int IncMultInt(int a, int b)
{
    a++;
    return a * b;
}

int main(int argc, char *argv[])

{
    int a = 5;
    int b = 7;

    printf("%d * %d = %d\n", a, b, IncMultInt(a, b));

    b = 9;

    // Create some local code with it's own local variable
    printf("%d * %d = %d\n", a, b,  ( { int _a = a+1; _a * b; } ) );

    return 0;
}
Вальтер
источник
Вы просто вызываете функцию. Как бы вы передали какую-то другую функцию вместо IncMultInt?
Теджас Пендсе