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 Ios | Znaczenie |
|---|---|
| app | Otwiera plik w trybie dopisywania |
| ate | Wyszukuje koniec pliku przed odczyt/zapis |
| binarny | Otwiera plik w trybie binarnym (zamiast w trybie tekstowym) |
| w | Otwiera plik w trybie odczytu (domyślnie dla ifstream) |
| out | Otwiera plik w trybie zapisu (domyślnie dla ofstream) |
| trunc | Usuwa 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.

