Vstup
Abychom mohli našim programům dávat příkazy nebo parametrizovat jejich chování, téměř vždy v nich
potřebujeme přečíst nějaké informace ze vstupu programu. V této sekci si ukážeme několik užitečných
funkcí ze standardní knihovny C, které nám to umožňují. Pro použití těchto
funkcí musíte ve svém programu vložit soubor <stdio.h>
.
Načtení jednoho znaku
Pro načtení jednoho znaku ze standardního vstupu (stdin
) můžeme použít funkci
getchar
. Ta nám vrátí jeden znak ze vstupu, popřípadě hodnotu
makra EOF
1, pokud již je vstup uzavřený a nelze z něj nic dalšího načíst nebo pokud došlo při
načítání k nějaké chybě.
1End-of-file
Načtení řádku
Načítat vstup po jednotlivých znacích je poměrně zdlouhavé. Velmi často chceme ze vstupu načíst
jeden řádek textu. Toho můžeme dosáhnout například použitím funkce
fgets
. Ta jako parametry přijímá ukazatel na řetězec, do kterého
zapíše načítaný řádek, maximální počet znaků, který lze načíst2. Třetí parametr je
soubor, ze kterého se má vstup načíst. O souborech se dozvíte více později,
pokud chcete načítat data ze standardního vstupu, tak použijte jako třetí parametr globální proměnnou
stdin
, která je nadefinována v souboru <stdio.h>
. Pro jednoduché zjištění délky řetězce, do
kterého zapisujete, můžete použít operátor sizeof
:
2Tato velikost je včetně znaku '\0'
, který je vždy zapsán na konec vstupního řetězce. Pokud
tak máte řetězec (pole) o délce 10
, předejte do fgets
hodnotu 10
. Funkce načte maximálně 9
znaků a na konec řetězce umístí znak '\0'
.
#include <stdio.h>
int main() {
char buf[80];
// načti řádek textu ze vstupu do řetězce `buf`
fgets(buf, sizeof(buf), stdin);
return 0;
}
Pokud tato funkce vrátí návratovou hodnotu NULL
, tak při načítání došlo k chybě. Tuto chybu byste
tak ideálně měli nějak ošetřit:
#include <stdio.h>
int main() {
char buf[80];
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("Nacteni dat nevyslo. Ukoncuji program\n");
return 1;
}
return 0;
}
Načtení formátovaného textu
Pokud chceme načítat text, který má očekávaný formát, popřípadě chceme text rovnou zpracovat,
například jej převést na číslo, můžeme použít formátované načítání vstupu pomocí funkce
scanf
. Této funkci předáme tzv. formátovací řetězec (format
string), který udává, jak má vypadat vstupní text. V tomto řetězci můžeme používat různé zástupné
znaky. Za každý zástupný znak ve formátovacím řetězci scanf
očekává jeden parametr s adresou, do
které se má uložit načtená hodnota popsaná zástupným znakem ze vstupu. Například tento kód načte
ze vstupu dvě celá čísla:
int x, y;
scanf("%d%d", &x, &y);
Pomocí formátovacího řetězce můžeme také vyžadovat, co musí v textu být. Například scanf("x%d", ...)
načte vstup pouze, pokud v něm nalezne znak 'x'
následovaný číslem.
Seznam všech těchto zástupných znaků naleznete v dokumentaci.
Načítat můžeme například celá čísla (%d
), desetinná čísla (%f
) či znaky (%c%
).
Funkce
scanf
načítá data ze standardního vstupu programu (stdin
). Obsahuje ovšem několik dalších variant, pomocí kterých může načítat formátovaná data z libovolného souboru (fscanf
) nebo třeba i z řetězce v paměti (sscanf
).
Funkce scanf
je jistě užitečná, zejména u krátkých "toy" programů, nicméně má také určité problémy,
které jsou popsány níže. Pokud to je tedy možné, pro načítání vstupu raději používejte funkci fgets
.
Načítání řetězců pomocí scanf
Pomocí scanf
můžeme načítat také celé řetězce pomocí zástupného znaku %s
. Zde si ovšem musíme
dávat pozor, abychom u něj uvedli i maximální délku řetězce, do kterého chceme text načíst3:
3Narozdíl od funkce fgets
se zde musí uvést délka o jedna menší, než je délka cílového řetězce,
do kterého znaky zapisujeme.
char buf[21];
scanf("%20s", buf);
Pokud bychom použili zástupný znak %s
bez uvedené velikosti cílového řetězce, snadno by se mohlo
stát, že nám uživatel zadá moc dat, které by funkce scanf
začala vesele zapisovat i za paměť předaného
řetězce, což může vést buď k pádu programu (v tom lepším případě) nebo ke vzniku bezpečnostní
zranitelnosti, pomocí které by uživatel našeho programu mohl například získat přístup k počítači,
na kterém program běží (v tom horším případě):
char buf[21];
// pokud uživatel zadá více než 20 znaků, může svým vstupem začít přepisovat paměť
// běžícího programu
scanf("%s", buf);
Zpracování bílých znaků
Funkce scanf
ignoruje bílé znaky (mezery, odřádkování, tabulátory atd.) mezi jednotlivými
zástupnými znaky ve formátovacím řetězci. Například v následujícím kódu je validním vstupem x8
,
x 8
i x 8
:
int a;
scanf("x%d", &a);
I když může toto chování být užitečné, někdy je také celkem neintuitivní. Problém může způsobovat
zejména, pokud se pro načítání vstupu kombinuje formátované načítání (scanf
) s neformátovaným
načítáním (např. fgets
). Funkce scanf
totiž bílé znaky nechá ve vstupu ležet, pokud je
nepotřebuje zpracovat.
Například, následující program načítá číslo pomocí funkce scanf
a poté se snaží načíst následující
řádek textu pomocí funkce fgets
:
int cislo;
scanf("%d", &cislo);
char radek[80];
fgets(radek, sizeof(radek), stdin);
Pokud tomuto programu předáme text 5\nahoj
, očekávali bychom, že se v řetězci radek
objeví
ahoj
. Nicméně funkce scanf
načte číslo 5
a nechá ve vstupu ležet znak odřádkování, protože
nic dalšího načíst nepotřebuje. Funkce fgets
poté uvidí znak odřádkování, načte jej a skončí
své provádění (načte prázdný řádek), což zřejmě není chování, které bychom od programu čekali.
Ošetření chyb
Funkce scanf
je problematická i co se týče ošetření chyb. Její návratová hodnota sice udává, kolik
zástupných znaků ze vstupu se jí podařilo načíst, problémem však je, že pokud se funkce načte třeba
pouze polovinu vstupu, tak již nemůžeme zavolat znovu se stejným formátovacím řetězcem, jinak by se
snažila načíst data, která již načetla. Například pokud bychom tomuto programu:
int x, y;
scanf("%d%d", &x, &y);
předali text 5 asd
, tak funkce vrátí hodnotu 1
, tj. načetla ze vstupu jedno číslo. Nyní ovšem už
funkci nemůžeme zavolat znovu (jakmile bychom např. ve vstupu přeskočili nevalidní text), protože
v této chvíli už bychom chtěli načíst pouze jedno číslo.
Parametry příkazového řádku
Další možností, jak předat nějaký vstup vašemu programu, je předat mu parametry při spuštění v terminálu:
$ ./program arg1 arg2 arg3
K těmto předaným řetězcům poté lze přistoupit ve funkci
main
.