Как читать и записывать файлы на C++
Если вы знаете, как использовать потоки ввода-вывода в C++, вы можете (в принципе) работать с любым типом устройства ввода-вывода.
В C++ чтение и запись файлов можно выполнять с помощью потоков ввода-вывода в сочетании с операторами потока >>
и <<
. При чтении или записи файлов эти операторы применяются к экземпляру класса, представляющего файл на жестком диске. Этот потоковый подход имеет огромное преимущество: с точки зрения C++ не имеет значения, что вы читаете или записываете, будь то файл, база данных, консоль или другой компьютер, к которому вы подключены через сеть. Следовательно, знание того, как записывать файлы с помощью потоковых операторов, можно перенести и в другие области.
Классы потоков ввода-вывода
Стандартная библиотека C++ предоставляет класс ios_base. Этот класс действует как базовый класс для всех классов, совместимых с потоками ввода-вывода, таких как Basic_ofstream и Basic_ifstream. В этом примере будут использоваться специализированные типы для чтения/записи символов: ifstream
и ofstream
.
ofstream
означает поток выходного файла, и доступ к нему можно получить с помощью оператора вставки<<
.ifstream
означает поток входного файла, и доступ к нему можно получить с помощью оператора извлечения>>
.
Оба типа определены внутри заголовка <fstream>
.
Класс, который наследуется от ios_base
, можно рассматривать как приемник данных при записи в него или как источник данных при чтении из него, полностью отделенный от самих данных. Этот объектно-ориентированный подход упрощает реализацию таких концепций, как разделение задач и внедрение зависимостей.
Простой пример
Этот пример программы довольно прост: он создает ofstream
, записывает в него, создает ifstream
и читает из него:
#include <iostream> // cout, cin, cerr etc...
#include <fstream> // ifstream, ofstream
#include <string>
int main()
{
std::string sFilename = "MyFile.txt";
/******************************************
* *
* WRITING *
* *
******************************************/
std::ofstream fileSink(sFilename); // Creates an output file stream
if (!fileSink) {
std::cerr << "Canot open " << sFilename << std::endl;
exit(-1);
}
/* std::endl will automatically append the correct EOL */
fileSink << "Hello Open Source World!" << std::endl;
/******************************************
* *
* READING *
* *
******************************************/
std::ifstream fileSource(sFilename); // Creates an input file stream
if (!fileSource) {
std::cerr << "Canot open " << sFilename << std::endl;
exit(-1);
}
else {
// Intermediate buffer
std::string buffer;
// By default, the >> operator reads word by workd (till whitespace)
while (fileSource >> buffer)
{
std::cout << buffer << std::endl;
}
}
exit(0);
}
Этот код доступен на GitHub. Когда вы скомпилируете и запустите его, вы должны получить следующий вывод:
(Стефан Авенведде, CC BY-SA 4.0)
Это упрощенный пример, удобный для начинающих. Если вы хотите использовать этот код в своем приложении, обратите внимание на следующее:
- Потоки файлов автоматически закрываются в конце программы. Если вы хотите продолжить выполнение, вам следует закрыть их вручную, вызвав метод
close()
. - Эти классы файловых потоков наследуют (на нескольких уровнях) от Basic_ios, который перегружает оператор
!
. Это позволяет вам реализовать простую проверку, можете ли вы получить доступ к потоку. На сайте cppreference.com вы можете найти обзор того, когда эта проверка будет успешной (и не будет) успешной, а также вы можете реализовать дальнейшую обработку ошибок. - По умолчанию
ifstream
останавливается на пробеле и пропускает его. Чтобы читать построчно, пока не достигнете EOF, используйте методgetline(...)
. - Для чтения и записи двоичных файлов передайте конструктору флаг
std::ios::binary
: это предотвращает добавление символов EOL в каждую строку.
Написание с точки зрения системы
При записи файлов данные записываются в буфер записи в памяти системы. Когда система получает системный вызов sync, содержимое этого буфера записывается на жесткий диск. Этот механизм также является причиной того, что вам не следует извлекать USB-накопитель, не сообщив об этом системе. Обычно sync регулярно вызывается демоном. Если вы действительно хотите перестраховаться, вы также можете вызвать sync вручную:
#include <unistd.h> // needs to be included
sync();
Краткое содержание
Чтение и запись файлов на C++ не так уж сложны. Более того, если вы знаете, как обращаться с потоками ввода-вывода, вы также знаете (в принципе), как обращаться с любым типом устройств ввода-вывода. Библиотеки для различных типов устройств ввода-вывода позволяют использовать операторы потока для облегчения доступа. Вот почему полезно знать, как работают пары ввода-вывода.