Wykład 5
Wejście i wyjście

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

a
b
c
d
e
f
g
h
Listing 5.3 Plik z danymi test.txt
C:\koder.exe < test.txt
cdefghij
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 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
ZnakTyp argumentuDana wejściowa
d, iintliczba dziesiętna
ointliczba ósemkowa bez znaku (bez wiodącego zera)
x, Xintliczba szesnastkowa bez znaku (bez wiodący 0x lub 0X)
uintliczba dziesiętna bez znaku
cintznak (jeden)
schar *ciąg znaków wypisywany do napotkania '\0' lub wyczerpania liczby znaków określonej przez precyzję
fdoubleliczba zmiennoprzecinkowa z domyślną ilością 6 cyfr po przecinku
e, Edoubleliczba zmiennoprzecinkowa zapisana w notacji naukowej z domyślną ilością 6 cyfr po przecinku
g, Gdoubleliczba 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
pvoid *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:
  1. 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.
  2. 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

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ą


#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