Указатели на функции в C: практические примеры и лучшие практики
Указатели на функции раскрывают мощные возможности в C, позволяя функциям манипулировать данными напрямую через адреса памяти вместо копирования значений. Передавая адреса указателям-параметрам, функция может изменять исходные переменные, реализуя паттерны вроде обмена на месте, динамических обратных вызовов и функций высшего порядка.
- Пример указателей на функции
- Функции с параметрами-массивами
- Функции, возвращающие массив
- Указатели на функции
- Массив указателей на функции
- Функции, использующие void-указатели
- Указатели на функции как аргументы
Пример указателей на функции
Классическая процедура swap демонстрирует, как функция может обменять значения двух переменных, оперируя их адресами.
void swap (int *a, int *b); int main() { int m = 25; int n = 100; printf("m is %d, n is %d\n", m, n); swap(&m, &n); printf("m is %d, n is %d\n", m, n); return 0; } void swap (int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; }
Вывод:
m is 25, n is 100 m is 100, n is 25

- Объявите
swapс двумя целочисленными указателями. - Инициализируйте
mиnвmainи выведите их. - Вызовите
swap(&m, &n)для передачи адресов. - Внутри
swapиспользуйте временную переменную для обмена значениями. - Разыменуйте указатели для чтения/записи фактических данных.
Функции с параметрами-массивами
Массивы распадаются в указатели при передаче в функции. Следующая программа суммирует элементы массива, итерируясь по указателю.
int add_array (int *a, int num_elements); int main() { int Tab[5] = {100, 220, 37, 16, 98}; printf("Total summation is %d\n", add_array(Tab, 5)); return 0; } int add_array (int *p, int size) { int total = 0; for (int k = 0; k < size; k++) { total += p[k]; /* эквивалентно total += *p; p++; */ } return total; }
Вывод:
Total summation is 471

- Определите
add_arrayдля принятия указателя и размера, возвращающей сумму. - Передайте имя массива
Tab(распавшийся в указатель) и его длину в функцию.
Функции, возвращающие массив
Функция может вернуть указатель на статический массив. Этот подход полезен, когда вызывающему нужен временный массив без управления выделением памяти.
#include <stdio.h> int * build_array(); int main() { int *a; a = build_array(); /* получить первые 5 четных чисел */ for (int k = 0; k < 5; k++) printf("%d\n", a[k]); return 0; } int * build_array() { static int Tab[5] = {1,2,3,4,5}; return Tab; }
Вывод:
1 2 3 4 5

- Объявите функцию, возвращающую указатель на статический массив.
- Используйте возвращенный указатель для итерации и печати содержимого массива.
Указатели на функции
Указатель на функцию хранит адрес точки входа функции, позволяя динамическую диспетчеризацию и обратные вызовы. Синтаксис объявления требует скобок вокруг звездочки, чтобы избежать неправильной интерпретации.
return_type (*function_name)(arguments)
Пример: вызов процедуры через указатель.
#include <stdio.h> void Hi_function (int times); /* function */ int main() { void (*function_ptr)(int); /* function pointer */ function_ptr = Hi_function; /* assignment */ function_ptr(3); /* invocation */ return 0; } void Hi_function (int times) { for (int k = 0; k < times; k++) printf("Hi\n"); }
Вывод:
Hi Hi Hi

- Определите обычную функцию.
- Объявите указатель на функцию с правильной сигнатурой.
- Присвойте указателю имя функции.
- Вызовите функцию через указатель.
Массив указателей на функции
Хранение нескольких указателей на функции в массиве позволяет табличный подход, похожий на оператор switch. Следующая программа демонстрирует простой калькулятор.
#include <stdio.h> int sum(int num1, int num2); int sub(int num1, int num2); int mult(int num1, int num2); int divi(int num1, int num2); int main() { int x, y, choice, result; int (*ope[4])(int, int); ope[0] = sum; ope[1] = sub; ope[2] = mult; ope[3] = divi; printf("Enter two integer numbers: "); scanf("%d%d", &x, &y); printf("Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: "); scanf("%d", &choice); result = ope[choice](x, y); printf("%d", result); return 0; } int sum(int x, int y) {return x + y;} int sub(int x, int y) {return x - y;} int mult(int x, int y) {return x * y;} int divi(int x, int y) {return y != 0 ? x / y : 0;}
Результат: Ввод 13 48 2 → 624

- Определите четыре арифметические функции.
- Создайте массив из четырех указателей на функции с совпадающими сигнатурами.
- Присвойте каждый указатель соответствующей функции.
- Получите выбор пользователя и вызовите выбранную функцию.
Функции, использующие void-указатели
Void-указатели предоставляют тип-агностический доступ к данным. Используемые в параметрах функций, они позволяют подпрограмме принимать любой тип данных, при условии, что вызывающий предоставляет правильное приведение.
void * cube (const void *);
Пример программы:
#include <stdio.h> void* cube (const void* num); int main() { int x = 4; int cube_int = (int)cube(&x); printf("%d cubed is %d\n", x, cube_int); return 0; } void* cube (const void *num) { int result = (*(int *)num) * (*(int *)num) * (*(int *)num); return (void*)result; }
Результат: 4 cubed is 64

- Определите функцию, принимающую const void указатель и возвращающую void указатель.
- Приведите указатель к
int *для доступа к значению. - Вычислите куб и приведите результат обратно к
void *для возврата.
Указатели на функции как аргументы
Передача указателя на функцию другой функции — распространенный паттерн для обратных вызовов. Стандартная библиотека qsort использует эту технику для сортировки массивов произвольных типов.
void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *));
Пример сортировки целочисленного массива:
#include <stdio.h> #include <stdlib.h> int compare (const void *, const void *); int main() { int arr[5] = {52, 14, 50, 48, 13}; size_t num = sizeof(arr)/sizeof(arr[0]); size_t width = sizeof(arr[0]); qsort((void *)arr, num, width, compare); for (size_t i = 0; i < num; i++) printf("%d ", arr[i]); return 0; } int compare (const void *elem1, const void *elem2) { int a = *(int *)elem1; int b = *(int *)elem2; return (a - b); }
Результат: 13 14 48 50 52

- Реализуйте
compareдля возврата отрицательного, нуля или положительного на основе двух целых чисел. - Определите размер массива и ширину элемента с помощью
sizeof. - Вызовите
qsortс массивом, количеством, шириной и функцией сравнения. - Выведите отсортированный массив для проверки операции.
Язык C
- Четыре проверенных паттерна пользовательских функций в C
- Цикл do-while в C++: синтаксис, примеры и вложенные циклы
- Указатели в C++: концепции, примеры и практические применения
- Перегрузка операторов в C++: практическое руководство с примерами кода
- Функции в C++: подробное объяснение с практическими примерами кода
- Коллекции в C#: практическое руководство с реальными примерами
- Указатели в C: полное руководство по типам, использованию и лучшим практикам
- Мастерство метода str.count() в Python: как подсчитывать символы и подстроки с примерами
- Освоение лямбда-функций Python: практические примеры и лучшие практики
- Функция round() в Python: подробное объяснение с практическими примерами