Ukazatele funkcí v programování C s příklady

⚡ Chytré shrnutí

Ukazatele funkcí v jazyce C ukládají adresu spustitelného kódu, což umožňuje programům volat funkce nepřímo, předávat je jako argumenty, vytvářet odesílací tabulky z polí a implementovat zpětná volání, jako je porovnávací rutina qsort, pro flexibilní a opakovaně použitelné návrhy.

  • 🔗 Prohlášení: Ukazatel funkce používá tvar return_type (*name)(arguments); závorky kolem názvu jsou povinné.
  • 🔄 Vyměnit podle adresy: Předávání adres proměnných parametrům ukazatele umožňuje funkci změnit původní hodnoty volajícího, jako v příkladu swap.
  • ???? Pole: Název pole je již ukazatel, takže funkce mohou pole přijímat nebo vracet ukazatel na statické pole.
  • 🎛️ Expediční stoly: Pole ukazatelů na funkce nahrazuje dlouhé řetězce switch nebo if přímým indexováním na zvolenou operaci.
  • 🧊 prázdné ukazatele: Parametr nebo návratový typ void * přijímá libovolný typ, který funkce před použitím přetypuje zpět.
  • 📞 Zpětná volání: Předání ukazatele na funkci jako argumentu pohání zpětná volání, jako je například porovnávací funkce qsort.
  • 🤖 Asistence AI: GitHub Copilot a AI assistants vytvářejí syntaxi ukazatelů funkcí, definice typů a signatury zpětných volání a poté označují neshodné parametry.

Ukazatele funkcí v jazyce C

Ukazatele dávají velké možnosti funkcím 'C', které jsme omezeni na vrácení jedné hodnoty. S parametry ukazatele mohou nyní naše funkce zpracovávat skutečná data spíše než kopie dat.

Aby bylo možné upravit skutečné hodnoty proměnných, předává volající příkaz adresy parametrům ukazatele ve funkci.

Funkce Ukazatele Příklad

Například následující program zamění dvě hodnoty po dvou:

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;}
}

Výstup:

m is 25, n is 100
m is 100, n is 25

Funkce Ukazatele Příklad

Program zamění skutečné hodnoty proměnných, protože funkce k nim přistupuje pomocí adresy ukazatel. Zde budeme diskutovat o průběhu programu:

  1. Deklarujeme funkci zodpovědnou za swapping dvě proměnné hodnoty, které přijímají dva celočíselné ukazatele jako parametry a při volání vracejí libovolnou hodnotu.
  2. Ve funkci main deklarujeme a inicializujeme dvě celočíselné proměnné ('m' a 'n'), poté vypíšeme jejich hodnoty.
  3. Funkci swap() zavoláme předáním adresy dvou proměnných jako argumentů pomocí symbolu ampersand. Poté vytiskneme nové prohozené hodnoty proměnných.
  4. Zde definujeme obsah funkce swap(), která bere jako parametry dvě adresy celočíselných proměnných a deklarujeme dočasnou celočíselnou proměnnou použitou jako třetí úložný box pro uložení jedné z hodnotových proměnných, která bude vložena do druhé proměnné.
  5. Uložte obsah první proměnné označené 'a' do dočasné proměnné.
  6. Uložte druhou proměnnou označenou b do první proměnné označené a.
  7. Aktualizujte druhou proměnnou (označenou b) o hodnotu první proměnné uložené v dočasné proměnné.

Funkce s parametry pole

V C nemůžeme předat funkci pole podle hodnoty. Zatímco název pole je ukazatel (adresa), takže pouze předáme název pole funkci, což znamená předat poli ukazatel.

Uvažujeme například následující program:

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);}

Výstup:

 Total summation is 471

Zde vysvětlíme programový kód s jeho podrobnostmi

Funkce s parametry pole

  1. Deklarujeme a definujeme funkci add_array(), která bere jako parametry adresu pole (ukazatel) s číslem prvku a vrací celkový souhrn těchto prvků. Ukazatel se používá k iteraci prvků pole (pomocí zápisu p[k]) a sumaci akumulujeme v lokální proměnné, která se vrátí po iteraci celého pole prvků.
  2. Deklarujeme a inicializujeme celočíselné pole s pěti celočíselnými prvky. Celkový součet vytiskneme předáním názvu pole (který funguje jako adresa) a velikosti pole add_array()nazývaná funkce jako argumenty.

Funkce, které vracejí pole

V C můžeme vrátit ukazatel na pole, jako v následujícím programu:

#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);}

Výstup:

1
2
3
4
5

A zde probereme detaily programu

Funkce, které vracejí pole

  1. Definujeme a deklarujeme funkci, která vrací adresu pole obsahující celočíselnou hodnotu a nebere žádné argumenty.
  2. Deklarujeme celočíselný ukazatel, který obdrží kompletní pole vytvořené po zavolání funkce a vytiskneme jeho obsah iterací celého pětiprvkového pole.

Všimněte si, že pro uložení adresy pole vrácené funkcí je definován ukazatel, nikoli pole. Všimněte si také, že když se z funkce vrací lokální proměnná, musíme ji ve funkci deklarovat jako statickou.

Ukazatele funkcí

Jak z definice víme, že ukazatele ukazují na adresu v libovolném paměťovém místě, mohou také ukazovat na začátek spustitelného kódu jako funkce v paměti.
Ukazatel na funkci je deklarován pomocí * , obecné prohlášení jeho deklarace je:

return_type (*function_name)(arguments)

Musíte si pamatovat, že závorky kolem (*název_funkce) jsou důležité, protože bez nich si kompilátor bude myslet, že název_funkce vrací ukazatel typu return_type.
Po definování ukazatele funkce jej musíme přiřadit funkci. Například následující program deklaruje běžnou funkci, definuje ukazatel na funkci, přiřadí ukazatel funkce k běžné funkci a poté funkci zavolá přes ukazatel:

#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");}

Výstup:

Hi
Hi
Hi

Ukazatele funkcí v C

  1. Definujeme a deklarujeme standardní funkci, která při volání funkce vypíše Hi text k krát indikovaný parametrem times
  2. Definujeme funkci ukazatele (s její speciální deklarací), která přebírá celočíselný parametr a nic nevrací.
  3. Naši funkci ukazatele inicializujeme funkcí Hi_function, což znamená, že ukazatel ukazuje na funkci Hi_function().
  4. Spíše než standardní volání funkce pomocí taping název funkce s argumenty, zavoláme pouze funkci ukazatele předáním čísla 3 jako argumentů a to je vše!

Mějte na paměti, že název funkce ukazuje na počáteční adresu spustitelného kódu jako název pole, který ukazuje na jeho první prvek. Proto jsou instrukce jako function_ptr = &Hi_function a (*funptr)(3) správné.
POZNÁMKA: Během přiřazování funkce a volání funkce není důležité vkládat operátor adresy & a operátor nepřímého směru *.

Pole funkčních ukazatelů

Řada ukazatelů funkcí může hrát roli přepínače nebo příkazu if pro rozhodování, jako v následujícím programu:

#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

Zde probíráme detaily programu:

Pole funkčních ukazatelů

  1. Deklarujeme a definujeme čtyři funkce které přijímají dva celočíselné argumenty a vracejí celočíselnou hodnotu. Tyto funkce sčítají, oddělujítract, vynásobte a vydělte dva argumenty týkající se toho, kterou funkci volá uživatel.
  2. Deklarujeme 4 celá čísla pro zpracování operandů, typu operace a výsledku. Také deklarujeme pole čtyř funkčních ukazatelů. Každý funkční ukazatel prvku pole přebírá dva celočíselné parametry a vrací celočíselnou hodnotu.
  3. Každý prvek pole přiřadíme a inicializujeme již deklarovanou funkcí. Například třetí prvek, který je třetím ukazatelem funkce, bude ukazovat na funkci operace násobení.
  4. Vyhledáváme operandy a typ operace od uživatele zadaného pomocí klávesnice.
  5. Zavolali jsme příslušný prvek pole (ukazatel funkce) s argumenty a výsledek vygenerovaný příslušnou funkcí uložíme.

Instrukce int (*ope[4])(int, int); definuje pole ukazatelů funkcí. Každý prvek pole musí mít stejné parametry a návratový typ.
Výsledek příkazu = ope[volba](x, y); spustí příslušnou funkci podle volby provedené uživatelem. Dvě zadaná celá čísla jsou argumenty předané funkci.

Funkce využívající void ukazatele

Během deklarace funkcí se používají ukazatele na neplatné. Pro vrácení jakéhokoli typu používáme povolení typu void *. Pokud předpokládáme, že se naše parametry při předání funkci nemění, deklarujeme ji jako konst.
Například:

 void * cube (const void *);

Zvažte následující program:

#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;}

Výsledek:

 4 cubed is 64

Zde probereme detaily programu:

Funkce využívající void ukazatele

  1. Definujeme a deklarujeme funkci, která vrací celočíselnou hodnotu a bere adresu neměnné proměnné bez konkrétního datového typu. Vypočítáme krychlovou hodnotu obsahové proměnné (x), na kterou ukazuje ukazatel num, a protože je to prázdný ukazatel, musíme ji zadat přetypovat na celočíselný datový typ pomocí specifického ukazatele zápisu (* datový typ) a vrátíme krychlová hodnota.
  2. Deklarujeme operand a výslednou proměnnou. Také inicializujeme náš operand s hodnotou „4“.
  3. Funkci kostky zavoláme předáním adresy operandu a návratovou hodnotu zpracujeme v proměnné result

Ukazatele funkcí jako argumenty

Další způsob, jak využít ukazatel funkce předáním jako argument jiné funkci, která se někdy nazývá „funkce zpětného volání“, protože přijímající funkce ji „zavolá zpět“.
V hlavičkovém souboru stdlib.h používá funkce Quicksort „qsort()“ tuto techniku, což je algoritmus určený pro třídění pole.

void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
  • void *base: void ukazatel na pole.
  • size_t num : Číslo prvku pole.
  • size_t width Velikost prvku.
  • int (*compare (const void *, const void *) : ukazatel funkce složený ze dvou argumentů a vrací 0, když mají argumenty stejnou hodnotu, <0, když arg1 přichází před arg2, a >0, když arg1 přichází za arg2.

Následující program třídí pole celých čísel od malého po velké číslo pomocí funkce 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;}

Výsledek:

 13 14 48 50 52

Zde probereme detaily programu:

Ukazatele funkcí jako argumenty

  1. Definujeme porovnávací funkci složenou ze dvou argumentů a vrací 0, když mají argumenty stejnou hodnotu, <0, když arg1 přichází před arg2, a >0, když arg1 je za arg2. Parametry jsou typ ukazatelů void přetypovaných na příslušný datový typ pole. (celé číslo)
  2. Definujeme a inicializujeme celočíselné pole Velikost pole je uložena v num a velikost každého prvku pole je uložena v proměnné šířky pomocí sizeof() predefined C operátor.
  3. Zavoláme funkci qsort a předáme jí název pole, jeho velikost, šířku a porovnávací funkci, kterou dříve definoval uživatel, abychom seřadili naše pole vzestupně. Porovnání se provede tak, že se v každé iteraci vezmou dva prvky pole, dokud nebude seřazeno celé pole.
  4. Vytiskneme prvky pole, abychom se ujistili, že naše pole je dobře seřazeno iterací celého pole pomocí pro smyčku.

Nejčastější dotazy

Ano. Ukazatel funkce musí přesně odpovídat návratovému typu a seznamu parametrů cílové funkce. Přiřazení funkce s jinou signaturou je nedefinované chování, i když se zkompiluje, protože volání by za běhu nesprávně odesílalo a četlo argumenty.

typedef dává komplexnímu ukazateli funkce krátký název. Zápis typedef int(*Operation)(int, int); umožňuje deklarovat Operapřidání; namísto opakování celé syntaxe ukazatele, což usnadňuje čtení polí, parametrů a členů struktury.

Ukazatele funkcí řídí zpětná volání, tabulky skoků nebo odesílání, systémy řízené pluginy a událostmi, stavové automaty a knihovní rutiny jako qsort a bsearch. OperaSystémy pro běh a vestavěný firmware se na ně spoléhají při výběru chování za běhu bez dlouhých podmíněných řetězců.

Ukazatel dat obsahuje adresu proměnné v paměti, zatímco ukazatel funkce obsahuje vstupní adresu spustitelného kódu. Voláním ukazatele funkce se funkce spustí, nikoli se pro čtení nebo zápis dat provádí její dereference.

Ano. Ukazatel funkce lze nastavit na NULL, což znamená, že žádná funkce není přiřazena. Volání ukazatele funkce s hodnotou NULL způsobí nedefinované chování, proto před voláním volání chraňte parametrem if (ptr != NULL), zejména v případě volitelných zpětných volání.

Ano. Umístění ukazatelů na funkce jako členů struktury umožňuje jedné struktuře sdružovat data se souvisejícími operacemi, napodobovat objekty a tabulky virtuálních metod. Tento vzorec je základem mnoha knihoven jazyka C a ovladačů zařízení, kde každá instance vybírá své vlastní implementace funkcí.

Ano. Asistent kódování s umělou inteligencí dokáže generovat deklarace ukazatelů funkcí, definice typů a signatury zpětných volání z příkazového řádku v jednoduchém jazyce, vysvětlovat matoucí syntaxi a identifikovat neshodné parametry nebo návratové typy. Vždy zkompilujte a otestujte navrhovaný kód, než mu budete důvěřovat.

GitHub Copilot navrhuje syntaxi ukazatelů funkcí, doplňuje těla zpětných volání z komentáře nebo signatury a navrhuje porovnávací funkce qsort. Otevřete příslušné hlavičkové soubory tak, aby jejich doplnění odpovídalo deklarovaným prototypům, a poté zkontrolujte logiku.

Shrňte tento příspěvek takto: