28.6 — Podstawowe we/wy plików

I/O plików w C++ działa bardzo podobnie do normalnego I/O (z kilkoma drobnymi dodatkowymi komplikacjami). W języku C++ istnieją trzy podstawowe klasy we/wy plików: ifstream (pochodzące z istream), ofstream (pochodzące z ostream) i fstream (pochodzące z iostream). Klasy te wykonują odpowiednio wejście, wyjście i wejście/wyjście do pliku. Aby użyć klas I/O plików, musisz dołączyć nagłówek fstream.

W przeciwieństwie do strumieni cout, cin, cerr i clog, które są już gotowe do użycia, strumienie plików muszą zostać jawnie skonfigurowane przez programistę. Jest to jednak niezwykle proste: aby otworzyć plik do odczytu i/lub zapisu, wystarczy utworzyć instancję obiektu odpowiedniej klasy I/O pliku, podając nazwę pliku jako parametr. Następnie użyj operatora wstawiania (<<) lub ekstrakcji (>>), aby zapisać lub odczytać dane z pliku. Kiedy już skończysz, istnieje kilka sposobów zamknięcia pliku: jawnie wywołaj funkcję close() lub po prostu pozwól, aby zmienna we/wy pliku wyszła poza zakres (destruktor klasy we/wy pliku zamknie plik za Ciebie).

Wyjście pliku

Aby w poniższym przykładzie wykonać wyjście pliku, użyjemy klasy ofstream. Jest to niezwykle proste:

#include <fstream>
#include <iostream>
 
int main()
{
    // ofstream jest używany do zapisywania plików
    // Utworzymy plik o nazwie Sample.txt
    std::ofstream outf{ "Sample.txt" };

    // Jeśli nie mogliśmy otworzyć strumienia pliku wyjściowego do zapisu
    if (!outf)
    {
        // Wydrukuj błąd i wyjdź
        std::cerr << "Uh oh, Sample.txt could not be opened for writing!\n";
        return 1;
    }

    // Zapisz dwie linie w tym pliku
    outf << "This is line 1\n";
    outf << "This is line 2\n";

    return 0;
	
    // Kiedy wychodzi poza zakres, ofstream
    // destruktor zamknij plik
}

Jeśli zajrzysz do katalogu projektu, powinieneś zobaczyć plik o nazwie Sample.txt. Jeśli otworzysz go za pomocą edytora tekstu, zobaczysz, że rzeczywiście zawiera on dwie linie, które napisaliśmy do pliku.

Zauważ, że możliwe jest również użycie funkcji put() do zapisania pojedynczego znaku do pliku.

Wprowadzenie pliku

Teraz weźmiemy plik, który napisaliśmy w ostatnim przykładzie i wczytamy go ponownie z dysku. Zauważ, że ifstream zwraca 0, jeśli dotarliśmy do końca pliku (EOF). Wykorzystamy ten fakt, aby określić, ile przeczytać.

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    // ifstream jest używany do odczytu plików
    // Będziemy czytać z pliku o nazwie Sample.txt
    std::ifstream inf{ "Sample.txt" };

    // Jeśli nie mogliśmy otworzyć strumienia pliku wyjściowego do odczytu
    if (!inf)
    {
        // Wydrukuj błąd i wyjdź
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    // Dopóki jest jeszcze coś do przetworzenia czytaj
    std::string strInput{};
    while (inf >> strInput)
        std::cout << strInput << '\n';
    
    return 0;
	
    // Gdy w zasięgu poza zakresem, funkcja ifstream
    // destruktor zamknij plik
}

Daje to wynik:

This
is
line
1
This
is
line
2

Hmmm, nie do końca o to nam chodziło. Pamiętaj, że operator ekstrakcji przerywa białe znaki. Aby czytać całe linie, będziemy musieli skorzystać z funkcji getline().

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    // ifstream jest używany do odczytu plików
    // Będziemy czytać z pliku o nazwie Sample.txt
    std::ifstream inf{ "Sample.txt" };

    // Jeśli nie mogliśmy otworzyć strumienia pliku wejściowego do odczytu
    if (!inf)
    {
        // Wydrukuj błąd i wyjdź
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    // Dopóki jest jeszcze coś do przetworzenia czytaj
    std::string strInput{};
    while (std::getline(inf, strInput))
	std::cout << strInput << '\n';
    
    return 0;
	
    // Gdy w zasięgu poza zakresem, funkcja ifstream
    // destruktor zamknij plik
}

Daje to wynik:

This is line 1
This is line 2

Buforowane wyjście

Wyjście w C++ może być buforowane. Oznacza to, że wszystko, co jest wysyłane do strumienia pliku, może nie zostać natychmiast zapisane na dysku. Zamiast tego można grupować i obsługiwać kilka operacji wyjściowych. Odbywa się to przede wszystkim ze względu na wydajność. Kiedy bufor jest zapisywany na dysk, nazywa się to ovoidniem bufora. Jednym ze sposobów opróżnienia bufora jest zamknięcie pliku — zawartość bufora zostanie opróżniona na dysk, a następnie plik zostanie zamknięty.

Buforowanie zwykle nie stanowi problemu, ale w pewnych okolicznościach może powodować komplikacje dla nieostrożnych. Głównym winowajcą w tym przypadku jest sytuacja, gdy w buforze znajdują się dane, a następnie program natychmiast się kończy (albo przez awarię, albo przez wywołanie funkcji exit()). W takich przypadkach destruktory klas strumieni plików nie są wykonywane, co oznacza, że ​​pliki nigdy nie są zamykane, co oznacza, że ​​nigdy nie są ovoidne bufory. W takim przypadku dane znajdujące się w buforze nie zostaną zapisane na dysku i zostaną utracone bezpowrotnie. Dlatego zawsze dobrze jest jawnie zamknąć wszystkie otwarte pliki przed wywołaniem funkcji exit().

Możliwe jest ręczne opróżnienie bufora za pomocą funkcji ostream::flush() lub wysłanie std::flush do strumienia wyjściowego. Każda z tych metod może być użyteczna, aby zapewnić natychmiastowe zapisanie zawartości buforu na dysk, na wypadek awarii programu.

Jedną interesującą notatką jest to, że std::endl; ovoid również strumień wyjściowy. W rezultacie nadmierne użycie std::endl (powodujące niepotrzebne ovoidnie buforów) może mieć wpływ na wydajność podczas wykonywania buforowanych operacji we/wy, gdzie ovoidnie jest kosztowne (np. zapisywanie do pliku). Z tego powodu programiści dbający o wydajność często używają „\n” zamiast std::endl, aby wstawić znak nowej linii do strumienia wyjściowego, aby uniknąć niepotrzebnego ovoidnia bufora.

Tryby plików

Co się stanie, jeśli spróbujemy zapisać do pliku, który już istnieje? Ponowne uruchomienie przykładu wyjściowego pokazuje, że oryginalny plik jest całkowicie nadpisywany przy każdym uruchomieniu programu. A co jeśli zamiast tego chcielibyśmy dodać więcej danych na końcu pliku? Okazuje się, że konstruktory strumieni plików przyjmują opcjonalny drugi parametr, który pozwala określić informację o tym, jak plik powinien zostać otwarty. Ten parametr nazywa się mode, a akceptowane przez niego prawidłowe flagi znajdują się w klasie ios.

Tryb pliku IosZnaczenie
appOtwiera plik w trybie dopisywania
ateWyszukuje koniec pliku przed odczyt/zapis
binarnyOtwiera plik w trybie binarnym (zamiast w trybie tekstowym)
wOtwiera plik w trybie odczytu (domyślnie dla ifstream)
outOtwiera plik w trybie zapisu (domyślnie dla ofstream)
truncUsuwa plik, jeśli już istnieje

Możliwe jest określenie wielu flag poprzez bitowe ORowanie ich razem (przy użyciu operatora |). ifstream domyślnie ma wartość std::ios::w trybie pliku. ofstream domyślnie pracuje w trybie pliku std::ios::out. A fstream domyślnie ma std::ios::in | tryb pliku std::ios::out, co oznacza, że ​​domyślnie możesz zarówno czytać, jak i zapisywać.

Wskazówka

Ze względu na sposób, w jaki zaprojektowano fstream, może on się nie powieść, jeśli zostanie użyte std::ios::in, a otwierany plik nie istnieje. Jeśli chcesz utworzyć nowy plik za pomocą fstream, użyj tylko trybu std::ios::out.

Napiszmy program, który doda dwie dodatkowe linie do utworzonego wcześniej pliku Sample.txt:

#include <iostream>
#include <fstream>

int main()
{
    // Przekażemy flagę ios:app, aby poinformować ofstream append
    // zamiast przepisywania pliku. Nie musimy przekazywać std::ios::out
    // ponieważ domyślnie ofstream to std::ios::out
    std::ofstream outf{ "Sample.txt", std::ios::app };

    // Jeśli nie mogliśmy otworzyć strumienia pliku wyjściowego do zapisu
    if (!outf)
    {
        // Wydrukuj błąd i wyjdź
        std::cerr << "Uh oh, Sample.txt could not be opened for writing!\n";
        return 1;
    }

    outf << "This is line 3\n";
    outf << "This is line 4\n";
    
    return 0;
	
    // Kiedy wychodzi poza zakres, ofstream
    // destruktor zamknij plik
}

Teraz, jeśli przyjrzymy się plikowi Sample.txt (używając jednego z powyższych przykładowych programów, który wypisuje jego zawartość lub ładując go do edytora tekstu), zobaczymy co następuje:

This is line 1
This is line 2
This is line 3
This is line 4

Jawne otwieranie plików przy użyciu metody open()

Tak jak możliwe jest jawne zamknięcie strumienia plików przy użyciu metody Close(), możliwe jest również jawne otwarcie strumienia plików przy użyciu metody open(). open() działa podobnie jak konstruktory strumieni plików - pobiera nazwę pliku i opcjonalny tryb pliku.

Na przykład:

std::ofstream outf{ "Sample.txt" };
outf << "This is line 1\n";
outf << "This is line 2\n";
outf.close(); // jawnie zamknij plik

// Ups, o czymś zapomnieliśmy
outf.open("Sample.txt", std::ios::app);
outf << "This is line 3\n";
outf.close();

Więcej informacji na temat funkcji open() można znaleźć tutaj.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
201 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze