STRUMIENIE
Mimo iż język C++ pozwala korzystać ze wszystkich funkcji wejścia/wyjścia języka C, jednak definiuje także własny, zorientowany obiektowo system wejścia/wyjścia, mający postać biblioteki klas.
Standardowe wejście zapewnia odbieranie danych pochodzących z urządzeń czy plików. W tym przypadku, domyślnym źródłem strumienia danych jest klawiatura.
Zatem, wejściowy strumień danych nazywać się będzie standardowym wejściem, natomiast wyjściowy strumień danych standardowym wyjściem.
Język C ma jeden z najbardziej uniwersalnych, a jednocześnie efektywnych systemów wejścia/wyjścia. Pomimo wszystkich jego zalet, system ten nie zawiera jednak mechanizmów umożliwiających tworzenie obiektów zdefiniowanych przez użytkownika. Właśnie z ego powodu w języku C++ zostały oddzielne funkcje wejścia/wyjścia. Na przykład w języku C można zdefiniować następującą strukturę:
Informacje wymagane przez system wejścia/wyjścia języka C++ znajdują się w pliku nagłówkowym iostream.h. W tym pliku zdefiniowane są dwie struktury hierarchiczne klas, wykorzystywane przez poszczególne operacje.
Klasy biblioteki iostream.h postrzegają przepływ danych z programu na ekran jako strumień, płynący bajt po bajcie. Jeśli punktem docelowym strumienia jest plik lub ekran, wtedy źródłem jest zwykle jakaś część programu. Gdy strumień jest odwrócony, dane mogą pochodzić z klawiatury lub z dysku i mogą „wypełniać” zmienne w programie.
Jednym z podstawowych zadań strumieni jest kapsułkowanie problemu pobierania danych z wejścia (na przykład dysku) i wysyłania ich do wyjścia (na przykład na ekran).
Buforowanie
Zapis danych na dysku lub ich odczyt z dysku zajmuje dużo czasu, a podczas operacji zapisu i odczytu działanie programu jest zablokowane. Aby rozwiązać ten problem, strumienie oferują „buforowanie”. Dane są zapisywane do strumienia, ale nie są zapisywane natychmiast na dysk. Zamiast tego bufor strumienia wypełnia się danymi; gdy się całkowicie wypełni, dane są zapisywane na dysk w ramach pojedynczej operacji.
W języku C++ strumienie i bufory traktowane są w sposób obiektowy:
Klasa streambuf -to klasa najniższego poziomu, umożliwia wykonywanie podstawowych operacji wejścia/wyjścia. Korzysta się z niej tylko podczas definiowania własnych klas pochodnych; klasa ta zarządza buforem, zaś jej funkcje składowe oferują możliwość wypełniania, opróżniania, zrzucania i innych sposobów manipulowania buforem.
Klasa ios -od tej klasy rozpoczyna się druga struktura; zawiera wiele funkcji składowych i zmiennych, które określają funkcjonowanie strumieni; podczas zwykłego korzystania z systemu wejścia/wyjścia języka C++ elementy klasy ios pozwalają wykonywać działania na wszystkich strumieniach; jest klasa bazową dla wielu innych klas pochodnych, służących do wykonywania działań na plikach dyskowych i formatowania danych w pamięci.
Klasy istream i ostream są wyprowadzone z klasy ios; klasy te stosuje się w celu utworzenia strumieni umożliwiających wykonywanie operacji wejścia, wyjścia, wejścia/wyjścia.
Klasa iostream jest wyprowadzona z zarówno z klasy istream, jak również ostream, dostarcza metod wejścia- wyjścia do wypisywania danych na ekranie i odczytywania z klawiatury.
Klasa fstream zapewnia wejście i wyjście z oraz do plików.
Gdy program C++, zawierający klasę iostream, rozpoczyna działanie, tworzy i inicjalizuje cztery obiekty (Biblioteka klasy iostream jest dodawana przez kompilator do programu automatycznie. Aby użyć jej funkcji, należy dołączyć do początku kodu swojego programu odpowiednią instrukcję #include<iostream.h>):
cin obsługuje wprowadzenie danych ze standardowego wejścia, czyli klawiatury.
cout obsługuje wprowadzenie danych na standardowe wyjście, czyli ekran.
cerr jest nie buforowany, tzn. że wszystkie przesłane do niego dane są wyprowadzone (wszystko co zostanie wysłane do cerr jest wypisywane na standardowym urządzeniu błędów natychmiast, bez oczekiwania na wypełnienie bufora lub nadejście polecenia zrzutu).
clog jest buforowane wprowadzanie danych na standardowe wyjście błędów, czyli ekran (zapisane dane są wprowadzone dopiero po zapełnieniu bufora).
Tworzenie inserterów i ekstraktorów
Język C++ umożliwia wykonywanie operacji wejścia/wyjścia na klasach przez przeładowanie operatorów << i >>.
Operator << nosi nazwę operatora wstawiania (ang. insertion operator), ponieważ wstawia on znaki do strumienia. Podobnie, operator >> nosi nazwę operatora wydobywania (ang. extraction operator), gdyż wydobywa on znaki ze strumienia. Funkcje przeładowujące operatory wstawiania i wydobywania są powszechnie nazywane inserterami i ekstraktorami. Są one już przeładowane w pliku iostream.h, aby umożliwić wykonywanie strumieniowych operacji wejścia/wyjścia na wbudowanych typach danych.
Wejście z użyciem cin
Każdy ekstraktor musi zwracać odwołanie do obiektu typu istream. Również pierwszy parametr musi być odwołaniem do tego typu. Natomiast drugi parametr powinien być odwołaniem do zmiennej, do której mają zostać wpisane wprowadzone dane. Dzięki temu, że jest on odwołaniem, ekstraktor może go zmodyfikować.
Ogólna postać ekstraktora wygląda następująco:
istream &operator>>(istream &strumień, klasa &obiekt)
{
//instrukcje specyficzne dla danej klasy
return łańcuch; //zwrócenie odwołania do strumienia
}
Obiekt cin odpowiada za wejście i jest dostępny dla programu po dołączeniu biblioteki iostream.
Program: Użycie obiektu cin cin.cpp
#pragma hdrstop
#pragma argsused
#include<iostream.h>
#include<conio.h>
#include<stdio.h>
using namespace std;
int main()
{
int myInt;
long myLong;
double myDouble;
float myFloat;
unsigned int myUnsigned;
cout<<"int:";
cin>>myInt;
cout<<"long:";
cin>>myLong;
cout<<"double:";
cin>>myDouble;
cout<<"float:";
cin>>myFloat;
cout<<"unsigned:";
cin>>myUnsigned;
cout<<"\n\nint:\t"<<myInt<<endl;
cout<<"long:\t"<<myLong<<endl;
cout<<"doudle:\t"<<myDouble<<endl;
cout<<"float:\t"<<myFloat<<endl;
cout<<"unsigned:\t"<<myUnsigned<<endl;
getch();
return 0;
}
Łańcuchy
Obiekt cin może także obsługiwać argumenty w postaci łańcuchów do znaków (char*); tak więc można stworzyć bufor znaków i użyć cin do jego wypełnienia.
Jeżeli naszą zmienną uzupełniamy znakami i ostatnim znakiem jest null- wówczas cin kończy automatycznie na nim łańcuch, dlatego w buforze musi być wystarczająca ilość miejsca na pomieszczenie całego łańcucha oraz kończącego znaku null. Dla funkcji biblioteki standardowej znak null oznacza koniec łańcucha.
Obiekt cin ma jednak wady, próbując wpisać do łańcucha swoje pełne imię i nazwisko napotykamy problemy, ponieważ obiekt cin traktuje spacje lub znak nowej linii jako separatory, zakłada, że dane wejściowe są kompletne i w przypadku łańcuchów, dodaje na ich końcu znak null.
program wadycin.cpp
#include<conio.h>
#include<stdio.h>
#pragma hdrstop
#include<iostream.h>
#pragma argsused
int main()
{
char TwojeImie[50];
std::cout<<"Podaj imie:";
std::cin>>TwojeImie;
std::cout<<"Masz na imie:"<<TwojeImie<<std::endl;
std::cout<<"Podaj imie i nazwisko:";
std::cin>>TwojeImie;
std::cout<<"Nazywasz sie:"<<TwojeImie<<std::endl;
getch();
return 0;
}
Wyjście przez cout
Język C++ umożliwia bardzo proste tworzenie inserterów samodzielnie definiowanych klas.
Ogólna postać insertera wygląda następująco:
ostream &operator<<(ostream &strumień, klasa &obiekt)
{
//instrukcje specyficzne dla danej klasy
return łańcuch; //zwrócenie odwołania do strumienia
}
Właściwe zadanie wykonywane przez insertera zależy od programisty. Jedynym warunkiem stawianym przez język C++ jest konieczność zwrócenia parametru łańcuch. Całkowicie poprawne jest deklarowanie parametru obiekt jako odwołania do obiektu, a nie samego obiektu.
Stosowanie manipulatorów
System wejścia/wyjścia języka C++ udostępnia stosowanie specjalnych funkcji zwanych manipulatorami, które można umieszczać we wprowadzanych i wyprowadzanych wyrażeniach, używa się ich w celu zmiany parametrów sformatowania strumieni. Warunkiem korzystania z manipulatorów pobierających argumenty jest włączenia do programu pliku nagłówkowego iomanip.h.
Manipulatory są używane z obiektem cout.
Manipulatory, które nie wymagają pliku iomanip:
Manipulator |
cel |
wejście/wyjście |
Dec |
wprowadzanie i wyprowadzanie liczb w systemie dziesiętnym |
wejście i wyjście |
Endl |
wyprowadzanie znaku nowego wiersza i wymycie strumienia |
wyjście |
Ends |
wyprowadzenie znaku zerowego |
wyjście |
Flush |
wymycie strumienia, zrzuca bufor wyjściowy |
wyjście |
Hex |
wprowadzanie i wyprowadzanie liczb w systemie szesnastkowym |
wejście i wyjście |
Oct |
wprowadzanie i wyprowadzanie liczb w systemie ósemkowym |
wejście i wyjście |
resetiosflagf(long ) |
wyzerowanie flag określonych w parametrze |
wejście i wyjście |
setbase(int base) |
ustawienie systemu liczenia o podstawie base |
wyjście |
setfill(int ch) |
ustawienie znaku wypełnienia o kodzie ch |
wyjście |
setiosflags(long ) |
ustawienie flag określonych w parametrze |
wejście i wyjście |
setprecision(int p) |
ustawienie liczby cyfr po kropce dziesiętnej na p |
wyjście |
setw(int w) |
ustawienie szerokości pola na w |
wyjście |
Ws |
pominięcie początkowych odstępów |
wejście |
Manipulatory mogą należeć do wejścia/wyjścia.
Program pokazuje zmianę formatu wyprowadzania danych przez zastosowanie manipulatorów: setiosflags, setprecision i setw.
program setiosflags.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<iomanip.h>
int main()
{
cout<<setiosflags(ios::fixed);
cout<<setprecision(2)<<1000.243<<endl;
cout<<setw(25)<<"Witamy wszystkich.";
getch();
return 0;
}
Można tworzyć własne manipulatory. Dzielą się one na dwie kategorie: pobierające argumenty i nie pobierające argumentów. Między sposobami ich tworzenia istnieją drobne różnice.
Szkielet wszystkich manipulatorów wyjścia bez parametrów wygląda następująco:
ostream &nazwa_manip(ostream &strumień)
{
//instrukcje manipulatora
return strumień;
}
Program tworzy manipulator o nazwie setup(), który powoduje wyrównanie tekstu do lewej strony, ustawia szerokość pola na 10 znaków i zmienia znak wypełnienia na znak dolara :
program setup.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<iomanip.h>
ostream &setup(ostream &stream)
{
stream.setf(ios::left);
stream<<setw(10)<<setfill('$');
return stream;
}
int main()
{
cout<<""<<setup<<10;
getch();
return 0;
}
Ogólna postać wszystkich manipulatorów wejścia bez parametrów wygląda następująco:
istream &nazwa_manip(istream &strumień)
{
//instrukcje manipulatora
return strumień;
}
Program tworzy manipulator prompt(), który wyświetla tekst i przekształca wprowadzoną liczbę do postaci szesnastkowej:
program promet.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<iomanip.h>
istream &prompt(istream &stream)
{
cin>>hex;
cout<<"Wprowadz liczbe szesnastkowa:";
return stream;
}
int main()
{
int i;
cin>>prompt>>i;
cout<<i;
getch();
return 0;
}
Tworzenia manipulatora pobierającego argument nie jest już takie proste. Wynika to z faktu, że manipulatory z parametrami korzystają z klas ogólnych. Klasy ogólne tworzy się z zastosowaniem słowa kluczowego template.
W celu utworzenia manipulatora z parametrem konieczne jest włączenia do programu pliku nagłówkowego iomanip.h (zawiera on definicje m.in. klas omanip (do tworzenia manipulatorów wyjścia) oraz imanip ( manipulatorów wejścia)).
Ogólna postać manipulatorów wyjścia z parametrami jest następująca:
ostream &nazwa_manip(ostream &strumień, typ param)
{
//instrukcje manipulatora
return strumień;
}
Formatowanie danych z użyciem funkcji składowych klasy ios
Definiowanie formatu informacji wyświetlanych na ekranie, np. przez podanie szerokości pola czy określenie sposobu wyrównania danych, czyli działania, na które pozwalała nam funkcja printrf(), można wykonać korzystając z systemu wejścia/wyjścia języka C++ . Formatowanie danych wyjściowych za pomocą funkcji składowych klasy ios, to drugi sposób obok korzystania z manipulatorów.
W pliku nagłówkowym iostream.h zdefiniowane są tzw. flagi formatowania:
ios::left - wyrównuje wynik do lewej
ios::right - wyrównuje wynik do prawej
ios::internal - znak lub przedrostek zostaje wyrównany do lewej, a liczba do prawej
ios::dec - dziesiętny system liczbowy
ios::oct - ósemkowy system liczbowy
ios::hex - szesnastkowy system liczbowy
ios::showbase - dodaje przedrostek 0x do liczb szesnastkowych i przedrostek 0 do liczb ósemkowych
ios::showpoint - dodaje zera końcowe, zgodnie z wymaganą dokładnością
ios::uppercase - litery w liczbach szesnastkowych i w wartościach wyświetlanych w zapisie wykładniczym są wyświetlane jako wielkie
ios::showpos - przed wartościami dodatnimi wyświetlany jest znak plus
ios::scientific - liczby zmiennoprzecinkowe są wyświetlane w zapisie wykładniczym
ios::fixed - liczby zmiennoprzecinkowe są wyświetlane w zapisie dziesiętnym
Funkcja setf()
Obiekt iostream pamięta swój stan dzięki przechowywanym znacznikom. Można je wywołać wykorzystując funkcję setf() i przekazując jej jedną z predefiniowanych stałych wyliczeniowych. Obiekty posiadają stan wtedy, gdy któraś lub wszystkie z ich danych reprezentują warunki, które mogą ulegać zmianom podczas działania programu. Znaczniki klasy ios mogą być dodatkowo stosowane z manipulatorami
Program:
program set.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<iomanip.h>
using namespace std;
int main()
{
const int number=185;
cout<<"Liczba to"<<number<<endl;
cout<<"Liczba to"<<hex<<number<<endl;
cout.setf(ios::showbase);
cout<<"Liczba to"<<hex<<number<<endl;
cout<<"Liczba to";
cout.width(10);
cout<<hex<<number<<endl;
cout<<"Liczba to";
cout.width(10);
cout.setf(ios::left);
cout<<hex<<number<<endl;
cout<<"Liczba to";
cout.width(10);
cout.setf(ios::internal);
cout<<hex<<number<<endl;
cout<<"Liczba to:"<<setw(10)<<hex<<number<<endl;
getch();
return 0;
}
Program: setf.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
int main()
{
cout.setf(ios::showpos);
cout.setf(ios::scientific);
cout<<123<<""<<123.23<<"";
getch();
return 0;
}
Powyższy program wyświetla dane w następującym formacie:
+123 +1.232300e+02
Aby wyzerować flagi, należy skorzystać z funkcji unsetf(). Czasami potrzebna jest znajomość aktualnego stanu flag. Można go uzyskać przez wywołanie funkcji flags(): unsetflags.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
void showflags (long f);
int main()
{
long f;
f=cout.flags();
showflags(f);
cout.setf(ios::showpos);
cout.setf(ios::scientific);
f=cout.flags();
showflags(f);
cout.unsetf(ios::scientific);
f=cout.flags();
showflags(f);
getch();
return 0;
}
void showflags(long f)
{
long i;
for(i=0x8000;i;i=i>>1)
if(i&f)cout<<"1";
else cout <<"0";
cout<<"\n";
}
System wejścia/wyjścia języka C++ pozwala nie tylko korzystać z flag formatowania, ale także określać szerokość pola strumienia, znak wypełniający i liczbę cyfr wyświetlanych za kropką dziesiętną. Działania te są wykonywane za pomocą funkcji:
int width(int len);
char fill(char ch);
int precision(int num);
Program pokazujący wykorzystanie w/w funkcji: width.cpp
#pragma hdrstop
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#pragma argsused
int main()
{
cout.setf(ios::showpos);
cout.setf(ios::scientific);
cout<<123<<""<<123.23<<"\n";
cout.precision(2);
cout.width(10);
cout<<123<<""<<123.23<<"\n";
cout.fill('#');
cout.width(10);
cout<<123<<""<<123.23;
getch();
return 0;
}
Operacje wejścia/wyjścia przeprowadzane na plikach
Systemu wejścia/wyjścia języka C++ można także używać w celu operacji na plikach. Mimo, iż uzyskuje się ten sam wynik końcowy co w przypadku stosowania standardu ANSI C, to jednak między tymi dwoma metodami istnieją różnice.
W języku C++ plik otwiera się przez połączenie go ze strumieniem. Istnieją trzy typy strumieni: wejściowe, wyjściowe i wejściowo-wyjściowe. Aby otworzyć strumień wejściowy, należy go zadeklarować jako obiekt klasy ifstream. Strumienie wyjściowe są obiektami klasy ofstream, natomiast strumienie, na których będą wykonywane zarówno operacje wejścia i wyjścia , obiektami klasy fstream.
ifstream in; // wejście
ofstream out; // wyjście
fstream both; // wejście i wyjście
Jeżeli strumień został utworzony, to jednym ze sposobów połączenia go z plikiem polega na wywołaniu funkcji open(), będącej funkcją składową wszystkich trzech klas strumieni.
ios::app - powoduje dołączenie wyprowadzanych danych na koniec pliku
ios::ate - powoduje, że wskaźnik otwieranego pliku zostaje ustawiony na jego końcu
ios::binary -
ios::in - umożliwia otwieranie pliku w trybie wejściowym
ios::nocreplace - to działanie funkcji open() kończy się błędem, jeżeli określony plik nie istnieje
ios::noreplace - jeżeli została podana ta wartość (i nie zostały podane wartości ios::app i ios::ate), to funkcja open() nie otworzy pliku, który już istnieje.
ios::out - umożliwia otwieranie pliku w trybie wyjściowym
ios::trunc - funkcja open() usuwa zawartość istniejących plików.
Otwieranie pliku w trybie wyjściowym:
Np:
ofstream out;
out.open(“test”, ios::out, 0);
Otwieranie pliku w trybie wejściowym:
Np.
ifstream mystream(“myfile”);
Aby otworzyć strumień w trybie wejściowo-wyjściowym, należy podać jednocześnie wartość ios::in i ios::out.
Odczytywanie i zapisywanie plików tekstowych
Aby odczytać dane z pliku tekstowego lub zapisać dane do takiego pliku, należy po prostu użyć operatorów << i >> z otwartym strumieniem.
Program zapisuje liczbę całkowitą i zmiennoprzecinkową oraz łańcuch w pliku TEST:
zapisywaniepliku.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<fstream.h>
int main()
{
ofstream out("test");
if(!out)
{
cout<<"Blad otwarcia pliku.\n";
getch();
return 1;
}
out<<10<<""<<123.23<<"\n";
out<<"To jest krotki plik tekstowy.\n";
out.close();
getch();
return 0;
}
Program, który z pliku utworzonego w poprzednim programie wczytuje liczbę całkowitą i zmiennoprzecinkową, znak oraz łańcuch:
odczytywaniepliku.cpp
#pragma hdrstop
#pragma argsused
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<fstream.h>
int main()
{
char ch;
int i;
float f;
char str[80];
ifstream in("test");
if(!in)
{
cout<<"Blad otwarcia pliku.\n";
return 1;
}
in>>i;
in>>f;
in>>ch;
in>>str;
cout<<i<<""<<f<<""<<ch<<"\n";
cout<<str;
in.close();
getch();
return 0;
}
Operacje binarne
Niektóre systemy operacyjne, takie jak DOS, dokonują rozróżnienia pomiędzy plikami tekstowymi a binarnymi. Pliki tekstowe przechowują wszystko jako tekst (np. liczbę 54 325 przechowują jako łańcuch cyfr(`5','4','3','2','5')nieefektywne, pozwala jednak na odczyt przez proste programy. Aby odróżnić plik tekstowy od binarnego język C++ udostępnia znacznik ios::binary. Pliki binarne mogą przechowywać nie tylko liczby i łańcuchy, ale także całe struktury danych.
Można do nich zapisywać i odczytywać wszystkie dane jednocześnie, używając funkcji składowych write() i read() klasy fstream.
Funkcja write() działa podobnie jak operator wstawienia (<<), ale przyjmuje parametr określający maksymalną ilość znaków, jaka może zostać wypisana.
Program:
#pragma hdrstop
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#include<string.h>
#pragma argsused
using namespace std;
int main()
{
char One[]="Litwo Ojczyzno moja";
int fullLength=strlen(One);
int tooShort=fullLength -4;
int tooLong=fullLength +6;
cout.write(One,fullLength)<<"\n";
cout.write(One,tooShort)<<"\n";
cout.write(One,tooLong)<<"\n";
getch();
return 0;
}
Drugim sposobem zapisywania i odczytywania danych binarnych jest stosownie metody z użyciem funkcji put() i get().
Program: write.cpp
#pragma hdrstop
#include<stdio.h>
#include<conio.h>
#include<iostream.h>
#pragma argsused
int main()
{
std::cout.put('c').put('z').put('e').put('s').put('c').put('\n');
getch();
return 0;
}
Funkcja składowa get() służy do pobierania pojedynczego znaku ze standardowego wejścia, może zostać wywołana bez parametrów (zwraca ona odczytany znak lub znak EOF (znak końca pliku- end of file) w chwili dojścia do końca pliku), bądź z parametrem.
Program użycia funkcji get() bez parametrów: getbezparametrow.cpp
#pragma hdrstop
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#pragma argsused
int main()
{
char ch;
while((ch=std::cin.get())!=EOF)
{
std::cout<<"ch:"<<ch<<std::endl;
}
std::cout<<"\nGotowe!\n";
getch();
return 0;
}
Program użycia funkcji get() z parametrem: getzparametrem.cpp
#pragma hdrstop
#include<conio.h>
#include<stdio.h>
#include<iostream.h>
#pragma argsused
int main()
{
char a,b,c;
std::cout<<"Wpisz trzy litery:";
std::cin.get(a).get(b).get(c);
std::cout<<"a:"<<a<<"\nb:";
std::cout<<b<<"\nc:"<<c<<std::endl;
getch();
return 0;
}