| ← funkce | C/C++ | vytváření typů → |
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 slova, která zapíšete za název programu v příkazové řádce. Například v příkazu MOVE S1 S2 jsou slova S1 a S2 argumenty. Záměrně mluvím o slovech, neboť slova která jsou od sebe oddělena mezerou nebo tabulátorem jsou předávána zvlášť (za chvíli uvidíte jak). Pokud budete chtít předat větu jako jeden argument, pak jí musíte uzavřít do uvozovek.
Argumenty příkazové řádky je možné číst pomocí funkce 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] 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 volit libovolně, 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ážeme si 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. Abychom mohli porovnat jaké argumenty byli předány, napíšeme si funkci, která bude porovnávat dva textové řetězce. Bude vracet 1 (TRUE) v případě, že jsou schodné, jinak 0 (FALSE).
1: /*------------------------------------------------*/ 2: /* argum.c */ 4: #include <stdio.h> 6: #define VELKE "velke" 7: #define MALE "male" 9: #define VAROVANI printf("Zadejte: %s %s | %s ARGUMENT\n",argv[0],VELKE,MALE); 11: int shodne(char *v1, char *v2) 12: { 13: int i = 0; 14: /* pomoci zarazek '\0' zjistujeme konec retezce */ 15: while ((v1[i] != '\0') && (v2[i] != '\0')) { 16: if (v1[i] != v2[i]) 17: return 0; /* nejsou shodne; */ 18: i++; 19: } 20: if (v1[i] || v2[i]) { /* jeden retezec jeste nezkoncil, nejsou stejne */ 21: return 0; 22: } 23: return 1; /* vsechny znaky byly shodne */ 24: } 26: void velke(char *); 27: void male(char *); 29: int main(int argc, char *argv[]) 30: { 31: if (argc != 3) { 32: VAROVANI return 1; 33: } 35: if (shodne(VELKE, argv[1])) 36: velke(argv[2]); 37: else if (shodne(MALE, argv[1])) 38: male(argv[2]); 39: else { 40: VAROVANI return 1; 41: } 43: printf("Druhy argument byl zmenen na: %s\n", argv[2]); 44: return 0; 45: } 47: /* ve funkcich velke a male vyuzijeme vlastnosti ASCII tabulky */ 49: void velke(char *v) 50: { 51: int i = 0; 52: while (v[i]) { /* '\0' odpovida hodnote FALSE */ 53: if (v[i] >= 'a' && v[i] <= 'z') { 54: v[i] += ('A' - 'a'); /* rozdil v ASCII tabulce mezi 55: velkymi a malymi pismeny */ 56: } 57: i++; 58: } /* konec while */ 59: } 61: void male(char *v) 62: { 63: int i = 0; 64: while (v[i]) { 65: if (v[i] >= 'A' && v[i] <= 'Z') { 66: v[i] += ('a' - 'A'); 67: } 68: i++; 69: } 70: } 72: /*------------------------------------------------*/
Tento příklad 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 rovnou 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í.
V příkladu také využíváme 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íme 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 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 našem příkladě snižuje přenositelnost kódu.
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 nemá lokální proměnná platnost a tudíž
mimo blok používáte globální proměnnou). Možná to zní trochu
zmateně, ale v příkladu vám to bude jistě jasné.
Do lokálních proměnných lze započítat i argumenty funkcí.
Jejich priorita je mezi globálními a lokálními proměnnými.
1)
1: /*------------------------------------------------*/ 2: /* promenne.c */ 3: #include <stdio.h> 5: int a = 0, b = 0, c = 0; /* globalni promenne */ 7: void funkce(int a, int b) 8: { 9:int a = -5; /* lokalni promenne */1) 10: printf("fce: a = %i, b = %i, c = %i\n", a, b, c); 11: } 13: int main(void) 14: { 15: int c = 25; 17: printf("main: a = %i, b = %i, c = %i\n", a, b, c); 18: funkce(100, 200); 19: return 0; 20: } 22: /*------------------------------------------------*/
Výstup z programu:
main: a = 0, b = 0, c = 25 fce: a = -5, 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 statické proměnné. Statické proměnné se deklarujíc 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ůžeme tak díky ní třeba zaznamenávat kolikrát byla funkce spuštěna. Oproti tomu se „obyčejná“ proměnná inicializuje při volání funkce vždy znova. Další výhodou je, že při skončení funkce máme zajištěno, že statická proměnná stále existuje a tudíž jí pomocí adresy můžeme 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 (statickou proměnnou nemůže, a to je její 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ší.
1: /*------------------------------------------------*/ 2: /* static.c */ 3: #include <stdio.h> 5: char *funkce(void) 6: { 7: static char pole[] = "Ahoj!"; 8: static int x = 1000; 9: int y = 1000; 10: x++; 11: y++; 12: printf("Funkce je volana po %i (%i)\n", x, y); 13: return pole; 14: } 16: int main(void) 17: { 18: char *uk; 19: printf("%s\n", funkce()); 21: uk = funkce(); /* pomoci ukazatele zmenim statickou promennou */ 22: uk[0] = 'C'; 23: uk[1] = 'u'; 24: uk[2] = 's'; 25: uk[3] = '\0'; 27: printf("%s\n", funkce()); 29: return 0; 30: } 32: /*------------------------------------------------*/
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, přiřadí se při každém volání funkce. Zkuste si místo řádku 8 napsat: static int x; x = 1000;
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 (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 funkcí printf to budou nějaké funkce na zobrazování 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 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>.
1: /*------------------------------------------------*/ 2: /* dos1.h */ 3: #include <dos.h> 5: void cekej(int cas) 6: { 7: delay(cas); 8: } 10: /*------------------------------------------------*/
Soubor "windows1.h": používá k pozastavení programu funkci Sleep() z knihovny <windows.h>.
1: /*------------------------------------------------*/ 2: /* windows1.h */ 3: #include <windows.h> 5: void cekej(int cas) 6: { 7: Sleep(cas); 8: } 10: /*------------------------------------------------*/
Soubor "unix1.h": používá k pozastavení programu funkci usleep() z knihovny <unistd.h>.
1: /*------------------------------------------------*/ 2: /* unix1.h */ 3: #include <unistd.h> 5: void cekej(unsigned long cas) 6: { 7: usleep(cas); 8: } 10: /*------------------------------------------------*/
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á.
1: /*------------------------------------------------*/ 2: /* cekej.c */ 3: #include <stdio.h> 5: #ifdef unix 6: #include "unix1.h" 7: #define VERZE "UNIX" 8: #define CEKEJ 100000ul 9: #elif defined __MSDOS__ 10: #define VERZE "MSDOS" 11: #include "dos1.h" 12: #define CEKEJ 100 13: #elif defined __WINDOWS__ || defined _Windows || defined _WINDOWS || defined __WIN32__ || defined _WIN32 || defined WIN32 14: #include "windows1.h" 15: #define VERZE "WINDOWS" 16: #define CEKEJ 100 17: #endif 19: int main(void) 20: { 21: int i, j; 23: for (i = 0; i <= 100; i++) { 24: printf("\rZdrzuji: ["); 25: for (j = 0; j < 10; j++) { 26: printf("%c", (j * 10 <= i) ? '.' : ' '); 27: } 28: printf("] %3i%%", i); 29: fflush(stdout); /* vyprazdnime standardni vystup */ 30: cekej(CEKEJ); /* implementacne zavisla funkce */ 31: } 32: printf(" OK\n"); 33: return 0; 34: } 36: /*------------------------------------------------*/
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.
Funkce pozastaví provádění programu. To znamená, že se pozastaví i vypisování
funkcemi printf na standardní výstup stdout. Jelikož je
takovýto výstup značně pomalý, mohlo by se stát, že než se data na
výstupu vykreslí bude program pozastaven. 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á pozastaví provádění programu do té doby, než se souborový proud, který je
předáván jako argument, vyprázdní (tj. než se vše vypíše na obrazovku).
Připomínám jen, že escape sekvence
'\r' přesune kurzor na začátek řádku. Výstup z
programu si domyslete, nebo, ještě lépe, vyzkoušejte.
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že jen cekej.c, tak by bylo výhodnější mít všechny implementačně závislé věci v knihovnách. Stejně tak problém s unixovou funkcí usleep() by šel vyřešit tak, že by verze funkce cekej() pro unix svůj argument násobila 1000x. Makro CEKEJ by se pak definovalo jen 1x, pro všechny systémy stejně.
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ž 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).
| ← funkce | C/C++ | vytváření typů → |
