Podrozdziały


16.7 Pliki wewnętrzne

Rolę plików mogą też pełnić napisy, czyli tablice znaków w stylu C i napisy w stylu C++, a więc obiekty klasy string.


16.7.1 Tablice znaków jako pliki wewnętrzne

Napis w stylu C jest ciągłym obszarem pamięci, do którego odnosimy się jak do tablicy znaków. Obszar ten może być traktowany jako plik, do którego dane mogą być zapisywane lub z którego mogą być odczytywane.

Aby korzystać z tego rodzaju „plików”, należy dołączyć plik nagłówkowy strstream, który daje nam dostęp do klasy strumieniowej wejściowej istrstream i wyjściowej ostrstream.

Ten nagłówek został usunięty ze standardu, ale jest implementowany przez wszystkie kompilatory C++ dla zachowania wstecznej zgodności. Przy kompilacji mogą pojawić się ostrzeżenia!

Obiekt strumieniowy klasy wyjściowej ostrstream tworzymy na dwa sposoby:

Przykład:


P139: intfilo.cpp     Zapis do plików wewnętrznych

      1.  #include <iostream>
      2.  #include <strstream>
      3.  #include <cstdlib>   // free
      4.  using namespace std;
      5.  
      6.  int main() {
      7.      // wersja "gumowa"
      8.      ostrstream napis1;
      9.      napis1 << "Poczatek, " << "dalszy ciag, "
     10.             << "koniec."    << ends;
     11.      char* n = napis1.str();
     12.      cout << "Napis jest: " << n << endl;
     13.      free(n);
     14.  
     15.      // wersja tablicowa
     16.      char tab[30];
     17.      ostrstream napis2(tab,sizeof(tab));
     18.      napis2 << "Magda " << "Kasia " << "Marta" << ends;
     19.      cout << tab << endl;
     20.  }

Program ten wypisuje na ekranie
    Napis jest: Poczatek, dalszy ciag, koniec.
    Magda Kasia Marta
Zauważmy, że pamięci na napis n nie alokujemy. Jest ona alokowana automatycznie i jej adres jest zwracany przez metodę str (linia 11).

Podobnie można z tablicy znaków czytać, tworząc obiekt klasy istrstream.

Pliki wewnętrzne oparte na C-napisach są czasem używane do reprezentowania pliku dyskowego, szczególnie jeśli zachodzi potrzeba częstego „skakania” do różnych części pliku, co może być drogie, gdyż wymaga wielu operacji dostępu do pliku. Może wtedy być bardziej opłacalne wczytanie całego pliku do tablicy znaków i dalej używanie jej jak pliku wewnętrznego.


16.7.2 Napisy C++ jako pliki wewnętrzne

Podobnie jak tablice znaków, do reprezentowania plików wewnętrznych mogą być używane obiekty klasy string. Jest to łatwiejsze i bezpieczniejsze niż używanie tablic znaków.

Aby korzystać z tego udogodnienia, należy dołączyć, prócz pliku string, plik nagłówkowy sstream. Zdefiniowane tam są klasy strumieniowe wejściowa istringstream i wyjściowa ostringstream korzystające z napisów klasy string.

Strumień wyjściowy można utworzyć za pomocą konstruktora domyślnego:

       ostringstream strm;
i pisać do niego jak do normalnego strumienia
       strm << "To be" << " or not to be" << ", etc.";
Nie trzeba martwić się o przepełnienie, bo pamięć w miarę potrzeby jest alokowana automatycznie. Po zakończeniu pisania do strumienia, wywołujemy na jego rzecz metodę str, która zwraca napis (obiekt klasy string) zawierający rezultat, na przykład:
       string s = strm.str();
       cout << s << endl;
Można też skonstruować strumień z wpisanym już fragmentem w trybie dopisywania na końcu
       ostringstream ostr("Jakis napis", ios::ate);
       ostr << "Dalsza czesc";
a następnie dopisywać dalsze fragmenty, jak w przykładzie powyżej.

Istniejący obiekt klasy string też może być otwarty jako wejściowy plik wewnętrzny, jak w przykładzie poniżej:


P140: intstr.cpp     Napisy C++ jako pliki wewnętrzne

      1.  #include <iostream>
      2.  #include <sstream>
      3.  using namespace std;
      4.  
      5.  void words(const string& s) {
      6.      istringstream istr(s);
      7.  
      8.      string word;
      9.      while ( istr >> word )
     10.          cout << word << endl;
     11.  }
     12.  
     13.  int main() {
     14.      string s = "Bach Haydn";
     15.      ostringstream ostr(s, ios::ate);
     16.      ostr << " Chopin";
     17.      string s1 = ostr.str();
     18.      words(s1);
     19.  }

Najpierw, w linii 15, otwieramy wewnętrzny plik wyjściowy zainicjowany zawartością napisu s. Dodajemy do niego pewne dane (linia 16) i pobieramy wynikową zawartość do s1. Następnie s1 jest przekazywane do funkcji words, która ten napis otwiera do czytania jako wejściowy plik wewnętrzny (linia 6). Z pliku tego czytamy kolejne słowa oddzielone znakiem odstępu, według normalnych zasad czytania ze strumienia. Zauważmy, że po osiągnięciu końca pliku, stan strumienia przejdzie w  bad, co zakończy pętlę z linii 9-10. Program drukuje zatem
    Bach
    Haydn
    Chopin

Pokażemy jeszcze, jak czytać z i zapisywać do plików tekstowych. Spójrzmy na przykład


P141: RWfile.cpp     Reading and writing text files

      1.  #include <iostream>
      2.  #include <fstream>   // ifstream, ofstream
      3.  #include <string>
      4.  #include <sstream>   // stringstream
      5.  using namespace std;
      6.  
      7.  int main() {
      8.      string line{};
      9.  
     10.      ofstream outf{"RWfile.out"};                           
     11.      for (ifstream in{"RWfile.dat"}; getline(in, line);) {  
     12.          cout << line  << "\n";
     13.          string name;
     14.          int height;
     15.          double weight;
     16.          istringstream str{line};                           
     17.          str >> name >> height >> weight;
     18.          cout << name << ": height=" << height
     19.               << ", weight=" << weight << '\n';
     20.          outf << name << ": height=" << height              
     21.               << ", weight=" << weight << '\n';
     22.      }
     23.      outf.close();
     24.  
     25.        // again but in a while loop
     26.      ifstream in{"RWfile.dat"};
     27.      while (getline(in, line)) {
     28.          cout << line  << "\n";
     29.      }
     30.  }

W linii tworzymy obiekt outf typu ofstream ('of' od output stream). Obiekt ten reprezentuje strumień wyjściowy, jak cout ale związany z plikiem RWfile.out (który zostanie utworzony, jeśli nie istnieje). Jak widzimy w linii , używamy outf dokładnie jak cout, ale tekst jest zapisywany w pliku, a nie na ekranie. W linii tworzymy obiekt in typu ifstream — to jest strumień wejściowy, jak cin ale dla którego dane są wczytywane z pliku a nie z klawiatury. Funkcja getline (z nagłówka string header) pobiera strumień wejściowy i obiekt typu string (w naszym przypadku line), czyta jeden wiersz do line. Zwraca otrzymany strumień, który jest konwertowalny do wartości logicznej, która będzie false gdy napotkany zostanie koniec pliku.
Object str () jest typu istringstream (z nagłówka sstream) i reprezentuje strumień wejściowy, dla którego źródłem jest przekazany do konstruktora napis (w naszym przypadku line). Jak widzimy możemy używać tego obiektu jak cin aby wczytać dane z  line.
Z następującymi danymi w pliku RWfile.dat

    Mary 167 56.5
    Jane 162 55.7
    Kate 170 59.1
program wypisuje na ekran i do pliku RWfile.out
    Mary 167 56.5
    Mary: height=167, weight=56.5
    Jane 162 55.7
    Jane: height=162, weight=55.7
    Kate 170 59.1
    Kate: height=170, weight=59.1
    Mary 167 56.5
    Jane 162 55.7
    Kate 170 59.1

T.R. Werner, 23 lutego 2022; 19:40