Поиск…


Вступление

C ++-файл ввода-вывода выполняется через потоки . Ключевыми абстракциями являются:

std::istream для чтения текста.

std::ostream для std::ostream текста.

std::streambuf для чтения или записи символов.

В форматированном вводе используется operator>> .

Форматированный вывод использует operator<< .

В потоках используется std::locale , например, для подробностей форматирования и для перевода между внешними кодировками и внутренней кодировкой.

Подробнее о потоках: <iostream> Library

Открытие файла

Открытие файла выполняется одинаково для всех 3 потоков файлов ( ifstream , ofstream и fstream ).

Вы можете открыть файл непосредственно в конструкторе:

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.

Кроме того, вы можете использовать функцию члена потока файлов 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.

Вы всегда должны проверить, был ли файл успешно открыт (даже при написании). Ошибки могут включать: файл не существует, файл не имеет прав доступа, файл уже используется, произошли ошибки диска, отключен диск ... Проверка может быть выполнена следующим образом:

// 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");
}

Когда путь к файлу содержит обратную косую черту (например, в системе Windows), вы должны правильно их избежать:

// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs("c:\\folder\\foo.txt"); // using escaped backslashes
C ++ 11

или использовать исходный литерал:

// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs(R"(c:\folder\foo.txt)"); // using raw literal

или вместо этого используйте косые черты:

// Open the file 'c:\folder\foo.txt' on Windows.
std::ifstream ifs("c:/folder/foo.txt");
C ++ 11

Если вы хотите открыть файл с не-ASCII-символами в пути в Windows, в настоящее время вы можете использовать нестандартный аргумент пути к широкому символу:

// Open the file 'пример\foo.txt' on Windows.
std::ifstream ifs(LR"(пример\foo.txt)"); // using wide characters with raw literal

Чтение из файла

Существует несколько способов чтения данных из файла.

Если вы знаете, как форматируются данные, вы можете использовать оператор извлечения потока ( >> ). Предположим, у вас есть файл с именем foo.txt, который содержит следующие данные:

John Doe 25 4 6 1987
Jane Doe 15 5 24 1976

Затем вы можете использовать следующий код для чтения этих данных из файла:

// 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.

Оператор извлечения потока >> извлекает каждый символ и останавливается, если он находит символ, который нельзя сохранить, или если он является особым символом:

  • Для типов строк оператор останавливается в пробеле ( ) или в новой строке ( \n ).
  • Для чисел оператор останавливается с символом, отличным от числа.

Это означает, что следующая версия файла foo.txt также будет успешно прочитана предыдущим кодом:

John 
Doe 25
4 6 1987


Jane
Doe 
15 5
24
1976

Оператор извлечения потока >> всегда возвращает переданный ему поток. Поэтому несколько операторов могут быть соединены друг с другом, чтобы читать данные последовательно. Тем не менее, поток также может быть использован в качестве логического выражения (как показано в while петли в предыдущем коде). Это связано с тем, что классы потоков имеют оператор преобразования для типа bool . Этот оператор bool() вернет true пока поток не имеет ошибок. Если поток переходит в состояние ошибки (например, поскольку больше не может быть извлечено данных), то оператор bool() вернет false . Таким образом, в while цикл в предыдущем коде будет после того, как вышел из входного файла был прочитан до конца.

Если вы хотите прочитать весь файл в виде строки, вы можете использовать следующий код:

// 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>());

Этот код резервирует пространство для string , чтобы сократить ненужные распределения памяти.

Если вы хотите прочитать файл по строкам, вы можете использовать функцию 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.
}

Если вы хотите прочитать фиксированное количество символов, вы можете использовать функцию члена потока read() :

std::ifstream is("foo.txt");
char str[4];

// Read 4 characters from the file.
is.read(str, 4);

После выполнения команды чтения, вы всегда должны проверить , если состояние ошибки флаг failbit был установлен, поскольку он указывает на то удалось ли операция или нет. Это можно сделать, вызвав функцию члена файлового потока fail() :

is.read(str, 4); // This operation might fail for any reason.

if (is.fail())
    // Failed to read!

Запись в файл

Существует несколько способов записи в файл. Самый простой способ - использовать поток выходных файлов ( ofstream ) вместе с оператором вставки потока ( << ):

std::ofstream os("foo.txt");
if(os.is_open()){
    os << "Hello World!";
}

Вместо « << вы также можете использовать функцию члена потока выходного файла write() :

std::ofstream os("foo.txt");
if(os.is_open()){
    char data[] = "Foo";

    // Writes 3 characters from data -> "Foo".
    os.write(data, 3);
}

После записи в поток, вы всегда должны проверить , если состояние ошибки флаг badbit был установлен, поскольку он указывает на то удалось ли операция или нет. Это можно сделать, вызвав функцию члена потока выходного файла bad() :

os << "Hello Badbit!"; // This operation might fail for any reason.
if (os.bad())
    // Failed to write!

Режимы открытия

При создании потока файлов вы можете указать режим открытия. Режим открытия - это, в основном, параметр для управления тем, как поток открывает файл.

(Все режимы можно найти в пространстве имен std::ios .)

Режим открытия может быть предоставлен в качестве второго параметра конструктору файлового потока или его функции 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);

Следует отметить, что вам нужно установить ios::in или ios::out если вы хотите установить другие флаги, поскольку они неявно устанавливаются членами iostream, хотя они имеют правильное значение по умолчанию.

Если вы не укажете режим открытия, используются следующие режимы по умолчанию:

  • ifstream - in
  • ofstream - out
  • fstream - in и out

Режимы открытия файла, которые вы можете указать по дизайну:

Режим Имея в виду За Описание
app присоединять Выход Добавляет данные в конец файла.
binary двоичный Ввод, вывод Вход и выход выполняются в двоичном формате.
in вход вход Открывает файл для чтения.
out выход Выход Открывает файл для записи.
trunc усекать Ввод, вывод Удаляет содержимое файла при открытии.
ate в конце вход Открывается до конца файла.

Примечание. Установка binary режима позволяет считывать / записывать данные в точности как есть; не устанавливая его, это позволяет переносить символ новой строки '\n' / \ на определенный конец строки последовательности.

Например, в Windows конец строки - CRLF ( "\r\n" ).
Напишите: "\n" => "\r\n"
Читайте: "\r\n" => "\n"

Закрытие файла

Явное закрытие файла редко необходимо в C ++, так как поток файлов автоматически закрывает связанный файл в своем деструкторе. Тем не менее, вы должны попытаться ограничить время жизни объекта потока файлов, чтобы он не закрывал дескриптор файла дольше, чем необходимо. Например, это можно сделать, поместив все операции с файлами в собственную область ( {} ):

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.

Вызов close() явно необходимо , только если вы хотите использовать один и тот же fstream объект позже, но не хотите , чтобы сохранить файл открыть между ними:

// 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();

Промывка потока

Потоки файлов по умолчанию буферизуются, как и многие другие типы потоков. Это означает, что запись в поток не может привести к немедленному изменению базового файла. Чтобы заставить все буферизованные записи совершать сразу, вы можете очистить поток. Вы можете сделать это напрямую, вызывая метод flush() или с помощью манипулятора std::flush stream:

std::ofstream os("foo.txt");
os << "Hello World!" << std::flush;

char data[3] = "Foo";
os.write(data, 3);
os.flush();

Существует поток манипулятор std::endl который объединяет запись новой строки с потоком потока:

// Both following lines do the same thing
os << "Hello World!\n" << std::flush;
os << "Hello world!" << std::endl;

Буферизация может улучшить производительность записи в поток. Поэтому приложения, выполняющие много писем, должны избегать излишней очистки. Напротив, если операции ввода-вывода выполняются нечасто, приложения должны часто промывать, чтобы избежать застревания данных в объекте потока.

Чтение файла ASCII в строку std ::

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()`
}

Метод rdbuf() возвращает указатель на streambuf который может быть streambuf в buffer через stringstream::operator<< member.


Другая возможность (популяризированная в Effective STL от Scott Meyers ):

std::ifstream f("file.txt");

if (f)
{
  std::string str((std::istreambuf_iterator<char>(f)),
                  std::istreambuf_iterator<char>());

  // Operations on `str`...
}

Это хорошо, потому что требует небольшого кода (и позволяет читать файл непосредственно в любом контейнере STL, а не только в строках), но может быть медленным для больших файлов.

ПРИМЕЧАНИЕ . Дополнительные скобки вокруг первого аргумента конструктору строк необходимы для предотвращения наиболее неприятной проблемы синтаксического анализа .


Последний, но тем не менее важный:

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`...
}

что, вероятно, является самым быстрым вариантом (из трех предложенных).

Чтение файла в контейнер

В приведенном ниже примере мы используем std::string и operator>> для чтения элементов из файла.

    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);
    }

В приведенном выше примере мы просто итерации через файл, читающий один «элемент» за раз, используя operator>> . Этот же std::istream_iterator может быть достигнут с помощью std::istream_iterator который является итератором ввода, который считывает один «элемент» за раз из потока. Также большинство контейнеров можно построить с использованием двух итераторов, чтобы мы могли упростить приведенный выше код:

    std::ifstream file("file3.txt");

    std::vector<std::string>  v(std::istream_iterator<std::string>{file},
                                std::istream_iterator<std::string>{});

Мы можем расширить это, чтобы прочитать любые типы объектов, которые нам нравятся, просто указав объект, который мы хотим прочитать как параметр шаблона, для std::istream_iterator . Таким образом, мы можем просто расширить сказанное выше, чтобы читать строки (а не слова) следующим образом:

// 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>{});

Чтение `struct` из форматированного текстового файла.

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';
    }
}

file4.txt

Wogger Wabbit
2
6.2
Bilbo Baggins
111
81.3
Mary Poppins
29
154.8

Выход:

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

Копирование файла

std::ifstream  src("source_filename", std::ios::binary);
std::ofstream  dst("dest_filename",   std::ios::binary);
dst << src.rdbuf();
C ++ 17

С C ++ 17 стандартным способом копирования файла является заголовок <filesystem> и использование copy_file :

std::fileystem::copy_file("source_filename", "dest_filename");

Библиотека файловой системы была первоначально разработана как boost.filesystem и, наконец, объединена с ISO C ++ с C ++ 17.

Проверка конца файла внутри условия цикла, неправильная практика?

eof возвращает true только после чтения конца файла. Он НЕ указывает, что следующее чтение будет концом потока.

while (!f.eof())
{
  // Everything is OK

  f >> buffer;

  // What if *only* now the eof / fail bit is set?

  /* Use `buffer` */
}

Вы можете правильно написать:

while (!f.eof()) 
{  
  f >> buffer >> std::ws;

  if (f.fail())
    break;

  /* Use `buffer` */
}

но

while (f >> buffer)
{
  /* Use `buffer` */
}

проще и меньше подверженности ошибкам.

Дальнейшие ссылки:

  • std::ws : отбрасывает ведущие пробелы из входного потока
  • std::basic_ios::fail : возвращает true если произошла ошибка в связанном потоке

Запись файлов с нестандартными языковыми настройками

Если вам нужно записать файл с использованием разных настроек языкового стандарта по умолчанию, вы можете использовать std::locale и std::basic_ios::imbue() чтобы сделать это для определенного потока файлов:

Руководство для использования:

  • Перед открытием файла вы всегда должны применять локальный поток.
  • Как только поток будет пропитан, вы не должны изменять языковой стандарт.

Причины ограничений: при создании файлового потока с локалью имеет неопределенное поведение, если текущая локаль не является независимой от состояния или не указывает на начало файла.

Потоки UTF-8 (и другие) не являются независимыми от состояния. Также поток файлов с локалью UTF-8 может попробовать и прочитать маркер спецификации из файла при его открытии; поэтому просто открытие файла может читать символы из файла, и оно не будет в начале.

#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;
}

Явное переключение на классический язык «C» полезно, если ваша программа использует другой стандарт по умолчанию, и вы хотите обеспечить фиксированный стандарт для чтения и записи файлов. С предпочтительной локалью «C» пример записывает

78,123.456
78,123.456
78123.456

Если, например, предпочтительный языковой стандарт является немецким и, следовательно, использует другой формат чисел, пример записывает

78 123,456
78,123.456
78123.456

(обратите внимание на десятичную запятую в первой строке).



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow