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 is used for writing files
    // We'll make a file called Sample.txt
    std::ofstream outf{ "Sample.txt" };

    // If we couldn't open the output file stream for writing
    if (!outf)
    {
        // Print an error and exit
        std::cerr << "Uh oh, Sample.txt could not be opened for writing!\n";
        return 1;
    }

    // We'll write two lines into this file
    outf << "This is line 1\n";
    outf << "This is line 2\n";

    return 0;
	
    // When outf goes out of scope, the ofstream
    // destructor will close the file
}

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 is used for reading files
    // We'll read from a file called Sample.txt
    std::ifstream inf{ "Sample.txt" };

    // If we couldn't open the output file stream for reading
    if (!inf)
    {
        // Print an error and exit
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    // While there's still stuff left to read
    std::string strInput{};
    while (inf >> strInput)
        std::cout << strInput << '\n';
    
    return 0;
	
    // When inf goes out of scope, the ifstream
    // destructor will close the file
}

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 is used for reading files
    // We'll read from a file called Sample.txt
    std::ifstream inf{ "Sample.txt" };

    // If we couldn't open the input file stream for reading
    if (!inf)
    {
        // Print an error and exit
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    // While there's still stuff left to read
    std::string strInput{};
    while (std::getline(inf, strInput))
	std::cout << strInput << '\n';
    
    return 0;
	
    // When inf goes out of scope, the ifstream
    // destructor will close the file
}

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 opróżnianiem 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ą opróżniane 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; opróżnia również strumień wyjściowy. W rezultacie nadmierne użycie std::endl (powodujące niepotrzebne opróżnianie buforów) może mieć wpływ na wydajność podczas wykonywania buforowanych operacji we/wy, gdzie opróżnianie 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 opróżniania 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()
{
    // We'll pass the ios:app flag to tell the ofstream to append
    // rather than rewrite the file. We do not need to pass in std::ios::out
    // because ofstream defaults to std::ios::out
    std::ofstream outf{ "Sample.txt", std::ios::app };

    // If we couldn't open the output file stream for writing
    if (!outf)
    {
        // Print an error and exit
        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;
	
    // When outf goes out of scope, the ofstream
    // destructor will close the file
}

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(); // explicitly close the file

// Oops, we forgot something
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