Biblioteka strumieni C++

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

 

Klasa opakowująca stdio

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

 


Implementacja metod

 

#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;

}

 

Przykład użycia

 

#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

Pełne opakowanie

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ę

 

 


Operatory ekstrakcji i wstawiania

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 (wierszy) ze strumienia wejściowego

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.

Odczyt znaków ze strumienia wejściowego

Aby odczytać pojedynczy bajt należy użyć funkcji klasy istream

 

int get();

albo        

istream& get( char& rch );

 

Odczyt grupy bajtów ze strumienia wejściowego

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;

}

 

 

Obsługa błędów

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();

   

}

 

 


Strumienie plikowe

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;

    }

}

 

 

Strumienie strstream

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.

istrstream

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

ostrstream

Klasa umożliwia zapis i formatowanie do bufora dostarczonego przez użytkownika lub automatycznie przyrastającego bufora zarządzanego przez strumień.

Zapis do bufora dostarczonego przez użytkownika

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

Zapis do bufora zarządzanego przez strumień

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);

}

 

Formatowanie strumieni wyjściowych

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);

Flagi

 

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.

 

Grupy flag

 

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ą.

 

Sterowanie szerokością, wypełnieniem i precyzją

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ść.

 


Manipulatory

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.

 

Przykład

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
noshowbase

Wyświetlając liczby dziesiętnie, szesnastkowo lub ósemkowo sformatuj zgodnie ze składnią stałych w języku C++.

showpos
noshowpos

Wyświetl plus (+) dla wartości dodatnich.

uppercase
nouppercase

Wyświetl duże litery A-F dla wartości heksadecymalnych i  E dla notacji naukowej.

showpoint
noshowpoint

Wyświetl kropkę i zera dla liczb zmiennoprzecinkowych.

skipws
noskipws

Pomiń białe znaki na wejściu.

left
right
internal

Wyrównanie w lewo,
prawo,
z wypełnieniem pomiędzy przedrostkiem a wartością liczbową.

scientific
fixed

Format naukowy
stały

Manipulator setprecision() definiuje liczbę miejsc po przecinku

 

Manipulatory z argumentami

setiosflags
 (fmtflags n)

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
(int n)

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