
Programming / C++Programming / Qt / Qt-doc.ru / Работа с файлами и директориями
.pdfРедко встречается приложение, которое не обращается к файлам. Работа с директориями (папками, в терминологии ОС Windows) и файлами — это та область, в которой не все операции являются платформонезависимыми,
поэтому Qt предоставляет свою собственную поддержку этих операций, состоящую из следующих классов:
QDir — для работы с директориями;
QFile — для работы с файлами;
QFileInfо — для получения файловой информации;
QIODevice — абстрактный класс для ввода/вывода;
QBuffer — для эмуляции файлов в памяти компьютера.
Ввод/вывод. Класс QIODevice
QIODevice — это абстрактный класс, обобщающий устройство ввода/вывода, который содержит виртуальные методы для открытия и закрытия устройства ввода/вывода, а также для чтения и записи блоков данных или отдельных символов.
Реализация конкретного устройства происходит в унаследованных классах.
ВQt есть четыре класса, наследующие класс QIODevice:
QFile — класс для проведения операций с файлами;
QBuffer — позволяет записывать и считывать данные в массив QByteArray, как будто бы это устройство или файл;
QAbstractSocket — базовый класс для сетевой коммуникации посредством сокетов.
QProcess — этот класс предоставляет возможность запуска процессов с дополнительными аргументами и позволяет обмениваться информацией с этими процессами посредством методов, определенных в QIODevice.
Для работы с устройством его необходимо открыть в одном из режимов, определенных В заголовочном файле класса QIODevice:
QIODevice::NotOpen — устройство не открыто (это значение не имеет смысла передавать в метод open());
QIODevice::ReadOnly — открытие устройства только для чтения данных;
QIODevice::writeOnly — открытие устройства только для записи данных;
QIODevice::ReadWrite — открытие устройства для чтения и записи данных (то же, что и IO_ReadOnly | IO_WriteOnly);
QIODevice::Append — открытие устройства для добавления данных;
QIODevice::Unbuffered — открытие для непосредственного доступа к данным, в обход промежуточных буферов чтения и записи;
QIODevice::Text — применяются преобразования символов переноса строки в зависимости от платформы. Для ОС Windows, например — \r\n, а для MacOS X и
UNIX — /r;
QIODevice::Truncate — все данные устройства, по возможности, должны быть удалены при открытии.
Для того чтобы в любой момент времени исполнения программы узнать, в каком из режимов было открыто устройство, нужно вызвать метод openMode().
Считывать и записывать данные можно с помощью
методов read() и write()соответственно. Для чтения всех данных сразу определен

метод readAll(), который возвращает их в объекте типа QByteArray. Строку или символ можно прочитать методами readLine() и getChar() соответственно.
В классе QIODevice определен метод для смены текущего положения seek(). Получить текущее положение можно вызовом метода pos(). Но не забывайте, что эти методы применимы только для прямого доступа к данным. При последовательном доступе, каким является сетевое соединение, они теряют смысл. Более того, в этом случае теряет смысл и метод size(), возвращающий размер данных устройства. Все эти операции применимы только
для QFile, QBuffer и QTemporaryFile.
Для создания собственного класса устройства ввода/вывода, для которого Qt не
предоставляет поддержки, необходимо унаследовать класс QIODevice и реализовать в нем методы readData() и writeData(). В большинстве случаев может потребоваться перезаписать методы open(), close() и atEnd().
Благодаря интерфейсу класса QIODevice можно работать со всеми устройствами одинаково, при этом не имеет значения, является ли устройство файлом, буфером или другим устройством. Выведем на консоль данные из любого устройства.
void print(QIODevice *pdev)
{
char ch; QString str;
pdev->open(QIODevice::ReadOnly); for (; !pdev->atEnd();)
{
pdev->getChar(&ch); str += ch;
}
pdev->close(); qDebug() << str;
}
Класс QIODevice предоставляет ряд методов, с помощью которых можно получить информацию об устройстве ввода/вывода. Например, одни устройства могут только записывать информацию, другие — только считывать, а третьи способны делать и то, и другое. Чтобы узнать об этом, следует воспользоваться
методами isReadable() иisWriteable().
Класс QFile унаследован от класса QIODevice. В нем содержатся методы для работы с файлами: открытия, закрытия, чтения и записи данных. Создать объект можно, передав в конструкторе строку, содержащую имя файла. Можно ничего не
передавать в конструкторе, а сделать это после создания объекта, вызовом метода setName(). Например:
QFile file;

file.setName("file.dat");
В процессе работы с файлами иногда требуется узнать, открыт файл или нет. Для этого вызывается метод QIODevice::isOpen(), который вернет true, в том случае,
если файл открыт, иначе — false. Чтобы закрыть файл, нужно вызвать метод close(). С закрытием произведется запись всех данных буфера. Если
требуется произвести запись данных буфера в файл без его закрытия, то вызывается методQFile::flush().
Проверить, существует ли нужный вам файл, можно статическим
методом QFile::exists(). Этот метод принимает строку, содержащую полный или относительный путь к файлу. Если файл найден, то метод возвратит true, в противном случае — false. Для проведения этой операции существует и нестатический методQFile::exists().
Методы QIODevice::read() и QIODevice::write() позволяют считывать и записывать файлы блоками.
Продемонстрируем применение некоторых методов работы с файлами:
QFile file1("file1.dat");
QFile file2("file2.dat");
if(file2.exists())
{
//Файл уже существует. Перезаписать?
}
if (!file1.open(QIODevice::ReadOnly))
{
qDebug() << "Ошибка открытия для чтения";
}
if(!file2.open(QIODevice::WriteOnly))
{
qDebug() << "Ошибка открытия для записи";
}
char a [1024]; while(!file1.atEnd())
{
int nBlocksize = file1.read(a, sizeof(a)); file2.write(a, nBlocksize);

}
Если требуется считать или записать данные за один раз, то используют методыQIODevice::write() и QIODevice::readAll(). Все данные можно считать в объект класса QByteArray, а потом записать из него в другой файл:
QFile file1("file1.dat");
QFile file2("file2.dat");
if(file2.exists())
{
//Файл уже существует. Перезаписать?
}
if(!file1.open(QIODevice::ReadOnly))
{
qDebug() << "Ошибка открытия для чтения";
}
if(!file2.open(QIODevice::WriteOnly))
{
qDebug() << "Ошибка открытия для записи";
}
QByteArray a = file1.readAll(); file2.write(a);
file1.close();
file2.close();
Операция считывания всех данных сразу, в зависимости от размера файла, может занять много оперативной памяти, а значит, к этому следует прибегать только в случаях острой необходимости или в том случае, когда файлы занимают мало места. Расход памяти при считывании сразу всего файла можно значительно сократить при том условии, что файл содержит избыточную информацию. Тогда можно воспользоваться функциями сжатия qCompress() и qUncompress(), которые определены вместе с классом QByteArray. Эти функции получают, в качестве аргумента, объект класса QByteArray и возвращают, в качестве результата, новый объект классаQByteArray.
Для удаления файла класс QFile содержит статический метод remove(). В этот метод необходимо передать строку, содержащую полный или относительный путь удаляемого файла.

Класс QBuffer унаследован от QIODevice, и представляет собой эмуляцию файлов в памяти компьютера (memory mapped files). Это позволяет записывать информацию в оперативную память и использовать объекты как обычные файлы (открывать при помощи метода open() и закрывать методом close()). При помощи методов write() иread() можно считывать и записывать блоки данных. Можно это так же сделать при помощи потоков, которые будут рассмотрены далее. Рассмотрим пример использования класса QBuffer:
QByteArray arr;
QBuffer buffer(&arr);
buffer.open(QIODevice::WriteOnly);
QDataStream out(&buffer);
out << QString("Message");
Как видно из этого примера, сами данные сохраняются внутри объекта
класса QByteArray. При помощи метода buffer() можно получить константную ссылку к внутреннему объектуQByteArray, а при помощи
метода setBuffer() можно устанавливать другой объект QByteArray для его использования в качестве внутреннего.
Класс QBuffer полезен для проведения операций кэширования. Например, можно считывать файлы растровых изображений в объекты класса QBuffer, а затем, по необходимости, получать данные из них.
Иногда приложению может потребоваться создать временный файл. Это может быть связано, например, с промежуточным хранением большого объема данных или передачей этих данных какой-либо другой программе.
Класс QTemporaryFile представляет реализацию для временных файлов. Этот класс самостоятельно создает себе имя с гарантией его уникальности, для того чтобы не возникало конфликтов, в результате которых могли бы пострадать уже существующие файлы. Сам файл будет расположен в каталоге для промежуточных данных, местонахождение которого можно получить вызовом
метода QDir::tempPath(). С уничтожением объекта будет уничтожен и сам временный файл.
Разные платформы имеют разные способы представления путей. ОС Windows содержит буквы дисков, например: C:\Windows\System. UNIX использует root, например: /usr/bin. Обратите внимание, что для разделения имен директорий в обоих представлениях используются разные знаки. Для представления директорий в платформонезависимой форме Qt предоставляет класс QDir.
Для этих целей класс предоставляет целый ряд статических методов:
QDir::current() — возвращает путь директории приложения;
QDir::root () — возвращает root-директорию;
QDir::drives ()—возвращает указатель на список объектов класса
QFileinfo с узловыми директориями (root). Для Windows это будут С:\, D:\ и т.
д.;
QDir::home () — возвращает персональную директорию пользователя.
Класс QDir не предоставляет методов для определения текущего каталога приложения. Но если вам потребуется определить, из какого каталога было запущенно приложение, то следует воспользоваться

методомQApplication::applicationDirPath(), либо QApplication::applicationFilePath(),
возвращающим, в добавок ко всему, и имя приложения.
Существование директории можно проверить с помощью метода exists(). Чтобы перемещаться по директориям, можно использовать метод cd(), который принимает, в качестве параметра, абсолютный путь директории, и cdUp(). Вызов cd("..") эквивалентен вызову метода cdUp().
Для конвертирования относительного пути директории в абсолютный можно вызвать метод makeAbsolute().
Для создания директории нужно вызвать метод mkdir(). В случае успешного проведения этой операции метод вернет значение true, в случае неудачи — false.
Если вам потребуется переименовать директорию, то воспользуйтесь методомrename(). В этот метод первым параметром нужно передать старый путь, а вторым — новый. Если операция будет проведена успешно, то метод вернет true, иначе — false.
Удаление директорий производится методом rmdir(), который получает путь, и в случае успеха возвращает true, а в случае неудачи — false.
При помощи класса QDir можно получить содержимое указанной директории. При этом допускается применять различные фильтры, чтобы исключить из списка не интересующие вас файлы. Для этих целей в классе определены методыentryList() и entryInfoList(). Первый возвращает список имен элементов (QStringList), а второй — информационный список (QFileInfoList). Если вам нужно узнать только количество элементов, находящихся в директории, то просто вызовите
метод count().
Программа, показанная на рисунке, осуществляет рекурсивный поиск файлов в директории, указанной в текстовом поле Directory (Директория). Нажатие кнопки с растровым изображением откроет диалоговое окно выбора нужной директории. В текстовом поле Mask (Маска) задается фильтр для отображаемых файлов. Например, для отображения исходных файлов на языке C++ нужно задать в полеMask (Маска) *.срр и *.h. После нажатия кнопки Find(Поиск) производится отображение файлов, в соответствии с заданными параметрами. Результаты отображаются в виджете многострочного текстового поля.
{рисунок}
FileFinder::FileFinder(QWidget* pwgt/*= 0*/) : QWidget(pwgt)
{
m_ptxtDir |
= new QLineEdit(QDir::current().absolutePath()); |
||
|
|
||
m_ptxtMask |
= new QLineEdit("*.cpp *.h"); |
||
m_ptxtResult |
= new QTextEdit; |
||
QLabel* |
plblDir |
= |
new QLabel("&Directory"); |
QLabel* |
plblMask = |
new QLabel("&Mask"); |
|
|
|
|
|
QPushButton* |
pcmdDir |
= |
new QPushButton(QPixmap(fileopen), ""); |
QPushButton* |
pcmdFind = |
new QPushButton("&Find"); |
|
|
|
|
|

connect(pcmdDir, SIGNAL(clicked()), SLOT(slotBrowse())); connect(pcmdFind, SIGNAL(clicked()), SLOT(slotFind()));
plblDir->setBuddy(m_ptxtDir); plblMask->setBuddy(m_ptxtMask);
//Layout setup
QGridLayout* pgrdLayout = new QGridLayout; pgrdLayout->setMargin(5); pgrdLayout->setSpacing(15); pgrdLayout->addWidget(plblDir, 0, 0); pgrdLayout->addWidget(plblMask, 1, 0); pgrdLayout->addWidget(m_ptxtDir, 0, 1); pgrdLayout->addWidget(m_ptxtMask, 1, 1); pgrdLayout->addWidget(pcmdDir, 0, 2); pgrdLayout->addWidget(pcmdFind, 1, 2); pgrdLayout->addWidget(m_ptxtResult, 2, 0, 1, 3); setLayout(pgrdLayout);
}
В конструкторе класса FileFinder создаются виджеты однострочного и многострочного текстовых полей
(указатели m_ptxtDir, m_ptxtMask и m_ptxtResult). Первый виджет инициализируется абсолютным путем, возвращаемым методомQDir::absolutePath(), который, в свою очередь, инициализируется значением текущей директории, возвращаемой методом QDir::current() . Второй виджет инициализируется строкой, предназначенной для фильтрации найденных файлов. Для старта операции поиска и открытия диалогового окна выбора директории создаются две кнопки нажатия— указатели pcmdFind и pcmdDir, которые соединяются со слотами slotFind() и slotBrowse(). В конструкторе создаются два виджета надписей, а с помощью методаsetBuddy() они ассоциируются с виджетами однострочных текстовых полей. Созданные виджеты размещаются в виде таблицы при помощи объекта классаQGridLayout.
void FileFinder::slotBrowse()
{
QString str = QFileDialog::getExistingDirectory
(
0,
"Select a Directory",
m_ptxtDir->text()

);
if (!str.isEmpty()) { m_ptxtDir->setText(str);
}
}
Метод slotBrowse(), который открывает диалоговое окно для выбора директории поиска. После закрытия этого окна директория записывается в текстовое полеDirectory (Директория) с помощью метода setText().
void FileFinder::slotFind()
{
start(QDir(m_ptxtDir->text()));
}
Метод slotFind() запускает операцию поиска и передает в метод start() выбранную пользователем директорию.
void FileFinder::start(const QDir& dir)
{
QApplication::processEvents();
QStringList listFiles = dir.entryList(m_ptxtMask->text().split(" "), QDir::Files);
foreach (QString file, listFiles) { m_ptxtResult->append(dir.absoluteFilePath(file));
}
QStringList listDir = dir.entryList(QDir::Dirs); foreach (QString subdir, listDir) {
if (subdir == "." || subdir == "..") { continue;
}
start(QDir(dir.absoluteFilePath(subdir)));
}
}

Процесс поиска заданных файлов может быть длительным, что может привести к "замиранию" графического интерфейса программы. Поэтому, для подавления этого нежелательного эффекта, при каждом вызове метода воспользуемся методомQApplication::processEvent() и дадим возможность обработаться накопившимся событиям. В методе start о переменная listFiles получает список файлов текущей директории. Для этого в методе установлены два параметра. Первый представляет собой список шаблонов поиска, которые распространяются на имена и расширения файлов. В нашем случае мы преобразуем строку в список, разделив ее посредством пробелов, с помощью QString::split(). Второй параметр (QDir::Files) указывает на то, что список должен содержать только файлы. Элементы полученного списка добавляются в виджет многострочного текстового поля с помощью метода append(). По завершению работы цикла добавления, в метод entryList() передается параметрQDir::Dirs для получения списка директорий. Для каждого из элементов списка, кроме "." и "..", вызывается метод start().
Задача этого класса состоит в предоставлении информации о свойствах файла, например: имя, размер, время последнего изменения, права доступа и т. д. Объект класса QFileInfo создается передачей в его конструктор пути к файлу, но можно передавать и объекты класса QFile.
Файл или каталог?
Иногда необходимо убедиться, что исследуемый объект является каталогом, а не файлом и наоборот. Для этой цели существуют методы isFile() и isDir().
В том случае, если объект является файлом, методisFile() возвращает значение булевого типа true, иначе — false. Если объект является директорией, то метод isDir() возвращает true, иначе — false. Кроме этих методов,
класс QFileInfo содержит методisSymLink(), возвращающий true, если объект является символьной ссылкой (symbolic link или shortcut в ОС Windows).
Символьные ссылки применяются в UNIX для обеспечения связи с файлами или каталогами. Создаются они при помощи команды "ln" с ключом "-s".
Путь и имя файла в Qt
Чтобы получить путь к файлу, нужно воспользоваться методом absoluteFilePath(). Для получения относительного пути к файлу следует использовать метод filePath(). Для получения имени файла нужно вызвать метод fileName(), который возвращает имя файла вместе с его расширением. Если нужно только имя файла, то следует вызвать метод baseName(). Для получения расширения используется
метод completeSuffix().
Информация о дате и времени файла в Qt
Иногда нужно узнать время создания файла, время его последнего изменения или чтения. Для этого класс QFileInfo предоставляет методы created(), lastModified() иlastRead() соответственно. Эти методы возвращают объекты класса QDateTime, которые можно преобразовать в строку методом toString(). Например:
//Дата и время создания файла fileInfo.created().toString();
//Дата и время последнего изменения файла fileInfo.lastModified().toString();

//Дата и время последнего чтения файла
fileInfo.lastRead().toString();
Получение атрибутов файла в Qt
Атрибуты файла дают информацию о том, какие операции можно проводить с файлом.
Для их получения в классе QFileInfо существуют следующие методы:
isReadable() —возвращает true, если из указанного файла можно читать информацию;
isWriteable() —возвращает true, если в указанный файл можно записывать информацию;
isHidden() — возвращает true, если указанный файл является скрытым;
isExecutable() —возвращает true, если указанный файл можно исполнять. В ОС UNIX это определяется не на основании расширения файла, как привыкли считать программисты в DOS и ОС Windows, а посредством свойств самого файла.
Определение размера файла в Qt
Метод size() класса QFileInf о возвращает размер файла в байтах. Размер файлов редко отображается в байтах, чаще используются специальные буквенные обозначения, сообщающие об его размере. Например, для килобайта — это буква К, для мегабайта — М, для гигабайта — G, а для терабайта — Т. Следующая функция позволяет сопровождать буквенными обозначениями размеры, лежащие даже в терабайтном диапазоне (вполне возможно, что через несколько лет это будет обычный размер файла):
QString fileSize(qint64 nSize)
{
qint64 i = 0;
for (; nSize > 1023; nSize /= 1024, ++i) { } return QString().setNum(nSize) + "BKMGT"[i];
}
Объекты файлов, сами по себе, обладают только элементарными методами для чтения и записи информации. Использование потоков делает запись и считывание файлов более простым и гибким. Для файлов, содержащих текстовую информацию, следует использовать класс QTextStream, а для двоичных файлов —
класс QDataStream.
Применение классов QTextStream и QDataStreamтакое же, как и для стандартного потока в языке C++ (iostream), с той лишь разницей, что они могут работать с объектами класса QIODevice. Благодаря этому, потоки можно использовать и для своих собственных классов, унаследованных отQIODevice.
Для записи данных в поток используется оператор <<, а для чтения данных из потока — >>.
Класс QTextStream