Указатели на функции в программировании на C с примерами
Указатели дают большие возможности функциям C, которые мы ограничены возвратом одного значения. Благодаря параметрам-указателям наши функции теперь могут обрабатывать реальные данные, а не их копии.
Чтобы изменить фактические значения переменных, вызывающий оператор передает адреса параметрам указателя в функции.
Пример указателей функций
Например, следующая программа меняет местами два значения по два:
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
Программа меняет местами фактические значения переменных, поскольку функция обращается к ним по адресу, используя указатель. Здесь мы обсудим процесс программы:
- Мы объявляем функцию, отвечающую за замену двух значений переменных, которая принимает в качестве параметров два целочисленных указателя и возвращает любое значение при вызове.
- В основной функции мы объявляем и инициализируем две целочисленные переменные («m» и «n»), а затем печатаем их значения соответственно.
- Мы вызываем функцию swap(), передавая адреса двух переменных в качестве аргументов, используя символ амперсанда. После этого мы печатаем новые замененные значения переменных.
- Здесь мы определяем содержимое функции swap(), которая принимает два адреса целочисленных переменных в качестве параметров и объявляет временную целочисленную переменную, используемую в качестве третьего ящика для хранения для сохранения одной из переменных значений, которые будут помещены во вторую переменную.
- Сохраните содержимое первой переменной, на которую указывает «а», во временную переменную.
- Сохраните вторую переменную, на которую указывает b, в первую переменную, на которую указывает a.
- Обновите вторую переменную (на которую указывает b) значением первой переменной, сохраненной во временной переменной.
Функции с параметрами массива
В C мы не можем передать массив по значению в функцию. Принимая во внимание, что имя массива является указателем (адресом), поэтому мы просто передаем имя массива функции, что означает передачу указателя на массив.
Например, рассмотрим следующую программу:
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; int k; for (k = 0; k < size; k++) { total += p[k]; /* it is equivalent to total +=*p ;p++; */} return (total);}
Вывод:
Total summation is 471
Здесь мы объясним программный код с его деталями.
- Мы объявляем и определяем функцию add_array(), которая принимает адрес массива (указатель) с количеством его элементов в качестве параметров и возвращает общую накопленную сумму этих элементов. Указатель используется для перебора элементов массива (с использованием нотации p[k]), а сумма суммируется в локальной переменной, которая будет возвращена после перебора всего массива элементов.
- Мы объявляем и инициализируем целочисленный массив пятью целочисленными элементами. Мы печатаем общую сумму, передавая имя массива (которое действует как адрес) и размер массива в add_array()вызываемая функция в качестве аргументов.
Функции, возвращающие массив
В C мы можем вернуть указатель на массив, как в следующей программе:
#include <stdio.h> int * build_array(); int main() { int *a; a = build_array(); /* get first 5 even numbers */ for (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 Declaration */ function_ptr = Hi_function; /* pointer assignment */ function_ptr (3); /* function call */ return 0;} void Hi_function (int times) { int k; for (k = 0; k < times; k++) printf("Hi\n");}
Вывод:
Hi Hi Hi
- Мы определяем и объявляем стандартную функцию, которая печатает приветственный текст k раз, указанный параметром, раз при вызове функции.
- Мы определяем функцию-указатель (со специальным объявлением), которая принимает целочисленный параметр и ничего не возвращает.
- Мы инициализируем нашу функцию указателя с помощью Hi_function, что означает, что указатель указывает на Hi_function().
- Вместо вызова стандартной функции путем записи имени функции с аргументами, мы вызываем только функцию-указатель, передавая число 3 в качестве аргумента, и все!
Имейте в виду, что имя функции указывает на начальный адрес исполняемого кода, как имя массива, указывающее на его первый элемент. Следовательно, такие инструкции, как function_ptr = &Hi_function и (*funptr)(3), являются правильными.
ПРИМЕЧАНИЕ. Необязательно вставлять оператор адреса & и оператор косвенного обращения * во время назначения и вызова функции.
Массив указателей на функции
Массив указателей на функции может играть роль переключателя или оператора if для принятия решения, как в следующей программе:
#include <stdio.h> int sum(int num1, int num2); int sub(int num1, int num2); int mult(int num1, int num2); int div(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] = div; 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 div(int x, int y) {if (y != 0) return (x / y); else return 0;}
Enter two integer numbers: 13 48 Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: 2 624
Здесь мы обсуждаем детали программы:
- Мы объявляем и определяем четыре Функции которые принимают два целочисленных аргумента и возвращают целочисленное значение. Эти функции складывают, вычитают, умножают и делят два аргумента относительно того, какая функция вызывается пользователем.
- Мы объявляем 4 целых числа для обработки операндов, типа операции и результата соответственно. Кроме того, мы объявляем массив из четырех указателей на функции. Каждый указатель функции элемента массива принимает два целочисленных параметра и возвращает целочисленное значение.
- Мы присваиваем и инициализируем каждый элемент массива уже объявленной функцией. Например, третий элемент, который является указателем третьей функции, будет указывать на функцию операции умножения.
- Мы ищем операнды и тип операции от пользователя, набираемого с клавиатуры.
- Мы вызвали соответствующий элемент массива (указатель на функцию) с аргументами и сохранили результат, сгенерированный соответствующей функцией.
Инструкция int (*ope[4])(int, int); определяет массив указателей на функции. Каждый элемент массива должен иметь одинаковые параметры и тип возвращаемого значения.
Результат оператора = ope[choice](x, y); запускает соответствующую функцию в соответствии с выбором пользователя. Два введенных целых числа являются аргументами, передаваемыми функции.
Функции, использующие указатели void
Пустые указатели используются во время объявлений функций. Мы используем тип возврата void *, позволяющий возвращать любой тип. Если мы предполагаем, что наши параметры не изменяются при переходе к функции, мы объявляем их как const.
Например:
void * cube (const void *);
Рассмотрим следующую программу:
#include <stdio.h> void* cube (const void* num); int main() { int x, cube_int; x = 4; cube_int = cube (&x); printf("%d cubed is %d\n", x, cube_int); return 0;} void* cube (const void *num) { int result; result = (*(int *)num) * (*(int *)num) * (*(int *)num); return result;}
Результат:
4 cubed is 64
Здесь мы обсудим детали программы:
- Мы определяем и объявляем функцию, которая возвращает целочисленное значение и принимает адрес неизменяемой переменной без определенного типа данных. Мы вычисляем кубическое значение переменной содержимого (x), на которую указывает указатель num, и, поскольку это указатель void, нам нужно привести ее к целочисленному типу данных, используя определенный указатель нотации (* datatype), и мы возвращаем стоимость куба.
- Мы объявляем операнд и переменную результата. Кроме того, мы инициализируем наш операнд значением «4».
- Мы вызываем функцию куба, передавая адрес операнда, и обрабатываем возвращаемое значение в переменной результата.
Указатели функций как аргументы
Другой способ использовать указатель на функцию, передав его в качестве аргумента другой функции, которую иногда называют «функцией обратного вызова», поскольку принимающая функция «вызывает ее обратно».
В заголовочном файле stdlib.h функция быстрой сортировки «qsort()» использует этот метод, который представляет собой алгоритм, предназначенный для сортировки массива.
void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
- void *base: недействительный указатель на массив.
- size_t num: номер элемента массива.
- size_t width Размер элемента.
- int (*compare (const void *, const void *) : указатель функции, состоящий из двух аргументов и возвращающий 0, если аргументы имеют одинаковое значение, <0, когда arg1 предшествует arg2, и >0, когда arg1 идет после arg2.
Следующая программа сортирует массив целых чисел от малого к большому с помощью функции qsort():
#include <stdio.h> #include <stdlib.h> int compare (const void *, const void *); int main() { int arr[5] = {52, 14, 50, 48, 13}; int num, width, i; num = sizeof(arr)/sizeof(arr[0]); width = sizeof(arr[0]); qsort((void *)arr, num, width, compare); for (i = 0; i < 5; i++) printf("%d ", arr[ i ]); return 0;} int compare (const void *elem1, const void *elem2) { if ((*(int *)elem1) == (*(int *)elem2)) return 0; else if ((*(int *)elem1) < (*(int *)elem2)) return -1; else return 1;}
Результат:
13 14 48 50 52
Здесь мы обсудим детали программы:
- Мы определяем функцию сравнения, состоящую из двух аргументов и возвращающую 0, когда аргументы имеют одинаковое значение, <0, когда arg1 предшествует arg2, и >0, когда arg1 идет после arg2. Параметры представляют собой тип указателей void, приведенный к соответствующему типу данных массива. (целое число)
- Мы определяем и инициализируем целочисленный массив. Размер массива хранится в Num переменная, а размер каждого элемента массива сохраняется в переменной ширины с использованием предопределенного sizeof(). C-оператор.
- Мы называем QSort функцию и передать имя массива, размер, ширину и функцию сравнения, определенную ранее пользователем, чтобы отсортировать наш массив в порядке возрастания. Сравнение будет осуществляться путем взятия на каждой итерации двух элементов массива, пока не будет отсортирован весь массив.
- Мы печатаем элементы массива, чтобы убедиться, что наш массив хорошо отсортирован, перебирая весь массив с помощью для цикла.