W języku C++ zaleca się używanie bibliotek strumieni iostream zamiast standardowych funkcji stdio .
Biblioteka iostream zapewnia jednolity interfejs do zapisu i odczytu:
· ze standardowego wejścia/wyjścia
· plików
· bloków pamięci
Przykład: plik, który będzie otwierany w konstruktorze i automatycznie zamykany w destruktorze. Dostęp do struktury FILE za pomocą funkcji składowej.
#ifndef FILECLAS_H
#define FILECLAS_H
#include
<cstdio>
class FileClass
{
std::FILE*
f;
public:
FileClass( const char* fname,
const char* mode="r");
~FileClass();
std::FILE*
fp();
};
#endif // FILECLAS_H
#include
"FileClass.h"
#include
<cstdlib>
using namespace std;
FileClass::FileClass(const
char* fname,
const char* mode)
{
f = fopen(fname,
mode);
if(f
== NULL) {
printf("%s:
file not found\n", fname);
exit(1);
}
}
FileClass::~FileClass()
{
if(f)fclose(f);
}
FILE*
FileClass::fp()
{
return f;
}
#include
"FileClass.h"
using namespace std;
int main()
{
FileClass
f("test.txt");
char
buf[256];
while(fgets(buf,
256, f.fp()))puts(buf);
} // Destruktor
automatycznie zamknie plik
Zabezpieczenia w powyższym przykładzie polegają na poprawnej inicjalizacji i automatycznym zamknięciu pliku.
Jednakże dalej można wykonać niebezpieczną operację:
int
main()
{
FileClass
f("FileClassTest.cpp");
fclose(f.fp());
}
W pełni bezpieczny dostęp do pliku powinien polegać na umieszczeniu wszystkich funkcji działających na strukturze FILE w klasie (równocześnie znika ich parametr FILE*)
class File {
std::FILE* f;
std::FILE* F(); // Zwraca wskaźnik pointer to f
public:
File(); // Stwórz obiekt bez otwarcia pliku
File(const
char* path, const char* mode = "r");
~File();
int open(const char* path, const char* mode
= "r");
int reopen(const char* path, const char*
mode);
int
getc();
int ungetc(int c);
int putc(int c);
int puts(const char* s);
char* gets(char* s, int n);
int printf(const char* format, ...);
size_t read(void* ptr, size_t size, size_t
n);
size_t write(const void* ptr, size_t size,
size_t n);
int
eof();
int close();
int flush();
int seek(long offset, int whence);
int getpos(fpos_t* pos);
int setpos(const fpos_t*
pos);
long tell();
void rewind();
void setbuf(char* buf);
int setvbuf(char* buf, int type, size_t sz);
int error();
void clearErr();
};
· Klasa File ma dodatkowy standardowy konstruktor bezargumentowy. Dzięki temu można plik otwierać później lub tworzyć tablicę plików lub użyć klasy jako atrybutu w kompozycji
· Standardowy konstruktor ustawia wskaźnik f na 0 (NULL)
Analogiczna klasa może zostać stworzona do opakowania dostępu do standardowego wejścia/wyjścia oraz do zapisu i odczytu bloków pamięci. Podobną drogą poszli twórcy biblioteki MFC tworząc klasę CFile i klasy pochodne.
Podstawową wadą jest zastosowanie funkcji printf – o zmiennej liczbie argumentów:
1. implementacja tego typu funkcji jest kosztowna obliczeniowo
2. łatwo popełnić błędy w formacie, które widoczne są dopiero w momencie wykonania
3. funkcja printf działa jedynie dla typów wbudowanych i nie da się jej rozszerzyć na inne typy
Biblioteka iostream została zaprojektowana tak, aby:
1. zaimplementować wszystkie funkcje stdio
2. ułatwić zapis i odczyt danych dodając równocześnie sprawdzenie poprawności operacji
3. umożliwić potencjalne rozszerzenie funkcji zapisu i odczytu na nowe wszystkie klasy implementowane przez programistę
Programując z wykorzystaniem biblioteki iostream wykorzystuje się przeciążanie operatorów << oraz >> (standardowo operatorów arytmetycznych do przesuwania bitów).
· Operator << nazywany jest operatorem wstawiania.
· Operator >> nazywany jest operatorem ekstrakcji.
Strumień jest obiektem, który pozwala na przechowywanie I formatowanie bajtów.
· Strumień wejściowy należy do klasy istream
· Strumień wyjściowy należy do klasy ostream
Strumienie mają swoje specjalizacje
ifstream |
wejściowy strumień związany z plikiem |
ofsteram |
wyjściowy strumień związany z plikiem |
istrstreams |
wejściowy strumień dla bloku pamięci char* |
ostrstream |
wyjściowy strumień dla bloku pamięci char* |
istringsteram |
wejściowy strumień dla obiektu klasy std::string |
ostringstream |
wyjściowy strumień dla obiektu klasy std::string |
Wszystkie te klasy mają identyczny interfejs, co pozwala na napisanie jednej funkcji do zapisu i jednej funkcji do odczytu zawartości klasy, która będzie działała dla wszystkich typów strumieni. W stdio trzebaby użyć fprintf, sprintf, fwrite, fscanf, sscanf, fread.
Jeżeli strumień jest w stanie dostarczać danych (bajtów) można z niego odczytywać informacje za pomocą operatora ekstrakcji. Operator ekstrakcji rozpoznaje format oczekiwany przez drugi operand i umieszcza w nim odpowiednie wartości. Operand musi być poprawną wartością lewostronną (l-value)
Przykład
int i;
cin
>> i;
float f;
cin
>> f;
char c;
cin
>> c;
char
buf[100];
cin
>> buf;
Strumienie wejściowe mają zdefiniowane operatory ekstrakcji dla typów wbudowanych, ale można je także zdefiniować dla dowolnych własnych klas i typów.
Operator wstawiania formatuje dane i przesyła do strumienia wyjściowego. Analogicznie – strumienie wyjściowe definiują standardowe operatory wstawiania dla typów wbudowanych ale można je rozbudować o własne operatory wstawiania dla implementowanych przez siebie klas.
Przykład
cout
<< "i = ";
cout
<< i;
cout
<< "\n";
cout
<< "f = ";
cout
<< f;
cout
<< "\n";
cout
<< "c = ";
cout
<< c;
cout
<< "\n";
cout
<< "buf = ";
cout
<< buf;
cout
<< "\n";
Operatory wstawiania i ekstrakcji mogą być łączone w wyrażenia w postaci łańcuchów operatorów i argumentów.
cout
<< "i = " << i << endl;
cout
<< "f = " << f << endl;
cout
<< "c = " << c << endl;
cout
<< "buf = " << buf << endl;
Odczyt linii tekstu umożliwiają dwie funkcje: getline i get.
istream&
getline(char* pch, int
nCount, char delim = '\n');
istream&
get(char* pch, int nCount,
char delim = '\n' );
Funkcje zatrzymują się po wypełnieniu bufora lub po osiągnięciu znacznika końca linii.
Funkcja get nie usuwa znacznika delim ze strumienia.
Aby odczytać pojedynczy bajt należy użyć funkcji klasy istream
int
get();
albo
istream& get( char&
rch );
Biblioteka iotream zawiera funkcje read i write umożliwiające odczyt i zapis ciągu bajtów.
istream& read( char* pch, int nCount );
ostream& write( const char* pch, int nCount );
Przykład
#include
<iostream>
#include
<fstream>
using
namespace std;
void
main()
{
int k=1234;
ofstream os("test.dat");
os.write(reinterpret_cast<char*>(&k),
sizeof(k));
os.close();
int l;
ifstream is("test.dat");
is.read((char*)&l,sizeof(l));
cout<<l<<endl;
}
Funkcje dokonujące odczytu ze strumienia nie zwracają wartości oznaczającej wynik operacji (sukces czy porażka).
W zamian w klasie strumienia iostream zdefiniowano kilka funkcji umożliwiających testowanie stanu strumienia po ostatniej operacji.
good() |
Nie są ustawione żadne bity błędu |
eof() |
Osiągnięto koniec strumienia |
bad() |
Błąd związany z odczytem z bufora strumienia. Po wyczyszczeniu flagi błędu – funkcja clear() – możliwa będzie kontynuacja. |
fail() |
Błąd nie związany z buforem. Kontynuacja niemożliwa. |
Referencja do strumienia może być użyta także wyrażeniach testowych.
ifstream
is("test.dat");
if(!is){
cerr<<”File open error”<<endl;
}
while(is){
is.get();
…
}
Biblioteka iostream definiuje klasy strumieni umożliwiające obsługę plików: fstream (strumień wejściowy i wyjściowy) ifstream (strumień wejściowy) oraz ofstream (strumień wyjściowy).
Pliki mogą być otwierane przy konstrukcji obiektu lub za pomocą funkcji open.
Przykładowe parametry
konstruktora
ofstream( const char* name,
int mode = ios::out,
int prot = filebuf::openprot );
mode – tryb otwarcia. Kombinacja flag:
ios::in |
Otwiera plik do
odczytu. |
ios::out |
Otwiera plik do
zapisu. |
ios::app |
Otwiera w celu
dopisywania na końcu. |
ios::ate |
Otwiera
istniejący plik i przesuwa się na jego koniec. |
ios::nocreate |
Otwiera plik
tylko wtedy, kiedy istnieje. |
ios::noreplace |
Otwiera plik
tylko wtedy, kiedy nie istnieje – tworzy nowy plik. |
ios::trunc |
Otwiera plik i
kasuje poprzednią zawartość. |
ios::binary |
Otwiera w trybie
binarnym. |
Parametr prot określa opcje dotyczące zezwalania innym procesom na dostęp do otwartego pliku.
Przykład
void main()
{
ifstream
is("example2.cpp");
if(!is)return;
ofstream
os;
os.open("example2.bak");
if(!os)return;
char
buf[256];
while(is.getline(buf,256)){
os<<buf<<endl;
}
}
Klasy zdefiniowane w <strstream> umożliwiają zapis, odczytu i formatowanie ciągów bajtów w pamięci za pośrednictwem strumieni.
· istrstream pozwala na odczyt z pamięci.
· ostrstream umożliwia zapis do bloku pamięci.
Klasa ma dwa konstruktory:
istrstream(char* buf);
istrstream(char* buf, int size);
W pierwszym przypadku konieczne jest dostarczenie wskaźnika do tekstu zakończonego znakiem 0, w drugim podanie rozmiaru bufora.
Przykład
#include
<iostream >
#include
<strstream>
using
namespace std;
void
main()
{
istrstream is("Ala ma 3 koty");
if(!is)return;
char buf[256];
is>>buf;
cout<<buf<<" ";
is>>buf;
cout<<buf<<" ";
int k;
is>>k;
cout<<k<<" ";
is>>buf;
cout<<buf<<endl;
}
Wynik:
Ala ma 3 koty
Klasa umożliwia zapis i formatowanie do bufora dostarczonego przez użytkownika lub automatycznie przyrastającego bufora zarządzanego przez strumień.
Chcąc zapisać dane do własnego bufora należy utworzyć strumień korzystając z konstruktora:
ostrstream(char *s, int size,
int mode = ios::out)
Użycie jako parametru mode dodatkowej flagi ios::app spowoduje dopisanie danych do istniejącego tekstu zakończonego znakiem 0.
Przykład
void
main()
{
char buf[1024];
ostrstream os(buf,1024);
os<<"Ala
ma kota"<<" ";
os<<3.45<<" ";
os<<hex<<1234<<"
";
os<<ends;
ostrstream os2(buf,1024,ios::out|ios::app);
os2<<"Dopisany
tekst";
os2<<ends;
cout<<buf<<endl;
}
Wynik:
Ala ma kota 3.45 4d2 Dopisany tekst
Chcąc zapisać do pamięci dane o nieokreślonym rozmiarze można skorzystać z opcji automatycznego zarządzania pamięcią przez strumień. W tym celu należy użyć standardowego konstruktora bezargumentowego:
ostrstream().
Bufor w pamięci będzie przyrastał w miarę dopisywania do niego kolejnych danych. Pamięć dla niego będzie alokowana dynamicznie z użyciem operatora new.
Przykład
void main()
{
ostrstream os;
os<<"Ala ma
kota"<<" ";
os<<3.45<<" ";
os<<hex<<1234<<"
";
os<<ends;
}
Zazwyczaj zapis lub formatowanie w pamięci ma
na celu późniejsze wykorzystanie danych. Aby uzyskać dostęp do bufora w pamięci
stworzonego przez strumień należy użyć funkcji char*ostrstream::str().
Funkcja ta zwraca wskaźnik do bufora i równocześnie zamraża strumień. Oznacza to, że dalej nie wolno dokonywać operacji zapisu. Dodatkowo, strumień nie zwolni automatycznie przydzielonej pamięci, stąd powinna być ona usunięta jawnie:
void
main()
{
ostrstream os;
os<<"Ala
ma kota"<<" ";
os<<3.45<<" ";
os<<hex<<1234<<"
";
os<<ends;
char *ptr = os.str();
cout<<ptr<<endl;
delete []ptr;
}
Alternatywą jest „odmrożenie” bufora. Po odmrożeniu strumień dalej jest właścicielem bufora, stąd:
· możliwy jest dalszy zapis
· destruktor strumienia automatycznie usunie przydzieloną pamięć.
void main()
{
ostrstream os;
os<<"Ala
ma kota"<<" ";
os<<3.45<<" ";
os<<hex<<1234<<"
";
os<<ends;
cout<<
os.str()<<endl;
os.rdbuf()->freeze(false);
}
Klasa ios , która stanowi korzeń hierarchii strumieni zawiera szereg flag i zmiennych sterujących formatowaniem danych zapisywanych do strumienia.
Flagi te mogą być ustawiane i odczytywane za pomocą odpowiednich funkcji:
fmtflags ios::flags(fmtflags newflags);
fmtflags ios::setf(fmtflags ored_flag);
fmtflags ios::unsetf(fmtflags clear_flag);
fmtflags ios::setf(fmtflags bits, fmtflags field);
ios::skipws |
Pomiń białe znaki |
ios::showbase |
Wyświetlając liczby dziesiętnie, szesnastkowo
lub ósemkowo sformatuj zgodnie ze składnią stałych w języku C++. |
ios::showpoint |
Wyświetl kropkę i zera dla liczb
zmiennoprzecinkowych. |
ios::uppercase |
Wyświetl duże litery A-F dla wartości
heksadecymalnych i E dla notacji
naukowej. |
ios::showpos |
Wyświetl plus (+) dla wartości dodatnich. |
ios::unitbuf |
Buforowanie jednostkowe. Bufor strumienia jest
opróżniany na dysk po każdej operacji wstawiania. |
ios::stdio |
Synchronizuje strumień ze standardowym wejściem
i wyjściem procesu. |
Na przykład, aby pokazywać znak (+) przed
liczbami dodatnimi, należy wcześniej wywołać funkcję: cout.setf(ios::showpos).
Aby odwołąć ten
sposób formatowania: cout.unsetf(ios::showpos).
Flaga ios::stdio powinna być ustawiona, jeżeli w programie działającym na konsoli mieszane są wywołania funkcji printf i zapis do strumienia cout. Jeżeli flaga nie jest ustawiona – ze względu na wewnętrzne buforowanie w strumieniach, kolejność wyświetlanych tekstów może nie być zgodna z wywołaniami funkcji.
ios::basefield |
Formatuj liczby całkowite jako |
ios::dec |
Liczby dziesiętne. |
ios::hex |
Szesnastkowe |
ios::oct |
Ósemkowe. |
ios::floatfield |
Steruje wyświetlaniem liczb
zmiennoprzecinkowych |
ios::scientific |
Format
naukowy Pole ios::precision definiuje liczbę cyfr po kropce. |
ios::fixed |
Format
o stałej liczbie znaków. Pole
ios::precision definiuje liczbę cyfr po kropce. |
Automatyczny (Żaden z bitów
nie jest ustawiony) |
Pole ios::precision definiuje całkowitą liczbę cyfr
znaczących. |
ios::adjustfield |
Steruje wyrównaniem |
ios::left |
Wyrównaj w lewo.
Uzupełnij wolne miejsce z prawej strony aktualnym znakiem wypełnienia. |
ios::right |
Wyrównaj w
prawo. Uzupełnij wolne miejsce z lewej strony aktualnym znakiem wypełnienia. |
ios::internal |
Dodaj znaki
wypełnienia po przedrostku (znak lub oznaczenie podstawy) ale przed wartością
liczbową. |
Funkcja |
|
int ios::width( ) |
Zwraca aktualną
szerokość pola (standardowo 0). Parametr jest używany zarówno przy zapisie,
jak i odczycie. |
int ios::width(int n) |
Ustawia
szerokość, zwraca poprzednią wartość. |
int ios::fill( ) |
Zwraca aktualny
znak wypełnienia (standardowo spacja) |
int ios::fill(int n) |
Ustawia znak
wypełnienia. Zwraca poprzednią wartość. |
int ios::precision( ) |
Zwraca aktualną
wartość precyzji dla liczb zmiennoprzecinkowych (Standardowo: 6.) |
int ios::precision(int n) |
Ustawia
precyzję. zwraca poprzednią wartość. |
Alternatywną metodą zmiany zachowania strumienia jest użycie manipulatorów.
Manipulatory są specjalnymi
· funkcjami (dla manipulatorów bezargumentowych) lub
· klasami (dla manipulatorów z argumentami),
które zmieniają stan strumienia wykonując odpowiednie funkcje ustawiające bity stanu i wartości pól. Niektóre manipulatory, jak endl, ends po prostu wstawiają wartość do strumienia.
Manipulator bezargumentowy endl jest zdefiniowany jako
ostream&
endl(ostream& os)
{
return
os << '\n' << flush;
}
Wołając:
cout<<”Hello world”;
cout<< endl;
Wstawiamy do strumienia wskaźnik do funkcji endl. Tym samym uruchamiamy przeciążoną wersję operatora << :
ostream&
operator<<(ostream& (__cdecl *
_f)(ostream&))
która wywołuje funkcję _f przekazując jako argument referencję do strumienia.
Podstawowe manipulatory są zdefiniowane w
nagłówku <iostream>, ich
rozszerzony zbiór znaleźć można w <iomanip>.
Podstawowe manipulatory to:
endl |
dodaje znak końca linii i opróżnia bufor strumienia wyjściowego |
flush |
opróżnia bufor strumienia wyjściowego: cout << flush |
ends |
specjalny manipulator dla ostrstream, który dodaje 0 na końcu |
dec |
zmienia podstawę liczbową na dziesiętną |
oct |
zmienia podstawę liczbową na ósemkową (oktalną) |
hex |
zmienia podstawę liczbową na szesnastkową (heksadecymalną): cout << hex << "0x" << i
<< endl |
ws |
ignoruje białe znaki w strumieniu wejściowym cin >>ws; |
Manipulatory zdefiniowane w <iomanip>
showbase |
Wyświetlając liczby dziesiętnie, szesnastkowo lub ósemkowo sformatuj zgodnie ze składnią stałych w języku C++. |
showpos |
Wyświetl plus (+) dla wartości dodatnich. |
uppercase |
Wyświetl duże litery A-F dla wartości heksadecymalnych i E dla notacji naukowej. |
showpoint |
Wyświetl kropkę i zera dla liczb zmiennoprzecinkowych. |
skipws |
Pomiń białe znaki na wejściu. |
left |
Wyrównanie w lewo, |
scientific |
Format naukowy Manipulator setprecision() definiuje liczbę miejsc po przecinku |
Manipulatory z argumentami
setiosflags |
Ustawia flagi zdefiniowane przez n |
resetiosflags
(fmtflags n) |
Zeruje flagi |
setbase(base n) |
Ustawia podstawę dla wypisywanych liczb (10, 16 lub 8) |
setfill(char n) |
Ustala znak wypełnienia |
setprecision |
Ustawia precyzję |
setw(int n) |
Ustawia szerokość |
void
main()
{
double values[] =
{ 1.23, 35.36, 653.7, 4358.24 };
for( int i = 0; i < 4; i++ )
cout<<setw (10)
<<setfill( '*' );
cout << values[i] << '\n';
}
Rezultat
******1.23
*****35.36
*****653.7
***4358.24
#include <iostream.h>
#include <iomanip.h>
void main()
{
double values[] =
{ 1.23, 35.36, 653.7, 4358.24 };
char *names[] =
{ "Zosia", "Jan", "Antoni", "Stefan" };
for ( int i = 0; i < 4; i++ )
cout << setiosflags( ios::left )
<< setw( 6 ) << names[i]
<< resetiosflags( ios::left )
<< setw( 10 ) << values[i] << endl;
}
Wynik
Zosia 1.23
Jan 35.36
Anatoni 653.7
Stefan 4358.24