Указатель на функцию как создать
Указатель на функцию (function pointer) хранит адрес функции. По сути указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции.
Самым распространенным указателем на функцию является ее имя. С помощью имени функции можно вызывать ее и получать результат ее работы.
Но также указатель на функцию мы можем определять в виде отдельной переменной с помощью следующего синтаксиса:
тип (*имя_указателя) (типы_параметров);
- тип представляет тип возвращаемого функцией значения.
- имя_указателя представляет произвольно выбранный идентификатор в соответствии с правилами о наименовании переменных.
- параметры определяют типы параметров через запятую (при их наличии).
Указатель может указывать только на такую функцию, которая имеет тот же возвращаемый тип и типы параметров, что и определение указателя на функцию.
Например, определим указатель на функцию:
void (*message) ();
В данном случае определен указатель, который имеет имя message . Он может указывать на функции без параметров, которые возвращают тип void (то есть ничего не возвращают).
Используем указатель на функцию:
#include void hello(); void goodbye(); int main() < void (*message)(); .// определение указателя на функцию message=hello; message(); message = goodbye; message(); >void hello() < std::cout void goodbye()
Указателю на функцию можно присвоить функцию, которая соответствует указателю по возвращаемому типу и спецификации параметров:
message=hello;
То есть в данном случае указатель message теперь хранит адрес функции hello. И посредством обращения к указателю мы можем вызвать эту функцию:
message();
В качестве альтернативы мы можем обращаться к указателю на функцию следующим образом:
(*message)();
Впоследствии мы можем присвоить указателю адрес другой функции, которая также соответствует определению указателя. В итоге результатом данной программы будет следующий вывод:
Hello, World Good Bye, World
При определении указателя стоит обратить внимание на скобки вокруг имени. Так, использованное выше определение
void (*message) ();
НЕ будет аналогично следующему определению:
void *message ();
Во втором случае определен не указатель на функцию, а прототип функции message, которая возвращает указатель типа void* .
Определение и инциализация указателя
Указатель можно при определении можно сразу инициализировать:
void (*message)() ; // указывает на функцию hello // или так void (*message2)() =hello; // указывает на функцию hello
Можно инициализировать значением nullptr :
void (*message)() < nullptr>;
Если указатель при определении инициализируется какой-либо функцией, то можно опустить все определение типа и просто использовать слово auto :
auto message < hello>; // указывает на функцию hello auto message2 = hello; // указывает на функцию hello
Можно подчеркнуть, что переменная является именно указателем, указав после auto символ звездочки:
auto* message < hello>;
Но особой разницы — что со звездочкой, что без звездочки нет.
Стоит отметить, что при присвоении функции мы можем применять операцию получения адреса:
auto message < &hello>;
Но в принципе применение такого символа, как и символа звездочки с auto, ни на что не влияет.
Указатель на функцию с параметрами
Рассмотрим еще один указатель на функцию:
#include int sum(int, int); int subtract(int, int); int main() < int a; int b; int (*operation)(int, int) ; // указатель operation указывает на функцию sum int result = operation(a, b); // result = (*operation)(a, b); // альтернативный вариант std::cout int, возвращающую также значение типа int. Соответственно мы можем присвоить указателю адреса функций add и subtract и вызвать их, передав при вызове указателю некоторые значения для параметров.Массивы указателей на функции
Кроме одиночных указателей на функции мы можем определять их массивы. Для этого используется следующий формальный синтаксис:
тип (*имя_массива[размер]) (параметры)double (*actions[]) (int, int)Здесь actions представляет массив указателей на функции, каждая из которых обязательно должна принимать два параметра типа int и возвращать значение типа double .
Посмотрим применение массива указателей на функции на примере:
#include void add(int, int); void subtract(int, int); void multiply(int, int); int main() < int a ; int b ; void (*operations[3])(int, int) = ; // получаем длину массива unsigned length = std::size(operations); for(unsigned i<>; i < length; i++) < operations[i](a, b); // вызов функции по указателю >> void add(int x, int y) < std::cout void subtract(int x, int y) < std::cout void multiply(int x, int y) < std::cout x + y = 15 x - y = 5 x * y = 50Указатель на функцию как создать
В языке программирования C функция тоже имеет адрес и может иметь указатель. Указатель на функцию представляет собой выражение или переменную, которые используются для представления адреса функции. Указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции.
Самым распространенным указателем на функцию является ее имя. С помощью имени функции мы можем вызывать ее и получать результат ее работы.
Но также указатель на функцию можно определять в виде отдельной переменной с помощью следующего синтаксиса:
тип (*имя_указателя) (типы_параметров);Здесь тип представляет тип возвращаемого функцией значения.
имя_указателя представляет произвольно выбранный идентификатор в соответствии с правилами о наименовании переменных.
После названия указателя в скобках идут через запятую типы параметров. Если функция не принимает параметров, то указывается void .
Например, определим указатель на функцию:
void (*message) (void);Здесь определен указатель, который имеет имя message . Он может указывать на функции без параметров, которые возвращают тип void (то есть ничего не возвращают). После названия указателя идет (void) , что указывает, что функция не принимает параметров.
Применим этот указатель на функцию:
#include void hello() < printf("Hello, World \n"); >void goodbye() < printf("Good Bye, World \n"); >int main(void) < // определяем указатель на функцию void (*message) (void); message=hello; // указатель указывает на функцию hello message(); // вызываем функцию, на которую указыывет указатель message = goodbye; // указатель указывает на функцию goodbye message(); // вызываем функцию, на которую указыывет указатель return 0; >Указателю на функцию можно присвоить функцию, которая соответствует указателю по возвращаемому типу и спецификации параметров:
message=hello;То есть в данном случае указатель message теперь хранит адрес функции hello. И посредством обращения к указателю мы можем вызвать эту функцию:
message();Впоследствии мы можем присвоит указателю адрес другой функции, как в данном случае. В итоге результатом данной программы будет следующий вывод:
Hello, World Good Bye, WorldПри определении указателя стоит обратить внимание на скобки вокруг имени. Так, использованное выше определение
void (*message) (void);НЕ будет аналогично следующему определению:
void *message (void);Во втором случае определен не указатель на функцию, а прототип функции message, которая возвращает указатель типа void* .
Следует учитывать, что указатель на функцию должен по типу возвращаемого значения и типу параметров соответствовать функции, иначе он не сможет соответствовать этой функции.
Рассмотрим еще один указатель на функцию, которая возвращает значение типа int и принимает два параметра типа int :
#include int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int main(void) < int a = 10; int b = 5; int result; int (*operation)(int, int); operation=add; result = operation(a, b); printf("result = %d \n", result); // result=15 operation = subtract; result = operation(a, b); printf("result = %d \n", result); // result=5 return 0; >Здесь определен указатель operation, который может указывать на функцию с двумя параметрами типа int , возвращающую также значение типа int . Соответственно мы можем присвоить указателю адреса функций add и subtract и вызвать их, передав при вызове в указатель нужные значения для параметров.
Массивы указателей на функции
Кроме одиночных указателей на функции мы можем определять их массивы. Для этого используется следующий формальный синтаксис:
тип (*имя_массива[размер]) (параметры)double (*actions[]) (int, int)Здесь actions представляет массив указателей на функции, каждая из которых обязательно должна принимать два параметра типа int и возвращать значение типа double .
Посмотрим применение массива указателей на функции на примере:
#include void add(int x, int y) < printf("x + y = %d \n", x + y); >void subtract(int x, int y) < printf("x + y = %d \n", x - y); >void multiply(int x, int y) < printf("x * y = %d \n", x * y); >int main(void) < int a = 10; int b = 5; void (*operations[3])(int, int) = ; // получаем длину массива int length = sizeof(operations)/sizeof(operations[0]); for(int i=0; i < length; i++) < operations[i](a, b); // вызов функции по указателю >return 0; >Здесь массив operations содержит три функции add, subtract и multiply, которые последовательно вызываются в цикле через перебор массива в функции main.
Документация
Можно создать указатели на функцию к именованным и анонимным функциям. Можно сохранить несколько указателей на функцию в массиве, и сохранить и загрузить их, когда вы были бы любая другая переменная.
Что такое указатель на функцию?
Указателем на функцию является MATLAB ® тип данных, который хранит ассоциацию к функции. Косвенно вызывание функции позволяет вам вызвать функцию независимо от того, откуда вы вызываете его. Типичное использование указателей на функцию включает:
- Передача функции к другой функции (часто названный функциональными функциями ). Например, передавая функцию интегрированию и оптимизационным функциям, таким как integral и fzero .
- Задавая функции обратного вызова (например, коллбэк, который отвечает на событие UI или взаимодействует с оборудованием сбора данных).
- Построение указателей на функции, определяемые встраивает вместо сохраненного в программном файле (анонимные функции).
- Вызывание локальных функций снаружи основной функции.
Вы видите если переменная, h , использование указателя на функцию isa (h,'function_handle') .
Создание Указателей на функции
Чтобы создать указатель для функции, предшествуйте имени функции с @ знак. Например, если у вас есть функция под названием myfunction , создайте указатель под названием f можно следующим образом:
f = @myfunction;
Вы вызываете функцию с помощью указателя тем же путем, вы вызываете функцию непосредственно. Например, предположите, что у вас есть функция с именем computeSquare , заданный как:
function y = computeSquare(x) y = x.^2; end
Создайте указатель и вызовите функцию, чтобы вычислить квадрат четыре.
f = @computeSquare; a = 4; b = f(a)
b = 16
Если функция не требует никаких входных параметров, то можно вызвать функцию с пустыми круглыми скобками, такой как
h = @ones; a = h()
a = 1
Без круглых скобок присвоение создает другой указатель на функцию.
a = h
a = @ones
Указатели на функцию являются переменными, которые можно передать другим функциям. Например, вычислите интеграл x 2 на области значений [0,1].
q = integral(f,0,1);
Указатели на функцию хранят свой абсолютный путь, поэтому когда у вас есть допустимый указатель, можно вызвать функцию от любого местоположения. Вы не должны задавать путь к функции при создании указателя, только имя функции.
Помните о следующем при создании указателей на функции:
- Назовите длину — Каждая часть имени функции (включая пакет и имена классов) должна быть меньше номера, заданного namelengthmax . В противном случае MATLAB обрезает последнюю часть имени.
- Осциллограф Функция должна быть в осциллографе в то время, когда вы создаете указатель. Поэтому функция должна быть на пути MATLAB или в текущей папке. Или для указателей на локальные или вложенные функции функция должна быть в текущем файле.
- Приоритет — Когда существует несколько функций с тем же именем, MATLAB, использует те же правила приоритета, чтобы задать указатели на функцию, как это делает, чтобы вызвать функции. Для получения дополнительной информации смотрите Порядок приоритета функций.
- Перегружаясь — Когда указатель на функцию вызывается с одним или несколькими аргументами, MATLAB определяет доминирующий аргумент. Если доминирующий аргумент является объектом, MATLAB определяет, имеет ли класс объекта метод, который перегружает то же имя как присоединенная функция указателя на функцию. Если это делает, то метод объекта вызывается вместо присоединенной функции.
Анонимные функции
Можно создать указатели на анонимные функции. Анонимной функцией является короткая основанная на выражении функция MATLAB, которая не требует программного файла. Создайте указатель на анонимную функцию путем определения тела функции, anonymous_function , и список, разделенный запятыми входных параметров к анонимной функции, arglist . Синтаксис:
h = @(arglist)anonymous_function
Например, создайте указатель, sqr , к анонимной функции, которая вычисляет квадрат номера и вызывает анонимную функцию с помощью ее указателя.
sqr = @(n) n.^2; x = sqr(3)
11.7 – Указатели на функции
В уроке «10.8 – Знакомство с указателями» вы узнали, что указатель – это переменная, которая содержит адрес другой переменной. Указатели на функции аналогичны, за исключением того, что они указывают не на переменные, а на функции!
Рассмотрим следующую функцию:
int foo()
Идентификатор foo – это имя функции. Но какого типа эта функция? Функции имеют свой собственный тип l-значения (l-value тип) – в данном случае тип функции, который возвращает целое число и не принимает параметров. Как и переменные, функции хранятся в памяти по назначенному адресу.
Когда функция вызывается (через оператор () ), выполнение переходит к адресу вызываемой функции:
int foo() // код для foo начинается с адреса памяти 0x002717f0 < return 5; >int main() < foo(); // переходим к адресу 0x002717f0 return 0; >
В какой-то момент вашей карьеры программиста вы, вероятно, сделаете простую ошибку (если еще ее не сделали):
#include int foo() // код начинается с адреса памяти 0x002717f0 < return 5; >int main() < std::cout
Вместо вызова функции foo() и печати возвращаемого значения мы непреднамеренно отправили функцию foo непосредственно в std::cout . Что происходит в этом случае?
На машине автора это напечатало:
0x002717f0
… но на вашем компьютере этот код может напечатать какое-то другое значение (например, 1), в зависимости от того, как ваш компилятор решит преобразовать указатель на функцию в другой тип для печати. Если ваш компьютер не печатает адрес функции, вы можете заставить его сделать это, преобразовав функцию в указатель void и распечатав его:
#include int foo() // код начинается с адреса памяти 0x002717f0 < return 5; >int main() < // Сообщаем C++, интерпретировать функцию foo как указатель void std::cout (foo)
Так же, как можно объявить неконстантный указатель на обычную переменную, также можно объявить неконстантный указатель на функцию. В оставшейся части этого урока мы рассмотрим эти указатели на функции и их использование. Указатели на функции – довольно сложная тема, и тем, кто ищет только основы C++, оставшуюся часть этого урока можно смело пропустить или просмотреть по диагонали.
Указатели на функции
Синтаксис для создания неконстантного указателя на функцию – одна из самых уродливых вещей, которые вы когда-либо видели в C++:
// fcnPtr - указатель на функцию, которая не принимает аргументов и возвращает int int (*fcnPtr)();
В приведенном выше фрагменте fcnPtr – это указатель на функцию, которая не имеет параметров и возвращает int . fcnPtr может указывать на любую функцию, соответствующую этому типу.
Скобки вокруг *fcnPtr необходимы из-за приоритета, поскольку int *fcnPtr() будет интерпретироваться как предварительное объявление для функции с именем fcnPtr , которая не принимает параметров и возвращает указатель на int .
Чтобы создать константный указатель на функцию, const идет после звездочки:
int (*const fcnPtr)();
Если вы поместите const перед int , это будет означать, что функция, на которую указывает указатель, возвращает const int .
Присваивание функции указателю на функцию
Указатели на функции могут быть инициализированы функцией (а неконстантным указателям на функции может быть присвоена функция). В приведенном выше примере мы использовали foo напрямую, и она была преобразована в указатель на функцию. Как и в случае с указателями на переменные, мы также можем использовать &foo для получения указателя на функцию foo . Для согласованности синтаксиса с указателями переменных и указателями на функции-члены, которые мы рассмотрим в следующем уроке, мы будем использовать синтаксис &foo , а не просто foo .
int foo() < return 5; >int goo() < return 6; >int main() < int (*fcnPtr)()< &foo >; // fcnPtr указывает на функцию foo fcnPtr = &goo; // fcnPtr теперь указывает на функцию goo return 0; >
Одна из распространенных ошибок – это сделать так:
fcnPtr = goo();
На самом деле это приведет к присвоению возвращаемого значения из вызова функции goo() переменной fcnPtr , а это не то, что мы хотим. Мы хотим, чтобы fcnPtr был присвоен адрес функции goo , а не возвращаемое значение из функции goo() . Поэтому скобки не нужны.
Обратите внимание, что тип (параметры и тип возвращаемого значения) указателя функции должен соответствовать типу функции. Вот несколько примеров этого:
// прототипы функций int foo(); double goo(); int hoo(int x); // присваивание указателям на функции int (*fcnPtr1)()< &foo >; // ok int (*fcnPtr2)()< &goo >; // неправильно - типы возвращаемых данных не совпадают double (*fcnPtr4)()< &goo >; // ok fcnPtr1 = &hoo; // неправильно - fcnPtr1 не имеет параметров, но hoo() имеет int (*fcnPtr3)(int)< &hoo >; // ok
В отличие от базовых типов, C++ при необходимости неявно преобразует функцию в указатель на функцию (поэтому для получения адреса функции вам не нужно использовать оператор адреса ( & )). Однако он не будет неявно преобразовывать указатели функций в обобщенные ( void ) указатели и наоборот.
Вызов функции с использованием указателя функции
Еще одна важная вещь, которую вы можете сделать с указателем на функцию, – это использовать его для фактического вызова функции. Есть два способа сделать это. Первый – через явное разыменование:
int foo(int x) < return x; >int main() < int (*fcnPtr)(int)< &foo >; // инициализируем fcnPtr функцией foo (*fcnPtr)(5); // вызов функции foo(5) через fcnPtr return 0; >
Второй способ – неявное разыменование:
int foo(int x) < return x; >int main() < int (*fcnPtr)(int)< &foo >; // инициализируем fcnPtr функцией foo fcnPtr(5); // вызов функции foo(5) через fcnPtr return 0; >
Как видите, метод неявного разыменования выглядит так же, как обычный вызов функции – чего и следовало ожидать, поскольку обычные имена функций в любом случае являются указателями на функции! Однако некоторые старые компиляторы не поддерживают метод неявного разыменования, но все современные компиляторы должны это делать.
Одно интересное замечание: параметры по умолчанию не работают для функций, вызываемых через указатели на функции. Параметры по умолчанию вычисляются во время компиляции (то есть, если вы не предоставляете аргумент для параметра по умолчанию, компилятор заменяет его вместо вас при компиляции кода). Однако указатели на функции вычисляются во время выполнения. Следовательно, параметры по умолчанию не могут быть вычислены при вызове функции с помощью указателя функции. В этом случае вам нужно будет явно передать значения для любых параметров по умолчанию.
Передача функций в качестве аргументов другим функциям
Одна из наиболее полезных вещей, которую можно сделать с указателями на функции, – это передать функцию в качестве аргумента другой функции. Функции, используемые в качестве аргументов для другой функции, иногда называют функциями обратного вызова (callback function).
Рассмотрим случай, когда вы пишете функцию для выполнения задачи (например, сортировки массива), но хотите, чтобы пользователь мог определить, как будет выполняться конкретная часть этой задачи (например, будет ли массив отсортирован в порядке возрастания или убывания). Давайте в качестве примера, который можно обобщить на другие аналогичные задачи, подробнее рассмотрим эту тему применительно к сортировке.
Многие алгоритмы сортировки, основанные на сравнении, работают по схожей концепции: алгоритм сортировки выполняет итерацию по списку чисел, выполняет сравнения пар чисел и меняет порядок чисел на основе результатов этих сравнений. Следовательно, изменяя сравнение, мы можем изменить способ сортировки алгоритма, не затрагивая остальную часть кода.
Вот наша процедура сортировки выбором из предыдущего урока:
#include // для std::swap void SelectionSort(int *array, int size) < // Пройдемся по каждому элементу массива for (int startIndex< 0 >; startIndex < (size - 1); ++startIndex) < // smallestIndex - это индекс самого маленького элемента, // с которым мы столкнулись до сих пор. int smallestIndex< startIndex >; // Ищем наименьший оставшийся элемент в массиве (начиная с startIndex+1) for (int currentIndex< startIndex + 1 >; currentIndex < size; ++currentIndex) < // Если текущий элемент меньше нашего ранее найденного наименьшего if (array[smallestIndex] >array[currentIndex]) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ < // Это новое наименьшее число для этой итерации smallestIndex = currentIndex; >> // Меняем местами наш начальный элемент с самым маленьким элементом std::swap(array[startIndex], array[smallestIndex]); > >
Давайте заменим это сравнение функцией для сравнения. Поскольку наша функция сравнения будет сравнивать два целых числа int и возвращать логическое значение, чтобы указать, следует ли поменять местами элементы, это будет выглядеть примерно так:
bool ascending(int x, int y) < return x >y; // поменять местами, если первый элемент больше второго >
А вот наша процедура сортировки выбором с использованием для сравнения функции ascending() :
#include // для std::swap void SelectionSort(int *array, int size) < // Пройдемся по каждому элементу массива for (int startIndex< 0 >; startIndex < (size - 1); ++startIndex) < // smallestIndex - это индекс самого маленького элемента, // с которым мы столкнулись до сих пор. int smallestIndex< startIndex >; // Ищем наименьший оставшийся элемент в массиве (начиная с startIndex+1) for (int currentIndex< startIndex + 1 >; currentIndex < size; ++currentIndex) < // Если текущий элемент меньше нашего ранее найденного наименьшего if (ascending(array[smallestIndex], array[currentIndex])) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ < // Это новое наименьшее число для этой итерации smallestIndex = currentIndex; >> // Меняем местами наш начальный элемент с самым маленьким элементом std::swap(array[startIndex], array[smallestIndex]); > >
Теперь, чтобы позволить вызывающему решать, как будет выполняться сортировка, вместо использования нашей собственной жестко запрограммированной функции сравнения мы разрешим вызывающему предоставить свою собственную функцию сортировки! Это делается с помощью указателя функции.
Поскольку функция сравнения вызывающего будет сравнивать два целых числа и возвращать логическое значение, указатель на такую функцию будет выглядеть примерно так:
bool (*comparisonFcn)(int, int);
Итак, мы позволим вызывающему передать нашей процедуре сортировки в качестве третьего параметра указатель на необходимую пользователю функцию сравнения, а затем мы воспользуемся этой функцией для сравнения.
Вот полный пример сортировки выбором, в которой используется параметр указателя функции для выполнения пользовательского сравнения, а также пример того, как ее вызвать:
#include // для std::swap #include // Обратите внимание, что наше пользовательское сравнение является третьим параметром void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int)) < // Пройдемся по каждому элементу массива for (int startIndex< 0 >; startIndex < (size - 1); ++startIndex) < // bestIndex - это индекс самого маленького / самого большого элемента, // с которым мы столкнулись до сих пор. int bestIndex< startIndex >; // Ищем самый маленький / самый большой элемент, оставшийся // в массиве (начиная с startIndex + 1)) for (int currentIndex< startIndex + 1 >; currentIndex < size; ++currentIndex) < // Если текущий элемент меньше/больше, чем наш ранее найденный if (comparisonFcn(array[bestIndex], array[currentIndex])) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ < // Это новое наименьшее/наибольшее число для этой итерации bestIndex = currentIndex; >> // Меняем местами наш начальный элемент с // самым маленьким / самым большим элементом std::swap(array[startIndex], array[bestIndex]); > > // Вот функция сравнения, которая сортирует по возрастанию // (Примечание: она точно такая же, как и предыдущая функция ascending()) bool ascending(int x, int y) < return x >y; // поменять местами, если первый элемент больше второго > // Вот функция сравнения, которая сортирует в порядке убывания bool descending(int x, int y) < return x < y; // поменять местами, если второй элемент больше первого >// Эта функция распечатывает значения в массиве void printArray(int *array, int size) < for (int index< 0 >; index < size; ++index) < std::cout std::cout int main() < int array[9]< 3, 7, 9, 5, 6, 1, 8, 2, 4 >; // Сортируем массив в порядке убывания с помощью функции descending() selectionSort(array, 9, descending); printArray(array, 9); // Сортируем массив в порядке возрастания с помощью функции ascending() selectionSort(array, 9, ascending); printArray(array, 9); return 0; >
Эта программа дает следующий результат:
9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9
Круто ведь? Мы дали вызывающему возможность контролировать, как наша сортировка выбором выполняет свою работу.
Вызывающий может даже определить свои собственные «странные» функции сравнения:
bool evensFirst(int x, int y) < // если x четное, а y нечетное, x идет первым (замена не требуется) if ((x % 2 == 0) && !(y % 2 == 0)) return false; // если x нечетное, а y четное, y идет первым (требуется замена) if (!(x % 2 == 0) && (y % 2 == 0)) return true; // в противном случае сортировать по возрастанию return ascending(x, y); >int main() < int array[9]< 3, 7, 9, 5, 6, 1, 8, 2, 4 >; selectionSort(array, 9, evensFirst); printArray(array, 9); return 0; >
Приведенный выше фрагмент дает следующий результат:
2 4 6 8 1 3 5 7 9
Как видите, использование указателя на функцию в этом контексте предоставляет прекрасный способ позволить вызывающему «привязать» свои собственные функции к чему-то, что вы ранее написали и протестировали, что помогает облегчить повторное использование кода! Раньше, если вы хотели отсортировать один массив в порядке убывания, а другой – в порядке возрастания, вам требовалось несколько версий процедуры сортировки. Теперь у вас может быть одна версия, которая может сортировать всё по желанию вызывающего!
Примечание. Если параметр функции относится к типу функции, он будет преобразован в указатель на тип функции. Это означает
void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int))
можно эквивалентно записать как:
void selectionSort(int *array, int size, bool comparisonFcn(int, int))
Это работает только для параметров функции, но не для указателей на автономные функции, и поэтому имеет несколько ограниченное использование.
Предоставление функций по умолчанию
Если вы собираетесь разрешить вызывающему передавать функцию в качестве параметра, часто бывает полезно предоставить вызывающему для его удобства какие-либо стандартные функции. Например, в приведенном выше примере сортировки выбором предоставление функций ascending() и descending() вместе с функцией selectionSort() упростило бы жизнь вызывающему, так как ему не пришлось бы переписывать ascending() или descending() каждый раз, когда он захочет их использовать.
Вы даже можете установить одну из них как параметр по умолчанию:
// По умолчанию сортировка по возрастанию void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int) = ascending);
В этом случае, пока пользователь просто вызывает selectionSort (не через указатель на функцию), параметр compareFcn по умолчанию будет ascending .
Делаем указатели на функции красивее с помощью псевдонимов типов
Посмотрим правде в глаза – синтаксис указателей на функции уродлив. Однако можно использовать псевдонимы типов, чтобы указатели на функции больше походили на обычные переменные:
using ValidateFunction = bool(*)(int, int);
Это определяет псевдоним типа с именем "ValidateFunction" , который является указателем на функцию, которая принимает два целых числа int и возвращает логическое значение.
Теперь вместо этого:
bool validate(int x, int y, bool (*fcnPtr)(int, int)); // уродливо
Вы можете сделать это:
bool validate(int x, int y, ValidateFunction pfcn) // чисто
Использование std::function
Альтернативный метод определения и хранения указателей на функции – использовать std::function , который является частью заголовка стандартной библиотеки. Чтобы определить указатель на функцию с помощью этого способа, объявите объект std::function следующим образом:
#include // метод std::function, который возвращает логическое значение // и принимает два параметра типа int bool validate(int x, int y, std::function fcn);
Как видите, и тип возвращаемого значения, и параметры заключены в угловые скобки, а параметры – внутри круглых скобок. Если параметров нет, круглые скобки можно оставить пустыми. Хотя это читается немного более подробно, оно также более явное, поскольку дает понять, какие ожидаются тип возвращаемого значения и параметры (тогда как способ с typedef скрывает их).
Обновление нашего предыдущего примера с помощью std::function :
#include #include int foo() < return 5; >int goo() < return 6; >int main() < // объявляем указатель на функцию, которая возвращает int // и не принимает параметров std::functionfcnPtr< &foo >; fcnPtr = &goo; // fcnPtr теперь указывает на функцию goo std::cout
Обратите внимание, что вы также можете ввести псевдоним std::function :
// псевдоним для чистого указателя на функцию using ValidateFunctionRaw = bool(*)(int, int); // псевдоним для std::function using ValidateFunction = std::function;
Вывод типа для указателей на функции
Подобно тому, как ключевое слово auto может использоваться для определения типа обычных переменных, оно также может определять тип указателя на функцию.
#include int foo(int x) < return x; >int main() < auto fcnPtr< &foo >; std::cout
Это работает точно так, как вы ожидаете, и синтаксис очень чистый. Обратной стороной является, конечно, то, что все подробности о типах параметров функции и типе возвращаемого значения скрыты, поэтому легче ошибиться при вызове функции или использовании ее возвращаемого значения.
Заключение
Указатели на функции полезны в первую очередь, когда вы хотите сохранить функции в массиве (или другой структуре) или когда вам нужно передать функцию другой функции. Поскольку собственный синтаксис для объявления указателей на функции уродлив и подвержен ошибкам, мы рекомендуем использовать std::function . В местах, где тип указателя функции используется только один раз (например, один параметр или возвращаемое значение), std::function может использоваться напрямую. В местах, где тип указателя на функцию используется несколько раз, лучшим выбором является псевдоним типа для std::function (чтобы не повторяться).
Небольшой тест
Вопрос 1
В этом тесте мы напишем версию нашего простейшего калькулятора, используя указатели на функции.
1a) Создайте короткую программу, предлагающую пользователю ввести два целых числа и математическую операцию ( '+' , '-' , '*' , '/' ). Убедитесь, что пользователь вводит допустимую операцию.
#include int getInteger() < std::cout ; std::cin >> x; return x; > char getOperation() < char op<>; do < std::cout > op; > while (op!='+' && op!='-' && op!='*' && op!='/'); return op; > int main() < int x< getInteger() >; char op< getOperation() >; int y< getInteger() >; return 0; >
1b) Напишите функции с именами add() , subtract() , multiply() и division() . Они должны принимать два параметра типа int и возвращать значение int .
int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int multiply(int x, int y) < return x * y; >int division(int x, int y) < return x / y; >
1c) Создайте псевдоним типа с именем ArithmeticFunction для указателя на функцию, которая принимает два параметра int и возвращает число int . Используйте std::function .
using ArithmeticFunction = std::function;
1d) Напишите функцию с именем getArithmeticFunction() , которая принимает символ оператора и возвращает соответствующую функцию как указатель на функцию.
ArithmeticFunction getArithmeticFunction(char op) < switch (op) < default: // по умолчанию будет add case '+': return &add; case '-': return &subtract; case '*': return &multiply; case '/': return &division; >>
1e) Измените функцию main() , чтобы она вызывала getArithmeticFunction() . Вызовите возвращаемое значение из этой функции с вашими входными данными и распечатайте результат.