Mám použít fgets nebo scanf s omezenou vstup v c?

0

Otázka

Mám použít fgets nebo formátovaný scanf jako scanf("%10s", foo).

S výjimkou, že scanf nečte prázdné znaky, které mohou být řešeny a udělat více krmiv s scanset, tak proč bych měl použít fgets místo scanf?

Jakýkoliv pomoci chtěl bych být ocenil.


Edit

Jedna věc, kterou chci se zeptat, je: i když používáme fgets co se stane, když uživatel zadat více znaků, než je hranice (tím myslím hodně postav), má to vést k přetečení vyrovnávací paměti? Pak, jak se s tím vypořádat?

c fgets input scanf
2021-11-23 13:53:00
4

Nejlepší odpověď

5

Na většině operačních systémů, vstup uživatele je ve výchozím nastavení linky na bázi. Jedním z důvodů pro to je, aby uživatel stisknout backspace na správný vstup, před odesláním vstup do programu.

Pro line-založené na vstup uživatele, je smysluplné a intuitivní program, číst jeden řádek z vstupní najednou. To je to, co funkce fgets ano (za předpokladu, že buffer je dostatečně velký pro uložení celý řádek vstupu).

Funkce scanfna druhou stranu, obvykle nemá číst jeden řádek z vstupní najednou. Například, když budete používat %s nebo %d konverze formátu specifikátor s scanfto nebude spotřebovávat celý řádek vstupu. Místo toho, to bude jen konzumovat, stejně jako vstupní odpovídá konverzní specifikátor formátu. To znamená, že znaku nového řádku na konec řádku bude obvykle neměly být konzumovány (což může snadno vést k programování chyby). Také, scanf nazývá se %d konverze formátu specifikátor zváží vstup jako 6sldf23dsfh2 jako platný vstup pro počet 6, ale žádné další volání scanf se stejným specifikátor se nezdaří, pokud zlikvidujte zbytek řádku ze vstupního proudu.

Toto chování scanf je pult-intuitivní, vzhledem k tomu, že chování fgets je intuitivní, při jednání s line-založené na vstup uživatele.

Po použití fgets, můžete použít funkci sscanf na provázku, pro analýzu obsahu jednotlivých řádek. To vám umožní pokračovat v používání scansets. Nebo můžete analyzovat line jiným způsobem. Buď jak buď, pokud používáte fgets místo scanf pro čtení vstupu, bude zpracování jeden řádek vstupu v čase, což je přirozené a intuitivní způsob, jak se vypořádat s line-založené na vstup uživatele.

Když jsme se použít fgets co se stane, když uživatel zadat více znaků, než je hranice (tím myslím hodně postav), má to vést k přetečení vyrovnávací paměti? Pak, jak se s tím vypořádat?

Pokud uživatel zadá více znaků, než vejde do vyrovnávací paměti, jak je uvedeno po druhé fgets argument funkce, pak to nebude přetečení vyrovnávací paměti. Místo toho, to bude jen extrakt jako mnoho znaků ze vstupního proudu, jak vejde do vyrovnávací paměti. Můžete určit, zda je celý řádek byl přečten kontrolu, zda řetězec obsahuje znaku nového řádku '\n' na konci.

2021-11-23 15:13:39

Mockrát vám děkuji, vaše odpověď opravdu užitečné pro mě.
Becker

Chování fgets() je pouze intuitivní pro vstupy, které nejsou delší, než se očekávalo.
supercat
2

Toto je často diskutované téma, plné názor, ale zajímavé, nic méně. Všiml jsem si, že velká většina z těch, které již reagovaly na podobné otázky na tomto webu padat na stranu fgets(). Já jsem jeden z nich. Najít fgets() mnohem lepší použít pro vstup uživatele, než scanf() s několika výjimkami. scanf() je zvažován mnoho jako sub-optimální metodu pro zpracování uživatelského vstupu. Například

"...to vám řekne, zda se to podařilo nebo nepodařilo, ale můžu říct jen přibližně, kde to nepodařilo, a už vůbec ne, jak nebo proč. Ty mají velmi málo příležitostí udělat žádnou chybu."
(jamesdlin). Ale v zájmu pokusu rovnováhy, začneme s odkazem na tuto diskuzi.

Pro vstup uživatele, který pochází z stdin, tj. vstup z klávesnice, fgets() bude lepší volbou. To je mnohem více tolerantní v tom, že řetězec to čte může být plně validovány před konverzí je pokus o

Jeden z mála případů pomocí formuláře scanf(): fscanf() by být v pořádku používat, může být při převodu vstup z velmi řízené zdroje, tj. od čtení čistě formátovaný soubor s opakující se předvídatelné pole.

Pro více diskuse, toto srovnání dvou zdůrazňuje další výhody a nevýhody obou.

Edit: na adresu OP doplňující otázku o přetečení:

"Jedna věc, kterou chci se zeptat, je: i když jsme použít fgets co se stane, když uživatel zadat více znaků, než je hranice (tím myslím hodně znaků), má to vést k přetečení vyrovnávací paměti? Pak, jak se vypořádat s to?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm je pěkně navržen tak, aby se zabránilo přetečení vyrovnávací paměti, jednoduše tím, že pomocí jeho parametry správně, např.:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Tím se zabrání vstupu větší než velikost vyrovnávací paměti, z které jsou zpracovávány, čímž se zabrání přetečení.

dokonce i pomocí scanf()předcházení přetečení vyrovnávací paměti je docela rovně vpřed: Použití specifikátor šířky ve formátu string. Pokud si chcete přečíst vstup pro příklad a limit velikosti vstupu od uživatele do 100 znaků, kód by zahrnovat následující:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

Nicméně s čísly, přetečení není tak příjemné použití scanf(). Prokázat, použijte tento jednoduchý kód, zadejte dvě hodnoty uvedené v komentáři jeden za běhu:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

Pro druhou hodnotu, můj systém hodí následující:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Zde je třeba číst v řetězci, pak postupujte s řetězec na číslo konverze pomocí jednoho z strto_() funkce: strtol(), strtod(), ...). Oba patří schopnost test na přetečení , než způsobí run-time upozornění nebo chyba. Všimněte si, že použití atoi(), atod() nebude chránit od přetečení.

2021-11-23 14:10:20

Děkuji, opravdu si cením vaší odpovědi.
Becker

Otázka "plné názor"? Musím zdvořile nesouhlasit. To není věc názoru, že scanf je téměř úplně k ničemu, dobrá na to nejlepší pro čtení jednotné, jednoduché vstupy v intro-k-C programů, ale příliš těžké udělat něco vzdáleně sofistikované s — tyhle jsou jasná fakta! :-)
Steve Summit
1

Zatím všechny odpovědi zde prezentovány složitosti scanf a fgets, ale to, co jsem přesvědčen, stojí za zmínku, je, že obě tyto funkce jsou zastaralé v C aktuální standard. Scanf je zvláště nebezpečné, protože to má všechny možné bezpečnostní problémy s přetečení vyrovnávací paměti. fgets není tak problematické, ale z mé zkušenosti, to má tendenci být trochu neohrabaný, a ne-tak-užitečné v praxi.

Pravdou je, že často nemáte opravdu vědět, jak dlouho uživatel vstup bude. Můžete se dostat kolem tohoto tím, že pomocí fgets s doufám, že to bude dostatečně velké vyrovnávací paměti, ale to není moc elegantní. Místo toho, co si často chtějí udělat, je mít dynamické vyrovnávací paměti, že bude růst dostatečně velké, aby ukládat, co uživatel vstup přinese. A to je, když getline funkce přichází do hry. Používá se k číst libovolný počet znaků od uživatele, dokud \n se setkal. V podstatě, to načte celý řádek do paměti, jako řetězec.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Tato funkce má ukazatel na dynamicky alokované řetězec jako první argument, a ukazatel na velikost přidělené vyrovnávací paměti jako druhý argument a proud jako třetí argument. (budete v podstatě místo stdin tam pro command-line input). A vrátí počet načtených znaků, včetně \n na konci, ale ne ukončení hodnotu null.

Zde můžete vidět příklad použití této funkce:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Samozřejmě, že použití této funkce vyžaduje základní znalosti o ukazatele a dynamické paměti v C, ale mírně složitější povahy getline rozhodně stojí za to, protože za předpokladu, bezpečnost a flexibilitu.

Můžete si přečíst více o této funkci a dalších vstupních funkcí, k dispozici v C, na této webové stránce: https://www.studymite.com/blog/strings-in-c Věřím, že to shrnuje složitosti C vstup docela dobře.

2021-11-23 19:18:00

Díky za radu a odkaz, to mi hodně pomáhá.
Becker
1

Pokud budete mít například charakter pole deklarované jako

char s[100];

a chcete si přečíst řetězec, který obsahuje vložené mezery, pak můžete použít buď scanf následujícím způsobem:

scanf( "%99[^\n]", s );

nebo fgets jako:

fgets( s, sizeof( s ), stdin );

Rozdíl mezi těmito dvěma telefony, je to volání scanf nečte znak nového řádku '\n' ze vstupní vyrovnávací paměti. Zatímco fgets čte znak nového řádku '\n' pokud tam je dost místa na pole znaků.

Odstranit znak nového řádku '\n' který je uložen do pole znaků po použití fgets můžete napsat například:

s[ strcspn( s, "\n" ) ] = '\0';

Pokud vstupní řetězec má více než 99 znaků pak oba hovory číst pouze 99 znaků a připojit posloupnost znaků s ukončovací nulový znak '\0'. Všechny zbývající znaky budou stále v vstupní vyrovnávací paměti.

To je problém s fgets. Například, pokud před fgets tam se používá scanf jako například:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

a vstup uživatele je:

10
Hello World

pak volání fgets bude číst pouze znak nového řádku '\n' který je uložen v paměti po stisknutí tlačítka Enter, když je celočíselná hodnota v call of scanf byla přečtena.

V tomto případě musíte napsat kód, který bude odstranit znak nového řádku '\n' před voláním fgets.

Můžete to udělat například následujícím způsobem:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Pokud používáte scanf pak v takové situaci, můžete napsat:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................