Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_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 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

  1. 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ů.

  2. 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.

  3. Naprogramujte vlastní verzi funkce printf, která s podporou tiskových direktiv %i, %f a %z, kde poslední direktiva vede k tisku zlomku. Při řešení lze využít funkci printf.

  4. 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.

  5. 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.

  6. S použitím příkladu z kapitoly vytvořte funkci pro binární vyhledávání zlomků.

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