Pliki wykład Dorota Pylak
Pliki 2 Większość programów komputerowych korzysta z plików, np. w edytorach tekstów tworzymy pliki dokumentów. Plik (ang. file) jest porcją danych zapisanych na jakimś nośniku. Pliki mogą zawierać dane z których chcemy korzystać w naszym programie - pliki danych lub możemy w nich zapisywać rezultaty - pliki wynikowe. Dzięki wykorzystaniu plików nie musimy wprowadzać wielokrotnie danych, a raz wprowadzone np. przy pomocy jakiegoś edytora używać wiele razy. Pliki wejścia/wyjścia mogą być otwierane w trybie tekstowym lub w trybie binarnym. Np. w formacie tekstowym, liczby są zapisywane, jako łańcuchy znaków, natomiast w formacie binarnym liczby są zapisywane tak jak w pamięci: cztery bajty na liczbę całkowitą (int) i osiem na liczbę zmiennoprzecinkową (double). Format tekstowy jest bardziej czytelny, pliki takie można edytować w zwykłych edytorach tekstu, natomiast format binarny zapewnia dokładniejszy zapis liczb, ponieważ zapisywana jest dokładna wewnętrzna reprezentacja liczby. Do przetwarzania plików w języku C++ stosowane są strumienie.
Strumienie Operacje we/wy realizuje się w C++ za pomocą strumieni. Strumień możemy wyobrażać sobie jako ciąg danych (informacji), w postaci bajtów, płynący od źródła do ujścia. Zachodzą przy tym dwie możliwości: informacja płynie od naszego programu do odbiornika (ujścia). Odbiornikiem może być terminal, plik, gniazdo sieciowe, obszar w pamięci, nazwany potok itd. O takim strumieniu mówimy, że jest strumieniem wyjściowym (ang. output stream), informacja płynie do naszego programu ze źródła. Źródłem może być terminal, plik, itd. O takim strumieniu mówimy, że jest strumieniem wejściowym (ang. input stream). Strumień służy do zapisywania bądź odczytywania informacji - dowolnych danych
Strumienie- uproszczony schemat klas 4 Do reprezentowania strumieni i obsługi operacji na nich zdefiniowane są specjalne klasy wyposażone w pola i metody.
Strumienie- podstawowe klasy Podstawową dla nas klasą jest klasa ios, po niej dziedziczą klasy: istream podstawowa klasa reprezentująca strumienie wejściowe. W niej, między innymi, zdefiniowane jest przeciążenie operatora '>>' i tej właśnie klasy obiektem jest cin, reprezentujący standardowy strumień wejściowy. Bardziej wyspecjalizowane klasy do obsługi strumieni wejściowych to m.in: istringstream źródłem jest obiekt klasy string, czyli napis. Dostępna po dołączeniu za pomocą dyrektywy #include<sstream>. ifstream (z ang. intput file stream) źródłem jest plik. Udostępniana po dołączeniu fstream. ostream podstawowa klasa reprezentująca strumienie wyjściowe. W niej właśnie zdefiniowane jest przeciążenie operatora '<<' i obiekty cout, reprezentujący standardowy strumień wyjściowy oraz cerr i clog, reprezentujące standardowy strumień błędów niebuforowany i buforowany, a także manipulator endl. Bardziej wyspecjalizowane klasy do obsługi strumieni wyjściowych to m.in.: ostringstream ujściem jest obiekt klasy string, czyli napis. Udostępniana po dołączeniu pliku sstream. ofstream (z ang. output file stream) odbiornikem jest plik. Udostępniana po dołączeniu pliku nagłówkowego fstream. 5
Strumienie- podstawowe klasy 6 Klasa iostream dziedziczy zarówno po istream, jak i po ostream, zatem dołączając iostream zapewniamy funkcjonalność obu. Podobnie, dołączając fstream zapewniamy dostęp do klas ifstream i ofstream, a dołączając sstream do istringstream i ostringstream. Podsumowując: jeśli w programie wykonujemy tylko konsolowe operacje we/wy (pisanie na ekran, czytanie z klawiatury), to wystarczy dołączyć plik nagłówkowy iostream; jeśli wykonujemy operacje na plikach, to trzeba włączyć plik nagłówkowy fstream; jeśli wykonujemy operacje we/wy, dla których źródłem lub ujściem są obiekty klasy string, to należy dołączyć sstream. Struktura programu działającego na strumieniach: kojarzy strumień z zewnętrznym źródłem/odbiornikiem, otwiera strumień, dodaje lub pobiera dane ze strumienia, zamyka strumień.
Pliki program zapisujący dane do pliku Struktura programu, który zapisuje dane do pliku: 1) Dyrektywa preprocesora #include <fstream> //zapewnia dostęp do klas ifstream i ofstream 2) deklaracja zmiennej (strumienia) typu ofstream ofstream plikwy; 3) wywołanie funkcji wiążącej strumień z konkretnym plikiem i otwierającej go plikwy.open("wyniki.txt"); Kroki 2) i 3) tj. utworzenie obiektu i skojarzenie go z plikiem możemy też zrealizować za pomocą jednej instrukcji: ofstream plikwy("wyniki.txt"); //2)3) drugi sposób 4) działania na plikach: możemy teraz używać strumienia plikwy w taki sam sposób jak obiektu cout, np. plikwy << "jakieś dane do zapisu" << endl << "kolejne dane"; 5) zamknięcie połączenia z plikiem plikwy.close();//obiekt plikwy istnieje nadal i możemy go użyć //do połączenia z jakimś plikiem 7
Pliki program zapisujący dane do pliku - uwagi Uwaga 1: Otwarcie w ten sposób pliku do zapisu spowoduje utworzenie nowego pliku, jeśli nie ma jeszcze pliku o tej nazwie, a jeśli plik o tej nazwie istnieje jego zawartość zostanie skasowana i operacje wyjścia rozpoczną zapis do pustego pliku. Uwaga 2: Ścieżki do pliku mogą być względne oraz bezwzględne. Ścieżka względna to taka, która nie zawiera pełnej ścieżki do pliku, np. sama nazwa pliku wraz z rozszerzeniem, bądź ścieżka względem katalogu roboczego aplikacji. Ścieżka bezwzględna określa natomiast pełną ścieżkę do pliku i zaczyna się od nazwy dysku, a kończy się na pełnej nazwie pliku np. "D:\\plik.txt" lub "D:/mojkatalog/plik.txt". Uwaga 3. Nazwa pliku w funkcji open jak i w konstruktorze jest C-napisem. Dlatego jeśli jest ona pobierana do zmiennej string musimy przekonwertować ją do C-napisu przy pomocy metody c_str() : string snazwapliku; cout<<"podaj nazwę pliku: "; cin>>snazwapliku; ofstream plik; plik.open( snazwapliku.c_str() ); //C++ 98 od C++11 istnieje druga wersja funkcji open, z parametrem typu string plik.open( snazwapliku ); //C++ 11 8
Pliki program odczytujący dane z pliku 9 Operacje niezbędne do realizacji odczytu z pliku są podobne do operacji zapisu do pliku 1) dołączamy plik nagłówkowy fstream #include <fstream> //zapewnia dostęp do klas ifstream i ofstream 2) deklarujemy zmienną (strumienia) typu ifstream ifstream plikwe; 3) kojarzymy strumień z konkretnym plikiem i otwieramy go plikwe.open("dane.txt"); Kroki 2) i 3) tj. utworzenie obiektu i skojarzenie go z plikiem możemy też zrealizować za pomocą jednej instrukcji: ifstream plikwe("dane.txt"); //2)3) drugi sposób 4) działania na plikach: możemy teraz używać strumienia plikwe w taki sam sposób jak strumienia cin (operator >> pomija białe znaki), np. jeśli wiemy, ze w pliku jest zapisana liczba całkowita int n; plikwe >> n; 5) zamykamy połączenie strumienia z plikiem plikwe.close(); //obiekt plikwe istnieje nadal i możemy go użyć //do połączenia z innym (lub ponownie z tym samym) plikiem
10 Kontrola strumienia i metoda is_open() Stan strumienia sprawdzamy, aby dowiedzieć się czy ostatnia operacja powiodła się, np. czy powiodło się otwarcie pliku (plik nie istnieje, nie posiadamy uprawnień do odczytu/modyfikacji). Po wykonaniu operacji, wewnątrz obiektu ustawiane są odpowiednie flagi (znaczniki), które informują o tym, czy się ona powiodła, czy też nie. Funkcje sprawdzające stan strumienia to m.in.: good() i fail(): ifstream fin; fin.open("nazwapliku.txt"); if(fin.fail()) //proba otwarcia zakończyła się niepowodzeniem //lub if(!fin.good()) //otwarcie nie powiodło się //lub równoważnie krócej możemy użyć konstrukcji: if(!fin) //gdyż obiekt fin podlega konwersji na bool //lub zalecane if(!fin.is_open()) //otwarcie nie powiodło się Zaleca się korzystanie z funkcji is_open(), gdyż wykrywa ona wszystkie błędy wykrywane przez good(), a ponadto wykrywa próby otwarcia pliku z zastosowaniem nieodpowiedniego trybu otwarcia pliku.
11 Przykład 1 W pliku "mapa.txt" (znajdującym się w tym samym katalogu co pisany program) znajdują się następujące dane: skala odl_1 odl_2... odl_n. Skala określa jaka odległość w kilometrach odpowiada jednemu milimetrowi na mapie. Napisz program, który obliczy długość trasy w terenie na podstawie odczytów z mapy w danej skali. Otrzymany wynik zapisze w pliku "trasa.txt". Przed podaniem łącznej długości trasy, w pliku tym zapisze w osobnych liniach numery oraz długości poszczególnych odcinków trasy w km. Dane poprzedzić odpowiednimi nagłówkami. Np. dla pliku "mapa.txt" zawierającego dane: 2 1.6 3.3 3.75 4.2 1.4 plik " trasa.txt" ma wyglądać następująco: Odcinek długość RAZEM: 1 3.2 2 6.6 3 7.5 4 8.4 5 2.8 28.5 km
12 Przykład 1 #include <iostream> #include <iomanip> #include <fstream> using namespace std; int main() { ifstream pwe; //deklaracja strumienia wejściowego pwe.open("mapa.txt"); //kojarzymy z plikiem i otwieramy if(!pwe.is_open()) //jeśli błąd otwarcia pliku { //konczymy program cout<<"blad otwarcia pliku"<<endl; return 1; }
13 Przykład 1 ofstream pwy; //tworzymy strumień wyjściowy pwy.open("trasa.txt"); //kojarzymy z plikiem i otwieramy if(!pwy.is_open()) { //kończymy program cout<<"blad otwarcia pliku wynikowego"<<endl; return 1; } pwy<<"odcinek dlugosc\n"; //wstawiamy tekst nagłówka pwy << fixed << setprecision(1); //ustalamy formatowanie wyników float skala, odlmm, //odległość z mapy w mm odlkm, //odległość przeliczona w kilometrach trasa=0.0f; //łączna długość trasy int nr = 0; //zmienna do numeracji kolejnych odcinków pwe>>skala; //wczytanie skali z pliku wejściowego
Przykład 1 14 } while (pwe>>odlmm) { //mamy kolejny odcinek nr++ ; odlkm = odlmm*skala ; //przeliczamy odleglosc //zapisujemy odcinek do pliku wynikowego pwy << setw(6) << nr << setw(8) << odlkm << endl; //i dodajemy do ogólnej długości trasy trasa = trasa + odlkm; } // odczytaliśmy wszystkie dane z pliku "mapa.txt" // należy dopisać ostatnią linię z podsumowaniem pwy << "RAZEM:" << setw (8 ) << trasa << " km"; pwe.close(); pwy.close(); return 0;
Przykład 2: pliki i funkcje 15 Dany jest plik tekstowy liczbyr.txt zawierający liczby rzeczywiste oddzielone znakami białymi (tabulatory, spacje, znaki końca linii). Napisz dwie wersje funkcji logicznej SumaPliku, które obliczają sumę elementów pliku: dla pierwszej funkcji plik podany jest przez nazwę pliku, a druga funkcja otrzymuje plik jako strumień gotowy do pracy. Wartością funkcji ma być true w przypadku, gdy wszystkie operacje na pliku powiodły się i false w przeciwnym wypadku. Jeśli plik był pusty funkcja zwraca false. Obliczona suma przekazywana ma być przez parametr.
Przykład 2 (funkcja dostaje nazwę pliku) 16 #include<iostream> #include<fstream> #include<string> using namespace std; //funkcja oblicza sumę liczb w pliku bool SumaPlik(string nazwa, double &suma) { ifstream pwe; pwe.open(nazwa.c_str()); //w C++11: pwe.open(nazwa); if(!pwe.is_open()) return false;
Przykład 2 (funkcja dostaje nazwę pliku) 17 suma=0.0; double x; if (!(pwe>>suma)) return false; //plik pusty lub błąd while(pwe>>x) suma+=x; //w naszych programach będziemy zakładać, że pliki mają //poprawną strukturę, ale jeśli by tak nie było //to możemy sprawdzić, czy osiągnęliśmy koniec pliku //czy był błąd, pwe>>x nie powiodło się (plik jest w stanie błędu) } if(!pwe.eof())//jeśli nie doszliśmy do końca pliku return false; pwe.close(); return true;
18 Przykład 2 (funkcja dostaje nazwę pliku) int main() { //funkcja dostaje nazwę pliku string npliku; cout << "podaj nazwe pliku "; cin >> npliku; double sum; } if(sumaplik(npliku,sum)) cout << "suma liczb w pliku = " << sum << endl; else cout << "blad operacji plikowych" << endl; return 0;
Przykład 2 (funkcja dostaje referencję na strumień) 19 //funkcja oblicza sumę liczb w pliku, jako parametr dostaje // strumień gotowy do użycia bool SumaPlik(ifstream &pwe, double &suma) { if(!pwe) return false; suma = 0.0; double x; if (!(pwe>>suma)) return false; //plik pusty lub błąd while(pwe >> x) suma += x; return true; }
Przykład 2 (funkcja dostaje referencję na strumień) 20 int main() { //funkcja dostaje & na strumień string nazwa; cout << "podaj nazwe pliku: "; cin >> nazwa; double s; ifstream pwe(nazwa.c_str()); //ifstream pwe(nazwa); //w C++11 if(sumaplik(pwe,s)) cout<<"suma liczb w pliku = "<<s<<endl; else cout<<"blad operacji plikowych"<<endl; pwe.close(); return 0; }
Przykład 2 wersja z dokładnym określeniem rodzaju błędu 21 enum ResultType{OK, OPEN_ERROR, EMPTY_FILE, READ_ERROR}; //funkcja oblicza sumę liczb w pliku, dokładnie określany jest rodzaj błędu ResultType SumaPlikEnum(string nazwa, double &suma) { ifstream pwe(nazwa.c_str()); //w C++11: ifstream pwe(nazwa); if(!pwe.is_open()) return OPEN_ERROR; suma=0.0; double x; if (!(pwe>>suma)) return pwe.eof()?empty_file:read_error; //plik pusty lub błąd while(pwe>>x) suma += x; if(!pwe.eof()) //jesli nie doszliśmy do końca pliku return READ_ERROR; pwe.close(); return OK; }
22 Przykład 2 wersja z dokładnym określeniem rodzaju błędu int main() { //funkcja z wykorzystaniem enum string nplik; cout << "podaj nazwe pliku: "; cin >> nplik; double suma; switch(sumaplikenum(nplik,suma)) { case OK: cout<<"suma elementow= "<<suma<<endl; break; case OPEN_ERROR: cout<<"blad otwarcia pliku "<<endl; break; case EMPTY_FILE: cout<<"plik pusty"<<endl; case READ_ERROR: cout<<"blad odczytu z pliku"<<endl; } return 0; }