Як ви передаєте функцію як параметр у C?


616

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


12
Якщо ви використовуєте функції / масиви як змінні, ВИНАГИ використовуйте typedef.
— Mooing Duck

3
Ми називаємо це покажчик функцій
— AminM

Назва функції має бути понтером функції. Більшість людей, які навчаються C, охоплюють qsort рано чи пізно, що робить саме це?
— mckenzm

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. Розглянемо спочатку, як виглядатиме цикл для виклику друку безпосередньо:

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

Оскільки funcв оголошенні параметра ' p ' сказано, що fце ім'я для вказівника на потрібну функцію, ми спочатку нагадаємо, що, якщо fвказівник, то *fце те, на що fвказує (тобто функція 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 замовчують код і тому не повинні використовуватися.
— ММ

129

Це питання вже має відповідь для визначення покажчиків функцій, однак вони можуть стати дуже безладним, особливо якщо ви збираєтеся передавати їх навколо своєї програми. Щоб уникнути цієї неприємності, я б рекомендував ввести покажчик функції в щось більш читабельне. Наприклад.

typedef void (*functiontype)();

Оголошує функцію, яка повертає недійсним і не приймає аргументів. Щоб створити вказівник функції на цей тип, ви можете виконати:

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
Якщо ви хочете "перетворити покажчик функції на тип", вам не потрібна бібліотека підвищення. Просто використовуйте typedef; це простіше і не вимагає зайвих бібліотек.
— wizzwizz4

72

Оскільки 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));

З якого документа ви цитуєте? Будь-яке посилання на нього?
— Рафаель Ейн

1
Я цитую зі стандарту ISO C11.
— doppelheathen


-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?
— Техас Пендсе
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.