Funkcja "scanf" w języku C również jest częścią standardowej obsługi wejścia-wyjścia 🔧. Tym razem, w odróżnieniu od "printf", obsługuje ona komunikację pomiędzy człowiekiem, a komputerem w drugą stronę - oczekując wprowadzenia danych i zatwierdzenia ✅. Sprawdź na co musisz koniecznie zwrócić uwagę podczas korzystania z funkcji "scanf" oraz dlaczego niewłaściwe jej użycie poskutkuje wyłożeniem się programu 💀!

"SCANF" W JĘZYKU C POBIERZE CI DANE ZE STRUMIENIA WEJŚCIOWEGO!

Tytułowa funkcja (będąca skrótem od "scan formatted"), umożliwia przekazywanie wartości do zmiennych w postaci wprowadzanych znaków z klawiatury, lecz to znowu jest uproszczenie - tak naprawdę to dotyczy strumienia wejściowego "stdin" (ang. standard input), którym wcale nie musi być klawiatura ⚠️!

Przy użyciu tej funkcji, jesteś w stanie pobrać wpisane dane, a następnie przekazać je do wskazanego miejsca w pamięci ✅. Natomiast trzeba liczyć się z pewną pułapką, na którą wejdziesz na 99%, jeśli korzystasz z funkcji po raz pierwszy w życiu 😏! Pokażę o co chodzi podczas prezentacji przykładu kodu.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Teraz prosty przykład użycia. Dajmy na to, że interesuje nas pobranie od użytkownika jego wieku. Po wprowadzeniu danej, zostanie wypisany komunikat z podaną liczbą. No to proszę bardzo 👇:

#include <stdio.h>

int main(void)
{
	int typedAge;
	
	printf("Ile masz lat?\n");
	scanf("%d", &typedAge);
	printf("Podany wiek to: %d\n", typedAge);

	return 0;
}

Na pierwszy rzut oka, nie ma niczego wzbudzającego niepokój. Jednak przyjrzyj się wywołaniu "scanf" w języku C 👀. Czy widzisz ten subtelny szczegół? Może wytnę ten fragment:

scanf("%d", &typedAge);

Już 🙂? Jeszcze raz przycięcie, aż do zostawienia kluczowego fragmentu 🔍:

&typedAge

Ampersand przed nazwą zmiennej! To jest kolejna rzecz, której nie zobaczysz w takich językach, jak Java, C# czy JavaScript. Tu robimy przystanek na wyjaśnienie znaczenia tego symbolu ✋.

ADRES DO ZMIENNEJ

Znak ampersandu w języku C ma kilka znaczeń. Jednym z nich jest pobranie adresu zmiennej. Adres to miejsce w pamięci zapisane w systemie szesnastkowym (heksadecymalnym) ℹ️. Więcej informacji w odrębnym artykule.

Na potrzeby funkcji "scanf" w języku C wystarczy żebyś wiedział(a), że chcąc przypisać liczbę całkowitą, znak czy cokolwiek innego, co zostaje wprowadzone "z palca" 👆, za parametr wstawiasz adres do zmiennej, a nie samą zmienną! Powodem jest sama operacja utrwalania wprowadzonej danej.

Typy proste nie mają zdolności do utrwalania zmodyfikowanych wartości po zakończeniu działania funkcji, w której ta zmiana zaszła ⚠️. Wtedy musimy skorzystać ze wskaźnika, aby "przesiąść się" na przekazywanie przez referencję (więcej szczegółów o wspomnianych terminach znajdziesz w odrębnych artykułach) ℹ️.

Co się stanie, gdy zapomnimy wstawić ampersand 🤔?

scanf("%d", typedAge);

Pisząc krótko: wyłoży się program 💣. Pisząc fachowo: dojdzie do niezdefiniowanego zachowania, które w języku C jest bardzo niebezpieczne 💥! Także pamiętaj - używamy adresu zmiennej, aby dokonać trwałej modyfikacji!

2 wyjątki, które nie wymagają ampersandu podczas korzystania ze "scanf", to 👇:

  1. wskaźnik (wraz z tablicą),
  2. łańcuch znaków.

Powód znów logiczny: oba należą do typów referencyjnych (same w sobie są wskaźnikami) ℹ️. Czyli automatycznie "nabywają" zdolności do utrwalenia danych 😄.

PARAMETRY FUNKCJI "SCANF"

Zeszliśmy z omawiania samej postaci funkcji "scanf", także już opisuję z czego się ona składa 😁.

Pierwszym parametrem jest specyfikator. Tak, ten sam poznany przy tłumaczeniu funkcji "printf". Trzeba uważać przy wpisywaniu, ponieważ on zależy od typu danych ⚠️. Pełna lista wspieranych specyfikatorów znajduje się w dalszej części tego artykułu ℹ️.

Drugim parametrem jest wspomniany adres do zmiennej, która ma posiadać wartość jaką wprowadzimy. Tutaj tak samo trzymamy się zgodności pomiędzy specyfikatorem, a typem danych zmiennej (bo inaczej narażasz się na niezdefiniowane zachowanie 💣) ⚠️!

Najprostsza postać wywołania "scanf" w języku C kończy się na tych dwóch parametrach 👍. Jednak, jak się domyślasz, to nie koniec niespodzianek 😉!

WIĘCEJ ZMIENNYCH, NIŻ JEDNA

Są sytuacje, w których będziesz potrzebować pobrać więcej wartości, niż tylko jedna. Oczywiście, można skorzystać z dwóch osobnych wywołań 👇:

scanf("%d", &typedAge);
scanf("%d", &monthlyIncome);

albo zmodyfikować łańcuch znaków, tak aby posiadał DWA specyfikatory, zamiast jednego:

scanf("%d %d", &typedAge, &monthlyIncome);

Wtedy program będzie oczekiwać wprowadzenia dwóch wartości w jednym czasie 💥! W trakcie oczekiwania podania przez nas danych, mamy 2 możliwości ich wpisywania:

  1. wprowadzamy tyle ile potrzeba, jednym ciągiem oddzielając wartości spacją,
  2. wprowadzamy jedna po drugiej zatwierdzając Enterem, aż do wyczerpania.
IGNOROWANIE WPROWADZONEJ WARTOŚCI

"scanf" w języku C umożliwia dosłowne "przeskakiwanie" pewnych wartości ✈️. Na przykład możesz chcieć oczekiwać wprowadzenia 3 liczb, a tylko ostatnia zostanie uwzględniona. To da się zrobić używając asterysku, czyli znaku "gwiazdki" 👇:

scanf("%*d %*d %d", &typedAge);

Uwaga ⚠️! Spójrz na liczbę adresów do zmiennych. Nie wprowadzasz wszystkich jak leci, tylko tyle, ile odpowiada liczba specyfikatorów bez asterysku (to są te, które bierzemy pod uwagę 😉). Natomiast przy uruchomieniu programu, podajemy grzecznie trzy liczby całkowite 🔥!

Ignorowanie wskazanych wartości przydaje się bardziej podczas przetwarzania danych z plików, gdy występują jakieś kolumny z danymi, które nas nie interesują, a nie powinniśmy ich bezlitośnie usuwać ❌. Pamiętaj, że strumieniem wejściowym nie musi być wyłącznie klawiatura ⚠️!

"SCANF" ZWRACA WARTOŚĆ!

Ostatnia ważna cecha funkcji, o jakiej chciałem napisać 📝. "scanf" w języku C zwraca wartość wynikową w postaci liczby całkowitej 🔢! Przypisując wywołanie funkcji do zmiennej typu "int" 👇:

int numberOfCorrectlyTypedValues = scanf("%d", &typedAge);

otrzymasz wartość określającą ile oczekiwanych wartości zostało uznanych za prawidłowe ✅.

Tylko uwaga! Nie bierze pod uwagę tych specyfikatorów, które mają zostać zignorowane (zobacz poprzedni nagłówek) 🚨!

NIEWŁAŚCIWE DANE = "TO NIE MOŻE SIĘ UDAĆ"

Muszę Ci uświadomić przykrą rzecz. "scanf" w języku C nie jest odporne na wpisywanie niewłaściwych wartości 🚫. Robiąc "na złość", narażamy się na nieprzewidywalne konsekwencje 😱!

Już wiesz co się stanie, jak zapomnisz wstawić znak ampersandu przed nazwą zmiennej 👇:

scanf("%d", typedAge);

Działanie programu zostanie brutalnie przerwane ❌!

W przypadku, gdy "scanf" oczekuje liczby:

scanf("%d", &typedAge);

a my wprowadzimy coś innego np. łańcuch znaków:

"Tekst zamiast liczby xD"

to program sobie "poleci" z następnymi wywołaniami "scanf", już bez jakichkolwiek pytań Ciebie o dane 😁! Ponadto, kolejne wartości będą kompletnie "z czapy" 🧢.

W języku C nie ma miejsca na pomyłki ✋! Tylko Ty odpowiadasz za różne "fikołki" programu, jakie powstają w wyniku zostawionych błędów! Dlatego przy programowaniu (już obojętnie w jakim języku) należy zawsze weryfikować wprowadzony ciąg znaków, tak aby nie doszło do żadnych bzdur i dziwnych "odlotów" 😅.

SPOSÓB NA WYMUSZENIE POPRAWNOŚCI DANYCH

Mogę ze swej strony pokazać jedną sztuczkę z użyciem pętli "do-while" 🔔. Korzystając z wiedzy, że "scanf" zwraca wartość w postaci liczby poprawnie odczytanych danych, możesz wstawić wynik wywołania bezpośrednio do pętli i sprawdzać co iterację czy równa się oczekiwanej wartości 👇:

do
{
	printf("Podaj 3 liczby\n");
}
while (scanf("%d %d %d", &a, &b, &c) != 3 && streamWasCleaned());

Tylko oprócz tego, trzeba skorzystać z niekonwencjonalnego podejścia stosując drugą pętlę "while" i funkcję "getchar":

int streamWasCleaned(void)
{
	while (getchar() != '\n');

	return 1;
}

Ujmując najprościej jak to możliwe: "obleć" wszystkie wprowadzone znaki w jednym ciągu, dopóki nie natrafisz na Enter, które zatwierdziło wprowadzone dane. Chodzi o "przeczyszczenie" strumienia danych ze wszystkich znaków jakie zostały wprowadzone, gdyż bez tego, mielibyśmy nieskończoną pętlę 🔥!

Taki zapis sprawdza się świetnie w kwestii walidacji danych ✅.

PODSUMOWANIE

Kod źródłowy zbierający wszystkie możliwe kombinacje - tym razem wstawiony pod sam koniec punktu 👇😄:

#include <stdio.h>

int main(void)
{
	int a;
	char word[10];
	int b;
	int c;
	int d;
	
	scanf("%d", &a);
	scanf("%s", word);
	scanf("%d %d", &b, &c);
	scanf("%*d %*d %d", &d);

	printf("%d\n", a);
	printf("%s\n", word);
	printf("%d\n", b);
	printf("%d\n", c);
	printf("%d\n", d);

	return 0;
}

Pominąłem sprawdzanie poprawności, aby jeszcze nie bardziej nie rozciągać i tak już zbyt długiego artykułu 😅.

Dodatkowy komentarz do wywołania z łańcuchem znaków:

scanf("%s", word);

"scanf" akceptuje łańcuchy, natomiast przyjmuje tylko jeden ciąg, do natrafienia na znak spacji (reszta łańcucha zostanie "ucięta" ✂️) 😲. Z tego powodu, to nie jest najlepsza metoda na wprowadzanie łańcuchów ❌. Lepiej już użyć chociażby "fgets", które już uwzględnia spacje 👍.

LISTA SPECYFIKATORÓW FORMATU FUNKCJI

Tak jak w poprzednim wpisie tego typu, łap "ściągawkę" wszystkich istniejących specyfikatorów, które dotyczą opisywanej funkcji "scanf" w języku C 👇:

SPECYFIKATOR ZNACZENIE
%hd liczba całkowita w systemie dziesiętnym ze znakiem (signed short int)
%hu liczba całkowita w systemie dziesiętnym bez znaku (unsigned short int)
%d / %i liczba całkowita w systemie dziesiętnym ze znakiem (signed int)
%u liczba całkowita w systemie dziesiętnym bez znaku (unsigned int)
%ld liczba całkowita w systemie dziesiętnym ze znakiem (signed long int)
%lu liczba całkowita w systemie dziesiętnym bez znaku (unsigned long int)
%lld liczba całkowita w systemie dziesiętnym ze znakiem (signed long long int)
%llu liczba całkowita w systemie dziesiętnym bez znaku (unsigned long long int)
%o liczba całkowita w systemie oktalnym (ósemkowym)
%x / %X liczba całkowita w systemie heksadecymalnym (szesnastkowym)
%f liczba zmiennoprzecinkowa w systemie dziesiętnym (float)
%lf liczba zmiennoprzecinkowa w systemie dziesiętnym (double)
%Lf liczba zmiennoprzecinkowa w systemie dziesiętnym (long double)
%c pojedynczy znak
%s łańcuch znaków bez spacji
%p wskaźnik jako adres (*)

Ta sama porada - nie kuj tego wszystkiego na siłę ✋. Jak będziesz sobie to ćwiczyć i jak zacznie być potrzebne, to wtedy tu przyjdź, popatrz, użyj. Tak dużo lepiej się uczyć ✔️.

Funkcja "scanf" w języku C

Funkcja "scanf" w języku C pobiera od strumienia wejściowego dane o konkretnym typie zwracając uwagę na prawidłowość.


Wreszcie koniec o funkcji "scanf" w języku C 🙂. Spory materiał wyszedł, to trzeba przyznać 😅!

PODOBNE ARTYKUŁY