Эта глава раскроет, как выстроить ввод/вывод так, чтобы код оставался единым для файлов, памяти и даже процессов. Вы обнаружите неочевидный подход: работать не с «файлами», а с абстракцией устройства — и за счет этого ускорить разработку, повысить надежность и упростить тестирование. Профессиональные разработчики используют этот стиль, потому что он дает понятный контраст “до/после”: меньше условностей, больше повторного использования.
Внутри — 3 режима мышления для QIODevice (чтение/запись/позиционирование), практичные приемы с QDir + фильтрами и более безопасное удаление через QFile::moveToTrash() в Qt6. Плюс — готовая архитектура для наблюдения изменений через QFileSystemWatcher, чтобы интерфейс обновлялся асинхронно и без блокировок.
Если эти приемы не применить сейчас — следующая «пустяковая» работа с файлами снова съест вечер.
В этой главе вы найдёте готовые к использованию примеры кода.
Самопроверка по главе
Почему класс QIODevice является абстрактным и какие методы необходимо реализовать при создании собственного устройства ввода/вывода?Ответ
Правильный ответ: QIODevice абстрактный, чтобы обеспечить единый интерфейс для всех устройств ввода/вывода. При наследовании обязательно реализовать readData() и writeData(), а обычно также open(), close() и atEnd().
Зачем в классе QDataStream нужно явно вызывать метод setVersion() перед записью данных?Ответ
Правильный ответ: Формат QDataStream менялся между версиями Qt. Явное указание версии гарантирует, что данные, записанные в одной версии Qt, можно будет правильно прочитать в другой версии.
В чём принципиальная разница между QTextStream и QDataStream, и когда какой использовать?Ответ
Правильный ответ: QTextStream предназначен для текстовых данных в Unicode и преобразует числа в текст; QDataStream — для двоичных данных в платформонезависимом формате. Используйте QTextStream для текстовых файлов, QDataStream — для сериализации объектов и сетевого обмена.
Почему метод readAll() следует использовать с осторожностью, и как можно снизить расход памяти при его применении?Ответ
Правильный ответ: readAll() загружает весь файл в память, что может вызвать проблемы с большими файлами. Расход памяти можно снизить, применив функции qCompress() и qUncompress() к данным QByteArray, если файл содержит избыточную информацию.
Зачем в примере с рекурсивным поиском файлов вызывается QApplication::processEvents() внутри метода start()?Ответ
Правильный ответ: Поиск файлов может быть длительным и выполняется в основном потоке, что приводит к «замиранию» GUI. processEvents() позволяет обработать накопившиеся события и сохранить отзывчивость интерфейса.
В чём преимущество использования QFile::moveToTrash() в Qt6 вместо remove()?Ответ
Правильный ответ: moveToTrash() перемещает файл в корзину ОС, обеспечивая возможность восстановления, в то время как remove() удаляет файл безвозвратно.
Какая практическая польза от класса QBuffer и в каких сценариях его стоит применять?Ответ
Правильный ответ: QBuffer эмулирует файл в оперативной памяти, что существенно быстрее дисковых операций. Полезен для кеширования растровых изображений, временного хранения данных и тестирования кода ввода/вывода без создания реальных файлов.
Почему для получения путей к пользовательским каталогам следует использовать QStandardPaths вместо жестко заданных строк?Ответ
Правильный ответ: Пути к стандартным каталогам различаются между платформами и могут изменяться. QStandardPaths обеспечивает платформонезависимый и корректный доступ к каталогам документов, настроек, кеша и другим.
Что произойдёт, если открыть QFile в режиме QIODevice::Truncate?Ответ
Правильный ответ: Все существующие данные файла будут удалены при открытии, файл станет пустым и готовым к записи новых данных с начала.
Как класс QFileSystemWatcher уведомляет об изменениях, и почему это не блокирует основной поток?Ответ
Правильный ответ: QFileSystemWatcher использует сигналы fileChanged() и directoryChanged() для асинхронных уведомлений. Наблюдение происходит в фоновом режиме без блокировки основного потока приложения.
Что произойдёт при попытке использовать методы seek() и pos() для сетевого соединения (QAbstractSocket)?Ответ
Правильный ответ: Эти методы теряют смысл для последовательного доступа (как сетевое соединение), так как они применимы только для прямого доступа — QFile, QBuffer и QTemporaryFile.
Зачем использовать флаг QDir::NoDotAndDotDot вместо явной проверки на “.” и “..”?Ответ
Правильный ответ: Флаг делает код чище, понятнее и менее подверженным ошибкам, автоматически исключая специальные каталоги из списка при вызове entryList().
В чём преимущество использования QTemporaryFile перед самостоятельным созданием временного файла?Ответ
Правильный ответ: QTemporaryFile автоматически генерирует уникальное имя (избегая конфликтов), размещает файл в правильном временном каталоге и автоматически удаляет файл при уничтожении объекта.
Что случится, если при работе с QDataStream записать данные с версией Qt_6_9, а прочитать без указания версии?Ответ
Правильный ответ: Данные могут быть прочитаны некорректно или вообще не прочитаться, так как QDataStream будет использовать формат по умолчанию, который может отличаться от формата записи.
Практические задания
Простой уровень
Просмотрщик информации о файле
Создайте Qt-приложение с текстовым полем для ввода пути к файлу и кнопкой “Показать информацию”. При нажатии кнопки приложение должно отобразить: имя файла, расширение, размер в байтах (и в удобочитаемом формате), дату создания, дату последнего изменения, а также проверку — является ли файл исполняемым, скрытым, доступным для чтения и записи.
Подсказки: Используйте QFileInfo для получения всей информации о файле. Метод exists() поможет проверить существование файла. Для преобразования размера используйте функцию из примера главы или реализуйте свою. Методы created(), lastModified() возвращают QDateTime, который можно преобразовать в строку через toString().
Средний уровень
Текстовый редактор с автосохранением
Разработайте простой текстовый редактор с QTextEdit, который автоматически сохраняет содержимое в файл каждые 30 секунд (используйте QTimer). Реализуйте меню с пунктами “Открыть”, “Сохранить как”, “Экспорт в верхний регистр”. При открытии файла используйте QTextStream для чтения. Добавьте статус-бар, отображающий время последнего сохранения и размер текущего документа.
Подсказки: QTimer с интервалом 30000 мс для автосохранения. Используйте QTextStream для чтения/записи текстовых файлов. QFileDialog::getOpenFileName() и getSaveFileName() для диалогов. Метод toPlainText() у QTextEdit даёт весь текст. QString::toUpper() для преобразования. QStatusBar для отображения статуса.
Сложный уровень
Менеджер резервного копирования с наблюдением
Создайте приложение для резервного копирования выбранного каталога. Используйте QFileSystemWatcher для отслеживания изменений в исходном каталоге. При обнаружении изменений автоматически копируйте изменённые файлы в каталог резервных копий. Реализуйте фильтрацию файлов по маске (например, только *.cpp и *.h). Добавьте возможность сжатия резервных копий через qCompress() и сохранения в QDataStream. Отображайте лог операций в QTextEdit с временными метками. Предусмотрите настройки: выбор исходного и целевого каталога, включение/отключение автоматического копирования, маску файлов.
Подсказки: Используйте QFileSystemWatcher::addPath() для наблюдения. Сигнал fileChanged() для отслеживания изменений. QDir::entryList() с фильтрами для выбора файлов по маске. QFile::copy() для копирования или комбинация read()/write() для сжатых копий. QDataStream с qCompress()/qUncompress() для сжатия. QDateTime::currentDateTime().toString() для временных меток. QSettings для сохранения настроек между запусками.
💬 Присоединяйтесь к обсуждению!
Разобрались с иерархией QIODevice и различиями между потоками? Возникли вопросы о том, когда использовать QTextStream, а когда QDataStream?
Поделитесь своим опытом работы с файлами и каталогами в Qt, расскажите о нестандартных решениях или задайте вопросы по материалу главы. Возможно, у вас есть интересные кейсы использования QFileSystemWatcher или QBuffer?