2012-02-14 13 views
34

Wiele osób twierdzi, że scanf nie powinno być używane w "bardziej poważnym programie", tak samo jak w przypadku getline.Jaki jest najlepszy sposób uzyskania danych wejściowych od użytkownika w C?

Zacząłem się gubić: jeśli każda funkcja wejściowa, którą spotkałem, powiedziała, że ​​nie powinienem używać żadnego z nich, to co powinienem użyć? Czy istnieje "standardowy" sposób na uzyskanie informacji, których nie jestem świadomy?

+1

To bardzo zależy od tego, co robi twój program zrobić. Polecam (prawie w każdej sytuacji) użycie 'scanf' i sprawdzenie jego wartości zwracanej. – asaelr

+1

'scanf' jest standardową metodą uzyskiwania sformatowanych danych wejściowych w języku C, a' fgets'/'fgetc' jest zalecaną funkcją standardową służącą do uzyskiwania całych linii lub pojedynczych znaków. Większość innych funkcji jest niestandardowych lub zależnych od platformy. –

+1

Nie ma powodu, aby nie używać 'getline' w poważnym programie. Jeśli twoja platforma go nie ma, możesz ją zaimplementować w terminach 'fgets' i' malloc'. –

Odpowiedz

33

Ogólnie rzecz biorąc, fgets() jest uważana za dobrą opcję. Czyta całe linie w buforze, a stamtąd możesz zrobić to, czego potrzebujesz. Jeśli chcesz zachowanie takie jak scanf(), możesz przekazać ciągi, które czytasz, do sscanf().

Główną zaletą tego jest to, że jeśli ciąg nie może się przekonwertować, łatwo jest odzyskać, natomiast z scanf() masz dane wejściowe na stdin, które musisz odcedzić. Co więcej, nie wpadniesz w pułapkę mieszania wejścia zorientowanego liniowo z scanf(), co powoduje bóle głowy, gdy na przykład zostaje pozostawione na stdin, co zwykle prowadzi nowych programistów do przekonania, że ​​połączenia wejściowe zostały całkowicie zignorowane.

Coś takiego może być do swoich potrzeb:

char line[256]; 
int i; 
if (fgets(line, sizeof(line), stdin)) { 
    if (1 == sscanf(line, "%d", &i)) { 
     /* i can be safely used */ 
    } 
} 

Przede należy pamiętać, że fgets() powraca NULL na EOF lub błędów, dlatego ja owinął je w if. Wywołanie sscanf() zwraca liczbę pól, które zostały pomyślnie przekonwertowane.

Należy pamiętać, że fgets() może nie odczytać całej linii, jeśli linia jest większa niż bufor, co w "poważnym" programie jest z pewnością czymś, co należy wziąć pod uwagę.

+1

Co powiecie na dodanie 'fflush (stdin)' zaraz po 'fgets()', aby upewnić się, że zbyt duży bufor wejściowy (więcej niż 255 znaków) nie wpłynie na kolejne wywołania funkcji 'fgets()'? – Twonky

+1

@Twonky: 'fflush (stdin)' jest niezdefiniowane jeśli chodzi o C (to * robi * to, co sugerujesz na niektórych platformach). Ale tak, to może być bardziej odporne pod tym względem. – FatalError

11

Aby uzyskać proste wejście, w którym można ustawić stały limit długości wejścia, polecam odczytanie danych z terminala przy pomocy fgets().

To dlatego fgets() pozwala określić rozmiar bufora (w przeciwieństwie do gets(), które z tego powodu powinny dość dużo nigdy być wykorzystany do odczytu wejścia od ludzi):

char line[256]; 

if(fgets(line, sizeof line, stdin) != NULL) 
{ 
    /* Now inspect and further parse the string in line. */ 
} 

pamiętać, że zachowa np znak (y) wprowadzający linię, co może być zaskakujące.

AKTUALIZACJA: Jak wskazano w komentarzu, istnieje lepsza alternatywa, jeśli jesteś w porządku z otrzymaniem odpowiedzialności za śledzenie pamięci: getline(). Jest to prawdopodobnie najlepsze rozwiązanie ogólnego przeznaczenia dla kodu POSIX, ponieważ nie ma żadnego statycznego ograniczenia długości czytanych linii.

+0

"proste wejście, w którym można ustawić stały limit długości wejścia" nie brzmi jak "poważne" programy. Funkcja POSIX 'getline' jest znacznie bardziej elastyczna. –

+0

@larsmans Świetny połów, dzięki za wskazanie tego na zewnątrz. – unwind

+0

Gdybym mógł umieścić 2 zwycięzców na moje pytanie, byłbyś drugi, ze względu na szczegół getline(), a zachowanie się wyjaśni. Ale odpowiedź FatalError miała więcej szczegółów na temat korzystania z tej techniki. – user1115057

2

Użyj fgets, aby uzyskać dane i użyj sscanf (lub innej metody), aby je zinterpretować.

Zobacz tę stronę, aby dowiedzieć się, dlaczego lepiej jest użyć fgets + sscanf zamiast scanf

http://c-faq.com/stdio/scanfprobs.html

5

Istnieje kilka problemów związanych z użyciem scanf:

  • czytanie tekstu ze równinie Specyfikacja konwersji %s ma to samo ryzyko co przy korzystaniu z gets(); jeśli użytkownik wpisze ciąg, który jest dłuższy niż rozmiar bufora docelowego, który ma być zatrzymany, otrzymasz przepełnienie bufora;

  • przypadku korzystania %d lub %f czytać wejście numerycznej, niektóre złe wzorce nie mogą zostać pochwycone i odrzucone całkowicie - jeśli czytasz liczbę całkowitą z %d a użytkownik wpisze „12r4”, scanf przekonwertuje i przypisać 12 pozostawiając r4 w strumieniu wejściowym, aby zakarzyć następny odczyt;

  • niektóre specyfikatory konwersji pomijają wiodące białe znaki, inne nie, a niepoprawne uwzględnienie tego może prowadzić do problemów, w których niektóre dane wejściowe są całkowicie pomijane;

Zasadniczo zajmuje dużo dodatkowego wysiłku, aby kuloodporny czyta użyciu scanf.

Dobrą alternatywą jest, aby przeczytać wszystkie dane wejściowe jako tekst przy użyciu fgets(), a następnie tokenize i konwertować dane wejściowe za pomocą sscanf lub kombinacje strtok, strtol, strtod itd

+1

Dzięki, teraz wiem, dlaczego ludzie mówią, że scanf może być zły. – user1115057

Powiązane problemy