Szukaj…
Wprowadzenie
We / wy pliku C ++ odbywa się za pośrednictwem strumieni . Kluczowe abstrakcje to:
std::istream
do czytania tekstu.
std::ostream
do pisania tekstu.
std::streambuf
do czytania lub pisania znaków.
Sformatowane dane wejściowe używają operator>>
.
Sformatowane dane wyjściowe używają operator<<
.
Strumienie używają std::locale
, np. Do szczegółów formatowania i tłumaczenia między kodowaniem zewnętrznym a kodowaniem wewnętrznym.
Więcej informacji o strumieniach: <iostream> Library
Otwieranie pliku
Otwieranie pliku odbywa się w ten sam sposób dla wszystkich 3 strumieni plików ( ifstream
, ofstream
i fstream
).
Możesz otworzyć plik bezpośrednio w konstruktorze:
std::ifstream ifs("foo.txt"); // ifstream: Opens file "foo.txt" for reading only.
std::ofstream ofs("foo.txt"); // ofstream: Opens file "foo.txt" for writing only.
std::fstream iofs("foo.txt"); // fstream: Opens file "foo.txt" for reading and writing.
Alternatywnie możesz użyć funkcji członka strumienia plików open()
:
std::ifstream ifs;
ifs.open("bar.txt"); // ifstream: Opens file "bar.txt" for reading only.
std::ofstream ofs;
ofs.open("bar.txt"); // ofstream: Opens file "bar.txt" for writing only.
std::fstream iofs;
iofs.open("bar.txt"); // fstream: Opens file "bar.txt" for reading and writing.
Należy zawsze sprawdzić, czy plik został otwarty z powodzeniem (nawet podczas pisania). Błędy mogą obejmować: plik nie istnieje, plik nie ma odpowiednich praw dostępu, plik jest już w użyciu, wystąpiły błędy dysku, dysk odłączony ... Sprawdzanie można wykonać w następujący sposób:
// Try to read the file 'foo.txt'.
std::ifstream ifs("fooo.txt"); // Note the typo; the file can't be opened.
// Check if the file has been opened successfully.
if (!ifs.is_open()) {
// The file hasn't been opened; take appropriate actions here.
throw CustomException(ifs, "File could not be opened");
}
Gdy ścieżka do pliku zawiera ukośniki odwrotne (na przykład w systemie Windows), powinieneś je odpowiednio uciec:
// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs("c:\\folder\\foo.txt"); // using escaped backslashes
lub użyj surowego literału:
// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs(R"(c:\folder\foo.txt)"); // using raw literal
lub użyj zamiast tego ukośników:
// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs("c:/folder/foo.txt");
Jeśli chcesz otworzyć plik ze znakami spoza ASCII w ścieżce w systemie Windows, możesz użyć niestandardowego argumentu szerokiej ścieżki znaków:
// Open the file 'пример\foo.txt' on Windows.
std::ifstream ifs(LR"(пример\foo.txt)"); // using wide characters with raw literal
Odczytywanie z pliku
Istnieje kilka sposobów odczytu danych z pliku.
Jeśli wiesz, jak formatowane są dane, możesz użyć operatora ekstrakcji strumienia ( >>
). Załóżmy, że masz plik o nazwie foo.txt, który zawiera następujące dane:
John Doe 25 4 6 1987
Jane Doe 15 5 24 1976
Następnie możesz użyć następującego kodu, aby odczytać te dane z pliku:
// Define variables.
std::ifstream is("foo.txt");
std::string firstname, lastname;
int age, bmonth, bday, byear;
// Extract firstname, lastname, age, bday month, bday day, and bday year in that order.
// Note: '>>' returns false if it reached EOF (end of file) or if the input data doesn't
// correspond to the type of the input variable (for example, the string "foo" can't be
// extracted into an 'int' variable).
while (is >> firstname >> lastname >> age >> bmonth >> bday >> byear)
// Process the data that has been read.
Operator ekstrakcji strumienia >>
wyodrębnia każdy znak i zatrzymuje się, jeśli znajdzie znak, którego nie można zapisać lub jeśli jest to znak specjalny:
- W przypadku typów ciągów operator zatrzymuje się na spacji (
) lub w nowej linii (
\n
). - W przypadku liczb operator zatrzymuje się na znaku innym niż cyfra.
Oznacza to, że następująca wersja pliku foo.txt zostanie pomyślnie odczytana przez poprzedni kod:
John
Doe 25
4 6 1987
Jane
Doe
15 5
24
1976
Operator ekstrakcji strumienia >>
zawsze zwraca przekazany mu strumień. Dlatego wielu operatorów można łączyć ze sobą, aby odczytywać dane kolejno. Jednak strumień może być również stosowany jako logicznej ekspresji (jak pokazano na while
w pętlę poprzedniego kod). Jest tak, ponieważ klasy strumienia mają operator konwersji dla typu bool
. Ten operator bool()
zwróci wartość true
dopóki strumień nie będzie zawierał błędów. Jeśli strumień przechodzi w stan błędu (na przykład, ponieważ nie można wyodrębnić więcej danych), operator bool()
zwróci false
. Dlatego też, while
pętla w poprzednim kodzie zostanie zakończony po plik wejściowy został odczytany do końca.
Jeśli chcesz odczytać cały plik jako ciąg, możesz użyć następującego kodu:
// Opens 'foo.txt'.
std::ifstream is("foo.txt");
std::string whole_file;
// Sets position to the end of the file.
is.seekg(0, std::ios::end);
// Reserves memory for the file.
whole_file.reserve(is.tellg());
// Sets position to the start of the file.
is.seekg(0, std::ios::beg);
// Sets contents of 'whole_file' to all characters in the file.
whole_file.assign(std::istreambuf_iterator<char>(is),
std::istreambuf_iterator<char>());
Ten kod rezerwuje miejsce na string
, aby ograniczyć niepotrzebne przydziały pamięci.
Jeśli chcesz czytać plik linia po linii, możesz użyć funkcji getline()
:
std::ifstream is("foo.txt");
// The function getline returns false if there are no more lines.
for (std::string str; std::getline(is, str);) {
// Process the line that has been read.
}
Jeśli chcesz odczytać określoną liczbę znaków, możesz użyć funkcji członka strumienia read()
:
std::ifstream is("foo.txt");
char str[4];
// Read 4 characters from the file.
is.read(str, 4);
Po wykonaniu polecenia odczytu zawsze powinieneś sprawdzić, czy flaga stanu błędu została ustawiona jako failbit
, ponieważ wskazuje, czy operacja się nie powiodła. Można to zrobić, wywołując funkcję fail()
elementu strumienia pliku:
is.read(str, 4); // This operation might fail for any reason.
if (is.fail())
// Failed to read!
Zapis do pliku
Istnieje kilka sposobów zapisu do pliku. Najprostszym sposobem jest użycie strumienia pliku wyjściowego ( ofstream
) razem z operatorem wstawiania strumienia ( <<
):
std::ofstream os("foo.txt");
if(os.is_open()){
os << "Hello World!";
}
Zamiast <<
możesz również użyć funkcji członka strumienia wyjściowego write()
:
std::ofstream os("foo.txt");
if(os.is_open()){
char data[] = "Foo";
// Writes 3 characters from data -> "Foo".
os.write(data, 3);
}
Po zapisaniu do strumienia zawsze powinieneś sprawdzić, czy flaga stanu błędu badbit
została ustawiona, ponieważ wskazuje to, czy operacja się nie powiodła. Można to zrobić, wywołując funkcję członkowską strumienia pliku wyjściowego bad()
:
os << "Hello Badbit!"; // This operation might fail for any reason.
if (os.bad())
// Failed to write!
Tryby otwierania
Podczas tworzenia strumienia plików możesz określić tryb otwierania. Tryb otwierania to w zasadzie ustawienie kontrolujące sposób, w jaki strumień otwiera plik.
(Wszystkie tryby można znaleźć w przestrzeni nazw std::ios
.)
Tryb otwierania można podać jako drugi parametr do konstruktora strumienia plików lub jego funkcji open()
:
std::ofstream os("foo.txt", std::ios::out | std::ios::trunc);
std::ifstream is;
is.open("foo.txt", std::ios::in | std::ios::binary);
Należy zauważyć, że musisz ustawić ios::in
lub ios::out
jeśli chcesz ustawić inne flagi, ponieważ nie są one domyślnie ustawione przez członków iostream, chociaż mają poprawną wartość domyślną.
Jeśli nie określisz trybu otwierania, używane są następujące tryby domyślne:
-
ifstream
-in
-
ofstream
-out
-
fstream
-in
iout
Tryby otwierania plików, które możesz określić według projektu, to:
Tryb | Znaczenie | Dla | Opis |
---|---|---|---|
app | dodać | Wynik | Dołącza dane na końcu pliku. |
binary | dwójkowy | Wejście wyjście | Wejście i wyjście odbywa się w postaci binarnej. |
in | Wejście | Wejście | Otwiera plik do odczytu. |
out | wynik | Wynik | Otwiera plik do zapisu. |
trunc | ścięty | Wejście wyjście | Usuwa zawartość pliku podczas otwierania. |
ate | na koniec | Wejście | Przechodzi do końca pliku podczas otwierania. |
Uwaga: Ustawienie trybu binary
pozwala na odczyt / zapis danych dokładnie takim, jaki jest; brak ustawienia umożliwia translację znaku nowej linii '\n'
na / z sekwencji końca linii specyficznej dla platformy.
Na przykład w systemie Windows sekwencją końca linii jest CRLF ( "\r\n"
).
Napisz: "\n"
=> "\r\n"
Przeczytaj: "\r\n"
=> "\n"
Zamykanie pliku
Jawne zamknięcie pliku rzadko jest konieczne w C ++, ponieważ strumień plików automatycznie zamknie skojarzony plik w swoim destruktorze. Należy jednak spróbować ograniczyć czas życia obiektu strumienia plików, aby nie utrzymywał otwartego uchwytu pliku dłużej niż to konieczne. Na przykład można to zrobić, umieszczając wszystkie operacje na plikach we własnym zakresie ( {}
):
std::string const prepared_data = prepare_data();
{
// Open a file for writing.
std::ofstream output("foo.txt");
// Write data.
output << prepared_data;
} // The ofstream will go out of scope here.
// Its destructor will take care of closing the file properly.
Wywołanie metody close()
jest konieczne tylko wtedy, gdy chcesz ponownie użyć tego samego obiektu fstream
później, ale nie chcesz, aby plik był otwarty między:
// Open the file "foo.txt" for the first time.
std::ofstream output("foo.txt");
// Get some data to write from somewhere.
std::string const prepared_data = prepare_data();
// Write data to the file "foo.txt".
output << prepared_data;
// Close the file "foo.txt".
output.close();
// Preparing data might take a long time. Therefore, we don't open the output file stream
// before we actually can write some data to it.
std::string const more_prepared_data = prepare_complex_data();
// Open the file "foo.txt" for the second time once we are ready for writing.
output.open("foo.txt");
// Write the data to the file "foo.txt".
output << more_prepared_data;
// Close the file "foo.txt" once again.
output.close();
Opróżnianie strumienia
Strumienie plików są domyślnie buforowane, podobnie jak wiele innych rodzajów strumieni. Oznacza to, że zapis do strumienia nie może spowodować natychmiastowej zmiany pliku źródłowego. Aby zmusić wszystkie buforowane zapisy do natychmiastowego wykonania, możesz opróżnić strumień. Możesz to zrobić bezpośrednio, wywołując metodę flush()
lub za pomocą manipulatora strumienia std::flush
:
std::ofstream os("foo.txt");
os << "Hello World!" << std::flush;
char data[3] = "Foo";
os.write(data, 3);
os.flush();
Istnieje manipulator strumienia std::endl
który łączy pisanie nowego wiersza z opróżnianiem strumienia:
// Both following lines do the same thing
os << "Hello World!\n" << std::flush;
os << "Hello world!" << std::endl;
Buforowanie może poprawić wydajność zapisu do strumienia. Dlatego aplikacje, które dużo piszą, powinny unikać niepotrzebnego opróżniania. Przeciwnie, jeśli operacje we / wy są wykonywane rzadko, aplikacje powinny rozważyć częste opróżnianie, aby uniknąć zablokowania danych w obiekcie strumienia.
Odczytywanie pliku ASCII do std :: string
std::ifstream f("file.txt");
if (f)
{
std::stringstream buffer;
buffer << f.rdbuf();
f.close();
// The content of "file.txt" is available in the string `buffer.str()`
}
Metoda rdbuf()
zwraca wskaźnik do streambuf
który może zostać wypchnięty do buffer
za pomocą funkcji stringstream::operator<<
.
Inną możliwością (spopularyzowaną w Effective STL przez Scotta Meyersa ) jest:
std::ifstream f("file.txt");
if (f)
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
// Operations on `str`...
}
Jest to przydatne, ponieważ wymaga niewielkiego kodu (i pozwala na odczyt pliku bezpośrednio do dowolnego kontenera STL, nie tylko łańcuchów), ale może być powolne w przypadku dużych plików.
UWAGA : dodatkowe nawiasy wokół pierwszego argumentu konstruktora łańcucha są niezbędne, aby zapobiec najbardziej irytującemu problemowi analizy .
Nie mniej ważny:
std::ifstream f("file.txt");
if (f)
{
f.seekg(0, std::ios::end);
const auto size = f.tellg();
std::string str(size, ' ');
f.seekg(0);
f.read(&str[0], size);
f.close();
// Operations on `str`...
}
co jest prawdopodobnie najszybszą opcją (spośród trzech proponowanych).
Odczytywanie pliku do kontenera
W poniższym przykładzie używamy std::string
i operator>>
do odczytu elementów z pliku.
std::ifstream file("file3.txt");
std::vector<std::string> v;
std::string s;
while(file >> s) // keep reading until we run out
{
v.push_back(s);
}
W powyższym przykładzie po prostu iterujemy po pliku, odczytując jeden „element” naraz, używając operator>>
. Ten sam std::istream_iterator
można osiągnąć za pomocą std::istream_iterator
który jest iteratorem wejściowym, który odczytuje jeden „element” na raz ze strumienia. Również większość kontenerów można zbudować przy użyciu dwóch iteratorów, dzięki czemu możemy uprościć powyższy kod w celu:
std::ifstream file("file3.txt");
std::vector<std::string> v(std::istream_iterator<std::string>{file},
std::istream_iterator<std::string>{});
Możemy to rozszerzyć, aby odczytać dowolne typy obiektów, które lubimy, po prostu określając obiekt, który chcemy odczytać jako parametr szablonu do std::istream_iterator
. W ten sposób możemy po prostu rozszerzyć powyższe do czytających wiersze (a nie słowa) w następujący sposób:
// Unfortunately there is no built in type that reads line using >>
// So here we build a simple helper class to do it. That will convert
// back to a string when used in string context.
struct Line
{
// Store data here
std::string data;
// Convert object to string
operator std::string const&() const {return data;}
// Read a line from a stream.
friend std::istream& operator>>(std::istream& stream, Line& line)
{
return std::getline(stream, line.data);
}
};
std::ifstream file("file3.txt");
// Read the lines of a file into a container.
std::vector<std::string> v(std::istream_iterator<Line>{file},
std::istream_iterator<Line>{});
Odczytywanie `struct` ze sformatowanego pliku tekstowego.
struct info_type
{
std::string name;
int age;
float height;
// we define an overload of operator>> as a friend function which
// gives in privileged access to private data members
friend std::istream& operator>>(std::istream& is, info_type& info)
{
// skip whitespace
is >> std::ws;
std::getline(is, info.name);
is >> info.age;
is >> info.height;
return is;
}
};
void func4()
{
auto file = std::ifstream("file4.txt");
std::vector<info_type> v;
for(info_type info; file >> info;) // keep reading until we run out
{
// we only get here if the read succeeded
v.push_back(info);
}
for(auto const& info: v)
{
std::cout << " name: " << info.name << '\n';
std::cout << " age: " << info.age << " years" << '\n';
std::cout << "height: " << info.height << "lbs" << '\n';
std::cout << '\n';
}
}
plik4.txt
Wogger Wabbit
2
6.2
Bilbo Baggins
111
81.3
Mary Poppins
29
154.8
Wynik:
name: Wogger Wabbit
age: 2 years
height: 6.2lbs
name: Bilbo Baggins
age: 111 years
height: 81.3lbs
name: Mary Poppins
age: 29 years
height: 154.8lbs
Kopiowanie pliku
std::ifstream src("source_filename", std::ios::binary);
std::ofstream dst("dest_filename", std::ios::binary);
dst << src.rdbuf();
W C ++ 17 standardowym sposobem kopiowania pliku jest copy_file
nagłówka <filesystem>
i użycie copy_file
:
std::fileystem::copy_file("source_filename", "dest_filename");
Biblioteka systemu plików została pierwotnie opracowana jako boost.filesystem
i ostatecznie została połączona z ISO C ++ od C ++ 17.
Sprawdzanie końca pliku wewnątrz warunku pętli, zła praktyka?
eof
zwraca true
dopiero po odczytaniu końca pliku. NIE oznacza to, że następny odczyt będzie końcem strumienia.
while (!f.eof())
{
// Everything is OK
f >> buffer;
// What if *only* now the eof / fail bit is set?
/* Use `buffer` */
}
Możesz poprawnie napisać:
while (!f.eof())
{
f >> buffer >> std::ws;
if (f.fail())
break;
/* Use `buffer` */
}
ale
while (f >> buffer)
{
/* Use `buffer` */
}
jest prostszy i mniej podatny na błędy.
Dalsze referencje:
-
std::ws
: odrzuca wiodące białe znaki ze strumienia wejściowego -
std::basic_ios::fail
: zwracatrue
jeśli wystąpił błąd w powiązanym strumieniu
Zapisywanie plików z niestandardowymi ustawieniami regionalnymi
Jeśli chcesz zapisać plik domyślnie przy użyciu różnych ustawień regionalnych, możesz użyć std::locale
i std::basic_ios::imbue()
aby to zrobić dla określonego strumienia plików:
Wskazówki dotyczące użytkowania:
- Przed otwarciem pliku zawsze należy zastosować lokalny do strumienia.
- Po nasyceniu strumienia nie należy zmieniać ustawień regionalnych.
Powody ograniczeń: Nasycenie strumienia pliku ustawieniami narodowymi ma niezdefiniowane zachowanie, jeśli bieżące ustawienia narodowe nie są niezależne od stanu lub nie wskazują na początku pliku.
Strumienie UTF-8 (i inne) nie są niezależne od stanu. Również strumień plików z ustawieniami regionalnymi UTF-8 może próbować odczytać znacznik BOM z pliku, gdy jest on otwierany; więc samo otwarcie pliku może odczytać znaki z pliku i nie będzie na początku.
#include <iostream>
#include <fstream>
#include <locale>
int main()
{
std::cout << "User-preferred locale setting is "
<< std::locale("").name().c_str() << std::endl;
// Write a floating-point value using the user's preferred locale.
std::ofstream ofs1;
ofs1.imbue(std::locale(""));
ofs1.open("file1.txt");
ofs1 << 78123.456 << std::endl;
// Use a specific locale (names are system-dependent)
std::ofstream ofs2;
ofs2.imbue(std::locale("en_US.UTF-8"));
ofs2.open("file2.txt");
ofs2 << 78123.456 << std::endl;
// Switch to the classic "C" locale
std::ofstream ofs3;
ofs3.imbue(std::locale::classic());
ofs3.open("file3.txt");
ofs3 << 78123.456 << std::endl;
}
Jawne przejście do klasycznych ustawień regionalnych „C” jest przydatne, jeśli program używa innych domyślnych ustawień regionalnych i chcesz zapewnić stały standard odczytu i zapisu plików. W przypadku preferowanego ustawienia narodowego „C” przykład pisze
78,123.456
78,123.456
78123.456
Jeśli na przykład preferowanym miejscem narodowym jest niemiecki i dlatego używa innego formatu liczb, przykład pisze
78 123,456
78,123.456
78123.456
(zwróć uwagę na przecinek dziesiętny w pierwszym wierszu).