Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Piotr Szawdyński
Kurs C++

Podstawy obsługi plików

[lekcja] Rozdział 32. W rozdziale opisano podstawową obsługę plików, tj. w jaki sposób otwiera się pliki, jak sprawdzić czy otwarcie pliku się powiodło, jak należy zamykać pliki oraz w jaki sposób można wczytać zawartość pliku tekstowego przy pomocy funkcji std::getline.

Wprowadzenie

Każdorazowe wprowadzanie danych do utworzonego programu jest użyteczne, jednak w większości przypadków jest to po prostu niepraktyczne. Każda komercyjna gra jak i każda aplikacja korzysta z możliwości odczytywania i zapisywania danych. Nawet w najprostszych grach wykorzystuje się pliki do zapamiętywania najlepszych osiągniętych wyników bądź do zapisywania konfiguracji klawiszy, umożliwiających sterowanie grą. W niniejszym rozdziale nauczysz się w jaki sposób można odczytywać tekst umieszczony w pliku tekstowym.

Dostępne narzędzia do obsługi plików

Dostęp do plików można uzyskać na wiele sposobów. Jeżeli programujesz pod Windowsem, możesz użyć funkcji należących do biblioteki WinAPI. Jeżeli nie chcesz uzależniać się od systemu operacyjnego, możesz użyć funkcji wywodzących się ze standardu C, bądź skorzystać z pakietu narzędzi jakie pojawiły się wraz ze standardem C++. Czego używać najlepiej? Trudno powiedzieć. Programiści wytwarzający aplikacje tylko i wyłącznie pod Windowsa korzystają zazwyczaj z niskopoziomowych funkcji WinAPI. W wielu programach pisanych pod Linuksa wykorzystywane są funkcje dostarczone wraz ze standardem C, co wynika w dużej mierze z zaszłości historycznych. Narzędzia, które pojawiły się wraz ze standardem C++ są jednak całkiem wygodne i również mają swoje grono zwolenników. Ponieważ niniejszy kurs jest poświęcony nauce C++ to rozdział ten będzie omawiał obsługę plików w oparciu o narzędzia dostarczone wraz ze standardem C++.

Biblioteka do obsługi plików

Po całkiem długim wprowadzeniu, czas przejść do omawiania obsługi plików. Aby mieć możliwość korzystania z narzędzi do obsługi plików, dostarczonych wraz ze standardem C++, należy na początku kodu źródłowego dołączyć odpowiedni plik nagłówkowy. Jeżeli w naszej aplikacji będziemy chcieli odczytywać lub zapisywać pliki to wystarczy, że dołączymy plik nagłówkowy fstream, czyli:
C/C++
#include <fstream>
Nazwa pliku jest łatwa do zapamiętania, ponieważ jest to skrót z angielskiego tj. od File Stream, co po przetłumaczeniu na nasz język oznacza strumień plików.

Uchwyt do obsługi pliku

Kolejnym etapem jest utworzenie obiektu, który umożliwi nam komunikację ze wskazanym przez nas plikiem. Obiekt ten tworzy się analogicznie do zmiennych. Typem danych będzie teraz std::ifstream, natomiast nazwa zmiennej może być dowolna, przykładowo:
C/C++
std::ifstream plik;
Teraz za pomocą zmiennej plik możemy komunikować się z dowolnym plikiem znajdującym się na dysku. Należy mieć jednak świadomość, że klasa std::ifstream posiada tylko i wyłącznie metody umożliwiające odczytywanie zawartości pliku, co akurat jest dla Ciebie w chwili obecnej bardzo korzystne, ponieważ nie nadpiszesz sobie niechcący zawartości odczytywanego pliku i jednocześnie będziesz mógł się oswoić z jego obsługą. Warto również wiedzieć, że zmienna za pomocą której można dostać się do danych pliku zazwyczaj nazywana jest uchwytem do pliku. W przypadku obsługi plików przy pomocy funkcji należących do standardu C (lub funkcji biblioteki WinAPI) nazwa uchwyt do pliku jest jak najbardziej poprawna. W przypadku stosowania standardu C++ jest to lekkim nadużyciem, bowiem zmienna plik jest w rzeczywistości obiektem, wewnątrz którego znajduje się uchwyt do pliku. Nie do końca to rozumiesz? Obecnie nie musisz :) Gdy zaczniesz się uczyć programowania obiektowego to wszystko stanie się jasne. W każdym razie jeżeli zmienna typu std::ifstream zostanie nazwana uchwytem do pliku to należy traktować to jako duży skrót myślowy, ułatwiający komunikację, a nie jako precyzyjne sformułowanie.

Otwieranie wybranego pliku

Mając utworzony już 'uchwyt' do pliku, wystarczy teraz wskazać jaki plik na dysku chcielibyśmy otworzyć do odczytu. Czynność tą wykonuje się przy pomocy metody open, gdzie pierwszym argumentem metody jest ścieżka do pliku, który chcielibyśmy otworzyć. Załóżmy więc, że mamy na dysku C:\ plik, który nosi nazwę "odczyt.txt". Otworzenie wspomnianego pliku sprowadzi się więc do następujących linijek:
C/C++
std::ifstream plik;
plik.open( "C:\\odczyt.txt" );
Jeżeli wspomniany plik będzie istniał na dysku oraz nie będzie on zablokowany do odczytu przez inną aplikację, to wówczas otwarcie pliku zakończy się powodzeniem.

Ścieżka do pliku

Stosunkowo nudną, ale bardzo cenną informacją jest znajomość prawidłowego zapisu ścieżek do pliku w językach C i C++ oraz zdobycie wiedzy, w jaki sposób są obsługiwane ścieżki przez system operacyjny.

Ścieżki względne i ścieżki bezwzględne

Ś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, tj. nie rozpoczyna się ona od litery (czy też nazwy) dysku. Tym samym ścieżką względną będzie np. podanie samej nazwy pliku (wraz z jego rozszerzeniem!), bądź podanie ścieżki względem katalogu roboczego aplikacji. Ścieżka bezwzględna określa natomiast pełną ścieżkę do pliku i zaczyna się od litery (nazwy) dysku, a kończy się na pełnej nazwie pliku.

Informacja
Stwierdzenie 'litera dysku' dotyczy systemu Windows, natomiast stwierdzenie 'nazwa dysku' dotyczy sytemu Linuks.

Katalog roboczy

Katalog roboczy jest to katalog od którego rozpoczyna się poszukiwanie pliku na dysku w przypadku, gdy podana ścieżka do pliku jest ścieżką względną. Katalogiem roboczym zazwyczaj jest katalog, w którym znajduje się uruchamiany plik *.exe. Katalog roboczy może być jednak inny, niż katalog w którym znajduje się plik *.exe, co bardzo często uprzykrza życie początkującym programistom, którzy stwierdzają, że: 'plik jest na dysku, ścieżka względna jest podana poprawnie, a pliku otworzyć się nie da'. Należy więc mieć świadomość, że katalog roboczy może być inny niż katalog, w którym znajduje się plik *.exe w sytuacji, gdy:
  • aplikacja jest uruchamiana za pomocą wiersza poleceń poprzez podanie ścieżki bezwzględnej do aplikacji;
  • aplikacja jest uruchamiana za pomocą wiersza poleceń poprzez podanie ścieżki względnej, wskazującej na aplikację znajdującą się w innym katalogu niż katalog widoczny w wierszu poleceń;
  • aplikacja jest uruchamiana przez środowisko programistyczne (w każdym szanującym się środowisku programistycznym istnieje możliwość ustawiania katalogu roboczego dla uruchamianej aplikacji);
  • do utworzonej aplikacji utworzono skrót, w którym zdefiniowano ustawianie innego katalogu roboczego niż domyślny;
  • uruchamiana aplikacja samodzielnie zmienia katalog roboczy, bo tak została zaprogramowana przez programistę.
Ostatni przypadek dotyczy programistów działających świadomie, natomiast cała reszta jest źródłem wielu problemów dla osób posiadających niewielką wiedzę z zakresu działania różnych mechanizmów w systemach operacyjnych.

Ścieżka do pliku, a znak backslash '\\'

Jak już zapewne zauważyłeś, podczas podawania ścieżki do pliku został użyty dwukrotnie znak backslasha (czyli znak \ ). Podając znaki, bądź łańcuchy znaków w języku C++ należy pamiętać, że backslash jest znakiem specjalnym, który umożliwia łatwe wstawienie chociażby znaku nowej linii. Tym samym zapisanie pojedynczego backslasha do zmiennej tekstowej (bądź do zmiennej znakowej) wymaga napisania dwóch znaków '\\'. Początkowi programiści prawie zawsze o tym zapominają, a potem godzinami wpatrują się w kod źródłowy szukając błędu wszędzie, tylko nie w podanej ścieżce do pliku. Wniosek? Zapamiętaj ten fakt! :)

Slashe i backslashe w ścieżce do pliku

Innym, znacznie lepszym rozwiązaniem jest podawanie w ścieżkach slashy (znak /) zamiast backslashy (znak \). Slashe jak również backslashe są traktowane jako znaki równoważne w ścieżkach do plików, ale dla programistów C++ znak slasha jest znacznie wygodniejszy, bowiem nie jest on znakiem specjalnym, a zatem nie trzeba pamiętać o wspomnianym wcześniej technicznym szczególe i tym samym dużo trudniej popełnić ewentualny błąd przy podawaniu ścieżki.

Ścieżki do plików - podsumowanie

Podając ścieżki do plików warto używać slashy zamiast backslashy. Pamiętaj również, że jeżeli nie możesz otworzyć pliku podając ścieżkę względną, winę może ponosić ustawiony katalog roboczy - wówczas warto spróbować podać ścieżkę bezwzględną w aplikacji, aby mieć pewność, że problem faktycznie dotyczył tylko i wyłącznie właściwego ustawienia katalogu roboczego.

Sprawdzenie czy udało się otworzyć plik

Powróćmy teraz do właściwej nauki obsługi plików. Otworzenie pliku może zakończyć się zarówno powodzeniem jak i fiaskiem. Czynników wpływających na sukces bądź porażkę otwarcia pliku jest wbrew pozorom wiele, a więc zanim zaczniesz wykonywać jakiekolwiek operacje na pliku, warto upewnić się, że faktycznie udało się uzyskać do niego dostęp (czyli udało się otworzyć plik). Do tego celu używa się zazwyczaj metody good, należącej do klasy std::ifstream. Linijka za pomocą której możemy sprawdzić czy udało się otworzyć plik, może wyglądać następująco:
C/C++
if( plik.good() )
{
    //INFO: plik udało się otworzyć
} else
{
    //INFO: otwarcie pliku się nie powiodło
} //else

Odczytywanie tekstu z pliku

Zaletą narzędzi C++ jest niewątpliwie łatwość ich używania. Odczytywanie tekstu z pliku sprowadza się bowiem do użycia strumienia >>, bądź zastosowania znanej już Ci funkcji std::getline, która została omówiona w rozdziale » Kurs C++ » Poziom 3Wczytywanie tekstu - standardowy strumień wejścia lekcja. W związku z tym, że zasada działania wspomnianej funkcji została już omówiona we wspomnianym wyżej rozdziale, to jej zachowanie nie będzie ponownie omawiane. Zamiast tego omówię jak należy używać funkcji std::getline do pracy z otwartym plikiem:
C/C++
std::string odczytanyTekst
std::getline( plik, odczytanyTekst );
Powyższy zapis wczytuje jeden wiersz z pliku, którego 'uchwyt' został podany poprzez pierwszy argument omawianej funkcji, natomiast wczytana treść zostanie zapisana do zmiennej odczytanyTekst. W tym miejscu warto również wspomnieć, że wywołanie powyższego polecenia nie gwarantuje nam odczytania tekstu. Wczytanie kolejnego wiersza tekstu zakończy się niepowodzeniem, gdy w pliku nie będzie więcej tekstu do odczytania. Odczyt może zakończyć się również niepowodzeniem w wyniku innych czynników, takich jak np. awaria urządzenia (np. wysunięto płytę CD, z której odczytywaliśmy zawartość pliku). W dużym uproszczeniu możesz przyjąć, że funkcja std::getline zwraca wartość logiczną true w przypadku sukcesu, natomiast false w przypadku niepowodzenia. Tym samym, możesz napisać odczytywanie zawartości całego pliku w następujący sposób:
C/C++
std::string wiersz;
while( std::getline( plik, wiersz ) )
     std::cout << wiersz << std::endl;

Inny sposób odczytania zawartości całego pliku został zawarty w przykładzie, który znajduje się w dalszej części niniejszego rozdziału.

Uwaga!
Funkcja std::getline w rzeczywistości nie zwraca wartości logicznej. Rzeczywisty mechanizm, który jest odpowiedzialny za zwracaną wartość wspomnianej funkcji jest jednak na tyle trudny do omówienia w jasny sposób, że na obecnym poziomie Twojej wiedzy lepiej pozostać przy zastosowanym w treści uproszczeniu.

Inne metody odczytywania zawartości pliku tekstowego

Inne metody odczytywania zawartości pliku tekstowego zostaną omówione w kolejnym rozdziale, w tym również wspomniany odczyt tekstu za pomocą strumienia >>.

Zamykanie otwartego pliku

Skoro już wiesz jak otworzyć plik oraz odczytać z niego informacje, wypadałby również wiedzieć w jaki sposób należy go zamknąć. Wiedz, że każdy otwarty plik należy zamykać zaraz po zakończeniu z nim pracy. Do tego celu służy metoda close, której wywołanie wyglądać może następująco:
C/C++
plik.close();

Przykład

A teraz część praktyczna :) Przykład prezentujący w jaki sposób odczytuje się zawartość pliku tekstowego został zaprezentowany poniżej. Do prowadzenia eksperymentów z odczytywaniem pliku warto mieć przygotowany jakiś plik tekstowy. Żeby Ci ułatwić naukę, utworzyłem krótki plik tekstowy, który możesz teraz pobrać i zapisać w stosownym katalogu na dysku (plik: cpp0x.txt).
C/C++
#include <iostream>
#include <fstream>
#include <string>

bool wyswietlZawartoscPliku( std::string sNazwaPliku )
{
    std::ifstream plik;
    plik.open( sNazwaPliku.c_str() );
    if( !plik.good() )
         return false;
   
    std::string wiersz;
    while( std::getline( plik, wiersz ) )
         std::cout << wiersz << std::endl;
   
    plik.close();
    return true;
}

int main()
{
    if( !wyswietlZawartoscPliku( "cpp0x.txt" ) )
         std::cout << "Nie udalo sie otworzyc pliku o podanej nazwie." << std::endl;
   
    return 0;
}
Jeżeli skorzystałeś z pliku tekstowego, wspomnianego w niniejszym kursie, to wówczas w oknie konsoli powinieneś zobaczyć następującą treść:
--==[ Kurs C++ | http://cpp0x.pl ]==--
Obecnie uczysz sie obslugi plikow. Link do czytanego rozdzialu:
http://cpp0x.pl/kursy/Kurs-C++/Poziom-4/Wczytywanie-tekstu-z-pliku/355
Udalo Ci sie odczytac plik?

Wykonaj teraz prace domowa ze wspomnianego rozdzialu! :)

Podsumowanie

Po ukończeniu tego rozdziału powinieneś wiedzieć jak otworzyć plik tekstowy, jak odczytać jego zawartość oraz jak prawidłowo należy kończyć z nim pracę. Jeżeli uważasz, że zrozumiałeś treść całego niniejszego rozdziału, zachęcam Cię do próby samodzielnego rozwiązania pracy domowej.

Zadanie domowe

Napisz program, który odczyta zawartość pliku, a następnie wypisze na ekranie tylko te wiersze, w których znajduje się wyraz wprowadzony przez użytkownika.
Poprzedni dokument Następny dokument
Poziom 4 Wczytywanie danych z pliku za pomocą operatora >>