13. díl - Funkcie s variabilným počtom a typom argumentov (stdarg.h)

C a C++ Céčko Pokročilé konstrukce Funkcie s variabilným počtom a typom argumentov (stdarg.h)

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Nedávno som sa pri potulkách internetom stretol s celkom zaujímavou knižnicou a rozhodol som sa, že vás s ňou trochu zoznámim. Jedná sa o knižnicu stdarg.h.

Všetky štandardné vstupno výstupné funkcie (knižnica stdio.h) s formátovaním používajú knižnicu stdarg.h. Sú to všetky funkcie, ktoré majú v časti názvu slovo printf alebo scanf, napríklad: printf(), sprintf(), fprintf(), a pod., scanf(), sscanf(), fscanf(), a pod... Keď som si prezeral súbor stdio.h, bol som veľmi prekvapený, koľko je tam funkcií, s ktorými som sa ešte nepotýkal.

V tomto článku si v krátkosti vysvetlíme, ako sa funkcia používa v prípade, že chceme použiť premenlivý počet argumentov.

Ďalej si ukážeme príklad funkcie, kde použijeme premenlivý počet a aj premenlivé typy argumentov.

A nakoniec si vysvetlíme ako s pomocou knižnice stdarg.h pracovať vo funkcii s argumentami ako sú pointery na funkcie.

Dajme tomu, že ste dostali za úlohu napísať jednoduchý program s týmto zadaním:

Zadání

„Napíšte funkciu, ktorá zo zadaných celých čísiel (argumentov) typu 'int' vyberie najväčšie a vráti ho. Prvé číslo stanovuje počet čísiel, z ktorých sa bude vyberať najväčšie. Počet zadaných čísiel môže byť rôzny.“

Volanie funkcie môže byť takéto:

1. najvacsie(2, 25, 68);  // 2 – počet argumentov, 25 a 68 – argumenty ( výsledok 68)
2.najvacsie(5, 56, 58, 98, 45, 33);  // 5 – počet argumentov, 56 …33 - argumenty (výsledok 98)

Priznám sa, že som dlho premýšľal, ako to napísať bez použitia stdarg.h a nakoniec som na nič neprišiel. Ale ak má niekto nejaký nápad, napíšte to v komentároch, doplním to sem.

Klasicky by sa to dalo riešiť asi len takto:

1. int najvacsie(const int pocet, int a, int b);
2. int najvacsie(const int pocet, int a, int b, int c, int d, int e);

Ale to nerieši jednu funkciu na variabilný počet vstupov, preto sa pozrieme ako to napísať s použitím knižnice stdarg.h.

Najprv som napísal to čo chcem dosiahnuť, čiže funkciu main().

#include <stdio.h>

int main(void)
{
    printf("Najvacsie cislo: %d\n", najvacsie(4, 5, 78, 78, -46));
    printf("Najvacsie cislo: %d\n", najvacsie(7, 2565, 1178, 925, 12546, 2568, 12547, 1234));
    printf("Najvacsie cislo: %d\n", najvacsie(4, -85, -78, -78, -46));

    return 0;
}

Teraz si napíšeme funkciu s variabilným počtom argumentov.

Úplný funkčný prototyp funkcie vyzerá takto:

int najvacsie(const int pocet_argumentov, ...);

Pekné, že áno?

Samotná funkcia je tu:

int najvacsie(const int pocet_argumentov, ...) // 1
{
    int i = -1, argumenty_int, pom = INT_MIN, vysledok; // 3
    va_list argumenty; // 4
    va_start(argumenty, pocet_argumentov); // 5

    while((++i) ^ pocet_argumentov) { // 7
        argumenty_int = va_arg(argumenty, int); // 8
        vysledok = pom > argumenty_int ? pom : argumenty_int; // 9
        pom = vysledok;
    }
    va_end(argumenty); // 12

    return vysledok;
}

Vysvetlíme si jednotlivé časti kódu:

  • 3 – deklarácia premenných
  • 4 – vytvorenie ukazovateľa, ktorý môže ukazovať na blok argumentov /typ va_list z knižnice/
  • 5 – spustí spoluprácu s blokom argumentov /funkcia va_start() z knižnice/
  • 6 – prechod všetkými argumentami v bloku argumentov
  • 7 – vloženie získaného argumentu do premennej
  • 8-9 – výpočet najväčšieho čísla
  • 12 – ukončenie práce s blokom argumentov /funkcia va_end() z knižnice/

A to je celé.

Ďalej tu mám pre vás ukážku funkcie miniprintf() s variabilným počtom a typom argumentov.

Ukázka funkce miniprintf()

Je to funkcia, ktorá vypíše na obrazovku zadané premenné typu int, char a string, ale dala by sa samozrejme rozšíriť aj na ostatné formátovacie prvky.

#include <stdio.h>
#include <stdarg.h>

void miniprintf(char *fmt, …) // *fmt je pointer na makro, ktoré rieši formátovacie prvky
{
    va_list ap;
    int d;
    char c, *s;

    va_start(ap, fmt);
    while (*fmt)
        switch(*fmt++) {
        case 's':  /* string */
                s = va_arg(ap, char *);
                printf("string %s\n", s);
                break;
        case 'd':  /* int */
                d = va_arg(ap, int);
                printf("int %d\n", d);
                break;
        case 'c':  /* char */
                c = (char) va_arg(ap, int);
                printf("char %c\n", c);
                break;
    }
    va_end(ap);
}

int main(void)
{
    int i = 25;
    char c  = 'A';
    char *s = "test";

    miniprintf("%d\n%c\n%s\n", i, c, s);

    return 0;
}

Musím sa priznať, že ako pracuje *fmt, __fmt alebo *format v týchto funkciách mi nie je celkom jasné, aj keď som sa dosť túlal po nete. Ak to niekto vie, prosím o doplnenie.

Na koniec si vysvetlíme prácu funkcie s argumentami typu pointer na funkciu.

Práca funkcie s argumentami typu pointer na funkciu

Najskôr si pozrieme dve funkcie main() (samozrejme, že kódu je v programe podstatne viac), ktoré robia to isté, ale v kóde sa pomerne dosť odlišujú.

int main(void)
{
    int vyber;

    srand(time(0));
    printf("Ak je to tvoja prva hra, stlac cislo 1, ak nie, stlac cislo 2 ");
    scanf(" %d", &vyber);
    if (vyber == 1) {
        vytvor_pole(pole);
        zahajenie();
        ulohy(pole);
        vypis(pole);
        uloz_txt(pole);
        uvolni_pole(pole);
    }
    else {
        vytvor_pole(pole);
        nacitaj_txt(pole);
        zahajenie();
        vypis(pole);
        ulohy(pole);
        vypis(pole);
        uloz_txt(pole);
        uvolni_pole(pole);
    }

    return 0;
}
int main(void)
{
    int vyber;

    srand(time(0));
    printf("Ak je to tvoja prva hra, stlac cislo 1, ak nie, stlac cislo 2 ");
    scanf(" %d", &vyber);
    if (vyber == 1) {
        foo(7, vytvor_pole, zahajenie, ulohy, vypis, uloz_txt, uvolni_pole);
    }
    else {
        foo(7, vytvor_pole, nacitaj_txt, zahajenie, vypis, ulohy, vypis, uloz_txt, uvolni_pole);
    }

    return 0;
}

Určite ste si všimli, že jediný rozdiel je v tom, že v prvej funkcii sa funkcie volajú klasicky, teda názvom, zátvorkou a vstupnými parametrami a v druhej za pomoci funkcie foo(), ako jej variabilné parametre, ale už sa volajú iba názvom.

Môže to byť dosť výhodné, ak treba pri písaní, alebo ladení programu často s funkciami manipulovať, takto je to myslím rýchlejšie a jednoduchšie.

Ako teda vyzerá funkcia foo()?

Úplný funkčný prototyp je jednoduchý:

void foo(const int, ...);

a vlastne ani samotný kód nie je zložitý:

void foo(const int num_of_func, ...)
{
    va_list args;
    int i;
    va_start(args, num_of_func);
    for (i = 0; i < num_of_func; i++) {
        void (*bar)() = va_arg(args, void (*)());
        (*bar)();
    }
    va_end(args);
}

Oproti tomu, čo sme si tu dnes povedali, sú tu len dve „odlišnosti“:

void (*bar)() = va_arg(args, void (*)());

vloží do prázdneho pointera void (*bar)() pointer na konkrétnu funkciu z bloku

a

(*bar)();

zavolá konkrétnu funkciu.

Ako som už v úvode písal, nemám s tým ešte veľké skúsenosti, ale aj napriek tomu som vás chcel nasmerovať na niečo, čo ste možno ešte nepoužili a bolo by to pre vás výhodné.

Do prílohy som vložil nejaké zdrojáky, aby ste si to mohli bezpracne vyskúšať. Je tam aj jeden projektík, ale scompilovaný na linuxe. Nie je však problém scompilovať ho ja na win.

Ak máte výhrady, iné skúsenosti, poprípade nejaké nápady na vylepšenie článku, napíšte do komentárov, budem sa tešiť. ;-)


 

Stáhnout

Staženo 31x (17.56 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

 

Článek pro vás napsal Libor Šimo (libcosenior)
Avatar
Jak se ti líbí článek?
1 hlasů
Obľúbil som si jazyk C a snažím sa ho spoznať čo najhlbšie. Začal som používať c# WPF, je to krása.
Aktivity (2)

 

 

Komentáře

Avatar
tomisoka
Redaktor
Avatar
tomisoka:19.2.2015 18:08

Co ti není jasné na tom "fmt"?

(v tom tvém miniprintf)
Procházíš znak po znaku a pokud narazíš na 's', 'd' nebo 'c' tak vypíšeš další argument.
(v printf)
Procházíš znak po znaku a vypisuješ je, pokud narazíš na '%' tak načteš co je za ním a podle toho vypíšeš další argument.

Jinak tak jak jsi to napsal tak stačí:

miniprintf("dcs", i, c, s);

Ty ostatní znaky pro tu funkci nic neznamenají.

 
Odpovědět 19.2.2015 18:08
Avatar
Odpovídá na tomisoka
Libor Šimo (libcosenior):19.2.2015 18:17

Skúšal som tam použiť iný pointer, napr. *mmm a to nefungovalo, teda *fmt musí mať preddefinované miesto v pamäti aj s nejakými parametrami a tie ma zaujímajú.

Odpovědět 19.2.2015 18:17
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
tomisoka
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
tomisoka:19.2.2015 18:37

To je divné, dělal jsem si dřív vlastní printf a normálně mi to funguje s *c:

void myprintf(char *c, ...){
  va_list args;
  va_start(args, c);
  int i=0;
  while(*c){
    if(*c=='%'){
      *c++;
      switch(c[i]){
      case '%':
        putchar(c[i]);
        break;
      case 'd':
        printf("%d",va_arg(args, int32_t));
        break;
      case 'c':
        putchar((char)va_arg(args, uint32_t));
        break;
      case 's':
        printf("%s",va_arg(args, char*));
        break;
      case 'x':
      case 'X':
        printf("%X",va_arg(args, uint32_t));
        break;
      }
    }else{
      putchar(*c);
    }
    *c++;
  }
  va_end(args);
}

int main(){

  myprintf("test %s %c %d %X\n", "hoj!", 'c', 42, 123333);
  return 0;
}

(pro rejpaly : v původní funkci jsem samozřejmě nepoužíval na výpis printf, ale vlastní funkce)

 
Odpovědět 19.2.2015 18:37
Avatar
Odpovídá na tomisoka
Libor Šimo (libcosenior):19.2.2015 18:44

Skúšal si v case použiť aj iné písmená ako sú predpísané?
napr. case 'z':

Editováno 19.2.2015 18:45
Odpovědět 19.2.2015 18:44
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
tomisoka
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
tomisoka:19.2.2015 18:49

Ano, zkoušel, nějak mě nenapadá žádný důvod proč by to nefungovalo.

 
Odpovědět 19.2.2015 18:49
Avatar
Libor Šimo (libcosenior):19.2.2015 18:51

Proste mi tam niečo nešlo, ale ako som písal v článku, nemám to úplne preskúmané, tak sa môžem mýliť.

Odpovědět 19.2.2015 18:51
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Raiper34
Redaktor
Avatar
Raiper34:21.2.2015 20:31

Toto sme vyuzivali, ked sme robili nas Pascal interpreter na to aby sme mohli premeny pocet prvkov hadzat do nasho stacku :P

Odpovědět 21.2.2015 20:31
Posledná vydaná hra: http://www.islandsoft.cz/index.php?art=hra-akcna-space-resistance
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovídá na Raiper34
David Novák:21.2.2015 20:41

Nejsi náhodou druhák na VUT FIT? :D

Odpovědět 21.2.2015 20:41
Chyba je mezi klávesnicí a židlí.
Avatar
Raiper34
Redaktor
Avatar
Odpovídá na David Novák
Raiper34:21.2.2015 21:53

Som ;) a ty si tusim prvak :P

Odpovědět 21.2.2015 21:53
Posledná vydaná hra: http://www.islandsoft.cz/index.php?art=hra-akcna-space-resistance
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovídá na Raiper34
David Novák:21.2.2015 21:56

Jop :D
Mám kámoše ve druháku.. Tak mi vykládal jak jste si "užili" IFJ.. :P

Odpovědět  +1 21.2.2015 21:56
Chyba je mezi klávesnicí a židlí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 10.