Указатель на функцию как создать

от admin

Указатель на функцию как создать

Указатель на функцию (function pointer) хранит адрес функции. По сути указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции.

Самым распространенным указателем на функцию является ее имя. С помощью имени функции можно вызывать ее и получать результат ее работы.

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

Здесь тип представляет тип возвращаемого функцией значения.

имя_указателя представляет произвольно выбранный идентификатор в соответствии с правилами о наименовании переменных.

И параметры определяют тип и название параметров через запятую при их наличии.

Например, определим указатель на функцию:

В данном случае определен указатель, который имеет имя message . Он может указывать на функции без параметров, которые возвращают тип void (то есть ничего не возвращают).

Используем указатель на функцию:

Указателю на функцию можно присвоить функцию, которая соответствует указателю по возвращаемому типу и спецификации параметров:

То есть в данном случае указатель message теперь хранит адрес функции hello. И посредством обращения к указателю мы можем вызвать эту функцию:

В качестве альтернативы мы можем обращаться к указателю на функцию следующим образом:

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

При определении указателя стоит обратить внимание на скобки вокруг имени. Так, использованное выше определение

НЕ будет аналогично следующему определению:

Во втором случае определен не указатель на функцию, а прототип функции message, которая возвращает указатель типа void* .

Рассмотрим еще один указатель на функцию:

Здесь actions представляет массив указателей на функции, каждая из которых обязательно должна принимать два параметра типа int и возвращать значение типа double .

C Language
Указатели функций

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

Синтаксис

returnType (* name) (параметры)

typedef returnType (* name) (параметры)

typedef returnType Name (параметры);
Имя * имя;

typedef returnType Name (параметры);
typedef Имя * NamePtr;

Назначение указателя функции

Возвращаемые указатели функций из функции

Лучшие практики

Использование typedef

Было бы удобно использовать typedef вместо объявления указателя функции каждый раз вручную.

Синтаксис объявления typedef для указателя функции:

Пример:

Положим, что у нас есть функция, sort , которая ожидает, что указатель функции на функцию compare , что:

compare — Функция сравнения для двух элементов, которая должна быть предоставлена ​​функции сортировки.

«сравнение», как ожидается, возвращает 0, если два элемента считаются равными, положительное значение, если первый переданный элемент является «большим» в некотором смысле, чем последний элемент, и в противном случае функция возвращает отрицательное значение (это означает, что первый элемент равен «меньше», чем последний).

Без typedef мы typedef бы указатель функции как аргумент функции следующим образом:

С typedef мы напишем:

и тогда мы могли бы изменить подпись функции sort :

оба определения sort будут принимать любую функцию вида

Указатели функций — это единственное место, где вы должны указать свойство указателя типа, например, не пытайтесь определить такие типы, как typedef struct something_struct *something_type . Это относится даже для структуры с членами , которые не предполагается доступ непосредственно API абонентов, например, stdio.h FILE типа (который , как вы уже заметите это не указатель).

Использование контекстных указателей.

Указатель функции должен почти всегда принимать предоставленный пользователем void * в качестве указателя контекста.

пример

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

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

Смотрите также

Вступление

Точно так же, как char и int , функция является фундаментальной особенностью C. Как таковая, вы можете объявить указатель на один: это означает, что вы можете передать какую функцию вызывать другой функции, чтобы помочь ей выполнить свою работу. Например, если у вас есть функция graph() которая отображает график, вы можете передать какую функцию графику в graph() .

использование

Таким образом, вышеприведенный код будет отображать любую функцию, которую вы передали в нее, — пока эта функция удовлетворяет определенным критериям: а именно, что вы передаете double код и получаете double . Существует много таких функций: sin() , cos() , tan() , exp() и т. Д., Но их много, например, graph() !

Синтаксис

Итак, как вы определяете, какие функции вы можете передать в graph() а какие из них вы не можете? Обычным способом является использование синтаксиса, который может быть нелегко прочитать или понять:

Вышеупомянутая проблема состоит в том, что одновременно можно определить две вещи: структуру функции и тот факт, что это указатель. Итак, разделите два определения! Но, используя typedef , может быть достигнут лучший синтаксис (легче читать и понимать).

Мнемоника для написания указателей функций

Все функции C находятся в указателях актуальности на месте в памяти программы, где существует некоторый код. Основное использование указателя функции заключается в предоставлении «обратного вызова» для других функций (или для имитации классов и объектов).

Синтаксис функции, определенный ниже на этой странице:

returnType (* name) (параметры)

Мнемоника для написания определения указателя функции — это следующая процедура:

  1. Начните с написания объявления нормальной функции: returnType name(parameters)
  2. Оберните имя функции с помощью синтаксиса указателя: returnType (*name)(parameters)

основы

Так же, как вы можете иметь указатель на int , char , float , array / string , struct и т. Д. — вы можете иметь указатель на функцию.

Объявление указателя принимает возвращаемое значение функции , имя функции и тип аргументов / параметров, которые она получает .

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

Вы можете объявить и инициализировать указатель на эту функцию:

Если у вас есть функция void, она может выглядеть так:

Тогда объявление указателя на него будет:

Доступ к самой функции потребует разыменования указателя:

Как видно из более сложных примеров в этом документе, объявление указателя на функцию может стать беспорядочным, если функции передано более нескольких параметров. Если у вас есть несколько указателей на функции, которые имеют идентичную структуру (тот же тип возвращаемого значения и параметры одного и того же типа), лучше всего использовать команду typedef, чтобы сэкономить вам некоторое написание и сделать код более понятным:

Вы также можете создать массив указателей функций . Если все указатели имеют одну и ту же «структуру»:

Вы можете узнать больше здесь и здесь .

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

Name already in use

hse-caos-2020 / 06-function-pointers / README.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Указатели на функции

При определении переменной её тип может дополняться так называемыми квалификаторами (qualifiers). Язык Си определяет четыре квалификатора const , volatile , restrict и _Atomic . Квалификаторы volatile , restrict и _Atomic будут рассмотрены позднее. Квалификаторы могут стоять на любом месте в спецификации типа: до имени типа, после имени типа, и даже в середине имён типов, состоящих из нескольких ключевых слов, например:

— все правильные спецификации типа и описывают один и тот же тип. Однако, предпочтительнее использовать const unsigned long .

Квалификатор const означает, что определяемый объект не может быть модифицирован, потому что например, находится в ПЗУ или в памяти, запись в которую запрещена операционной системой.

определяет переменную nproc типа int, которая не может быть модифицирована в данной единице компиляции. Переменная, описанная с квалификатором const , не может быть использована в константных выражениях, которые, в частности, задают количество элементов массива. Следующий фрагмент не является правильным в языке Си:

Правильный, хотя и не очень эстетичный способ определения констант периода компиляции в Си — ключевое слово enum :

Если квалификатор const используется для определения переменной-указателя, его семантика меняется в зависимости от того, где он расположен: до символа * или после него. Определение

вводит указатель ptr, который и сам может изменяться, и память по адресу, на который указывает данный указатель, также может быть изменена.

определяет указатель ptr на неизменяемую область памяти. Сам указатель может изменяться.

определяет неизменяемый указатель на изменяемую область памяти.

определяет неизменяемый указатель на неизменяемую область памяти. Из всех вышеперечисленных комбинаций чаще всего используется const char * . Такой тип, например, имеют параметры многих стандартных функций. Например, функция strcmp определена следующим образом:

Ключевое слово const здесь является частью контракта между функцией и ее окружением, оно показывает всем пользователям функции strcmp , что области памяти, на которые указывают s1 и s2 не будут модифицированы. Компилятор может расчитывать на это для выполнения оптимизаций, а программист может предполагать, что строки s1 и s2 не изменятся.

При передаче параметров и возврате значения не имеет смысла писать const char * const или const int , например,

Лишние const ничего не дают с точки зрения контракта функции, так как параметры при передаче в функцию копируются, как и возвращаемые значения при выходе из функции. Поэтому правильно писать:

Объявление переменных в Си/Си++ состоит из базового типа, идентификатора объявляемой переменной и разных модификаторов. Например,

На самом деле конструкции, модифицирующие тип, такие как указатели, массивы, функции комбинируются в синтаксической конструкции, называемой «декларатором». Таким образом, полное определение переменной выглядит следующим образом:

Декларатор содержит имя определяемого объекта, но в некоторых местах может быть «анонимным», то есть не содержащим имя определяемого объекта. Анонимные деклараторы допускаются в операции приведения типа и при описании формальных параметров в прототипах функций. Пример декларатора:

Анонимный декларатор может выглядеть следующим образом:

Такая (на первый взгляд «странная») форма определения производных типов на самом деле введена по аналогии с выражениями. Декларатор можно рассматривать как некоторое выражение над типом. В таком выражении есть три операции:

OP приоритет описание
[] постфиксная массив из заданного количества элементов
() постфиксная функция с заданными параметрами
* префиксная указатель
() группировка членов в выражении

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

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

decl описание
int a[3][4]; массив из 3 элементов типа массива из 4 элементов типа int (матрица 3 × 4 целых)
char **b; указатель на указатель на char
char *c[]; массив из неопределённого количества элементов типа указатель на тип char
int *d[10]; массив из 10 элементов типа указатель на тип int
int (*e)[10]; указатель на массив из 10 элементов типа int
int *f(); функция, возвращающая указатель на int
int (*g)(); указатель на функцию, возвращающую int
int *(*g)(); указатель на функцию, возвращающую указатель на int

Класс декларации typedef

Чтобы не нагромождать деклараторы и облегчить их чтение, введено специальное ключевое слово typedef . Оно записывается перед именем базового типа в декларации, например

В этом случае имя pint определяется как синоним для типа int * , то есть далее в определениях переменных это имя можно использовать наравне с именем базового типа, например

Конструкция typedef не вводит новый тип, а задаёт ещё одно имя для типа, которое может использоваться наравне со старым. Поэтому переменная pint a; и переменная int *b; имеют один и тот же тип int * .

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

после преобразования получаем

Указатели на функции

читается следующим образом: pfunc — это указатель на функцию, принимающую два параметра типа int и возвращающую значение типа int . То есть pfunc — это переменная указатель на функцию. С помощью typedef переменная может быть объявлена следующим образом:

Любая переменная-указатель на функцию может принимать значение NULL (или 0, или nullptr в Си++). Если переменная-указатель на функцию не равна NULL, она должна указывать на функцию, совместимую по передаваемым параметрам и возвращаемому значению.

Операция взятия адреса & , примененная к имени функции, дает значение типа указателя на функцию с соответствующим количеством и типом параметров и типом возвращаемого значения, например,

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

это то же самое, что

Поэтому, как правило, явную операцию взятия адреса перед именами функций не пишут.

Операция разыменования * , примененная к указателю NULL или к указателю, не указывающему на функцию, совместимую по передаваемым параметрам и возвращаемому значению, дает неопределенное поведение (undefined behavior). Как обычно, компилятор имеет право исходить из предположения, что в программе при ее выполнении никогда не возникает неопределённое поведение, и оптимизировать программу соответственно.

Если указатель на функцию корректен, то операция разыменования дает в результате значение (точнее, function designator), к которому применима операция вызова функции. Например,

Если указатель на функцию используется там, где требуется function designator, он неявно разыменовывается. Таким образом, явная операция * для вызова функции по указателю не нужна и часто опускается.

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

Использование указателей на функции

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

Функция qsort сортирует массив данных, расположенный в памяти по указателю base и содержащий nmemb элементов. Размер одного элемента задается параметром size . При выполнении сортировки для сравнения элементов массива будет вызываться функция сортировки, указатель на которую передается в параметре compar . Этой функции сортировки будут передаваться указатели на два элемента сортируемого массива, и функция сортировки должна вернуть 0, если элементы равны, отрицательное значение, если первый элемент массива меньше второго, и положительное значение, если первый элемент массива больше второго. Функция qsort может реализовывать нестабильную сортировку, то есть в отсортированном массиве не гарантируется, что равные элементы сохранят свой относительный порядок.

Предположим, что требуется отсортировать массив целых чисел в порядке неубывания.

Функция принимает параметр data , указывающий на начало сортируемого массива, и параметр count , содержащий число сортируемых элементов массива. Тогда вызов функции qsort может быть записан следующим образом:

Предположим, что в процессе сортировки потребовалось выполнить сравнение элементов data[i] и data[j], где i и j — некоторые индексы. Тогда будет вызвана функция сравнения, указатель на которую был передан в параметре sort_func , и ей в качестве аргументов будут переданы указатели на сравниваемые значения, то есть &data[i] и &data[j] . Функция сравнения может быть написана следующим образом:

При написании функции сравнения может возникнуть идея написать нужные типы аргументов в функции sort_func , а в вызов qsort добавить явное преобразование типа:

Это — плохая, нет, очень плохая идея! В этом случае явное преобразование типа в вызове qsort скроет возможное рассогласование между фактическими параметрами функции sort_func и параметрами, передаваемыми ей функцией qsort . Не делайте так!

Передача пользовательского контекста

Функция, которая передается в качестве аргумента в другую функцию для того, чтобы быть вызванной позже при наступлении какого-либо условия, называется функцией обратного вызова (callback function). Например, гипотетическая функция установки таймера может требовать функции, которая будет вызвана при истечении указанного интервала времени.

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

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

Поэтому, как правило, во всех функциях, которые принимают в качестве параметра указатель на функцию обратного вызова, принимается и дополнительный параметр user типа void * . Этот параметр без изменений передается в функцию обратного вызова.

Указатели на функции

К ак уже обсуждалось ранее функции – это набор команд, которые расположены в соответствующей области памяти. Вызов функции – это сохранение состояния, передача аргументов и переход по адресу, где располагается функция. В си есть возможность создавать указатели на функции. Указатели на функции позволяют упростить решение многих задач. Совместно с void указателями можно создавать функции общего назначения (например, сортировки и поиска). Указатели позволяют создавать функции высших порядков (функции, принимающие в качестве аргументов функции): отображение, свёртки, фильтры и пр. Указатели на функции позволяют уменьшать цикломатическую сложность программ, создавать легко масштабируемые конструкции. Рассмотрим пример. Создадим функцию и указатель на эту функцию

Синтаксис объявления указателей на функцию
<возвращаемый тип> (* <имя>)(<тип аргументов>);

В этом примере мы создали функцию отображения, которая применяет ко всем элементам массива функцию, которая передаётся ей в качестве аргумента. Когда мы вызываем функцию map, достаточно просто передавать имена функций (они подменяются указателями). Запишем теперь функцию map, которая получает в качестве аргумента массив типа void:

Вот где нам понадобились указатели типа void. Так как map получает указатель на функцию, то все функции должны иметь одинаковые аргументы и возвращать один и тот же тип. Но аргументы функций должны быть разного типа, поэтому мы делаем их типа void. Функция map получает указатель типа void (*)(void*), поэтому ей теперь можно передавать любую из четырёх функций.
Пример другой функции: функция filter получает указатель на массив и возвращает размер нового массива, оставляя в нём только те элементы, для которых переданный предикат возвращает логическую истину (предикат – функция, которая возвращает истину или ложь). Сначала напишем для массива типа int:

Теперь для массива типа void

Ещё одна функция – свёртка. Она получает в качестве аргументов массив и функцию от двух аргументов. Эта функция действует следующим образом: сначала она применяется к первым двум аргументам, затем она применяется к третьему аргументу и результату предыдущего вызова, затем к четвёртому аргументу и результату предыдущего вызова и т.д. С помощью свёртки можно, например, найти сумму всех элементов массива, максимальный элемент массива, факториал числа и т.п.

Последний пример: функция сортировки вставками для массива типа void. Так как тип массива не известен, то необходимо передавать функцию сравнения.

Массив указателей на функции

М ассив указателей на функции определяется точно также, как и обычный массив – с помощью квадратных скобок после имени:

Точно также можно было создать массив динамически

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

Ещё один пример: функция any возвращает 1, если в переданном массиве содержится хотя бы один элемент, удовлетворяющий условию pred и 0 в противном случае.

qsort и bsearch

В библиотеке stdlib си имеется несколько функций, которые получают в качестве аргументов указатели на функции. Функция qsort получает такие же аргументы, как и написанная нами функция insertionSort: массив типа void, размер массива, размер элемента и указатель на функцию сравнения. Давайте посмотрим простой пример — сортировка массива строк:

Функция bsearch проводит бинарный поиск в отсортированном массиве и получает указатель на функцию сравнения, такую же, как и функция qsort. В случае, если элемент найден, то она возвращает указатель на этот элемент, если элемент не найден, то NULL.

Всё ещё не понятно? – пиши вопросы на ящик

Читать:
Как добавить ярлык в пуск windows 10

Похожие статьи