Указатели на функции в программировании на 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

Пример указателей функций

Программа меняет местами фактические значения переменных, поскольку функция обращается к ним по адресу, используя указатель. Здесь мы обсудим процесс программы:

  1. Мы объявляем функцию, отвечающую за замену двух значений переменных, которая принимает в качестве параметров два целочисленных указателя и возвращает любое значение при вызове.
  2. В основной функции мы объявляем и инициализируем две целочисленные переменные («m» и «n»), а затем печатаем их значения соответственно.
  3. Мы вызываем функцию swap(), передавая адреса двух переменных в качестве аргументов, используя символ амперсанда. После этого мы печатаем новые замененные значения переменных.
  4. Здесь мы определяем содержимое функции swap(), которая принимает два адреса целочисленных переменных в качестве параметров и объявляет временную целочисленную переменную, используемую в качестве третьего ящика для хранения для сохранения одной из переменных значений, которые будут помещены во вторую переменную.
  5. Сохраните содержимое первой переменной, на которую указывает «а», во временную переменную.
  6. Сохраните вторую переменную, на которую указывает b, в первую переменную, на которую указывает a.
  7. Обновите вторую переменную (на которую указывает 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

Здесь мы объясним программный код с его деталями.

Функции с параметрами массива

  1. Мы объявляем и определяем функцию add_array(), которая принимает адрес массива (указатель) с количеством его элементов в качестве параметров и возвращает общую накопленную сумму этих элементов. Указатель используется для перебора элементов массива (с использованием нотации p[k]), а сумма суммируется в локальной переменной, которая будет возвращена после перебора всего массива элементов.
  2. Мы объявляем и инициализируем целочисленный массив пятью целочисленными элементами. Мы печатаем общую сумму, передавая имя массива (которое действует как адрес) и размер массива в 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

Здесь мы обсудим детали программы

Функции, возвращающие массив

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

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

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

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

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

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

  1. Мы определяем и объявляем стандартную функцию, которая печатает приветственный текст k раз, указанный параметром, раз при вызове функции.
  2. Мы определяем функцию-указатель (со специальным объявлением), которая принимает целочисленный параметр и ничего не возвращает.
  3. Мы инициализируем нашу функцию указателя с помощью Hi_function, что означает, что указатель указывает на Hi_function().
  4. Вместо вызова стандартной функции путем записи имени функции с аргументами, мы вызываем только функцию-указатель, передавая число 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

Здесь мы обсуждаем детали программы:

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

  1. Мы объявляем и определяем четыре Функции которые принимают два целочисленных аргумента и возвращают целочисленное значение. Эти функции складывают, вычитают, умножают и делят два аргумента относительно того, какая функция вызывается пользователем.
  2. Мы объявляем 4 целых числа для обработки операндов, типа операции и результата соответственно. Кроме того, мы объявляем массив из четырех указателей на функции. Каждый указатель функции элемента массива принимает два целочисленных параметра и возвращает целочисленное значение.
  3. Мы присваиваем и инициализируем каждый элемент массива уже объявленной функцией. Например, третий элемент, который является указателем третьей функции, будет указывать на функцию операции умножения.
  4. Мы ищем операнды и тип операции от пользователя, набираемого с клавиатуры.
  5. Мы вызвали соответствующий элемент массива (указатель на функцию) с аргументами и сохранили результат, сгенерированный соответствующей функцией.

Инструкция 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

Здесь мы обсудим детали программы:

Функции, использующие указатели void

  1. Мы определяем и объявляем функцию, которая возвращает целочисленное значение и принимает адрес неизменяемой переменной без определенного типа данных. Мы вычисляем кубическое значение переменной содержимого (x), на которую указывает указатель num, и, поскольку это указатель void, нам нужно привести ее к целочисленному типу данных, используя определенный указатель нотации (* datatype), и мы возвращаем стоимость куба.
  2. Мы объявляем операнд и переменную результата. Кроме того, мы инициализируем наш операнд значением «4».
  3. Мы вызываем функцию куба, передавая адрес операнда, и обрабатываем возвращаемое значение в переменной результата.

Указатели функций как аргументы

Другой способ использовать указатель на функцию, передав его в качестве аргумента другой функции, которую иногда называют «функцией обратного вызова», поскольку принимающая функция «вызывает ее обратно».
В заголовочном файле 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

Здесь мы обсудим детали программы:

Указатели функций как аргументы

  1. Мы определяем функцию сравнения, состоящую из двух аргументов и возвращающую 0, когда аргументы имеют одинаковое значение, <0, когда arg1 предшествует arg2, и >0, когда arg1 идет после arg2. Параметры представляют собой тип указателей void, приведенный к соответствующему типу данных массива. (целое число)
  2. Мы определяем и инициализируем целочисленный массив. Размер массива хранится в Num переменная, а размер каждого элемента массива сохраняется в переменной ширины с использованием предопределенного sizeof(). C-оператор.
  3. Мы называем QSort функцию и передать имя массива, размер, ширину и функцию сравнения, определенную ранее пользователем, чтобы отсортировать наш массив в порядке возрастания. Сравнение будет осуществляться путем взятия на каждой итерации двух элементов массива, пока не будет отсортирован весь массив.
  4. Мы печатаем элементы массива, чтобы убедиться, что наш массив хорошо отсортирован, перебирая весь массив с помощью для цикла.