Variadické funkce jsou funkce s proměnným počtem parametrů. Variadické funkce mají povinné argumenty, těch je v každém zavolání stený počet, a volitelné argumenty, jejichž počet se může mezi voláními může lišit. Každá variadická funkce musí mít alespoň jeden povinný argument.
Zjištění počtu a typu volitelných argumentů uvnitř funkce se neděje automaticky, řeší se konvencí. Existují v zásadě tři přístupy.
V prvním předpokládáme pevný typ volitelných argumentů, v povinném argumentu předáváme jejich počet.
//hlavicka
double sum_of_doubles(int count, ...); // hlavicka
// volani
sum_of_doubles(3, 1.2, 2.2, 3.3); // vysledek je 6.6
sum_of_doubles(2, 2.1, 3.3); // vysledek je 5.4Ve druhém také předpokládáme pevný typ argumentů, ale poslední nepovinný argument má speciální hodnotu, například 0.
//hlavicka
int sum_of_ints(int first, ...); // hlavicka
// volani
sum_of_ints(1, 2, 3, 0); // vysledek je 6
sum_of_ints(2, 2, 0); // vysledek je 4Ve třetím přístupu předáme informaci o typech i počtu volitelných
argumentů pomocí povinných argumentů, příkladem takové funkce je
printf.
K nepovinným argumentům přistupujeme pomocí maker z
stdarg.h.
va_list pro udržování nezpracovaných
argumentů,va_start nastaví va_list na všechny
volitelné argumenty,va_arg vrátí hodnotu prvního argumentu ve
va_list, potřebuje znát typ, první argument odstraní z
va_list.va_end ukončí práci s nepovinnými argumenty.Podrobnosti v následujících příkladech.
double sum_of_doubles(int n, ...)
{
va_list args;
double ret = 0;
va_start(args, n); // tady se musi predat va_list a posledni povinny argument
for (int i = 0; i < n; i += 1)
{
ret += va_arg(args, double); // tady se musi predat va_list typ argumentu
}
va_end(args); // ukonceni prace s argumenty
return ret;
}
//
// nutno volat aspon dvema argumenty !!!
// jinak UB
//
int sum_of_ints(int first, ...)
{
va_list args;
int ret = first;
va_start(args, first);
while (1)
{
int val = va_arg(args, int);
if (!val) break;
ret += val;
}
va_end(args);
return ret;
}
void almost_printf(char *format, ...)
{
va_list args;
int index = 0;
va_start(args, format);
while(format[index])
{
switch(format[index])
{
case 'i':
printf("%i ", va_arg(args,int));
break;
case 'd':
printf("%f ", va_arg(args,double));
break;
}
index += 1;
}
va_end(args);
}Pozor, při předávání variadických argumentů dochází ke automatické
konverzi typů: float je konvertován na double,
char a short jsou konvertovány na
int, jejich bezznaménkové varianty na
unsigned int.
S pomocí pointerů na funkce lze v omezené míře pracovat s funkcemi vyššího řádu. Pointer na funkci je adresa, kde je funkce uložena v paměti. S pomocí této adresy lze funkci volat. Podobně jako u polí, jméno funkce degeneruje na pointer na funkci.
Pointer na funkci sebou nese i typové informace: typ návratové
hodnoty a počet a typy argumentů. Zápis tohoto typu podobný jako zápis
hlavičky funkce bez jmen argumentů, pouze jméno funkce nahradíme
(*).
int (*)(int*, int) // typ pointer na fci, ktera vraci int,
// jeji argumenty jsou typu int* a int.Při definici proměnné se identifikátor píše dovnitř závorky za
*.
int (*fce_ptr)(int*, int); // definice pointeru Předchozí syntax je trochu nešikovná, proto se často používá
typedef.
typedef int (*p_fun)(int* , int); // typ se jmenuje p_fun
p_fun ptr; // definice promenneS pointery na funkce nelze provádět aritmetiku, ani aplikovat operátory adresy a dereference (resp, tyto operátory nedělají nic). Můžeme ovšem funkci volat, syntax volání je přitom stejná jako u běžného volání funkce.
typedef int (*p_fun)(int* , int);
int array_sum(int *array, int n) {
int r = 0;
for (int i = 0; i < n; i += 1) {
ret += i;
}
return ret;
}
int array_product(int *array, int n) {
int ret = 1;
for (int i = 0; i < n; i += 1) {
ret += i;
}
return ret;
}
int main() {
int a[] = { 1, 2, 3, 4, 5 };
int n = sizeof(a)/sizeof(*a);
p_fun reduce = array_sum; // reduce ukazuje na funkci array_sum
printf("%i\n", reduce(a, m)); // vytiskne 15, vola array_sum
reduce = array_product;
printf("%i\n", reduce(a, m)); // vytiskne 120, vola array_product
return 0;
}S pointery na funkce lze pracovat jako s jinými typy v C. Lze tvořit jejich pole, předávat je do funkcí jako argumenty, mohou být návratovými hodnotami funkcí atd.
Pointer na funkci může být neplatný, v tom případě vede pokus o volání k nedefinovanému chování. Pointeru na funkci je možné přiřadit 0 a pointery na funkce lze porovnávat.
Jedno z využití pointerů na funkce je tvorba analogii generických funkcí známých z jiných jazyků. Ideou je, že funkci naprogramujeme jednou a ona poté pracuje pro více typů. Ukážeme si to na příkladu binárního vyhledávání.
typedef int compare_fn(void *, void *); // argumenty jsou void*, aby byla funkce obecna
int compare_int(void *a, void *b) {
int *aa = a; // tuto funkci budeme volat pouze s int*,
int *bb = b; // muzeme bezpecne pretypovat
if (*aa < *bb) return -1;
if (*bb < *aa) return 1;
return 0;
}
int compare_long(void *a, void *b) {
long* aa = a;
long* bb = b;
if (*aa < *bb) return -1;
if (*bb < *aa) return 1;
return 0;
}
//
// genericka funkce pro binarni vyhledavani
//
int binary_search(void *array, // pole, ve kterem vyhledavame
void *key, // ukazatel na hodnotu klice
size_t n, // pocet prvku pole
size_t element_size, // velikost jednoho prvku v bajtech
compare_fn* cmp) // funkce pro porovnani dvou prvku
{
unsigned char* bytes = array;
int l = 0;
int p = n-1;
while (l <= p) {
int s = (l + p) / 2;
unsigned char* s_ptr = bytes + s * element_size; // tady adresu prvku na indexu s
int cmp_result = cmp(key, s_ptr); // zjistime vysledek porovnani
if (!cmp_result) {
return s;
}
else if (cmp_result > 0) { // klic je vetsi
l = s + 1;
}
else {
p = s - 1;
}
}
return -1;
}
//
// pro jednotlive typy vytvorem funkce tvorici rozhrani
//
int binary_search_int(int* array, int key, size_t n) {
return binary_search(array, &key, n, sizeof(int), compare_int);
}
int binary_search_long(long* array, long key, size_t n) {
return binary_search(array, &key, n, sizeof(long), compare_long);
}
int main() {
int array_int[20];
long array_long[20];
for(int i = 0; i < 20; i += 1) {
array_int[i] = i;
array_long[i] = i;
}
int key_int_ok = 10;
int key_int_fail = 30;
long key_long_ok = 5;
long key_long_fail = -3;
printf("index of %i in array_int is %i\n",
key_int_ok,
binary_search_int(array_int, key_int_ok, 20));
printf("index of %i in array_int is %i\n",
key_int_fail,
binary_search_int(array_int, key_int_fail, 20));
printf("index of %li in array_long is %i\n",
key_long_ok,
binary_search_long(array_long, key_long_ok, 20));
printf("index of %li in array_long is %i\n",
key_long_fail,
binary_search_long(array_long, key_long_fail, 20));
return 0;
}