Потоки: единая абстракция ввода-вывода
В C++ ввод-вывод строится на потоках (streams) — объектах, через которые данные «текут» от источника к приёмнику. Экран, клавиатура, файл, строка в памяти — всё это может быть потоком.
readData(istream& in)
может принимать и cin, и ifstream, и istringstream.
Пишете логику один раз — работает с любым источником.
Демонстрация 1: текстовый файл — запись и чтение
Введите текст, сохраните его «в файл» и прочитайте обратно построчно.
Так работают ofstream и ifstream в обычном режиме.
Шаг 1: напишите содержимое
Файл на диске
Консоль программы
#include <fstream>
#include <string>
using namespace std;
int main() {
// ЗАПИСЬ
{
ofstream out("data.txt"); // открыт на запись
out << "Привет, файл!" << endl;
out << "Это вторая строка." << endl;
} // out.~ofstream() автоматически закрывает файл
// ЧТЕНИЕ
{
ifstream in("data.txt"); // открыт на чтение
if (!in) { cerr << "Не удалось открыть" << endl; return 1; }
string line;
int lineNum = 1;
while (getline(in, line)) { // построчно
cout << lineNum++ << ": " << line << endl;
}
}
}
ofstream)
данные принудительно сбрасываются на диск. Если забыть закрыть — возможна потеря
последних записей при аварийном завершении. RAII делает это автоматически.
Режимы открытия файла
Второй аргумент конструктора ofstream задаёт режим. По умолчанию
ofstream открывается в ios::out, ifstream —
в ios::in. Режимы можно комбинировать через |.
Интерактивно: что произойдёт с существующим файлом?
Содержимое файла до открытия:
Содержимое после открытия в выбранном режиме:
Демонстрация 2: текстовый vs бинарный файл
Запишем одну и ту же структуру двумя способами и сравним размер, читаемость и скорость. Разница станет видна сразу.
struct Product {
int id; // 4 байта
char name[16]; // 16 байт
double price; // 8 байт
};
📄 Текстовый файл
📦 Бинарный файл
ofstream out("product.txt");
out << p.id << " "
<< p.name << " "
<< p.price << endl;
// Файл:
// 42 Apple 3.14\n
ofstream out("product.dat",
ios::binary);
out.write((char*)&p, sizeof(p));
// 28 байт «как есть» в памяти
// Без разделителей, без текста
Демонстрация 3: позиционирование в файле
У каждого файлового потока есть «голова чтения-записи». Её можно перемещать в любое место — это позволяет читать и писать не последовательно, а в произвольном порядке (прямой доступ).
in.seekg(offset, origin); // для чтения (g = get)
out.seekp(offset, origin); // для записи (p = put)
// origin задаёт точку отсчёта:
// ios::beg — от начала файла
// ios::cur — от текущей позиции
// ios::end — от конца файла
in.tellg(); // текущая позиция чтения
out.tellp(); // текущая позиция записи
Интерактивно: перемещение по файлу
Файл содержит 8 чисел. Нажимайте кнопки, чтобы переместить «голову» и прочитать значение.
seekg не читает данные — только перемещает «голову».
Проверка ошибок при работе с файлами
Файл может не открыться (не существует, нет прав, диск занят). Всегда проверяйте состояние потока перед использованием.
ifstream in("data.txt");
if (!in) { /* файл не открылся */ }
if (in.fail()) { /* ошибка открытия или операции */ }
if (in.bad()) { /* серьёзная ошибка (потеря потока) */ }
if (in.eof()) { /* достигнут конец файла */ }
// Хорошая практика: явная проверка сразу после открытия
ifstream in("data.txt");
if (!in.is_open()) {
cerr << "Не удалось открыть файл" << endl;
return 1;
}
while (!in.eof()). Классическая ошибка —
читать в цикле с проверкой eof(). Флаг eof выставляется
только после попытки чтения за концом файла. Правильно — проверять результат
операции чтения: while (getline(in, line)).
Лучшие практики
1. Всегда проверяйте открытие файла
ifstream in("data.txt");
if (!in) {
cerr << "Файл не найден" << endl;
return 1;
}
2. Используйте RAII — файл закроется сам
ifstream и ofstream закрывают файл в деструкторе. Не нужно
вызывать close() вручную — и не нужно бояться, что что-то останется открытым.
3. Читайте построчно через getline
Для текстовых файлов это самый естественный способ. Одна строка — одна итерация цикла.
Если нужно разбить строку на слова — используйте istringstream.
string line;
while (getline(in, line)) {
istringstream iss(line);
string word;
while (iss >> word) {
// обрабатываем слово
}
}
4. Для бинарных данных — фиксированный формат
Не записывайте структуры целиком через write((char*)&p, sizeof(p)),
если файл должен читаться на другой платформе. Записывайте каждое поле отдельно:
out.write((char*)&p.id, sizeof(p.id)); и так далее. Это дольше,
но переносимо.
Антипаттерн: писать всё в текстовый файл
Текстовый формат удобен для логов и конфигов. Но для больших массивов чисел он неэффективен: каждое число занимает несколько байт, плюс разделители. Бинарный формат даёт сжатие в 2–5 раз и более быструю запись-чтение. Выбирайте по задаче.
Антипаттерн: забывать ios::binary
Если открыть файл без ios::binary и записать в него байты, ОС может
вставить дополнительные символы (перевод строки \n → \r\n
в Windows) или изменить поток. Файл станет «порченым» при чтении на другой платформе.
Шпаргалка
| Задача | Код |
|---|---|
| Открыть файл на чтение | ifstream in("file.txt"); |
| Открыть файл на запись | ofstream out("file.txt"); |
| Открыть на чтение и запись | fstream f("file.txt", ios::in | ios::out); |
| Открыть в бинарном режиме | ofstream out("file.dat", ios::binary); |
| Дописать в конец | ofstream out("file.txt", ios::app); |
| Проверить, открылся ли | if (!in) { ... } |
| Прочитать строку | getline(in, line); |
| Прочитать одно слово/число | in >> x; |
| Записать текст | out << "text" << endl; |
| Записать бинарные данные | out.write((char*)&x, sizeof(x)); |
| Прочитать бинарные данные | in.read((char*)&x, sizeof(x)); |
| Переместить позицию чтения | in.seekg(offset, ios::beg); |
| Получить текущую позицию | auto pos = in.tellg(); |
Задание
- Создайте текстовый файл, запишите в него три строки. Прочитайте его через
ifstreamи выведите каждую строку с номером. - Посчитайте количество слов в текстовом файле. Используйте
ifstreamиistringstream. - Создайте структуру
Student { int id; char name[20]; double gpa; }. Запишите массив из 5 студентов в текстовый файл, затем прочитайте обратно. - То же самое — в бинарный файл. Сравните размеры файлов.
- Используя
seekg, прочитайте из бинарного файла только третьего студента, не читая первых двух. - Реализуйте программу «дневник»: пользователь вводит строки, они дописываются
в файл
diary.txtв режимеios::app. При запуске программа выводит все прошлые записи. - Скопируйте содержимое одного текстового файла в другой. Используйте построчное чтение.
- Что произойдёт, если открыть файл в режиме
ios::outвторой раз? А в режимеios::app? Проверьте экспериментально.