Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 6444 razy

Zapis/odczyt danych tekstowych

W przypadku zapisu danych składających się często z rekordów o zmiennej długości wygodnie jest wczytywać i zapisywać dane jako tekst, gdzie kolejne elementy danego rekordu są oddzielone umownym znakiem. Takim umownym znakiem może być np. tabulator, średnik i w zasadzie każdy znak, który nie będzie kolidował z formatem zapisywanych danych. W celu skorzystania z klasy odpowiedzialnej za otwieranie i zapis danych w pliku konieczne jest załączenie pliku nagłówkowego fstream, w którym to znajduje się deklaracja klasy o tej samej nazwie, czyli fstream, która to zostanie wykorzystana do zapisu i odczytu danych w pliku tekstowym.

#include <iostream> #include <fstream> #include <string.h> using namespace std; int main(){ fstream file("file.txt", // ścieżka do otwieranego pliku (w tym przypadku względna) std::ios_base::out // tryb otwarcia do zapisu ); // utworznie obiektu typu fstream if(file.is_open()){ // metoda is_open() sprawdza, czy plik rzeczywiście został otwarty (równie dobrze można użyć metody .good()) for(int i = 0; i < 10; i++){ // od 0 do 9 file << "Tekst "<<i + 1<<endl; // zapis tekstu z znakiem nowej linii na końcu } file.close(); // zamykanie pliku } file.open("file.txt",std::ios_base::in); // otwarcie tego samego pliku w trybie do odczytu if(file.is_open()){ // jeżeli otwarte char buffor[255]; // bufor danych while(!file.eof()){ // dopuki nie osiągnięto końca pliku file.getline(buffor, sizeof(buffor) / sizeof(char)); // wczytywanie linii tekstu z pliku (do znaku końca linii) cout<<buffor<<endl<<endl; // wyświetlanie tekstu } file.close(); // zamykaj plik } file.open("file.txt",std::ios_base::in); // otwarcie tego samego pliku w trybie do odczytu if(file.is_open()){ // jeżeli otwarte string str; // bufor danych while(!file.eof()){ // dopuki nie osiągnięto końca pliku getline(file, str); // wczytywanie linii tesktu z pliku (do znaku końca linii) do zmiennej typu string cout<<str<<endl<<endl; // wyświetlanie tekstu } file.close(); // zamykaj plik } cout<<"Wcisnij enter, aby zamknac program..."; cin.get(); return 0; }

Jak wynika z powyższego kodu, już przy tworzeniu obiektu klasy fstream można otworzyć plik, podając konstruktorowi tejże klasy: względną (np. "file.txt") lub bezwzględną ścieżkę dostępu do pliku (np. "C:\file.txt"). Kolejny argument określa tryb dostępu do pliku, który może być kombinacją dwóch ustawień: std::ios::in - do odczytu; std::ios::out - do zapisu. Aby otworzyć plik do odczytu i do zapisu trzeba podać argument: std::ios::in|std::iso::out. Istnieje też możliwość otworzenia pliku za pomocą wewnętrznej metody open, która przyjmuje te same argumenty. Poniżej zamieszczam opis możliwych do użycia trybów odczytu pliku:

Po otworzeniu pliku trzeba się upewnić, czy aby na pewno ten został otworzony. Do tego celu można posłużyć się jedną z dwóch dostępnych metod: is_open() lub good(), obie te metody zwracają true, gdy otwarcie pliku się powiodło, zaś false w przeciwnym przypadku. Równie ważna jest metoda eof(), która zwraca true, gdy podczas odczytu osiągnięto koniec pliku.

W powyższym przykładzie dwukrotnie otworzyłem ten sam plik. W pierwszym jednak przypadku użyłem metody wewnętrznej getline(buffer, bufferSize) gdzie buffer jest typu const char*, a bufferSize jest typu unsigned int; natomiast w drugim funkcji std::getline(file, str) gdzie z kolei pierwszy argument jest typu fstream, drugi zaś typu string.

Czas zrobić nieco ciekawszy przykład, na bazie programu, który wykonałem na stronie Programowanie → Podstawy C++ → Metody wirtualne, czysto wirtualne oraz klasy abstrakcyjne, nie będę przepisywał całego projektu, powiem tylko co gdzie należy dodać. Do definicji klasy abstrakcyjnej iObject należy dodać taką oto czysto wirtualną metodę:

virtual void WriteToFile(std::fstream &f) const =0;// metoda czysto wirtualna związana z obsługą zapisu danych do pliku

Do definicji klasy point2d metodę:

inline virtual void WriteToFile(std::fstream &f) const {f <<"Point2d: x="<<x<<"; y="<<y<<";"<<std::endl;}; // obsługa zapisu do pliku

Do definicji klasy point3d metodę:

inline virtual void WriteToFile(std::fstream &f) const {f <<"Point3d: x="<<x<<"; y="<<y<<"; z="<<z<<";"<<std::endl;}; // obsługa zapisu do pliku

Do definicji klasy line metodę:

inline virtual void WriteToFile(std::fstream &f) const {f <<"Line: x1="<<firstPoint.GetX()<<"; y2="<<firstPoint.GetY()<<"; x2="<<secondPoint.GetX()<<"; y2="<<secondPoint.GetY()<<";"<<std::endl;}; // obsługa zapisu do pliku

Do definicji klasy circle metodę:

inline virtual void WriteToFile(std::fstream &f) const {f <<"Circle: xc="<<x<<"; yc="<<y<<"; ray="<<ray<<";"<<std::endl;}; // obsługa zapisu do pliku

De definicji klasy ellipse metodę:

inline virtual void WriteToFile(std::fstream &f) const {f <<"Ellipse: xc="<<x<<"; yc="<<y<<"; r1="<<r1<<"; r2="<<r2<<";"<<std::endl;}; // obsługa zapisu do pliku

Na koniec kilka funkcji dodatkowych:

void ParseString(std::string str_to_parse, std::vector<std::string> &tString,std::string parse_signs){ if(tString.size()){ tString.clear(); } std::string::size_type sizeBegin = 0; std::string::size_type sizeEnd = str_to_parse.find_first_of(parse_signs, sizeBegin); while(sizeEnd != std::string::npos){ if(sizeEnd != sizeBegin){ tString.push_back( str_to_parse.substr(sizeBegin, sizeEnd - sizeBegin) ); } tString.push_back( str_to_parse.substr(sizeEnd,1) ); sizeBegin = sizeEnd + 1; sizeEnd = str_to_parse.find_first_of(parse_signs, sizeBegin); }; if(sizeBegin < str_to_parse.size()){ tString.push_back( str_to_parse.substr(sizeBegin) ); } } void SaveToFile(std::vector<iObject*> &tObject){ // funkcja zapisująca dane o utworzonych obiektach do pliku tekstowego std::fstream file("SaveFile.txt",std::ios_base::out); // otwieranie pliku do zapisu if(file.is_open()){ // czy otwarty for(std::vector<iObject*>::iterator i = tObject.begin(); i < tObject.end(); i++){ // iterowanie po interfejsach obiektów zawartych w kontenerze (*i)->WriteToFile(file); // zapis } }else{ std::cout<<"Wzielo sie i nie zapisalo"<<std::endl<<std::endl; } } void ReadFromFile(std::vector<iObject*> &tObject){ // czytanie danych z pliku tekstowego std::fstream file("SaveFile.txt",std::ios_base::in); // otwieranie pliku if(file.is_open()){ // sprawdzanie, czy się otworzył while(!file.eof()){ // czy koniec pliku został osiągnięty, jak nie to std::string temp; // pomocnicza do wczytywania zmienna std::getline(file, temp); // wczytuję std::vector<std::string> tStr; // tablica do parsowania tekstu z zmiennej pomocniczej temp ParseString(temp, tStr, " ;="); // parsuję tekst względem spacjji, średnika i znaku równości if(tStr.size()){ // jak tStr.size() nie jest równe zero to if(tStr[0].compare("Point2d:") == 0){ // jak pierwszy element zawiera tekst "Point2d:" to if(tStr.size() == 11){ // jak liczba elementów w tablicy sparsowanego tekstu się zgadza to std::cout<<"Wczytano Point2d "<<tStr.size()<<std::endl; // info, że tak, syćko jest w pożądku tObject.push_back(new point2d(atol(tStr[4].c_str()), atol(tStr[9].c_str()))); // i dodaję do tablicy interfejsów nowy obiekt }else{ // a jak nie, to znaczy się, że ktoś spartolił formatowanie i się nie wczyta std::cout<<"Cos nie tak w zapisie \apos"<<temp<<std::endl<<std::endl; } }else if(tStr[0].compare("Point3d:") == 0){ // to samo tylko dla "Point3d:" if(tStr.size() == 16){ std::cout<<"Wczytano Point3d "<<tStr.size()<<std::endl; tObject.push_back(new point3d(atol(tStr[2].c_str()), atol(tStr[4].c_str()), atol(tStr[14].c_str()))); }else{ std::cout<<"Cos nie tak w zapisie \apos"<<temp<<std::endl<<std::endl; } }else if(tStr[0].compare("Line:") == 0){ // to samo tylko dla "Line:" if(tStr.size() == 21){ std::cout<<"Wczytano Line "<<tStr.size()<<std::endl; int x1 = 0, y1 = 0, x2 = 0, y2 = 0; sscanf(temp.c_str(),"Line: x1=%i; y2=%i; x2=%i; y2=%i;",&x1, &y1, &x2, &y2); tObject.push_back(new line(x1, y1, x2, y2)); }else{ std::cout<<"Cos nie tak w zapisie \apos"<<temp<<std::endl<<std::endl; } }else if(tStr[0].compare("Circle:") == 0){ // to samo tylko dla "Circle:" if(tStr.size() == 16){ std::cout<<"Wczytano Circle "<<tStr.size()<<std::endl; int xc = 0, yc = 0, ray = 0; sscanf(temp.c_str(),"Circle: xc=%i; yc=%i; ray=%i;",&xc, &yc, &ray); tObject.push_back(new circle(xc, yc, ray)); }else{ std::cout<<"Cos nie tak w zapisie \apos"<<temp<<std::endl<<std::endl; } }else if(tStr[0].compare("Ellipse:") == 0){ // to samo tylko dla "Ellipse:" if(tStr.size() == 21){ std::cout<<"Wczytano Ellipse "<<tStr.size()<<std::endl; int xc = 0, yc = 0, r1 = 0, r2 = 0; sscanf(temp.c_str(),"Ellipse: xc=%i; yc=%i; r1=%i; r2=%i",&xc, &yc, &r1, &r2); tObject.push_back(new ellipse(xc, yc, r1, r2)); }else{ std::cout<<"Cos nie tak w zapisie \apos"<<temp<<std::endl<<std::endl; } } } } }else{ std::cout<<"Wzielo sie i nie wczytalo"<<std::endl<<std::endl; } }

Oraz zmienione nieco instrukcje wewnątrz funkcji głównej programu:

int main(){ setlocale(LC_CTYPE,"Polish"); // to po to, żeby mieć polskie znaki std::vector<iObject*> tObject; ReadFromFile(tObject); // wczytywanie obiektów na podstawie danych zapisanych w pliku tekstowym WhatYouWantToDo(tObject); SaveToFile(tObject); // zapisywanie obiektów w pliku tekstowym DeleteAllObject(tObject,false); return 0; }

Zapis/odczyt danych binarnych

Gdy istnieje konieczność zapisania dużej liczby danych stosuje się nieco inny sposób wczytywania. Po pierwsze potrzebna będzie struktura, która będzie nagłówkiem tworzonego pliku. W nagłówku powinny zostać zawarte informacje dotyczące rozmiaru wczytywanych danych. Oto prościutki przykład programu, który zapisuje dane do pliku i wczytuje dane z pliku:

#include <iostream> #include <fstream> #include <string.h> using namespace std; struct headerfile{ // a to będzie struktura nagłówkowa unsigned int sizedata; // rozmiar danych zawartych w pliku zaraz za nagłówkiem char text[21]; // jakiś tam dodatkowy tekst w nagłówku char author_name[21]; // autor pliku }; int main(){ fstream file("file.txt",std::ios_base::out|std::ios::binary); // otwieranie do odczytu if(file.is_open()){ // jak otwarty cout<<"otworzylem"<<endl; string data = "dane do zapisu w pliku binarnym"; headerfile *hf = new headerfile; // deklaruję pamięć nagłówka hf->sizedata = data.size() + 1; // zapisuję informację o rozmiarze danych cout<<data.size() + 1<<endl<<endl; strcpy(hf->text, "Jakis tekst"); // wczytuje tekst do pola nagłówka text strcpy(hf->author_name, "Programista"); // to samo dla pola autor_name file.write((char*)hf, sizeof(headerfile)); // tutaj zapisuję nagłówek w pliku file.write(data.c_str(), data.size()); // tutaj zapisuję dane dodatkowe w pliku delete hf; // zwalniam przydzieloną pamięć hf = NULL; file.close(); // zamykanie pliku } file.open("file.txt",std::ios_base::in|std::ios::binary); // otwieranie do odczytu if(file.is_open()){ // jak otwarty cout<<"otworzylem"<<endl; char* bf = new char[sizeof(headerfile)]; // przygotowuję bufor danych dla nagłówka file.read(bf, sizeof(headerfile)); // czytam nagłówek headerfile *hf = (headerfile*)bf; // uzyskuję dane nagłówka char* data = NULL; // tutaj będę przechwytywał dane dodatkowe z pliku string datastr; // a tu je zapiszę jako string if(hf->sizedata){ // jeżeli sizedata nie równe zero to data = new char[hf->sizedata]; // rezerwowanie pamięci potrzebnej do pozyskania danych file.read(data, hf->sizedata); // wczytanie danych data[hf->sizedata - 1] = 0; // na końcu zero powinno być datastr = data; // przepisanie danych do stringa delete data; // zwolnienie pamięci } cout<<hf->sizedata<<" "<<hf->text<<" "<<hf->author_name<<" "<<datastr<<endl<<endl; // wypisanie na ekranie wczytanych danych delete bf; // zwalnianie pamięci bufora hf = NULL; // zerowanie wskaźnika nagłówka bf = NULL; // zerowanie wskaźnika bufora file.close(); // zamykanie pliku } cout<<"Wcisnij enter, aby zaknac program..."; cin.get(); return 0; }
Dodatkowe informacje:
  1. cplusplus.com - spis wszystkich metod i operatorów fstream
  2. cplusplus.com - opis metody open wraz z opcjami otwierania