• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

[C] fgets vs scanf

VPS Starter Arubacloud
0 głosów
1,558 wizyt
pytanie zadane 28 marca 2017 w C i C++ przez Kamil Duljas Użytkownik (990 p.)

Witam. Zastanawiam sie nad działaniem funkcji fgets. Otóż powinna działać podobnie do funkcji scanf, a przynajmniej wczytywać poprawnie znaki. Mam tutaj prostą funkcję zaczerpniętą z książki "C. Rusz głową". Autor podaje zastosowanie funkcji fgets w programie, który ma na celu znalezienie fragmentu łańcucha i zwrócenie odpowiedniego wiersza tablicy. Problemem jest to, że użycie fgets nie działa poprawnie, a zastosowanie funkcji scanf daje poprawny wynik. Co więcej, próba wywołania dwa razy funkcji, pierw dla scanf, potem dla fgets, kończy się jej pominięciem:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char tracks[][80] =
{"raz",
"dwa",
"trzy",
"cztery"
};

void find(char search[])
{
int i;
for(i=0; i<5; i++){
    if(strstr(tracks[i],search))
    fprintf(stdout,"Utwor %i: '%s'\n", i, tracks[i]);
}}

int main()
{   char lista[80];
    printf("Wpisz utwor (scanf):\n");
    scanf("%79s",lista);
    find(lista);
    printf("Wpisz utwor (fgets):\n");
    fgets(lista,79,stdin);
    find(lista);
printf("koniec");
    return 0;
}

Co może być przyczyną tego, że funkcja fgets nie przekazuje łańcucha do funkcji find?

 

1 odpowiedź

+1 głos
odpowiedź 28 marca 2017 przez Evelek Nałogowiec (28,960 p.)
edycja 30 marca 2017 przez Evelek
 
Najlepsza

Powodem takiego zachowania jest to, że scanf() wczytuje wszystkie znaki aż do napotkania znaku nowej linii '\n' lub wczytaniu 79 znaków które podałeś w tej funkcji: %79s. Gdy napotka znak '\n', zostawia go w buforze. Następnie gdy chcesz wczytać kolejny łańcuch znaków przez funkcję fgets() nie możesz tego zrobić, bo w buforze nadal siedzi znak nowej linii '\n'. Zostaje on wczytany przez funkcję fgets(), która również wczytuje znaki aż do napotkania znaku nowel linii '\n' lub przepełnienia bufora w tym przypadku podanego przez ciebie 79.

Rozwiązaniem tego problemu jest po każdym wczytaniu do bufora ciągu znaków jego "czyszczenie". Można to zrobić np. w ten sposób:

scanf("%79s", lista);
	while (getchar() != '\n')
		continue;

 

komentarz 29 marca 2017 przez Kamil Duljas Użytkownik (990 p.)
Okej, drugim problemem jest to, że funkcja "find" nie działa przy użyciu fgets. Czyli wyrzucamy scanf i próbujemy wczytać argumenty za pomocą fgets.
komentarz 29 marca 2017 przez Evelek Nałogowiec (28,960 p.)
Jak to nie działa?
komentarz 29 marca 2017 przez Kamil Duljas Użytkownik (990 p.)

Przepraszam za tą niejasność. Program nie wykonuje funkcji po wczytaniu poprzez fgets, bynajmniej nie wyświetla nic na standardowe wyjście.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char tracks[][80] =
{"raz",
"dwa",
"trzy",
"cztery"
};

void find(char search[])
{
int i;
for(i=0; i<5; i++){
    if(strstr(tracks[i],search))
    fprintf(stdout,"Utwor %i: '%s'\n", i, tracks[i]);
}}

int main()
{   char lista[80];
    printf("Wpisz utwor (fgets):");
    fgets(lista,79,stdin);
    find(lista);
printf("koniec");
    return 0;
}

 

komentarz 29 marca 2017 przez Evelek Nałogowiec (28,960 p.)
Z pamięci piszę bo nie mam kompilatora pod ręką, ale czy nie powinno być: char tracks[4][80] ? Poza tym lepsze jest: const char *tracks[4] = { ... }
komentarz 29 marca 2017 przez Evelek Nałogowiec (28,960 p.)
A jako argument funkcji find zapisalbym char *search zamiast char search[].
komentarz 29 marca 2017 przez Kamil Duljas Użytkownik (990 p.)
Sprawdzałem i skutek ten sam. Chodzi o to, że ta funkcja działa bez zarzutu gdy wczytuje argument za pomocą scanf, a gdy próbuje to zrobić za pomocą fgets to program się wykona, ale na ekranie jest puste pole.
komentarz 29 marca 2017 przez Evelek Nałogowiec (28,960 p.)

Bo fgets() zostawia w łańcuchu znak nowej linii. Porównywane jest np. raz\n == raz i nigdy nie jest to spełnione. Trzeba samemu zamieniać '\n' na '\0', więc np.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *tracks[80] = { "raz", "dwa", "trzy", "cztery" };

void find(char search[])
{
	int i;
	for (i = 0; i<4; i++) {
		if (strstr(tracks[i], search))
			fprintf(stdout, "Utwor %i: '%s'\n", i, tracks[i]);
	}
}

char *wczytaj(char *z, int ile)
{
	char *wynik;
	int i = 0;

	wynik = fgets(z, ile, stdin);
	if (wynik) {
		while (z[i] != '\n' && z[i] != '\0')
			i++;
		if (z[i] == '\n')
			z[i] = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return wynik;
}

int main()
{
	char lista[80];
	printf("Wpisz utwor (fgets):");
	wczytaj(lista, 80);
	find(lista);
	printf("koniec");

	getchar();
	return 0;
}

 

komentarz 30 marca 2017 przez Kamil Duljas Użytkownik (990 p.)
Dziękuję :) Z równoległego źródła też znalazłem informację o tym, że fgets wstawia \n. Mam pytanie jeszcze o komendę "continue". Do której linii kodu będzie przekierowywać? Jest póki co mało intuicyjna dla mnie.
komentarz 30 marca 2017 przez Evelek Nałogowiec (28,960 p.)
continue powoduje, że dana pętla jest wykonywana raz jeszcze. Czyli tak jak w przykładzie: while(getchar() != '\n') continue; Czyli funkcja getchar() pobiera pojedynczy znak z bufora. Jeśli jest nim coś innego niż '\n' to pętla sie wykonuje ponownie (continue). Kiedy pobierze znak '\n' pętla while zostaje przerwana. W przypadku pętli masz dwa takie słowa kluczowe: continue, które powoduje, że pętla jest wykonywana raz jeszcze, oraz break które powoduje, że pętla jest przerywana. W przypadku dłuższych pętli gdzie jest więcej instrukcji wszystkie te które sie znajdują po słowie continue/break nie zostają wykonane czyli np. int i = 0; while () { i++; if (i == 5) break; puts("Hello"); }. Sam sprawdź ile razy wypisze sie słowo Hello. ;)

Podobne pytania

0 głosów
2 odpowiedzi 672 wizyt
pytanie zadane 29 listopada 2017 w C i C++ przez Krystek102 Bywalec (2,440 p.)
0 głosów
0 odpowiedzi 133 wizyt
pytanie zadane 8 września 2022 w C i C++ przez benny13 Obywatel (1,150 p.)
0 głosów
0 odpowiedzi 355 wizyt

92,452 zapytań

141,262 odpowiedzi

319,074 komentarzy

61,854 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...