Wejście i wyjście (w czasach zarazy)

 

Mało dowcipne rozpoczęcie tego tekstu powinno brzmieć: W czasie zarazy nie wychodzimy. Ale zakładam, że Państwo już trochę przywykliście do zakazów różnego rodzaju.

Zatem czego należy się wystrzegać podczas wejścia-wyjścia? I parę „dobrych” rad.

Funkcja printf()

Należy unikać sytuacji, że specyfikacja wydruku jest niezgodna z typem zmiennej. Na przykład:

W wyniku da 1321658176. A każde kolejne uruchomienie potrafi dać inny wynik

Podobnie będzie w tym przypadku:

Pewnym wyjątkiem są zmienne typu doublefloat. Obydwa typy mogą używać specyfikacji %f (co spowodowane jest domyślną promocją zmiennych typu float do typu double`).

Specyfikacja formatu mówi w jaki sposób należy dokonywać konwersji zawartości binarnej do postaci dziesiętnej.

Kompilator zgłasza ostrzeżenie

Funkcja scanf()

Numeryczne

Podobnie zachowuje się funkcja scanf().

Efektem działania programu

po wprowadzeniu 123 jest 1123418112.

Natomiast w przypadku kodu

po wprowadzeniu 123 jest 0.00000.

Tekstowe

Trzeba pamiętać, że funkcja scanf() pobiera tekst do pierwszego odstępu (lub znaku nowej linii). Kolejną sprawą, o której trzeba pamiętać to zarezerwowanie odpowiednio dużej tablicy na pobieranie tekstu.

Popatrzmy na następujący program:

Po uruchomieniu wprowadzam tekst alamakota.

Wynik działąnia programu jest następujacy:

Jak to wytłumaczyć?

Tablice a, bc zajmują ciągły obszar pamięci. Funkcjascanf() dostaje adres początku tablicy a. Wprowadzany tekst alamakota (10 bajtów) wpisywany jest do kolejnych komórek pamięci wypełniają tablicę a (litery: alam), b (akot) i c (a\0). Czyli polecenie nadpisuje dotychczasowa zawartość tablic.

Polecenie scanf()drukuje zawartość pamięci od podanego adresu do wystąpienia znaku \0.

Bardzo podobnie zachowa się funkcja gets() (która właściwie została usunięta ze specyfikacji języka C).

Funkcja fgets()

Do wprowadzania tekstu należy używać funkcji fgets(). Jej prototyp jest nast epujący:

Pierwszy parametr to adres tablicy tekstowej do której wpisujemy tekst, drugi to jej długość, a trzeci to specyfikacja strumienia, z którego czytamy1. W przypadku czytania z klawiatury używamy specyfikacji stdin.

Funkcja pobierze ze wskazanego strumienia tylko tyle znaków, żeby wypełnić tablicę, dodając na końcu znak \0 (o kodzie ASCII 0).

Wadą jej jest to, że wczytuje tekst łącznie ze znakiem przejścia do nowej linii (\n) generowanym przez klawisz enter.

Efektem działania programu

Po wpisaniu tekstu Ala ma kota będzie:

Specyfikacja %3d nakazuje wyprowadzać liczbę na co najmniej trzech polach (uzupełniając ją, ewentualnie, od lewej stron odstępami). Funkcja strlen() podaje długość tekstu, czyli liczy wszystkie znaki aż do wystąpienia znaku o kodzie ASCII 0.

Błędy wprowadzania

W przypadku, gdy znaki wprowadzane z klawiatury nie mogą być poprawnie zinterpretowane jako liczba, funkcja scanf() informuje o tym programistę. Poprawne użycie tej funkcji powinno być takie:

Funkcja służyć ma do czytania n wartości. Gdy zrobi to poprawnie — zmienna x przyjmie wartość n. Gdy x jest mniejsze od n oznacza to, że nie udało się przeczytać wszystkich wartości. Gdy x jest ujemne, oznacza to, że nastąpiła próba czytania „poza końcem pliku” (program chce przeczytać więcej danych niż jest w pliku).

Wartości zmiennych do których nie udało się przeczytać danych — pozostają niezmienione.

Do programisty należy obowiązek reagowania na takie sytuacje.

Chcemy przeczytać dwie wartości typu int. Operator wpisujący dane pomylił się i podał:

scanf() czyta dane z kolejki wejściowej interpretując na bieżąco dane.

Najpierw czyta znak 1 (to jest dobra wartość, która może budować wartość int). Następnie trafia na znak 2, który również może budować wartość int. Kolejny znak to . która nie buduje wartość całkowitej. Program kończy czytanie pozostawiając w strumieniu wejściowym kropkę. Przeczytane cyfry 12 konwertuje na wartość binarną, która trafia pod adres &a.

Ponieważ na liście parametrów jest jeszcze jeden — scanf() kontynuuje pracę. Pierwszy przeczytany ze strumienia wejściowego znak to kropka. Nie może ona służyć do zbudowania liczby całkowitej. Funkcja kończy pracę, zwraca wartość 1 (przeczytała poprawnie jedną wartość). Zmienna b pozostaje niezmieniona. x ma wartość 1.

Z tego powodu, porządnie napisany program, po każdym użyciu funkcji scanf() powinien sprawdzać czy funkcja skończyła się poprawnie.

EOF

Wszystkie funkcje czytające po dojściu do końca pliku zwracają wartość równą stałej EOF (zazwyczaj -1)

Poniższy program tworzy plik test.txt wpisuje do niego 7 liczb:

Zawartość pliku test.txt:

Kolejny program czyta liczby z pliku:

Efekt działania programu, to:

Oba programy można połączyć w jeden: najpierw zapisze do pliku, plik zamknie, otworzy w trybie do odczytu i przeczyta dane.

Można też zrezygnować z zamykania pliku. Otworzymy plik w trybie w+ czyli odczytu i zapisu; najpierw wykonywane będzie pisanie:

Polecenie rewind() „przewija”2 plik na początek.

Sytuację „koniec pliku” podczas wprowadzania z terminala można zasymulować naciskając równocześnie dwa klawisze na początku linii tekstu:

  • Ctrl D (linux),
  • Ctrl Z (Windows).

Czy zawsze trzeba otwierać plik?

W bardzo wielu prostych aplikacjach nie ma potrzeby korzystania z funkcji dostępu do plików na dysku (fopen(), fscanf(), fprintf(),… fclose()). Czytanie ze standardowego wejścia i pisanie na standardowe wyjście czasami może wystarczyć.

Poniżej prosty program kopiujący zawartość strumienia wejściowego do wyjściowego:

Zmienna znak jest typu int bo taki jest prototyp funkcji int getchar( void );

Załóżmy, że program nazywa się kopiuj to możemy uruchomić go tak:

żeby skopiować plik, albo tak:

żeby wypisać jego zawartość na ekranie. Poniższy program podaje długość pliku w bajtach:

Uruchamia się go bardzo podobnie:

lub

W pierwszym przypadku liczba znaków to 167, a w drugim 16744. Mogę to zprawdzić używając polecenia ls -l znaki*

Informacja w kolumnie tuż przed datą to długość pliku.

./znaki < znaki.c

Tekst w postaci pliku PDF…

…jest również dostępny.


  1. Jest to funkcja z tej samej grupy co fprintf() czy fscanf()fopen().
  2. Polecenie na pamiątkę tych (starych) czasów kiedy powszechnie używano taśm magnetycznych. Po zapisaniu danych, żeby je odczytać trzeba było przewinąć taśmę na początek.↩6