Funkce II

Argumenty příkazové řádky

command.com

Příkazový řádek ve Windows

Když spouštíte programy z příkazové řádky (ať už v Linuxové konzoli nebo v DOSu), můžete jim předávat nějaké argumenty. Argumenty příkazové řádky jsou všechny texty (oddělené mezerami), které zapíšete za název programu v příkazové řádce.

Například v příkazu MOVE S1 S2 jsou texty S1 a S2 argumenty. Argumenty jsou rozdělené tzv. bílými znaky (mezery, tabulátory) a programu jsou předávány jeden po druhém (za chvíli uvidíte jak). Pokud budete chtít předat více slov jako jeden argument, pak je stačí uzavřít do uvozovek.

Argumenty příkazové řádky jsou předávány funkci main(), která je deklarována následujícím způsobem:

int main(int argc, char *argv[]);

Parametr argc představuje počet argumentů příkazové řádky a parametr argv je pole ukazatelů na argumenty příkazové řádky. Argumenty příkazové řádky jsou textové řetězce (i když je to jenom jeden znak nebo číslo) a počítá se mezi ně i název programu, který je prvním argumentem.

Například když spustíte program takto:

$ program Zvolejte 3 krat hura

Pak argc bude mít hodnotu 5, argv[0] bude ukazatel na řetězec "program", argv[1] ukazatel na řetězec "Zvolejte", argv[2] ukazatel na řetězec "3" atd.
Co k tomu dodat. Snad jen to, že jména argc a argv si můžete libovolně změnit. Ovšem doporučuji je nechat taková, jaká jsou. Pokud si na to zvyknete, budou se vám vaše i cizí (třeba moje) programy lépe číst.

Ukáži teď program, který bude pracovat na základě argumentů příkazové řádky. Program převede svůj druhý argument na velká nebo malá písmena podle toho, co mu předáte jako první argument. Aby šlo porovnat jaké argumenty byly předány, napíši funkci, která bude porovnávat dva textové řetězce. Bude vracet 1 (TRUE) v případě, že jsou schodné, jinak 0 (FALSE).

  1. /*------------------------------------------------*/
  2. /* c14/argum.c                                    */
  3.  
  4. #include <stdio.h>
  5. #include <stddef.h>
  6.  
  7. #define VELKE "velke"
  8. #define MALE "male"
  9.  
  10. #define VAROVANI printf("Zadejte: %s %s | %s ARGUMENT\n",argv[0],VELKE,MALE);
  11.  
  12. int shodne(char *v1, char *v2)
  13. {
  14.     size_t i = 0;
  15.     /* pomoci zarazek '\0' zjistuti konec retezce */
  16.     while ((v1[i] != '\0') && (v2[i] != '\0')) {
  17.         if (v1[i] != v2[i])
  18.             return 0; /* nejsou shodne; */
  19.         i++;
  20.     }
  21.     if (v1[i] || v2[i]) {
  22.         return 0; /* jeden retezec jeste neskoncil, nejsou stejne */
  23.     }
  24.     return 1;    /* vsechny znaky byly shodne */
  25. }
  26.  
  27. void velke(char *);
  28. void male(char *);
  29.  
  30. int main(int argc, char *argv[])
  31. {
  32.     if (argc != 3) {
  33.         VAROVANI
  34.         return 1;
  35.     }
  36.  
  37.     if (shodne(VELKE, argv[1]))
  38.         velke(argv[2]);
  39.     else if (shodne(MALE, argv[1]))
  40.         male(argv[2]);
  41.     else {
  42.         VAROVANI
  43.         return 1;
  44.     }
  45.  
  46.     printf("Druhy argument byl zmenen na: %s\n", argv[2]);
  47.     return 0;
  48. }
  49.  
  50.  /* ve funkcich velke a male vyuziji vlastnosti ASCII tabulky */
  51.  
  52. void velke(char *v)
  53. {
  54.     size_t i = 0;
  55.     while (v[i]) {              /* '\0' odpovida hodnote FALSE */
  56.         if (v[i] >= 'a' && v[i] <= 'z') {
  57.             /* rozdil v ASCII tabulce mezi velkymi a malymi pismeny */
  58.             /* 'A' == 65, 'a' == 97 */
  59.             v[i] += ('A' - 'a');
  60.         }
  61.         i++;
  62.     }   /* konec while */
  63. }
  64.  
  65. void male(char *v)
  66. {
  67.     size_t i = 0;
  68.     while (v[i]) {
  69.         if (v[i] >= 'A' && v[i] <= 'Z') {
  70.             v[i] += ('a' - 'A');
  71.         }
  72.         i++;
  73.     }
  74. }
  75.  
  76. /*------------------------------------------------*/

Tento příklad taky není zrovna ukázkou programátorského umu. Je to spíše jen ukázka toho, co lze v jazyku C napsat. Tak například, než použít #define VAROVANI by bylo lepší napsat jednoduchou funkci, která by se dala v budoucnu snadněji rozšiřovat. Taky výraz (v1[i] != '\0') mohl být v podmínce if zjednodušen na v1[i], protože jak víte, konec pole je dán nulovým znakem a nulový znak se vyhodnocuje jako FALSE (nepravda). Taky neexistuje žádný rozumný důvod pro to, aby funkce shodne byla definována před funkcí main() a zbylé dvě funkce až za ní (buď si vyberu jedno nebo druhé, ale nepatlám to jak pejsek a kočička).

V příkladu využívám vlastnosti ASCII tabulky, ve které jsou malá i velká písmena uložena abecedně za sebou. Převedení velkého písmene na malé docílím přičtením vzdálenosti mezi velkými a malými písmeny. Typ char, jak víte, je osmibitový typ a jak vidíte, lze jej bez problémů používat jako číslo.

Ne všechny operační systémy využívají tabulku ASCII a národní znaky nemají velká a malá písmena vždy všechny stejně „daleko“. Využití vlastností ASCII tabulky jako v tomto příkladě snižuje přenositelnost kódu.

Ukázka použití (/tmp/argum je cesta k programu vytvořenému z argum.c):

$ /tmp/argum
Zadejte: /tmp/argum velke | male ARGUMENT
$ /tmp/argum velke "Ahoj Svete"
Druhy argument byl zmenen na: AHOJ SVETE
Visual Studio

Jak předat argumenty příkazové řádky ve Visual Studiu viz O Visual Studiu.

Lokální, globální a statické proměnné

O lokálních a globálních proměnných jsem již mluvil. Takže jen zopakuji, že lokální proměnné jsou proměnné definované v těle nějakého bloku, kdežto globální proměnné mimo něj. Dále platí, že lokální proměnné mají vyšší prioritu než globální. To znamená, že když vytvoříte lokální a globální proměnnou stejného jména, pak použitím proměnné s tímto jménem používáte lokální proměnou (samozřejmě v bloku, ve kterém byla definována, mimo něj / po skončení bloku nemá lokální proměnná platnost a tudíž mimo blok používáte globální proměnnou). Možná to zní trochu zmateně, ale z příkladu vám to bude jistě jasné.
Do lokálních proměnných lze započítat i parametry funkcí. Jejich priorita je mezi globálními a lokálními proměnnými. 1)

/*------------------------------------------------*/
/* c14/promenne.c                                 */
#include <stdio.h>

int a = 0, b = 0, c = 0;        /* globalni promenne */

void funkce(int a, int b)
{
    /*int a = -5;  // lokalni promenna se nemuze jmenovat jako parametr */
    printf("fce: a = %i, b = %i, c = %i\n", a, b, c);
}

int main(void)
{
    int c = 25;

    printf("main: a = %i, b = %i, c = %i\n", a, b, c);
    funkce(100, 200);
    return 0;
}

/*------------------------------------------------*/

Výstup z programu:

main: a = 0, b = 0, c = 25
fce: a = 100, b = 200, c = 0

Všimněte si, že díky lokální definici proměnné a ve funkci funkce() nelze použít ani globální proměnnou a, ani parametr a. 1)

O něčem trochu jiném jsou tzv. statické proměnné. Statické proměnné se deklarují pomocí klíčového slova static. Používají se v těle funkcí a rozdíl oproti „obyčejným“ proměnným je v tom, že se jejich hodnota po skončení průběhu funkce zachovává! Můžete tak díky ní třeba zaznamenávat kolikrát byla funkce spuštěna. Obyčejná, nestatická, proměnná se inicializuje při volání funkce vždy znova.
Další výhodou je, že při skončení funkce máte zajištěno, že statická proměnná stále existuje a tudíž ji (pomocí odkazu) můžete upravovat i mimo tělo funkce, zatímco paměť pro „obyčejnou“ proměnnou může překladač v zájmu optimalizace programu využívat pro jiné účely – po skončení funkce jsou všechny nestatické proměnné zničeny! Statické proměnné mají v paměti stále stejné (statické) místo, cože je i jejch malá nevýhoda, protože se tím sníží možná optimalizace programu.

Nikdy nepoužívejte jako návratovou hodnotu funkce adresu na nestatickou lokální proměnnou. Platnost nestatické lokální proměnné končí s ukončením těla bloku, ve kterém byla definována. Naopak statická lokální proměnná se do konce běhu programu nezruší.

/*------------------------------------------------*/
/* c14/static.c                                   */
#include<stdio.h>

char *funkce(void)
{
    static char pole[] = "Ahoj!";
    static int x = 1000;
    int y = 1000;
    x++;
    y++;
    printf("Funkce je volana po %i (%i)\n", x, y);
    return pole;
}

int main(void)
{
    char *uk;
    printf("%s\n", funkce());

    uk = funkce(); /* pomoci ukazatele zmenim statickou promennou */
    uk[0] = 'C';
    uk[1] = 'u';
    uk[2] = 's';
    uk[3] = '\0';

    printf("%s\n", funkce());

    return 0;
}

/*------------------------------------------------*/

Výstup z programu:

Funkce je volana po 1001 (1001)
Ahoj!
Funkce je volana po 1002 (1001)
Funkce je volana po 1003 (1001)
Cus

Statická proměnná se inicializuje jen jednou, ale pokud jí ve funkci přiřadíte hodnotu (mimo inicializaci), přiřadí se při každém volání funkce. Zkuste si místo řádku 8 napsat: static int x; x = 1000;

Statické funkce a globální proměnné

Klíčové slovo static můžete napsat i před globální proměnnou. Ta je ale statická už sama o sobě, ne? K čemu je to dobré?

Klíčové slovo static můžete napsat i před funkci. K čemu je to dobé?

Obojí má stejný význam, který je úplně jiný, než jsem psal o static výše. Takto označené funkce a globální proměnné jsou viditelné pouze v rámci svého zdrojového souboru. Z žádného jiného souboru, než ve kterém jsou definovány, se na ně nemůžete odkazovat.

Výhoda je jasná – můžete mít v různých zdrojových soborech stejně pojmenovanou funkci nebo globální proměnnou. Máte taky jistotu, že vám takovou globální proměnnou nezmění nikdo jiný, než jen funkce z vašeho souboru.

Zatím jsem se ještě nedostal k tomu, jak rozdělit zdroják do více souborů, takže vám tato informace momentálně není k užitku. Ale brzo bude :-)

Uživatelské knihovny funkcí

O využívání knihoven jsem již psal při popisování preprocesoru, takže je zde jenom připomenu.

Můžete si vytvářet knihovny funkcí (soubory mají nejčastěji příponou .h, pro jazyk C++ .hpp), v nichž budete mít napsány vlastní funkce, nebo definovaná makra. Při psaní velkého programu se tomu nevyhnete. V jednom souboru by měli být funkce, které spolu logicky souvisí. Například v jednom souboru funkce, které zpracovávají data (třeba něco počítají) a v jiném souboru funkce, která tato data zobrazují. Pokud pak takový program budete chtít přenést z textového režimu do grafického (třeba ve Windows), pak vám bude stačit změnit knihovnu, která zobrazuje výsledky (místo funkce printf() to bude nějaká funkce na zobrazování grafických dialogů atp.). To samé platí pro načítání vstupů atd. Vždy je dobré mít takovéto implementačně závislé funkce programu oddělené od těch nezávislých. Při přenosu programu z jednoho systém u na jiný se vám to tisíckrát vrátí.

V souboru .h můžete taky mít jenom deklarace funkcí a vlastní funkce můžete mít přeložené v nějaké knihovně funkcí (ve Windows jsou to soubory s příponou dll, v Linuxu s příponou so). Například stdio.h obsahuje deklarace funkcí, jejichž těla jsou v Linuxu přeloženy do systémové knihovny /lib/libc.so.6. Jak vytvářet takovéto knihovně budu popisovat až v části věnované programování v Linuxu (protože se to v každém OS dělá trochu jinak).

V ideálním případě by to mohlo vypadat tak, že máte např. soubor "main.c", ve kterém je definována funkce main(). Potom soubor "vypocty.h", který obsahuje všechny funkce pro výpočet a soubor "vystup1.h", který zobrazuje výsledek v textovém režimu a "vystup2.h", který obsahuje funkce se stejnými jmény a argumenty jako "vystup1.h", ale zobrazuje výsledky v grafickém režimu. Potom stačí v souboru main.c přidat buďto "vystup1.h" nebo "vystup2.h" a podle toho určit chování programu. Výpočty nezávisí na tom, jak chcete mít výsledky vypisovány – jsou implementačně nezávislé.

V jednoduchém příkladu využiji podmíněného překladu. Následující program je implementačně závislý. Přeložíte jej v Linuxu, Windows i v DOSu (např. ve starém dosovském překladači Borland C, který obsahuje knihovnu <dos.h> s funkcí delay()).

Soubor "dos1.h": používá k pozastavení programu funkci delay() z knihovny <dos.h>.

/*------------------------------------------------*/
/* c14/dos1.h                                     */
#include <dos.h>

void cekej(unsigned int cas)
{
    delay(cas);
}

/*------------------------------------------------*/

Soubor "windows1.h": používá k pozastavení programu funkci Sleep() z knihovny <windows.h>.

/*------------------------------------------------*/
/* c14/windows1.h                                 */
#include <windows.h>

void cekej(unsigned int cas)
{
    Sleep(cas);
}

/*------------------------------------------------*/

Soubor "unix1.h": používá k pozastavení programu funkci usleep() z knihovny <unistd.h>.

  1. /*------------------------------------------------*/
  2. /* c14/unix1.h                                    */
  3. #include <unistd.h>
  4.  
  5. void cekej(unsigned int cas)
  6. {
  7.     usleep((unsigned long) cas*1000);
  8. }
  9.  
  10. /*------------------------------------------------*/

V DOSu se k pozastavení programu používá funkce delay(), která je definována v knihovně <dos.h> a pozastaví program na zadaný počet milisekund, zatímco v Linuxu funkce usleep(), definována v knihovně <unistd.h>, pozastaví program na zadaný počet mikrosekund. Aby funkce cekej() dělala na obou OS to samé, linuxová verze svůj argument musí násobit 1000.

Funkce usleep má jako argument unsigned long, ale protože jsem chtěl funkci cekej() mít definovanou vždy stejně (s parametrem typu unsigned int), definoval jsem proměnnou cas jako unsigned int. Přetypování při použití s funkcí usleep() není nutné, překladač by to zvládnul i bez této nápovědy.

Soubor "cekej.c": všimněte si, kolik maker je třeba otestovat, abychom zjistili, zda jsme na Windows. Různé překladače totiž používají různá makra (nejsou nikde standardizována). Nejpravděpodobněji však budete mít definované makro __WINDOWS__ nebo __WIN32__ (a také __WIN16__ či __WIN64__ pro 16. a 64. bitové překladače – ty jsem v příkladu pro stručnost vynechal). Ostatní makra, bez dvou podtržítek na začátku a na konci, jsou již zastaralá.

*------------------------------------------------*/
/* c14/cekej.c                                    */
#include <stdio.h>

#if defined unix || defined __unix__
#include "unix1.h"
#define VERZE "UNIX"
#elif defined __MSDOS__
#define VERZE "MSDOS"
#include "dos1.h"
#elif defined __WINDOWS__ || defined _Windows || defined _WINDOWS || defined __WIN32__ || defined _WIN32 || defined WIN32
#include "windows1.h"
#define VERZE "WINDOWS"
#endif

#define CEKEJ 100

int main(void)
{
    int i, j;

    for (i = 0; i <= 100; i++) {
        printf("\rZdrzuji: [");
        for (j = 0; j < 10; j++) {
            printf("%c", (j * 10 <= i) ? '.' : ' ');
        }
        printf("] %3i%%", i);
        fflush(stdout);         /* vyprazdnime standardni vystup */
        cekej(CEKEJ);           /* implementacne zavisla funkce */
    }
    printf(" OK\n");
    return 0;
}

/*------------------------------------------------*/

Výstup z funkce printf() se obvykle ukládá do tzv. vyrovnávací paměti, než se skutečně vypíše na obrazovku. Pomáhá to zrychlovat vykreslování (protože vykreslit dlouhý text naráz je jednodušší, než vykreslit znak po znaku). Problém ale je, že když funkce cekej() pozastaví provádění programu, může být výstup z pfunkce printf() ještě pořád v bufferu a ne na obrazovce. Takže by to nakonec vypadalo tak, že se program jenom na několik vteřin pozastavil a pak vypsal vše naráz. Abych takovému chování předešel, použil jsem v programu funkci fflush(), která vypíše na obrazovku ihned všechno, co je ve vyrovnávací paměti (vyprázdní vyrovnávací paměť).

Ještě připomínámi, že escape sekvence '\r' přesune kurzor na začátek řádku. Jak bude probíhat výstup z programu si domyslete, nebo, ještě lépe, vyzkoušejte. Vyzkoušejte si taky přeložit a spustit program bez volání fflush().

Zdrzuji: [..........] 100% OK

A ještě jedna poznámka k příkladu na závěr. Logičtější by bylo umístit makro VERZE do knihoven k funkci cekej(). Tyto knihovny může využívat i jiný program než jen cekej.c, tak by bylo výhodnější mít všechny implementačně závislé věci v knihovnách.


1) Pokud má lokální proměnná stejné jméno jako argument funkce, pak jej „překryje“ a k hodnotě argumentu se už nijak nedostanete. Dříve vás na tento problém překladač upozornil varováním, dnes vám již vůbec nedovolí takovou konstrukci přeložit. Tím se situace zjednodušuje, argumenty funkce mají stejnou prioritu jako lokální proměnné (oba překrývají globální proměnné stejných jmen), ale navzájem nemohou mít stejná jména.

Komentář Hlášení chyby
Vytvořeno: 29.8.2003
Naposledy upraveno: 20.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..