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
C ++ 11

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");
C ++ 11

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 i out

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.

C ++ 11
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();
C ++ 17

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 : zwraca true 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).



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow