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
(3, 1.2, 2.2, 3.3); // vysledek je 6.6
sum_of_doubles(2, 2.1, 3.3); // vysledek je 5.4 sum_of_doubles
Ve 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
(1, 2, 3, 0); // vysledek je 6
sum_of_ints(2, 2, 0); // vysledek je 4 sum_of_ints
Ve 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;
(args, n); // tady se musi predat va_list a posledni povinny argument
va_start
for (int i = 0; i < n; i += 1)
{
+= va_arg(args, double); // tady se musi predat va_list typ argumentu
ret }
(args); // ukonceni prace s argumenty
va_endreturn ret;
}
//
// nutno volat aspon dvema argumenty !!!
// jinak UB
//
int sum_of_ints(int first, ...)
{
va_list args;
int ret = first;
(args, first);
va_start
while (1)
{
int val = va_arg(args, int);
if (!val) break;
+= val;
ret }
(args);
va_endreturn ret;
}
void almost_printf(char *format, ...)
{
va_list args;
int index = 0;
(args, format);
va_start
while(format[index])
{
switch(format[index])
{
case 'i':
("%i ", va_arg(args,int));
printfbreak;
case 'd':
("%f ", va_arg(args,double));
printfbreak;
}
+= 1;
index }
(args);
va_end}
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
.
Navhněte strukturu pro komplexní číslo a naprogramujte variadickou funkci pro výpočet sumy komplexních čísel využívají prvního přístupu k určení počtu volitelných argumentů.
Napište funkci
long double prumer(char* format, ...)
, která výpočítá
aritmetický průměr ze zadaných hodnot různých datových typů. Typy
předávaných hodnot jsou určeny pomocí parametru format
,
který může tvořit libovolná posloupnost znaků odpovídající typům
následujících parametrů - i
pro typ int
,
d
pro typ double
a l
pro long
double.
Naprogramujte vlastní verzi funkce printf
určenou
pro tisk do string_builder
z druhého bonusového úkolu.
(Vyberte rozumnou část formátovacích primitiv, kterou chcete
podporovat). Hlavička funkce je tedy například
int sb_printf(char *format, ...)
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
; // definice promenne p_fun ptr
S 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) {
+= i;
ret }
return ret;
}
int array_product(int *array, int n) {
int ret = 1;
for (int i = 0; i < n; i += 1) {
+= i;
ret }
return ret;
}
int main() {
int a[] = { 1, 2, 3, 4, 5 };
int n = sizeof(a)/sizeof(*a);
= array_sum; // reduce ukazuje na funkci array_sum
p_fun reduce ("%i\n", reduce(a, m)); // vytiskne 15, vola array_sum
printf= array_product;
reduce ("%i\n", reduce(a, m)); // vytiskne 120, vola array_product
printfreturn 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
* cmp) // funkce pro porovnani dvou prvku
compare_fn{
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
= s + 1;
l }
else {
= s - 1;
p }
}
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) {
[i] = i;
array_int[i] = i;
array_long}
int key_int_ok = 10;
int key_int_fail = 30;
long key_long_ok = 5;
long key_long_fail = -3;
("index of %i in array_int is %i\n",
printf,
key_int_ok(array_int, key_int_ok, 20));
binary_search_int
("index of %i in array_int is %i\n",
printf,
key_int_fail(array_int, key_int_fail, 20));
binary_search_int
("index of %li in array_long is %i\n",
printf,
key_long_ok(array_long, key_long_ok, 20));
binary_search_long
("index of %li in array_long is %i\n",
printf,
key_long_fail(array_long, key_long_fail, 20));
binary_search_long
return 0;
}
S použitím předchozího příkladu vytvořte funkci pro binární vyhledávání zlomků.
Napište funkci
double* map(double (*fce)(double), double* input, int len)
,
která na hodnoty pole input
aplikuje funkci
fce
a vrátí pole výsledných hodnot. Velikost pole
input
parametrem len
.
Napište funkci
double akumulator(double (*fce)(double, double), double numbers[], int len)
,
která zpracuje pomocí předané funkce fce
hodnoty z pole
numbers
, jehož velikost je dána parametrem
len
. Můžete předpokládat, že len
je vždy
alespoň 2.