В чем разница между этими двумя объявлениями? - PullRequest
2 голосов
/ 19 февраля 2011

Вот простой и деликатный вопрос. Может ли кто-нибудь объяснить разницу между a и b?

void (*a)(int x, int y)
void (*b(int x, int y))(int)

Этот вопрос возникает из следующего объявления функции Linux:

void (*signal(int sig, void (*func)(int)))(int);

Решение

Следующая программа делает хорошую демонстрацию.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig)
{
   printf("OUCH! - I got signal %d\n", sig);
   signal(SIGINT, SIG_DFL);
}


void (*g())(int) // just like the b
{
   return ouch;
}

int main()
{

   void (*f)(int); // just like the a
   f=g();
   signal(SIGINT, f);

   while(1)
   {
      printf("Hello, world!\n");
      sleep(1);
   }
}

Ответы [ 4 ]

10 голосов
/ 19 февраля 2011

Разработчики C решили именовать свои типы таким образом, чтобы сценарий использования типа максимально совпадал с тем, каким образом вы используете этот тип для получения значения. Так, например, когда у вас есть объявление типа

int a;

Вы можете просто сказать a, чтобы получить int. Если у вас есть тип как

int *a;

Затем вы должны разыменовать a, написав *a, чтобы получить int.

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

void (*a)(int x, int y)

Это говорит о том, что если вы разыграете a (написав *a), то у вас останется нечто, похожее на

void (int x, int y)

Функция принимает два int с и возвращает void. Другими словами, вы можете думать о a как о указателе на функцию; после разыменования вы возвращаете функцию.

Теперь для этого зверя:

void (*b(int x, int y))(int)

Этот хитрее. Идея заключается в следующем. Если вы возьмете b и передадите ему два аргумента, вы получите что-то похожее на это:

void (*)(int)

Указатель на функцию, принимающую int и возвращающую void. Другими словами, b - это функция, которая принимает два аргумента, затем возвращает указатель на функцию, который принимает один аргумент и возвращает void.

Несколько сложно декодировать эти типы, поэтому часто вам не кажется, что они написаны таким образом, и вместо этого вы используете typedef для упрощения вещей. Например, этот typedef:

typedef void (*FunctionTakingTwoInts)(int, int);

Говорит, что вы можете использовать FunctionTakingTwoInts для определения указателя на функцию, которая указывает на функцию, которая принимает два int с и возвращает void. Отсюда объявление a упрощается до

FunctionTakingTwoInts a;

Аналогично, в случае b, давайте определим тип

typedef void (*FunctionTakingOneInt)(int);

Теперь мы можем переписать b как

FunctionTakingOneInt b(int x, int y);

Из чего, я думаю, гораздо яснее, что на самом деле означает тип.

Надеюсь, это поможет!

3 голосов
/ 19 февраля 2011

Краткая версия: a объявляет указатель на функцию.b объявляет функцию, которая возвращает указатель на функцию.

Более длинная версия: Вам известен следующий шаблон для объявления указателя на функцию

void (*f)(int x, int y)

Теперь возьмите «f» и измените его.Например, сделайте его массивом

void (*f[3])(int x, int y)

Теперь, вместо указателя на функцию, у вас есть массив указателей на функции.Теперь, если вы вместо этого измените его как функцию, а не как массив, вы преобразуете первое объявление во второе объявление

void (*f(void))(int x, int y)

Вы изменили его как функцию, которая возвращает указатель на функцию.Вместо того, чтобы иметь параметр, как в этом случае, ваше объявление имеет два параметра.Одним из которых является int, а другим - еще один указатель на функцию.

Хотя для этого проще использовать typedefs, чем объединять деклараторы C.

// your example
typedef void signal_fn(int);
signal_fn *signal(int sig, signal_fn *func);

// my example above
typedef void f_fn(int x, int y);
f_fn *f(void);
1 голос
/ 19 февраля 2011

Заменить (*a) на foo:

void foo(int x, int y);

a - указатель на такую ​​функцию.

Аналогично замените (*b(int x, int y)) на foo:

void foo(int);

b(int x, int y) возвращает указатель на такую ​​функцию.

0 голосов
/ 19 февраля 2011

http://cdecl.org/ может быть хорошим подспорьем для таких объявлений. К сожалению, он не обрабатывает параметры именованных функций, поэтому они должны быть удалены. Кормление в

void ( сигнал (int, void () (int))) (int)

дает:

объявить сигнал как функцию (int, указатель на функцию (int), возвращающий void), возвращающий указатель на функцию (int), возвращающую void

...