Funkce II
Argumenty příkazové řádky
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:
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:
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).
- /*------------------------------------------------*/
- /* c14/argum.c */
- #include <stdio.h>
- #include <stddef.h>
- #define VELKE "velke"
- #define MALE "male"
- #define VAROVANI printf("Zadejte: %s %s | %s ARGUMENT\n",argv[0],VELKE,MALE);
- {
- /* pomoci zarazek '\0' zjistuti konec retezce */
- i++;
- }
- }
- }
- {
- VAROVANI
- }
- velke(argv[2]);
- male(argv[2]);
- else {
- VAROVANI
- }
- }
- /* ve funkcich velke a male vyuziji vlastnosti ASCII tabulky */
- {
- /* rozdil v ASCII tabulce mezi velkymi a malymi pismeny */
- /* 'A' == 65, 'a' == 97 */
- v[i] += ('A' - 'a');
- }
- i++;
- } /* konec while */
- }
- {
- v[i] += ('a' - 'A');
- }
- i++;
- }
- }
- /*------------------------------------------------*/
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):
Zadejte: /tmp/argum velke | male ARGUMENT
$ /tmp/argum velke "Ahoj Svete"
Druhy argument byl zmenen na: AHOJ SVETE
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>.
Soubor "windows1.h": používá k pozastavení programu funkci
Sleep()
z knihovny <windows.h>.
Soubor "unix1.h": používá k pozastavení programu funkci
usleep()
z knihovny <unistd.h>.
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.