Как мы сюда попали
Синтаксис C для объявления функциональных точек был предназначен для отражения использования. Рассмотрим обычное объявление функции следующим образом <math.h>
:
double round(double number);
Чтобы иметь точечную переменную, вы можете присвоить ее типу безопасности, используя
fp = round;
вам нужно было бы объявить эту fp
точечную переменную следующим образом:
double (*fp)(double number);
Так что все, что вам нужно сделать, это посмотреть, как вы будете использовать функцию, и заменить имя этой функции ссылкой на указатель, превращаясь round
в *fp
. Тем не менее, вам нужен дополнительный набор паренов, который, как говорят некоторые, делает его немного беспорядочным.
Возможно, это было проще в оригинальном C, который даже не имел сигнатуры функции, но давайте не будем возвращаться туда, хорошо?
Место, где это становится особенно неприятным, - выяснить, как объявить функцию, которая либо принимает в качестве аргумента, либо возвращает указатель на функцию, либо и то и другое.
Если у вас была функция:
void myhandler(int signo);
Вы можете передать его функции сигнала (3) следующим образом:
signal(SIGHUP, myhandler);
или если вы хотите сохранить старый обработчик, то
old_handler = signal(SIGHUP, new_handler);
что довольно легко. То, что довольно легко - ни красиво, ни легко - правильно делает декларации.
signal(int signo, ???)
Ну, вы просто возвращаетесь к объявлению своей функции и меняете имя для ссылки на точку:
signal(int sendsig, void (*hisfunc)(int gotsig));
Поскольку вы не декларируете gotsig
, вам может быть легче читать, если вы опустите:
signal(int sendsig, void (*hisfunc)(int));
А может и нет. :(
За исключением того, что это недостаточно хорошо, потому что signal (3) также возвращает старый обработчик, как в:
old_handler = signal(SIGHUP, new_handler);
Так что теперь вы должны выяснить, как объявить все это.
void (*old_handler)(int gotsig);
достаточно для переменной, которую вы собираетесь назначить. Обратите внимание, что вы на самом деле не заявляете gotsig
здесь, только old_handler
. Так что этого действительно достаточно:
void (*old_handler)(int);
Это приводит нас к правильному определению сигнала (3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs на помощь
К этому времени, я думаю, все согласятся, что это беспорядок. Иногда лучше назвать свои абстракции; часто, действительно. При правильном typedef
понимании это становится намного проще:
typedef void (*sig_t) (int);
Теперь ваша собственная переменная обработчика становится
sig_t old_handler, new_handler;
и ваша декларация для сигнала (3) становится просто
sig_t signal(int signo, sig_t handler);
что вдруг понятно. Избавление от * также избавляет от некоторых запутанных скобок (и они говорят, что парены всегда облегчают понимание - ха!). Ваше использование остается прежним:
old_handler = signal(SIGHUP, new_handler);
но теперь у вас есть шанс понять заявления для old_handler
, new_handler
и даже signal
когда вы впервые сталкиваетесь с ними или должны их написать.
Вывод
Оказывается, очень немногие программисты на Си способны самостоятельно разрабатывать правильные декларации для этих вещей, не обращаясь к справочным материалам.
Я знаю, потому что у нас когда-то был этот вопрос на наших собеседованиях для людей, выполняющих работу с ядром и драйвером устройства. :) Конечно, мы потеряли много кандидатов, потому что они разбились и сгорели на доске. Но мы также избегали нанимать людей, которые утверждали, что имели предыдущий опыт работы в этой области, но на самом деле не могли выполнить работу.
Однако из-за этой широко распространенной трудности, вероятно, не только разумно, но и разумно иметь способ обойти все эти декларации, которые больше не требуют, чтобы вы были программистом с тройным альфа-уровнем, сидящим на три сигмы выше среднего, просто чтобы использовать это вроде комфортно.
f :: (Int -> Int -> Int) -> Int -> Int