Operacje we/wy na plikach

Stronę tą wyświetlono już: 290 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.

Listing 1
  1. #include <iostream>
  2. #include <fstream>
  3. #include <string.h>
  4. using namespace std;
  5. int main(){
  6. fstream file("file.txt", // ścieżka do otwieranego pliku (w tym przypadku względna)
  7. std::ios_base::out // tryb otwarcia do zapisu
  8. ); // utworznie obiektu typu fstream
  9. if(file.is_open()){ // metoda is_open() sprawdza, czy plik rzeczywiście został otwarty (równie dobrze można użyć metody .good())
  10. for(int i = 0; i < 10; i++){ // od 0 do 9
  11. file << "Tekst "<<i + 1<<endl; // zapis tekstu z znakiem nowej linii na końcu
  12. }
  13. file.close(); // zamykanie pliku
  14. }
  15. file.open("file.txt",std::ios_base::in); // otwarcie tego samego pliku w trybie do odczytu
  16. if(file.is_open()){ // jeżeli otwarte
  17. char buffor[255]; // bufor danych
  18. while(!file.eof()){ // dopuki nie osiągnięto końca pliku
  19. file.getline(buffor, sizeof(buffor) / sizeof(char)); // wczytywanie linii tekstu z pliku (do znaku końca linii)
  20. cout<<buffor<<endl<<endl; // wyświetlanie tekstu
  21. }
  22. file.close(); // zamykaj plik
  23. }
  24. file.open("file.txt",std::ios_base::in); // otwarcie tego samego pliku w trybie do odczytu
  25. if(file.is_open()){ // jeżeli otwarte
  26. string str; // bufor danych
  27. while(!file.eof()){ // dopuki nie osiągnięto końca pliku
  28. getline(file, str); // wczytywanie linii tesktu z pliku (do znaku końca linii) do zmiennej typu string
  29. cout<<str<<endl<<endl; // wyświetlanie tekstu
  30. }
  31. file.close(); // zamykaj plik
  32. }
  33. cout<<"Wcisnij enter, aby zamknac program...";
  34. cin.get();
  35. return 0;
  36. }

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:

std::ios_base::appotwieranie z dopisywaniem danych do końca pliku;
std::ios_base::ateustawia wskaźnik pliku na jego koniec;
std::ios_base::binarywczytywanie danych binarnych;
std::ios_base::intryb do odczytu danych;
std::ios_base::outtryb zapisu danych do pliku;
std::ios_base::trunczrzuca zawartość pliku, która jest tracona w chwili jego otwierania.

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ę:

Listing 2
  1. 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ę:

Listing 3
  1. 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ę:

Listing 4
  1. 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ę:

Listing 5
  1. 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ę:

Listing 6
  1. 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ę:

Listing 7
  1. 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:

Listing 8
  1. void ParseString(std::string str_to_parse, std::vector<std::string> &tString,std::string parse_signs){
  2. if(tString.size()){
  3. tString.clear();
  4. }
  5. std::string::size_type sizeBegin = 0;
  6. std::string::size_type sizeEnd = str_to_parse.find_first_of(parse_signs, sizeBegin);
  7. while(sizeEnd != std::string::npos){
  8. if(sizeEnd != sizeBegin){
  9. tString.push_back(
  10. str_to_parse.substr(sizeBegin, sizeEnd - sizeBegin)
  11. );
  12. }
  13. tString.push_back(
  14. str_to_parse.substr(sizeEnd,1)
  15. );
  16. sizeBegin = sizeEnd + 1;
  17. sizeEnd = str_to_parse.find_first_of(parse_signs, sizeBegin);
  18. };
  19. if(sizeBegin < str_to_parse.size()){
  20. tString.push_back(
  21. str_to_parse.substr(sizeBegin)
  22. );
  23. }
  24. }
  25. void SaveToFile(std::vector<iObject*> &tObject){ // funkcja zapisująca dane o utworzonych obiektach do pliku tekstowego
  26. std::fstream file("SaveFile.txt",std::ios_base::out); // otwieranie pliku do zapisu
  27. if(file.is_open()){ // czy otwarty
  28. for(std::vector<iObject*>::iterator i = tObject.begin(); i < tObject.end(); i++){ // iterowanie po interfejsach obiektów zawartych w kontenerze
  29. (*i)->WriteToFile(file); // zapis
  30. }
  31. }else{
  32. std::cout<<"Wzielo sie i nie zapisalo"<<std::endl<<std::endl;
  33. }
  34. }
  35. void ReadFromFile(std::vector<iObject*> &tObject){ // czytanie danych z pliku tekstowego
  36. std::fstream file("SaveFile.txt",std::ios_base::in); // otwieranie pliku
  37. if(file.is_open()){ // sprawdzanie, czy się otworzył
  38. while(!file.eof()){ // czy koniec pliku został osiągnięty, jak nie to
  39. std::string temp; // pomocnicza do wczytywania zmienna
  40. std::getline(file, temp); // wczytuję
  41. std::vector<std::string> tStr; // tablica do parsowania tekstu z zmiennej pomocniczej temp
  42. ParseString(temp, tStr, " ;="); // parsuję tekst względem spacjji, średnika i znaku równości
  43. if(tStr.size()){ // jak tStr.size() nie jest równe zero to
  44. if(tStr[0].compare("Point2d:") == 0){ // jak pierwszy element zawiera tekst "Point2d:" to
  45. if(tStr.size() == 11){ // jak liczba elementów w tablicy sparsowanego tekstu się zgadza to
  46. std::cout<<"Wczytano Point2d "<<tStr.size()<<std::endl; // info, że tak, syćko jest w pożądku
  47. tObject.push_back(new point2d(atol(tStr[4].c_str()), atol(tStr[9].c_str()))); // i dodaję do tablicy interfejsów nowy obiekt
  48. }else{ // a jak nie, to znaczy się, że ktoś spartolił formatowanie i się nie wczyta
  49. std::cout<<"Cos nie tak w zapisie \""<<temp<<std::endl<<std::endl;
  50. }
  51. }else if(tStr[0].compare("Point3d:") == 0){ // to samo tylko dla "Point3d:"
  52. if(tStr.size() == 16){
  53. std::cout<<"Wczytano Point3d "<<tStr.size()<<std::endl;
  54. tObject.push_back(new point3d(atol(tStr[2].c_str()), atol(tStr[4].c_str()), atol(tStr[14].c_str())));
  55. }else{
  56. std::cout<<"Cos nie tak w zapisie \""<<temp<<std::endl<<std::endl;
  57. }
  58. }else if(tStr[0].compare("Line:") == 0){ // to samo tylko dla "Line:"
  59. if(tStr.size() == 21){
  60. std::cout<<"Wczytano Line "<<tStr.size()<<std::endl;
  61. int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
  62. sscanf(temp.c_str(),"Line: x1=%i; y2=%i; x2=%i; y2=%i;",&x1, &y1, &x2, &y2);
  63. tObject.push_back(new line(x1, y1, x2, y2));
  64. }else{
  65. std::cout<<"Cos nie tak w zapisie \""<<temp<<std::endl<<std::endl;
  66. }
  67. }else if(tStr[0].compare("Circle:") == 0){ // to samo tylko dla "Circle:"
  68. if(tStr.size() == 16){
  69. std::cout<<"Wczytano Circle "<<tStr.size()<<std::endl;
  70. int xc = 0, yc = 0, ray = 0;
  71. sscanf(temp.c_str(),"Circle: xc=%i; yc=%i; ray=%i;",&xc, &yc, &ray);
  72. tObject.push_back(new circle(xc, yc, ray));
  73. }else{
  74. std::cout<<"Cos nie tak w zapisie \""<<temp<<std::endl<<std::endl;
  75. }
  76. }else if(tStr[0].compare("Ellipse:") == 0){ // to samo tylko dla "Ellipse:"
  77. if(tStr.size() == 21){
  78. std::cout<<"Wczytano Ellipse "<<tStr.size()<<std::endl;
  79. int xc = 0, yc = 0, r1 = 0, r2 = 0;
  80. sscanf(temp.c_str(),"Ellipse: xc=%i; yc=%i; r1=%i; r2=%i",&xc, &yc, &r1, &r2);
  81. tObject.push_back(new ellipse(xc, yc, r1, r2));
  82. }else{
  83. std::cout<<"Cos nie tak w zapisie \""<<temp<<std::endl<<std::endl;
  84. }
  85. }
  86. }
  87. }
  88. }else{
  89. std::cout<<"Wzielo sie i nie wczytalo"<<std::endl<<std::endl;
  90. }
  91. }

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

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

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:

Listing 10
  1. #include <iostream>
  2. #include <fstream>
  3. #include <string.h>
  4. using namespace std;
  5. struct headerfile{ // a to będzie struktura nagłówkowa
  6. unsigned int sizedata; // rozmiar danych zawartych w pliku zaraz za nagłówkiem
  7. char text[21]; // jakiś tam dodatkowy tekst w nagłówku
  8. char author_name[21]; // autor pliku
  9. };
  10. int main(){
  11. fstream file("file.txt",std::ios_base::out|std::ios::binary); // otwieranie do odczytu
  12. if(file.is_open()){ // jak otwarty
  13. cout<<"otworzylem"<<endl;
  14. string data = "dane do zapisu w pliku binarnym";
  15. headerfile *hf = new headerfile; // deklaruję pamięć nagłówka
  16. hf->sizedata = data.size() + 1; // zapisuję informację o rozmiarze danych
  17. cout<<data.size() + 1<<endl<<endl;
  18. strcpy(hf->text, "Jakis tekst"); // wczytuje tekst do pola nagłówka text
  19. strcpy(hf->author_name, "Programista"); // to samo dla pola autor_name
  20. file.write((char*)hf, sizeof(headerfile)); // tutaj zapisuję nagłówek w pliku
  21. file.write(data.c_str(), data.size()); // tutaj zapisuję dane dodatkowe w pliku
  22. delete hf; // zwalniam przydzieloną pamięć
  23. hf = NULL;
  24. file.close(); // zamykanie pliku
  25. }
  26. file.open("file.txt",std::ios_base::in|std::ios::binary); // otwieranie do odczytu
  27. if(file.is_open()){ // jak otwarty
  28. cout<<"otworzylem"<<endl;
  29. char* bf = new char[sizeof(headerfile)]; // przygotowuję bufor danych dla nagłówka
  30. file.read(bf, sizeof(headerfile)); // czytam nagłówek
  31. headerfile *hf = (headerfile*)bf; // uzyskuję dane nagłówka
  32. char* data = NULL; // tutaj będę przechwytywał dane dodatkowe z pliku
  33. string datastr; // a tu je zapiszę jako string
  34. if(hf->sizedata){ // jeżeli sizedata nie równe zero to
  35. data = new char[hf->sizedata]; // rezerwowanie pamięci potrzebnej do pozyskania danych
  36. file.read(data, hf->sizedata); // wczytanie danych
  37. data[hf->sizedata - 1] = 0; // na końcu zero powinno być
  38. datastr = data; // przepisanie danych do stringa
  39. delete data; // zwolnienie pamięci
  40. }
  41. cout<<hf->sizedata<<" "<<hf->text<<" "<<hf->author_name<<" "<<datastr<<endl<<endl; // wypisanie na ekranie wczytanych danych
  42. delete bf; // zwalnianie pamięci bufora
  43. hf = NULL; // zerowanie wskaźnika nagłówka
  44. bf = NULL; // zerowanie wskaźnika bufora
  45. file.close(); // zamykanie pliku
  46. }
  47. cout<<"Wcisnij enter, aby zaknac program...";
  48. cin.get();
  49. return 0;
  50. }
Dodatkowe informacje:
  1. cplusplus.com - spis wszystkich metod i operatorów fstream
  2. cplusplus.com - opis metody open wraz z opcjami otwierania

Komentarze