Standardowe wejście i wyjście
Mechanizmy wejścia i wyjścia nie są częścią samego języka C, ale zostały zebrane jako zestaw funkcji odpowiedzialnych za ich reazlizację w standardowej bibliotece. Zaimplementowany model wejścia i wyjścia jest bardzo prosty i opiera się na znakach. W modelu tym strumień znaków składa się z ciągu wierszy. Do biblioteki należy zrobienie wszystkiego co niezbędne aby z punktu wiedzenia progrmisty każde urządzenie na którym można wykonać operację wejścia i/lub wyjścia odpowiadało temu modelowi bez względu na jego fizyczne własności. Uogólnienie to najbardziej widoczne jest w sytemach typu Unix gdzie na przykład dostęp do karty muzycznej odbywa się tak jak do zwykłego pliku.
getchar, putchar
Najprostszym mechanizmem wejścia jest czytanie po jednym znaku ze standardowego wejścia, którym zwykle jest klawiatura. Dokonujemy tego za pomocą funkcji getchar
int getchar(void)
Funkcja ta przy każdym wywołaniu podaje następny znak z wejścia lub
EOF, gdy napotkała koniec pliku. Używając przekierowania można klawiaturę zastąpić plikiem pisząc na przykład
koder.exe < dane.dat
Takie wywołanie pododuje, że pogram
koder.exe będzie czytał znaki z pliku
dane.dat a nie z klawiatury. Takie ,,przełączenie'' źródła odbywa się dla programu w sposób niewidoczny do tego stopnia, że tekst
< dane.dat nie jest dołączany do listy
argv argumentów wywołania programu. Podobnie wygląda sytuacja, gdy użyjemy potoku
program1.exe | program2.exe
Powyższe wywołanie uruchamia dwa programy:
program1.exe oraz
program2.exe, łącząc standardowe wyjście pierwszego ze standardowym wejściem drugiego.
Funkcja
int putchar(int)
służy do wypisywania pojedyńczych znaków na standardowym wyjściu, najczęściej ekranie. Funkcja zwraca wartość wypisanego znaku lub
EOF w przypadku wystąpniea błędu. Wyniki można przekierować do pliku pisząc
makemess.exe > file.txt
Mimo całej swej prostoty funkcje te dają dość duże możliwości. Przyjrzymy się zresztą poniższym przykładom
#include <stdio.h>
int main(void)
{
char c;
while((c=getchar())!=EOF)
putchar(c+2);
return 0;
}
|
|
Listing 5.1
Program kodujący koder.exe
|
#include <stdio.h>
int main(void)
{
char c;
while((c=getchar())!=EOF)
putchar(c-2);
return 0;
}
|
|
Listing 5.2
Program dekodujący dekoder.exe
|
|
Listing 5.3
Plik z danymi test.txt
|
C:\koder.exe < test.txt
cdefghij
C:\koder.exe < test.txt > testc.txt
C:\dekoder.exe < testc.txt
a
b
c
d
e
f
g
h
C:\koder.exe < test.txt | dekoder.exe
a
b
c
d
e
f
g
h
|
|
Output 5.1
Przykładowe sposoby wywołania programów z listingu 5.1 i 5.2 (żółtym kolorem wyróżniono tekst wprowadzany przez użytkownika)
|
printf - formatowane wyjście
int printf(char *format, arg1, arg2, ...)
Funkcja
printf pod nadzorem argumentu
format przekształca, formatuje i wypisuje swoje argumenty do standardowego wyjścia. Zwraca liczbę wypisanych znaków. Format oprócz zwykłych znaków kopiowanych do strumienia wyjściowego zawiera także specyfikację przekształceń; każda specyfikacja dotyczy jednego (kolejnego) argumentu. Każda specyfikacja rozpoczyna się od znaku %, a kończy znakiem charakterystycznym dla danego przekształcenia. Między znakiem % a znakiem przekształcenia może występować w podanej kolejności
- znak minus, wymuszający dosunięcie przekształconego argumentu do lewego krańca jego pola,
- liczba określająca minimalny rozmiar pola,
- kropka, oddzielająca rozmiar od precyzji,
- liczba określająca precyzję
- dla tekstu jest to maksymalna liczba znaków
- dla liczb zmiennoprzecinkowych jest to liczba cyfr po kropce dziesiętnej
- dla liczb całkowitych jest to liczba cyfr
- jedna z poniższych liter
- h - jeśli odpowiedni argument jest typu short lub unsigned short,
- l - jeśli odpowiedni argument jest typu long lub unsigned long,
- L - jeśli odpowiedni argument jest typu long double
Znaki przekształcenia zestawiono w poniższej tabeli; działanie funkcji nie jest określone, jeżeli znak następujący po % nie jest znakiem przekształcenia
Znak | Typ argumentu | Dana wejściowa |
d, i | int | liczba dziesiętna |
o | int | liczba ósemkowa bez znaku (bez wiodącego zera) |
x, X | int | liczba szesnastkowa bez znaku (bez wiodący 0x lub 0X) |
u | int | liczba dziesiętna bez znaku |
c | int | znak (jeden) |
s | char * | ciąg znaków wypisywany do napotkania '\0' lub wyczerpania liczby znaków określonej przez precyzję |
f | double | liczba zmiennoprzecinkowa z domyślną ilością 6 cyfr po przecinku |
e, E | double | liczba zmiennoprzecinkowa zapisana w notacji naukowej z domyślną ilością 6 cyfr po przecinku |
g, G | double | liczba zmiennoprzecinkowa wypisana w formacie %e lub %E jeśli wykładnik jest mniejszy niż -4 albo większy lub równy precyzji; w przeciwnym przypadku wypisana w formacie %f; nie wypisuje się nieznaczących zer i kończącej kropki dziesiętnej |
p | void * | wskaźnik (postać zależna od implementacji) |
% | | wypisanie znaku % |
Szerokość pola lub precyzję można zastąpić znakiem * oznaczającym, że żądaną liczbę należy obliczyć przekształcając, kolejny argument funkcji. Funkcja
printf używa pierwszego argumentu do określenia liczby i typów pozostałych argumentów. W przypadku podanie niewystarczającej liczby argumentów lub ich złego typu działanie funkcji jest nieokreślone, co najczęściej objawia się błędnymi lub jedynie z wyglądu poprawnymi wynikami.
Inne wcielenia printf-a: sprintf, fprintf
int sprintf (char *string, char *format, arg1, arg2, ...)
int fprintf (FILE *f, char *format, arg1, arg2, ...)
W obu funkcjach znaczenie argumentów
format oraz
arg1, arg2 jest takie jak w
printf. Ich działanie odróżni pierwszy argument:
- string oznacza tablicę w której będzie zapisany tekst (zakończony znakiem '\0'); tablica musi być na tyle duża aby pomieścić tekst wraz ze znakiem '\0'. Jako wynik funkcja zwraca liczbę wypisanych znaków, nie uwzględniając znaku '\0'. W przypadku błędu zwracana jest liczba ujemna.
- f oznacza strumień (plik), do którego zostaną wypisane znaki.
Warto zauważyć, że wywołanie
printf(...)
równoważne jest wywołaniu
fprintf(stdout,...)
#include <stdio.h>
#define ROZMIAR 10
#define PRECYZJA 1
int main(void)
{
char c1='a';
char c2=98;
char *napis="Napis";
int i1=-12,i2=12;
unsigned int ui1=-12,ui2=12;
float f1=1.2,f2=0.00001;
double d1=1.2,d2=0.00001;
//napis
printf(":%s:\n",napis);
printf(":%10s:\n",napis);
printf(":%.3s:\n",napis);
printf(":%-10s:\n",napis);
printf(":%10.3s:\n",napis);
printf(":%-10.3s:\n",napis);
//znaki mozna interpretowac jak liczby i odwrotnie
printf("%c %c %d %d\n",c1,c2,c1,c2);
printf("%o %o %x %x\n",c1,c2,c1,c2);
//proba interpretacji char'a jako float'a
printf("%f %e %f %e\n",c1,c2,c1,c2);
printf("%d %d %u %u\n",i1,ui1,i1,ui1);//tu chyba nie jest dobrze :))
printf("%d %d %u %u\n",i2,ui2,i2,ui2);
//zmiennoprzecinkowe
printf("%f %e %f %e\n",f1,f1,d1,d1);
printf("%5.2f %5.2e %5.2f %5.2e\n",f1,f1,d1,d1);
printf("%g %g %g %g\n",f1,f2,d1,d2);
printf("%*.*f\n",ROZMIAR,PRECYZJA,f1);
return 0;
}
|
|
Listing 5.4
|
:Napis:
: Napis:
:Nap:
:Napis :
: Nap:
:Nap :
a b 97 98
141 142 61 62
0.000000 2.079556e-312 0.000000 7.666593e+268
-12 -12 4294967284 4294967284
12 12 12 12
1.200000 1.200000e+000 1.200000 1.200000e+000
1.20 1.20e+000 1.20 1.20e+000
1.2 1e-005 1.2 1e-005
1.2
|
|
Output 5.2
Efekt działania programu z listingu 5.4
|
scanf - formatowane wejście
Odpowiednikiem funkcji printf działającym w przeciwnym kierunku jest funkcja scanf
int scanf (char * format, ...)
Funkcja scanf wczytuje znakie ze standardowego wejścia, interpretuje je zgodnie ze specyfikacjami zawartymi w argumencie format i zapamiętuje wyniki w miejscach określonych przez pozostałe argumenty, z których każdy musi być wskaźnikiem. Funkcja zatrzymuje się gdy zinterpretuje wszystkie znaki formatu lub gdy pewna dana nie pasuje do żądanej specyfikacji przkształcenia. Wartością zwracaną jest liczba wczytanych i przypisanych danych wejściowych. Po napotkaniu końca pliku funkcja zwraca EOF. Podobnie jak pintf także scanf posiada kilka innych wcieleń, po opis których odsyłam do odpowiednich książek :))
#include <stdio.h>
int main(void)
{
int i,r,m,d;
float f;
char s[11];
printf("Podaj int'a: ");
scanf("%d",&i);
printf("Podany int to: %d\n",i);
printf("Podaj float'a: ");
scanf("%f",&f);
printf("Podany float to: %f\n",f);
printf("Podaj string'a (max 10 znakow): ");
scanf("%s",&s);
printf("Podany string to: %s\n",s);
printf("Podaj date w formacie rrrr-mm-dd: ");
scanf("%d-%d-%d",&r,&m,&d);
printf("Podano date: %d-%d-%d",r,m,d);
return 0;
}
|
|
Listing 5.5
|
C:\program.exe
Podaj int'a: 12
Podany int to: 12
Podaj float'a: 1.2
Podany float to: 1.200000
Podaj string'a (max 10 znakow): fulmanp
Podany string to: fulmanp
Podaj date w formacie rrrr-mm-dd: 2004-05-16
Podano date: 2004-05-16
|
|
Output 5.3
Efekt działania programu z listingu 5.5 (żółtym kolorem wyróżniono tekst wprowadzany przez użytkownika)
|
Obsługa plików
Przed czytaniem pliku należy go otworzyć, czyli powiązać zewnętrzną nazwę czytelną dla człowieka, na przykład moje_wazne_dane.mp3 ze zmienną reprezentującą ten plik w programie. Dokonujemy tego za pomocą funkcji fopen. Przyjmuje ona jako argumenty nazwę (name)oraz tryb (mode)w jakim należy plik otworzyć, zwraca zaś wskaźnik (deskryptor) pliku
FILE * fopen(char *name, char *mode)
W przypadku błędu funkcja zwraca NULL Dostępne są następujące tryby
- r otwórz plik tekstowy do czytania;
- w otwórz plik tekstowy do pisania; jeśli istnieje, skasuj jego zawartość;
- a dopisuj; otwórz plik do dopisywania na końcu; jeśli nie istnieje, to utwórz;
- r+ otwórz plik tekstowy do aktualizacji (czytania i pisania);
- w+ utwórz plik tekstowy do aktualizacji; jeśli istnieje, skasuj jego zawartość
- a+ dopisuj; otwórz plik do aktualizacji, dopisuj na końcu; jeśli nie istnieje, to utwórz;
Aktualizacja oznacza możliwość jednoczesnego pisania i czytania z tego samego pliku. Między operacjami odczytu i zapisu należy wywoływać funkcję
fflush lub funkcje pozycjonujące plik. Jeśli w argumencie
mode po początkowej literze występuje litera
b, oznacza to plik binarny.
Jeśli plik został poprawnie otworzony to możemy użyć funkcji pozwalających na operowanie na nim. Nie zostaną one tutaj wymienione - odsyłam do odpowiedniej literatury.
Po zakończeniu operacji na pliku należy plik zamknąć czyli zwolnić używany deskryptor pliku. W tym celu posługujemy się funkcją fclose
int fclose(FILE *f)
Wprawdzie funkcja fclose wywoływana jest automatycznie dla wszystkich otwartych plików, gdy program kończy się normalnie, ale większość systemów operacyjnych nakłada ograniczenie na ilość jednocześnie otwartych plików w jednym programie, dlatego też należy zwalniać je samemu gdy tylko przestaną być potrzebne.
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fin,*fout;
char c;
int i;
if(argc!=3)
{
printf("Zla ilosc parametrow\n");
return -1;
}
fin=fopen(argv[1],"r");
fout=fopen(argv[2],"w");
if(fin==NULL)
{
printf("Nie moge otworzyc pliku %s\n",argv[1]);
return -1;
}
if(fout==NULL)
{
printf("Nie moge otworzyc pliku %s\n",argv[2]);
fclose(fin);
return -1;
}
for(i=1;(c=fgetc(fin))!=EOF;i++)
fputc(~c,fout);
fclose(fin);
fclose(fout);
return 0;
}
|
|
Listing 5.6
|
To jest
taki
sobie pliczek
testowy
|
|
Listing 5.7
Plik z danymi test.txt
|
Wywołanie:
C:\koder.exe test.txt testc.txt
Efekt (zawartość pliku testc.txt):
ŤßŚőőŚßŹś
őŚ
Wywołanie:
C:\koder.exe testc.txt testd.txt
Efekt (zawartość pliku testd.txt):
To jest
taki
sobie pliczek
testowy
|
|
Output 5.4
Wywołania i efekty działania programu z listingu 5.6 (żółtym kolorem wyróżniono tekst wprowadzany przez użytkownika)
|
#include <stdio.h>
int main(void)
{
char c=10;
int i=12;
float f=12.3;
FILE *fout;
fout=fopen("wyniki.bin","wb");
fwrite(&c,sizeof(char),1,fout);
fwrite(&i,sizeof(int),1,fout);
fwrite(&f,sizeof(float),1,fout);
fclose(fout);
fout=fopen("wyniki.bin","rb");
fread(&c,sizeof(char),1,fout);
fread(&i,sizeof(int),1,fout);
fread(&f,sizeof(float),1,fout);
fclose(fout);
printf("Odczytalem: %d %d %f\n",c,i,f);
return 0;
}
|
|
Listing 5.8
|
C:\prog.exe
Odczytalem: 10 12 12.300000
Zawartość pliku wyniki.bin:
ÍĚDA
|
|
Output 5.5
Wywołanie i efekt działania programu z listingu 5.8
|
#include <stdio.h>
int main(void)
{
FILE *fin;
int i;
char c;
if(fin=fopen("test.txt","r"))
for(i=1;(c=fgetc(fin))!=EOF;i++);
else
return -1;
printf("Ilosc odczytanych znakow: %d\n",i);
fclose(fin);
if(fin=fopen("test.txt","rb"))
for(i=1;(c=fgetc(fin))!=EOF;i++);
else
return -1;
printf("Ilosc odczytanych znakow: %d\n",i);
fclose(fin);
return 0;
}
|
|
Listing 5.9
|
C:\prog.exe
Ilosc znakow odczytanych w trybie tekstowym: 35
Ilosc znakow odczytanych w trybie binarnym: 38
|
|
Output 5.6
Wywołanie i efekt działania programu z listingu 5.9
|
Przy uruchamianiu programu napisanego w języku C środowisko systemu operacyjnego jest odpowiedzialne za otwarcie trzech plików i udostępnienie programowi ich wskaźników. Plikami tymi są
- standardowe wyjście stdout (normalnie powiązane z ekranem)
- standardowe wejście stdin (normalnie powiązane z klawiaturą)
- standardowe wyjście dla błędów stderr (normalnie powiązane z ekranem)
#include <stdio.h>
int main(void)
{
int i;
fscanf(stdin,"%d",&i);
fprintf(stdout,"Podana liczba to %d\n",i);
fprintf(stderr,"Nie ma bledu\n");
return 0;
}
|
|
Listing 5.10
|
C:\prog.exe
12
Podana liczba to 12
Nie ma bledu
C:\prog.exe > stdout.txt
12
Nie ma bledu
Zawartosc pliku stdout.txt:
Podana liczba to 12
|
|
Output 5.7
Wywołania i efekt działania programu z listingu 5.10
|