V jazyce C existuje úzká souvislost mezi funkcí a typem ukazatel. Díky ní se můžeme na jméno funkce dívat jako na ukazatel na funkci, a také s ním podle toho pracovat. Naopak, na ukazatel na funkci se zase můžeme dívat jako na funkci samu.

Je vám ten úvodní odstaveček povědomý ? Měl by - oproti tomu jak to bylo napsáno v C (17) - Pole a ukazatel dílu tohohle seriálu jsem změnil jen pár slov. Ale tak už to prostě je. Ukazatel na něco je prostě velmi blízko tomu "něčemu". A dnes tím něčím budou funkce.

Pokud si deklaraci funkce f schematicky napíšeme jako

navratovy_typ f(parametry)

pak proměnná q typu "ukazatel na funkci" kompatibilního typu bude deklarovaná jako

navratovy_typ (*q)(parametry)

Ukažme si to na zcela konkrétním příkladu:

#include <stdio.h>int f(char a) {
  putchar(a);
  return '0';
}

int (*q)(char a);

int
main ()
{
  q = f;

  f('O');
  q('K');

  return 0;
}


Funkce 'f' vypisuje jeden znak, který dostala jako parametr. Identifikátor funkce 'f' přiřadíme do proměnne 'q', která je ukazatelem na funkci stejného typu jako f a tím stane "aliasem' - funkcí, která "má stejnou funkci".

Tento konkrétní příklad tak nakonec vypíše 'OK'

Ta zmínka o stejném typu je podstatná. Při volání funkce musí program vědět kolik má parametrů i co vrací (nebo, že nic). Nelze do proměnné, která je ukazatelem na funkci s jedním parametrem přiřadit funkci, která má parametry tři. A pokud by se vám to přesto povedlo (s pomocí přetypování) tak buď sakra přesně víte co děláte a proč, nebo si prostě koledujete.

No dobře - tak teda umíme udělat ukazatel na funci. A praktické využití ?

Často jde o variantní kód, který na začátku a/nebo konci provádí něco co je stále stejné, ale uprostřed je nějaká operace, která má být v různých situacích různá.

Představme si, že chceme bezpečně ovládat dva motorky na robotovi. První motorek se ovládá funkcí void motor_1(int uhel), druhý pak void motor_2(int uhel). Motorky to jsou chytré, stačí jim poslat úhel v rozsahu 0-360 a ony se požadovaným směrem otočí. Ale větší číslo než 360 nebo menší než 0 se jim poslat nesmí - to by se ukroutily kabely. Takže chceme, před tím, než jim pokyn pošleme, zkontrolovat, že to není pokyn nebezpečný.

Dá se to samozřejmě napsat různými způsoby (a obzvlášť v tomhle jednoduchém příkladu), ale jedním z možných způsobů je vyrobit si funkci:

void motor_with_check(int uhel, void (*motor)(int) ) {
   if (uhel >= 0 && uhel < 360)
      motor(uhel);
}


Prvním parametrem té funkce je úhel, druhým pak ukazatel na funkci, která (po kontrole zda první parametr má přípustnou hodnotu) se má zavolat aby skutečně provedla to otočení.

Pokud teď chceme motor 1 natočit na úhel X a motor 2 na úhel Y stačí zavolat

motor_with_check(X,motor_1);
motor_with_check(Y,motor_2);

A je to.

Poměrně běžné je také použití callbacků. Asynchronní rozhraní vypadají tak, že my zadáváme požadavky, které se skládají do fronty a někdy později je něco provádí. Často se v takových případech v rámci zadávanýho požadavku předává i ukazatel na funkci typu je_hotovo() kterou ten, kdo požadavek vyřídil zavolá - když je hotovo. A já se tedy o tom dozvím aniž bych musel periodicky kontrolovat stav zadaných úkolů.

Další pěkný příklad využití callbacku je knihovní funkce qsort. To je implementace rychlého seřazení pole. Ale nepředstavujte si jednoúčelovou funkci, které umí setřídit vzestupně podle hodnoty čísla. Funkce qsort umí setřídit libovolně velké pole, složené z prvků libovolného formátu, a to podle libovolného kritéria. To by samozřejmě nebylo možné, když qsort sám obsahu prvků pole nerozumí. A právě na to potřebuje callback. Když chce qsort u dvou položek pole vědět, která z nich je "větší", zavolá vámi vytvořenou funkci (jejíž pointer dostal jako parametr), jako parametry uvede ty dvě položky a čeká, že callback, který obsahu položek rozumí, vrátí informaci, která z položek je "větší", ať už se pojmem "větší" myslí jakákoliv vlastnost. To se chvíli opakuje (qsort je velmi rychlý) a nakonec dostaneme setříděné pole.

Taková tabulka obsluhy přerušení, kterou najdeme i na těch nejprimitivnějších procesorech, je nakonec obvykle také prostým polem ukazatelů na obslužné funkce přerušení. Pravda je, že napsat správnou deklaraci začíná být i u těhle jednoduchých příkladů netriviální. Pokud je obluha přerušení funkce bez návratová hodnty a s jedním parametrem typu int:

void f(int n);


pak deklarace tabulky přerušení, která je polem osmi odkazů na takovou funkci vypadá takhle:

void (*IDT[3])(int n);


Abyste všichni pochopili, proč je nejvyšší čas skončit, posledním příkladem bude deklarace ukazatele na funkci f, která má dva parametry - první parametr je ukazatel na funkci vracející int a mající jeden parametr typu long int, druhým parametrem je prostý int. Návratovou hodnotou funkce f je ukazatel na funkci, jejíž návratovou hodnotou je int a která má jeden parametr typu char:

int (*f(int (*)(long),int))(char);


Takováhle zvěrstva si totiž v míře dostatečné užijeme příště, v C (23) - Složité deklarace a definice (odkaz samozřejmě začne fungovat teprve až článek vyjde). Ten příští díl pro vás, mimochodem, připravil po delší přestávce zase Kosťa.