Покажчики на функції в програмуванні на C із прикладами

⚡ Розумний підсумок

Вказівники на функції в C зберігають адресу виконуваного коду, дозволяючи програмам викликати функції опосередковано, передавати їх як аргументи, створювати таблиці відправлення з масивів та реалізовувати зворотні виклики, такі як процедура порівняння qsort, для гнучких, повторно використовуваних проектів.

  • 🔗 Декларація: Вказівник функції використовує форму return_type(*name)(arguments); дужки навколо назви обов'язкові.
  • 🔄 Обмін за адресою: Передавання адрес змінних параметрам-вказівникам дозволяє функції змінювати початкові значення викликаючої функції, як у прикладі з swap.
  • 📦 Масиви: Ім'я масиву вже є вказівником, тому функції можуть отримувати масив або повертати вказівник на статичний масив.
  • 🎛️ Таблиці відправлення: Масив вказівників на функції замінює довгі ланцюжки switch або if, індексуючи безпосередньо вибрану операцію.
  • 🧊 недійсні вказівники: Параметр void * або тип повернення приймає будь-який тип, який функція перетворює назад перед використанням.
  • 📞 Зворотні дзвінки: Передача вказівника на функцію як аргументу активує зворотні виклики, такі як функція порівняння qsort.
  • 🤖 Допомога AI: GitHub Copilot та AI assistants обробляють синтаксис вказівників функцій, typedef та сигнатури зворотних викликів, а потім позначають невідповідні параметри.

Вказівники функцій у 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. Оголошуємо функцію, відповідальну за swapping дві змінні значення, яка приймає два цілочисельні вказівники як параметри та повертає будь-яке значення під час виклику.
  2. У функції main ми оголошуємо та ініціалізуємо дві цілочисельні змінні ('m' і 'n'), а потім друкуємо їх значення відповідно.
  3. Ми викликаємо функцію swap(), передаючи адреси двох змінних як аргументи за допомогою символу амперсанда. Після цього ми друкуємо нові поміняні значення змінних.
  4. Тут ми визначаємо вміст функції swap(), яка приймає дві цілочисельні адреси змінних як параметри та оголошує тимчасову цілочисельну змінну, яка використовується як третій ящик для зберігання для збереження однієї зі змінних значень, яка буде поміщена до другої змінної.
  5. Збережіть вміст першої змінної, позначеної символом «a», у тимчасовій змінній.
  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. Ми визначаємо та оголошуємо стандартну функцію, яка друкує текст Hi k разів, указаний параметром разів, коли функція викликається
  2. Ми визначаємо покажчикову функцію (з її спеціальним оголошенням), яка приймає цілочисельний параметр і нічого не повертає.
  3. Ми ініціалізуємо нашу функцію покажчика Hi_function, що означає, що покажчик вказує на Hi_function().
  4. Замість стандартного виклику функції за допомогою taping ім'я функції з аргументами, ми викликаємо лише функцію-вказівник, передаючи число 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. Ми оголошуємо та визначаємо чотири Функції які приймають два цілочисельні аргументи та повертають ціле значення. Ці функції додають, підраховуютьtract, помножте та поділіть два аргументи щодо того, яку функцію викликає користувач.
  2. Ми оголошуємо 4 цілі числа для обробки операндів, типу операції та результату відповідно. Крім того, ми оголошуємо масив із чотирьох покажчиків на функції. Кожен покажчик на функцію елемента масиву приймає два цілих параметри та повертає ціле значення.
  3. Ми призначаємо та ініціалізуємо кожен елемент масиву вже оголошеною функцією. Наприклад, третій елемент, який є покажчиком третьої функції, вказуватиме на функцію операції множення.
  4. Ми шукаємо операнди та тип операції від користувача, введеного з клавіатури.
  5. Ми викликаємо відповідний елемент масиву (вказівник на функцію) з аргументами та зберігаємо результат, згенерований відповідною функцією.

Інструкція int (*ope[4])(int, int); визначає масив покажчиків на функції. Кожен елемент масиву повинен мати однакові параметри та тип повернення.
Результат оператора = ope[вибір](x, y); запускає відповідну функцію відповідно до вибору, зробленого користувачем Два введених цілих числа є аргументами, переданими функції.

Функції, що використовують покажчики void

Покажчики 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, ми повинні ввести його в цілочисельний тип даних, використовуючи вказівник на певну нотацію (* тип даних), і ми повертаємо значення куба.
  2. Ми оголошуємо операнд і змінну результату. Крім того, ми ініціалізуємо наш операнд значенням «4».
  3. Ми викликаємо функцію куба, передаючи адресу операнда, і обробляємо значення, що повертається, у змінній результату

Покажчики на функції як аргументи

Інший спосіб використання вказівника на функцію, передаючи його як аргумент іншій функції, яку іноді називають «функцією зворотного виклику», оскільки функція-одержувач «викликає її».
У файлі заголовка stdlib.h функція Quicksort “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. Ми друкуємо елементи масиву, щоб переконатися, що наш масив добре відсортовано, повторюючи весь масив за допомогою для петлі.

Поширені запитання

Так. Вказівник на функцію має точно відповідати типу повернення та списку параметрів цільової функції. Присвоєння функції з іншою сигнатурою є невизначеною поведінкою, навіть якщо вона компілюється, оскільки виклик неправильно надсилатиме та зчитуватиме аргументи під час виконання.

typedef надає складному вказівнику функції коротке ім'я. Запис typedef int(*Operation)(int, int); дозволяє оголосити Operaдодавання; замість повторення повного синтаксису вказівника, що значно спрощує читання масивів, параметрів та членів структури.

Вказівники на функції керують зворотними викликами, таблицями переходів або відправлення, системами, керованими плагінами та подіями, кінцевими автоматами та бібліотечними процедурами, такими як qsort та bsearch. OperaСистеми обробки даних та вбудоване програмне забезпечення покладаються на них для вибору поведінки під час виконання без довгих умовних ланцюжків.

Вказівник даних містить адресу змінної в пам'яті, тоді як вказівник функції містить адресу входу виконуваного коду. Ви викликаєте функцію через вказівник функції для її запуску, а не розіменовуєте її для читання або запису даних.

Так. Вказівник функції можна встановити на NULL, щоб не було призначено жодної функції. Виклик вказівника функції з NULL призводить до невизначеної поведінки, тому захистіть виклик за допомогою if (ptr != NULL) перед його викликом, особливо для необов'язкових зворотних викликів.

Так. Розміщення вказівників на функції як членів структури дозволяє одній структурі об'єднувати дані з пов'язаними операціями, імітуючи об'єкти та таблиці віртуальних методів. Цей шаблон лежить в основі багатьох бібліотек C та драйверів пристроїв, де кожен екземпляр вибирає власні реалізації функцій.

Так. Помічник зі штучного інтелекту може генерувати оголошення вказівників функцій, визначення типів та сигнатури зворотних викликів з командного рядка простою мовою, пояснювати заплутаний синтаксис та знаходити невідповідні параметри або типи повернення. Завжди компілюйте та тестуйте запропонований код, перш ніж довіряти йому.

Копілот GitHub пропонує синтаксис вказівника функцій, доповнює тіла зворотних викликів з коментаря або підпису та пропонує функції порівняння qsort. Відкрийте відповідні заголовкові файли, щоб їх доповнення відповідали вашим оголошеним прототипам, а потім перегляньте логіку.

Підсумуйте цей пост за допомогою: