Czy muszę użyć fgets lub scanf z ograniczoną wprowadzeniem w c?

0

Pytanie

Czy muszę używać fgets lub sformatowany scanf Podoba scanf("%10s", foo).

Za wyjątkiem tego, że scanf nie czyta pustych znaków, które można rozwiązać i zrobić więcej za pomocą scanset, więc dlaczego muszę korzystać z fgets zamiast scanf?

Każda pomoc będzie wdzięczna.


Edytuj

Jeszcze jedna rzecz, o której chcę zapytać: nawet gdy używamy fgets co się stanie, jeśli użytkownik wprowadzi więcej znaków granicy (mam na myśli dużo znaków), czy będzie to prowadzić do przepełnienia bufora? Więc jak z tym walczyć?

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

Najlepsza odpowiedź

5

W większości systemów operacyjnych użytkownika domyślnie jest oparty na wierszach. Jednym z powodów jest to, aby umożliwić użytkownikowi nacisnąć klawisz backspace, aby poprawki wprowadzić przed wysłaniem wprowadzania do programu.

Do wejścia użytkownika na podstawie wierszy program ma sens i intuicyjna dla odczytu jednego wiersza wprowadzania na raz. To jest to, że funkcjafgets sprawia, że (pod warunkiem, że bufor jest wystarczająco duży do przechowywania całej linii poleceń).

Funkcjascanfz drugiej strony, zwykle nie czyta w jednym wierszu wprowadzania na raz. Na przykład, kiedy używasz %s lub %d specyfikator formatu konwersji z scanfsię , że nie będzie spożywać cały wiersz tekstu. Zamiast tego będzie zużywać tylko tyle danych wejściowych, ile odpowiada спецификатору formatu konwersji. Oznacza to, że znak nowej linii na końcu wiersza nie jest zazwyczaj używany (co może łatwo prowadzić do błędów programowania). Również, scanf zadzwonił z %d specyfikator formatu konwersji będzie wziąć pod uwagę takie dane wejściowe, jak 6sldf23dsfh2 jako dopuszczalne do wprowadzania do liczby 6ale wszelkie dalsze wezwania do scanf z tym samym спецификатором nie powiedzie się, jeśli nie usunąć pozostałą część wiersza ze strumienia wejściowego.

Takie zachowanie scanf wbrew intuicji, podczas gdy zachowanie fgets intuicyjny podczas pracy z niestandardowym wprowadzeniem na podstawie wierszy.

Po użyciu fgets, można użyć funkcjisscanf w wierszu do analizy zawartości jednego wiersza. To pozwoli ci kontynuować używać zestawów skanowania. Czy można analizować wiersz w jakikolwiek inny sposób. W każdym razie, dopóki używasz fgets zamiast scanf do odczytu danych wejściowych można obsłużyć w jednym wierszu wprowadzania na raz, co jest w naturalny i intuicyjny sposób pracy z niestandardowym wprowadzeniem na podstawie wierszy.

Kiedy używamy fgets co się stanie, jeśli użytkownik wprowadzi więcej znaków granicy (mam na myśli dużo znaków), czy będzie to prowadzić do przepełnienia bufora? Więc jak z tym walczyć?

Jeśli użytkownik wprowadzi więcej znaków, niż zmieści się w buforze, jak wskazano drugim fgets argument funkcji, to on nie będzie przepełniać bufor. Zamiast tego będzie pobierać strumień wejściowy tylko tyle znaków, ile zmieści się do schowka. Można określić, czy odczytać cały wiersz, sprawdzając, czy zawiera ciąg znaków znak nowej linii '\n' w końcu.

2021-11-23 15:13:39

Dziękuję bardzo, twoja odpowiedź jest naprawdę dobre dla mnie.
Becker

Zachowanie funkcji fgets() intuicyjnie zrozumiałe tylko dla danych wejściowych, których długość nie przekracza oczekiwanego.
supercat
2

To często omawianym tematem, pełna opinii, ale tym nie mniej ciekawa. Zauważyłem, że zdecydowana większość tych, którzy już odpowiedział na podobne pytania na tej stronie, przylegają fgets(). Jestem jednym z nich. Uważam, że fgets() aby było o wiele lepiej jest użyć dla użytkownika, niż scanf() z nielicznymi wyjątkami. scanf() jest uważany przez wielu za неоптимальный metoda przetwarzania danych wejściowych użytkownika. Na przykład

"...on powie ci, czy udało mu się to czy nie, ale może ci mówić tylko w przybliżeniu, gdzie poniósł porażkę, a nie jak, ani dlaczego. Masz bardzo mało możliwości do poprawki błędów".
(джеймсдлин). Ale w interesie osiągnięcia równowagi, zacznę od tej dyskusji.

Dla użytkownika, który pochodzi od stdin, czyli wprowadzanie danych z klawiatury, fgets() będzie najlepszym wyborem. Jest to o wiele bardziej usprawiedliwiony, ponieważ odczytu wiersz może być w pełni zweryfikowane przed próbą przekształcenia

Jeden z niewielu przypadków, kiedy można skorzystać z formularza scanf(): fscanf (), może być stosowany podczas konwersji danych wejściowych z bardzo kontrolowanego źródła, Czyli Podczas czytania ściśle sformatowanego pliku z powtarzających się przewidywalne polami.

Do dalszej dyskusji to porównywanie dwóch z nich podkreśla dodatkowe zalety i wady obu.

Zmiana: w celu rozwiązania dodatkowego pytania o błędzie przepełnienia:

"Jeszcze jedna rzecz, którą chcę zapytać: nawet gdy używamy fgets, co się stanie, jeśli użytkownik wprowadzi więcej znaków granicy (mam na myśli wiele znaków), czy będzie to prowadzić do przepełnienia bufora? To jak z tym walczyć?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm dobrze zaprojektowany aby uniknąć przepełnienia bufora, po prostu poprawnie za pomocą jego parametry, np.:

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

To zapobiega traktowanie wprowadzania dłuższy niż rozmiar bufora, co zapobiega przepełnieniu.

nawet przy użyciu scanf()zapobieganie przepełnienie buforu dość proste: użyj deskryptor szerokości w pasku formatu. Na przykład, jeśli chcesz przeczytać wprowadzone dane i ograniczyć rozmiar wprowadzania przez użytkownika nie więcej niż 100 znaków, kod będzie obejmować:

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

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

Jednak z numerami przepełnienie nie jest tak przyjemny w użyciu scanf(). Aby wykazać, ten prosty kod, wprowadzając dwie wartości podane w komentarzu po jednym na start:

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

Do drugiego znaczenia mój system wydaje się następujący:

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

Tutaj trzeba przeczytać wiersz, a następnie przeprowadzić konwersję wiersze na liczbę za pomocą jednego z strto_() funkcje: strtol(), strtod(), ...). Oba zawierają możliwość sprawdzenia na przepełnienie przed tym, jak spowodować ostrzeżenie lub błąd w czasie wykonywania. Należy pamiętać, że za pomocą atoi(), atod() również nie chroni przed zalaniem.

2021-11-23 14:10:20

Dziękuję, naprawdę doceniam twoją odpowiedź.
Becker

Pytanie "jest pełen opinii"? Muszę z szacunkiem nie zgodzić. To nie jest kwestia opinii, że scanf prawie całkowicie bezużyteczny, w najlepszym razie, jest dobry dla odczytu poszczególnych prostych danych wejściowych w programach wejścia-wyjścia w języku C, ale nadzwyczaj skomplikowane do wykonania czegoś zdalnie skomplikowanego — to oczywiste fakty! :-)
Steve Summit
1

Do tej pory wszystkie odpowiedzi tutaj stanowiły zawiłości scanf i fgetsale uważam, że warto wspomnieć, że obie te funkcje są przestarzałe w obecnym standardzie C. Scanf szczególnie niebezpieczny, bo ma różnego rodzaju problemy z bezpieczeństwem w przypadku zapełnienia bufora. fgets to nie jest tak problematyczne, ale z mojego doświadczenia, to, jak zwykle, trochę niezdarnie i nie bardzo przydatne w praktyce.

Prawda jest taka, że często tak naprawdę nie wiesz, jak długo będzie wprowadzane przez użytkownika. Można to obejść za pomocą fgets, mam nadzieję, że to będzie na tyle duży bufor, ale nie jest to do końca эллегантно. Zamiast tego często chcesz mieć dynamiczny bufor, który będzie wystarczająco duży do przechowywania każdego użytkownika. I to jest, gdy getline w grę wchodzi funkcja. Jest on używany do odczytu dowolnej liczby znaków od użytkownika, dopóki nie spotka \n. W rzeczywistości, ładuje cały wiersz w swoją pamięć w postaci wiersza.

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

Ta funkcja przyjmuje wskaźnik na dynamicznie przydzielana wiersz jako pierwszy argument, indeks na rozmiar przydzielonego buforu jako drugi argument i strumień jako trzeci argument. (w zasadzie, umieść stdin tam do wpisania w wierszu polecenia). I zwraca liczbę odczytanych znaków, w tym \n na końcu, ale nie wieńczy wartość null.

Tutaj można zobaczyć przykład użycia tej funkcji:

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;
}

Oczywiście, korzystanie z tej funkcji wymaga podstawowej wiedzy o drogowskazami i dynamicznej pamięci w C, jednak nieco bardziej skomplikowana natura getline to na pewno warto, ze względu na jej bezpieczeństwo i elastyczność.

Możesz przeczytać więcej o tej funkcji i innych funkcji wprowadzania dostępnych w C, na tej stronie: https://www.studymite.com/blog/strings-in-c Uważam, że jest w nim dość dobrze zestawione subtelności wprowadzania C.

2021-11-23 19:18:00

Dzięki za rady i linki, to mi bardzo pomaga.
Becker
1

Jeśli masz, na przykład, tablica znaków, ogłaszający jak

char s[100];

i chcesz przeczytać ciąg znaków, który zawiera wbudowane spacje, wtedy można użyć albo scanf w następujący sposób:

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

lub fgets Lubię:

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

Różnica między tymi dwoma połączeniami polega na tym, że wyzwanie scanf nie odczytuje znak nowej linii '\n' z bufora wejściowego. Aż fgets odczytuje znak nowej linii '\n' jeśli w tablicy znaków wystarczająco dużo miejsca.

Aby usunąć nowy symbol polecenia '\n' to jest zapisane w tablicy znaków po użyciu fgets możesz napisać, na przykład:

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

Jeśli ciąg wejściowy zawiera więcej niż 99 znaków, to oba połączenia odczytują tylko 99 znaków i dodać ciąg znaków z końcowym zerowym symbolem '\0'. Wszystkie pozostałe znaki nadal będą znajdować się w buforze wejściowym.

Istnieje problem z fgets. Na przykład, jeśli wcześniej fgets tam używa scanf jak, na przykład:

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

i wprowadzanie danych przez użytkownika jest:

10
Hello World

następnie rozległo się wołanie fgets będzie odczytywać tylko znak nowej linii '\n' który jest zapisywany w buforze po naciśnięciu klawisza Enter, gdy wartość całkowita w wywołaniu scanf został odczytany.

W tym przypadku trzeba napisać kod, który usunie znak nowej linii '\n' zanim zadzwonić fgets.

Można to zrobić na przykład w następujący sposób:

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

Jeśli używasz scanf więc w takiej sytuacji można napisać:

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

W innych językach

Ta strona jest w innych językach

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