Указатели на функции в Си.
Описание и пояснения по указателям на функции и переменные;
Решено написать эту маленькую заметку, ибо уже несколько раз мне задавали вопрос, что это и зачем. Помню и сам не знал этого, хотя вполне себе мог писать простенькие программы.
Что такое указатели и как их готовить вы наверное уже в курсе, если нет, то быстренько объясню.
Указатель - это переменная, которая содержит в себе адрес другой переменной, на которую указывает. Если проводить грубую аналогию с файловой системой в Windows, то указатель - это ярлык, а файл на который он указывает - переменная, размышляя дальше можно сказать что функция - это программа на компьютере, а ярлык для запуска программы - указатель на функцию.
Объявляются указатели следующим образом:
uint16_t *pointer_ui16 = &variable;
void *pointer_Void
char *str = "Строка";
Здесь в первой строке объявляется указатель на тип uint16_t
(целый без знаковый тип размером в 16 бит) и говорится что он ссылается на переменную variable
. Как уже заметили есть ещё оператор &
, он то как раз и возвращает адрес нашей переменной.
uint16_t variable = 0x1234; // переменная равна 0x1234 храниться по адресу 0x20001F00
uint16_t *pointer = &variable;
printf("%x", pointer); // Здесь будет выведен адрес переменной: 0x20001F00
printf("%x", *pointer); // Здесь будет выведено значение переменной: 0x1234
variable = 0x4678;
printf("%x", pointer); // снова 0x20001F00
printf("%x", *pointer); // а тут уже 0x4678
То есть, для доступа к данным на которые ссылается указатель необходимо написать знак *
. С этим вроде разобрались.
Теперь о функциях. С ними тоже не сильно сложно. Для начала пример объявления, рассмотрим всего 4:
void ( *func_name_1 )(); // 1
void ( *func_name_2 )( void ); // 2
void ( *func_name_3 )( uint8_t, int16_t)); // 3
uint16_t ( *func_name_4 )( uint8_t, int16_t); // 4
- указатель на функцию ничего не возвращающую и с неизвестным количеством и типом параметров;
- указатель на функцию ничего не возвращающую и ничего не принимающую;
- указатель на функцию ничего не возвращающую, но принимающую два аргумента типами
uint8_t
иint16_t
; - указатель на функцию возвращающую значение типа
uint16_t
и принимающую два аргумента типамиuint8_t
иint16_t
Главное не забыть окружить скобками указатель, иначе получится не указать на функцию, а функция возвращающая указатель:
int16_t *func_1 ( void ); // функция func_1 возвращающая указатель на тип int16_t
int16_t ( *func_2 )( void ); // указатель func_2 на функцию возвращающую тип int16_t
Принцип использования можно посмотреть в примере:
// функция которая находит абсолютное значение
uint16_t usr_ABS(int16_t value) { return (value > 0) ? (value) : (-value); }
// функция которая возводит в квадрат
uint16_t usr_POW2(int16_t value) { return value*value; }
uint16_t (*func)(int16_t); // Указатель
int main ( void )
{
// Присваиваем указателю адрес на функцию usr_ABS
// Кстати говоря можно делать и так func = usr_ABS,
// так как просто имя функции и будет означать её адрес
func = &usr_ABS;
printf("%d", usr_ABS(-400)); // выводит 400 использую функцию
printf("%d", (*func)(-400)); // выводит 400 использую указатель
func = &usr_POW2; // а теперь указывает на usr_POW2
printf("%d", usr_ABS(30)); // выводит 30
printf("%d", (*func)(30)); // выводит 900
}
Получается что мы динамически изменили логику программы. Согласитесь не плохо и довольно интересно.
Как это применять вам подскажет логика и собственное чувство вкуса, самое главное теперь понятно как оно работает.