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 🔍:
&typedAgeAmpersand 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 👇:
- wskaźnik (wraz z tablicą),
- ł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:
- wprowadzamy tyle ile potrzeba, jednym ciągiem oddzielając wartości spacją,
- 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 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ć 😅!
