Я всегда был немного озадачен, когда читал код других людей, в котором были typedef для указателей на функции с аргументами. Я помню, что мне потребовалось некоторое время, чтобы обойти такое определение, пытаясь понять численный алгоритм, написанный на C некоторое время назад. Итак, не могли бы вы поделиться своими советами и мыслями о том, как написать хорошие определения типа для указателей на функции (что можно и нельзя), о том, почему они полезны и как понимать работу других? Спасибо!
237
Ответы:
Рассмотрим
signal()
функцию из стандарта C:Совершенно очевидно - это функция, которая принимает два аргумента, целое число и указатель на функцию, которая принимает целое число в качестве аргумента и ничего не возвращает, и она (
signal()
) возвращает указатель на функцию, которая принимает целое число в качестве аргумента и возвращает ничего.Если вы напишите:
тогда вы можете вместо этого объявить
signal()
как:Это означает то же самое, но обычно считается более легким для чтения. Понятно, что функция принимает a
int
и aSignalHandler
и возвращает aSignalHandler
.К этому нужно привыкнуть. Единственное, что вы не можете сделать, это написать функцию-обработчик сигнала, используя
SignalHandler
typedef
определение функции.Я все еще из старой школы, которая предпочитает вызывать указатель функции как:
Современный синтаксис использует только:
Я понимаю, почему это работает - я просто предпочитаю знать, что мне нужно искать место инициализации переменной, а не вызываемую функцию
functionpointer
.Сэм прокомментировал:
Давай еще раз попробуем. Первый из них взят прямо из стандарта C - я набрал его заново и проверил, что у меня были правильные скобки (пока я не исправил это - это сложный файл cookie, чтобы запомнить).
Прежде всего, помните, что
typedef
вводит псевдоним для типа. Итак, псевдоним естьSignalHandler
, а его тип:Часть «ничего не возвращает» пишется
void
; целочисленный аргумент (я верю) не требует пояснений. Следующая запись - это просто (или нет) то, как C передает указатель на функцию, принимая аргументы, как указано, и возвращая данный тип:После создания типа обработчика сигнала я могу использовать его для объявления переменных и так далее. Например:
Обратите внимание, как избежать использования
printf()
в обработчике сигнала?Итак, что мы сделали здесь - кроме пропуска 4 стандартных заголовков, которые понадобятся для корректной компиляции кода?
Первые две функции - это функции, которые принимают одно целое число и ничего не возвращают. Один из них на самом деле не возвращается вообще,
exit(1);
но другой возвращается после печати сообщения. Имейте в виду, что стандарт C не позволяет вам делать очень многое в обработчике сигналов; POSIX немного более щедр в том, что разрешено, но официально не разрешает звонитьfprintf()
. Я также распечатываю номер сигнала, который был получен. Вalarm_handler()
функции значение всегда будетSIGALRM
таким, поскольку это единственный сигнал, для которого он является обработчиком, ноsignal_handler()
может получитьSIGINT
илиSIGQUIT
в качестве номера сигнала, потому что одна и та же функция используется для обоих.Затем я создаю массив структур, где каждый элемент идентифицирует номер сигнала и устанавливает обработчик для этого сигнала. Я решил беспокоиться о 3 сигналах; Я часто беспокоиться о том
SIGHUP
,SIGPIPE
иSIGTERM
тоже , и о них определены ли (#ifdef
условной компиляции), но это только усложняет ситуацию. Возможно, я бы также использовал POSIXsigaction()
вместо этогоsignal()
, но это другая проблема; давайте придерживаться того, с чего мы начали.В
main()
функции перебирает список обработчиков , которые будут установлены. Для каждого обработчика он сначала вызывает,signal()
чтобы выяснить, игнорирует ли процесс в настоящее время сигнал, и при этом устанавливаетсяSIG_IGN
как обработчик, который гарантирует, что сигнал остается проигнорированным. Если сигнал ранее не игнорировался, онsignal()
снова вызывает , на этот раз, чтобы установить предпочтительный обработчик сигнала. (ПредположительноSIG_DFL
, другим значением является обработчик сигнала по умолчанию для сигнала.) Поскольку первый вызов метода signal () устанавливает обработчикSIG_IGN
иsignal()
возвращает предыдущий обработчик ошибок, значениеold
послеif
оператора должно бытьSIG_IGN
- отсюда и утверждение. (Ну, это может бытьSIG_ERR
если что-то пойдет не так, как надо - но тогда я узнаю об этом из увольнения.)Затем программа делает свое дело и выходит нормально.
Обратите внимание, что имя функции можно рассматривать как указатель на функцию соответствующего типа. Когда вы не применяете скобки вызова функции - как, например, в инициализаторах - имя функции становится указателем на функцию. Именно поэтому разумно вызывать функции через
pointertofunction(arg1, arg2)
нотацию; когда вы видитеalarm_handler(1)
, вы можете считать, чтоalarm_handler
это указатель на функцию и, следовательноalarm_handler(1)
, это вызов функции через указатель на функцию.Итак, до сих пор я показал, что
SignalHandler
переменная относительно проста в использовании, если у вас есть некоторые из правильных типов значений для ее присвоения - это то, что обеспечивают две функции обработчика сигналов.Теперь вернемся к вопросу - как две декларации
signal()
относятся друг к другу.Давайте рассмотрим второе объявление:
Если мы изменили имя функции и тип следующим образом:
у вас не возникло бы проблем с интерпретацией этого как функции, которая принимает аргументы
int
a и adouble
и возвращаетdouble
значение (не так ли? Может быть, вам лучше не признаваться, если это проблематично - но, возможно, вам следует быть осторожным, задавая вопросы так сложно как этот, если это проблема).Теперь, вместо того, чтобы быть
double
,signal()
функция принимает вSignalHandler
качестве второго аргумента, и она возвращает один в качестве результата.Механика, с помощью которой это также можно рассматривать как:
сложно объяснить - так что я, вероятно, облажаться. На этот раз я дал имена параметров - хотя имена не являются критическими.
В общем, в C механизм объявления таков, что если вы напишите:
тогда, когда вы пишете,
var
это представляет значение данногоtype
. Например:В стандарте
typedef
трактуется как класс хранения в грамматике, скорее какstatic
иextern
классы хранения.означает, что когда вы видите переменную типа
SignalHandler
(скажем, alarm_handler), вызываемую как:результат есть
type void
- нет результата. И(*alarm_handler)(-1);
является вызовомalarm_handler()
с аргументом-1
.Итак, если мы объявили:
это означает, что:
представляет пустое значение. И поэтому:
эквивалентно. Теперь
signal()
он более сложный, потому что он не только возвращает aSignalHandler
, он также принимает вSignalHandler
качестве аргументов int и a :Если это все еще смущает вас, я не уверен, как помочь - это все еще на некоторых уровнях загадочно для меня, но я привык к тому, как это работает, и поэтому могу сказать вам, что если вы будете придерживаться этого еще 25 лет или так, это станет вашей второй натурой (и, может быть, даже немного быстрее, если вы умны).
источник
extern void (*signal(int, void(*)(int)))(int);
означает , чтоsignal(int, void(*)(int))
функция возвращает указатель на функцию кvoid f(int)
. Когда вы хотите указать указатель функции в качестве возвращаемого значения , синтаксис усложняется. Вы должны поместить тип возвращаемого значения слева и список аргументов справа , в то время как это середина, которую вы определяете. И в этом случае самаsignal()
функция принимает указатель функции в качестве параметра, что еще больше усложняет ситуацию. Хорошая новость в том, что если вы можете прочитать это, Сила уже с вами. :).&
перед именем функции? Это совершенно не нужно; даже бессмысленно. И точно не "старая школа". Старая школа использует имя функции просто и понятно.Указатель на функцию похож на любой другой указатель, но он указывает на адрес функции, а не на адрес данных (в куче или стеке). Как и любой указатель, он должен быть набран правильно. Функции определяются их возвращаемым значением и типами параметров, которые они принимают. Таким образом, чтобы полностью описать функцию, вы должны включить ее возвращаемое значение и тип каждого параметра принимает. Когда вы вводите определение такого определения, вы присваиваете ему «понятное имя», которое облегчает создание и ссылку на указатели с использованием этого определения.
Например, предположим, у вас есть функция:
тогда следующий typedef:
может использоваться для указания на эту
doMulitplication
функцию. Это просто определение указателя на функцию, которая возвращает float и принимает два параметра, каждый из которых имеет тип float. Это определение имеет понятное названиеpt2Func
. Обратите внимание, что этоpt2Func
может указывать на ЛЮБУЮ функцию, которая возвращает число с плавающей точкой и принимает 2 числа с плавающей точкой.Таким образом, вы можете создать указатель, который указывает на функцию doMultiplication следующим образом:
и вы можете вызвать функцию, используя этот указатель следующим образом:
Это делает хорошее чтение: http://www.newty.de/fpt/index.html
источник
pt2Func myFnPtr = &doMultiplication;
вместо того , чтобы,pt2Func *myFnPtr = &doMultiplication;
какmyFnPtr
это уже указатель.myFunPtr
уже указатель на функцию, так что используйтеpt2Func myFnPtr = &doMultiplication;
Очень простой способ понять typedef указателя на функцию:
источник
cdecl
отличный инструмент для расшифровки странного синтаксиса, такого как объявления указателей на функции. Вы также можете использовать его для генерации.Что касается советов по упрощению разбора сложных объявлений для последующего обслуживания (самим или другими), я рекомендую создавать
typedef
небольшие куски и использовать эти маленькие кусочки в качестве строительных блоков для больших и более сложных выражений. Например:скорее, чем:
cdecl
может помочь вам с этим материалом:И (фактически) именно так я и создал этот безумный беспорядок наверху.
источник
Выход этого:
22
6
Обратите внимание, что один и тот же определитель math_func был использован для объявления обеих функций.
Тот же подход typedef может быть использован для extern struct. (Используя sturuct в другом файле.)
источник
Используйте typedefs для определения более сложных типов, т.е. указателей на функции
Я возьму пример определения конечного автомата в C
Теперь мы определили тип с именем action_handler, который принимает два указателя и возвращает int
определите свой конечный автомат
Указатель функции на действие выглядит как простой тип, а typedef в первую очередь служит этой цели.
Все мои обработчики событий теперь должны соответствовать типу, определенному action_handler
Ссылки:
Эксперт C программирование от Linden
источник
Это простейший пример указателей функций и массивов указателей функций, которые я написал в качестве упражнения.
источник