Variadické funkce
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í stejný 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.
-
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.4 -
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 4 -
Informaci o typech i počtu volitelných argumentů předáme pomocí povinných argumentů. Tento přístup používá například funkce
printf.
K nepovinným argumentům přistupujeme pomocí maker z stdarg.h.
- typ
va_listpro udržování nezpracovaných argumentů, va_startnastavíva_listna všechny volitelné argumenty,va_argvrátí hodnotu prvního argumentu veva_list, potřebuje znát typ, první argument odstraní zva_list.va_endukončí 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 a 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);
}
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.
Pointery na funkce
S pomocí pointerů na funkce lze v omezené míře pracovat s funkcemi
vyššího řádu.
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 promenne
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.
Podobně jako u polí, jméno funkce degeneruje na pointer na funkci.
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 analogie 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;
}
Úkoly
-
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í parametruformat, který může tvořit libovolná posloupnost znaků odpovídající typům následujících parametrů -ipro typint,dpro typdoublealpro long double. -
Naprogramujte vlastní verzi funkce
printf, která s podporou tiskových direktiv%i,%fa%z, kde poslední direktiva vede k tisku zlomku. Při řešení lze využít funkciprintf. -
Napište funkci
double* map(double (*fce)(double), double* input, int len), která na hodnoty poleinputaplikuje funkcifcea vrátí pole výsledných hodnot. Velikost poleinputparametremlen. -
Napište funkci
double akumulator(double (*fce)(double, double), double numbers[], int len), která zpracuje pomocí předané funkcefcehodnoty z polenumbers, jehož velikost je dána parametremlen. Můžete předpokládat, želenje vždy alespoň 2. -
S použitím příkladu z kapitoly vytvořte funkci pro binární vyhledávání zlomků.
-
Do následujícího kódu doprogramujte generickou funkci pro třídění a s její pomocí funkce pro třídění zlomků a
short int. (Můžete si doprogramovat libovolné pomocné funkce.) Je zakázáno použít třídicí funkci ze standardní knihovny.typedef struct { int numerator; int denominator; } Fraction; typedef int compare_fn(void*, void*); int compare_fraction(void *a, void *b) { // TODO } int compare_unsigned(void *a, void *b) { // TODO } void generic_sort(void *array, size_t n, size_t element_size, compare_fn* cmp) { // TODO } void fraction_sort(Fraction *array, int n) { // TODO } void unsigned_sort(unsigned int* array, int n) { // TODO }