В стандартной библиотеке c для работы с файлами не содержится класс
Запись и считывание данных ( работа с файлами ).
В этом разделе будут рассотрены два способа работы с фыйлами и стандартный класс MFC CFileDialog.
Работа с файлами в C ( работает и в C++ )..
Описание функций работы с файломи находятся в библиотеке stdio.h
Сначала надо создать указатель на переменную типа FILE ( FILE* file; ). Открытие файла производится вызовом функции fopen ( file = fopen( file_name, "w" ); ) Первый параметр этой функции - имя файла, второй - указывает в каком режиме должен быть открыт файл. "w" - открыть для записи, "r" - открыть для чтения, "a" - дополнение файла( это наиболее используемые режимы, хотя есть и другие ). Запись и считывание данных из файла осуществляется следующими функциями : fputc, fputs, fgetc, fgets, fprintf, fscanf( описание этих функций смотрите в stdio.h). Закрытие файла осуществляется вызовом функции fclose ( fclose( file ); ).
Работа с файлами с помощью MFC( классы CFile, CStdioFile, . ) и стандартный класс MFC CFileDialog.
В библиотеку MFC включено несколько классов для обеспечения работы с файлами. Рассматриваемые ниже классы наследуются от базового класса CFile.
Класс CFile
Класс CFile предназначен для обеспечения работы с файлами. Он позволяет упростить использование файлов, представляя файл как объект, который можно создать, читать, записывать и т.д.
Чтобы получить доступ к файлу, сначала надо создать объект класса CFile. Конструктор класса позволяет сразу после создания такого объекта открыть файл. Но можно открыть файл и позднее, воспользовавшись методом Open.
Открытие и создание файлов
После создания объекта класса CFile можно открыть файл, вызвав метод Open. Методу надо указать путь к открываемому файлу и режим его использования. Прототип метода Open имеет следующий вид:
В качестве параметра lpszFileName надо указать имя открываемого файла. Можно указать только имя файла или полное имя файла, включающее полный путь к нему.
- CFile::modeCreate - Создается новый файл. Если указанный файл существует, то его содержимое стирается и длина файла устанавливается равной нулю.
- CFile::modeNoTruncate - Этот файл предназначен для использования совместно с файлом CFile::modeCreate. Если создается уже существующий файл, то его содержимое не будет удалено.
- CFile::modeRead - Файл открывается только для чтения.
- CFile::modeReadWrite - Файл открывается для записи и для чтения.
- CFile::modeWrite - Файл открывается только для записи.
- CFile::typeText - Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в текстовом режиме. Текстовый режим обеспечивает преобразование комбинации символа возврата каретки и символа перевода строки.
- CFile::Binary - Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в двоичном режиме.
Необязательный параметр pError, который является указателем на объект класса CFileException, используется только в том случае, если выполнение операции с файлом вызовет ошибку. При этом в объект, указываемый pError, будет записана дополнительная информация.
Метод Open возвращает не нулевое значение, если файл открыт и нуль в случае ошибки. Ошибка при открытии файла может случиться, например, если методу Open указан для чтения несуществующий файл.
Идентификатор открытого файла
В состав класса CFile входит элемент данных m_hFile типа UINT. В нем хранится идентификатор открытого файла. Если объект класса CFile уже создан, но файл еще не открыт, то в переменной m_hFile записана константа hFileNull.
Обычно идентификатор открытого файла непосредственно не используется. Методы класса CFile позволяют выполнять практически любые операции с файлами и не требуют указывать идентификатор файла. Так как m_hFile является элементом класса, то реализация его методов всегда имеет свободный доступ к нему.
Закрытие файлов
После завершения работы с файлом, его надо закрыть. Класс CFile имеет для этого специальный метод Close. Нужно заметить, что если был создан объект класса CFile и открыт файл, а затем объект удаляется, то связанный с ним файл закрывается автоматически с помощью деструктора.
Чтение и запись файлов
Для доступа к файлам предназначено несколько методов класса CFile: Read, ReadHuge, Write, WriteHuge, Flush. Методы Read и ReadHuge предназначены для чтения данных из предварительно открытого файла. В 32-разрядных операционных системах оба метода могут одновременно считать из файла больше 65535 байт. Спецификация ReadHuge считается устаревшей и оставлена только для совместимости с 16-разрядными операционными системами.
Данные, прочитанные из файла, записываются в буфер lpBuf. Параметр nCount определяет количество байт, которое надо считать из файла. Фактически из файла может быть считано меньше байт, чем запрошено параметром nCount. Это происходит, если во время чтения достигнут конец файла. Методы возвращают количество байт, прочитанных из файла.
Для записи в файл предназначены методы Write и WriteHuge. В 32-разрядных операционных системах оба метода могут одновременно записывать в файл больше 65535 байт. Методы записывает в открытый файл nCount байт из буфера lpBuf. В случае возникновения ошибки записи, например переполнения диска, методы вызывает обработку исключения.
Метод Flush
Когда используется метод Write или WriteHuge для записи данных на диск, они некоторое время могут находиться во временном буфере. Чтобы убедиться, что необходимые изменения внесены в файл на диске, нужно воспользоваться методом Flush.
Операции с файлами
В состав класса входят методы, позволяющие выполнять над файлами различные операции, например копирование, переименование, удаление, изменение атрибутов.
Для изменения имени файла класс CFile включает статический метод Rename, выполняющий функции этой команды. Метод нельзя использовать для переименования каталогов. В случае возникновения ошибки метод вызывает исключение.
Для удаления файлов в классе CFile включен статический метод Remove, позволяющий удалить указанный файл. Этот метод не позволяет удалять каталоги. Если удалить файл невозможно, то метод вызывает исключение.
Чтобы определить дату и время создания файла, его длину и атрибуты, предназначен статический метод GetStatus. Существует две разновидности метода - первый определен как виртуальный, а второй - как статический метод.
Виртуальная версия метода GetStatus определяет состояние открытого файла, связанного с данным объектом класса CFile. Этот метод вызывается только тогда, когда объект класса CFile создан и файл открыт.
Статическая версия метода GetStatus позволяет определить характеристики файла, не связанного с объектом класса CFile. Чтобы воспользоваться этим методом, необязательно предварительно открывать файл.
Блокировка
В состав класса включены методы LockRange и UnlockRange, позволяющие заблокировать один или несколько фрагментов данных файла для доступа из других процессов. Если приложение пытается повторно блокировать данные, уже заблокированные раньше этим или другим приложением, вызывается исключение. Блокировка представляет собой один из механизмов, позволяющих нескольким приложениям или процессам одновременно работать с одним файлом, не мешая друг другу.
Установить блокировку можно с помощью метода LockRange. Чтобы снять установленные блокировки, надо воспользоваться методом UnlockRange. Если в одном файле установлены несколько блокировок, то каждая из них должна сниматься отдельным вызовом метода UnlockRange.
Позиционирование
Чтобы переместить указатель текущей позиции файла в новое положение, можно воспользоваться одним из следующих методов класса CFile - Seek, SeekToBegin, SeekToEnd. В состав класса CFile также входят методы, позволяющие установить и изменить длину файла, - GetLength, SetLength.
При открытии файла указатель текущей позиции файла находится в самом начале файла. Когда порция данных прочитана или записана, то указатель текущей позиции перемещается в сторону конца файла и указывает на данные, которые будут читаться или записываться очередной операцией чтения или записи в файл.
Чтобы переместить указатель текущей позиции файла в любое место, можно воспользоваться универсальным методом Seek. Он позволяет переместить указатель на определенное число байт относительно начала, конца или текущей позиции указателя.
Чтобы переместить указатель в начало или конец файла, наиболее удобно использовать специальные методы. Метод SeekToBegin перемещает указатель в начало файла, а метод SeekToEnd - в его конец.
Но для определения длины открытого файла совсем необязательно перемещать его указатель. Можно воспользоваться методом GetLength. Этот метод также возвращает длину открытого файла в байтах. Метод SetLength позволяет изменить длину открытого файла. Если при помощи этого метода размер файла увеличивается, то значение последних байт не определено.
Текущую позицию указателя файла можно определить с помощью метода GetPosition. Возвращаемое методом GetPosition 32-разрядное значение определяет смещение указателя от начала файла.
Характеристики открытого файла
Чтобы определить расположение открытого файла на диске, надо вызвать метод GetFilePath. Этот метод возвращает объект класса CString, в котором содержится полный путь файла, включая имя диска, каталоги, имя файла и его расширение.
Если требуется определить только имя и расширение открытого файла, можно воспользоваться методом GetFileName. Он возвращает объект класса CString, в котором находится имя файла. В случае, когда нужно узнать только имя открытого файла без расширения, пользуются методом GetFileTitle.
Следующий метод класса CFile позволяет установить путь файла. Это метод не создает, не копирует и не изменяет имени файла, он только заполняет соответствующий элемент данных в объекте класса CFile.
Класс CMemFile
В библиотеку MFC входит класс CMemFile, наследуемый от базового класса CFile. Класс CMemFile представляет файл, размещенный, в оперативной памяти. С объектами класса CMemFile так же, как и с объектами класса CFile. Отличие заключается в том, что файл, связанный с объектом CMemFile, расположен не на диске, а в оперативной памяти компьютера. За счет этого операции с таким файлом происходят значительно быстрее, чем с обычными файлами.
Работая с объектами класса CMemFile, можно использовать практически все методы класса CFile, которые были описаны выше. Можно записывать данные в такой файл или считывать их. Кроме этих методов в состав класса CMemFile включены дополнительные методы.
Для создания объектов класса CMemFile предназначено два различных конструктора. Первый конструктор CMemFile имеет всего один необязательный параметр nGrowBytes:
Этот конструктор создает в оперативной памяти пустой файл. После создания файл автоматически открывается (не нужно вызывать метод Open).
Когда начинается запись в такой файл, автоматически выделяется блок памяти. Для получения памяти методы класса CMemFile вызывают стандартные функции malloc, realloc и free. Если выделенного блока памяти недостаточно, его размер увеличивается. Увеличение блока памяти файла происходит по частям по nGrowBytes байт. После удаления объекта класса CMemFile используемая память автоматически возвращается системе.
Второй конструктор класса CMemFile имеет более сложный прототип. Это конструктор используется в тех случаях, когда программист сам выделяет память для файла:
Параметр lpBuffer указывает на буфер, который будет использоваться для файла. Размер буфера определяется параметром nBufferSize.
Необязательный параметр nGrowBytes используется более комплексно, чем в первом конструкторе класса. Если nGrowBytes содержит нуль, то созданный файл будет содержать данные из буфера lpBuffer. Длина такого файла будет равна nBufferSize.
Если nGrowBytes больше нуля, то содержимое буфера lpBuffer игнорируется. Кроме того, если в такой файл записывается больше данных, чем помещается в отведенном буфере, то его размер автоматически увеличивается. Увеличение блока памяти файла происходит по частям по nGrowBytes байт.
Класс CMemFile позволяет получить указатель на область памяти, используемую файлом. Через этот указатель можно непосредственно работать с содержимым файла, не ограничивая себя методами класса CFile. Для получения указателя на буфер файла можно воспользоваться методом Detach. Перед этим полезно определить длину файла (и соответственно размер буфера памяти), вызвав метод GetLength.
Метод Detach закрывает данный файл и возвращает указатель на используемый им блок памяти. Если опять потребуется открыть файл и связать с ним оперативный блок памяти, нужно вызвать метод Attach.
Нужно отметить, что для управления буфером файла класс CMemFile вызывает стандартные функции malloc, realloc и free. Поэтому, чтобы не нарушать механизм управления памятью, буфер lpBuffer должен быть создан функциями malloc или calloc.
Класс CStdioFile
Тем, кто привык пользоваться функциями потокового ввода/вывода из стандартной библиотеки C и C++, следует обратить внимание на класс CStdioFile, наследованный от базового класса CFile. Этот класс позволяет выполнять буферизированный ввод/вывод в текстовом и двоичном режиме. Для объектов класса CStdioFile можно вызывать практически все методы класса CFile.
В класс CStdioFile входит элемент данных m_pStream, который содержит указатель на открытый файл. Если объект класса CStdioFile создан, но файл еще не открыт, либо закрыт, то m_pStream содержит константу NULL.
Класс CStdioFile имеет три различных конструктора. Первый конструктор класса CStdioFile не имеет параметров. Этот конструктор только создает объект класса, но не открывает никаких файлов. Чтобы открыть файл, надо вызвать метод Open базового класса CFile.
Второй конструктор класса CStdioFile можно вызвать, если файл уже открыт и нужно создать новый объект класса CStdioFile и связать с ним открытый файл. Этот конструктор можно использовать, если файл был открыт стандартной функцией fopen. Параметр метода должен содержать указатель на файл, полученный вызовом стандартной функции fopen.
Третий конструктор можно использовать, если надо создать объект класса CStdioFile, открыть новый файл и связать его с только что созданным объектом.
Для чтения и записи в текстовый файл класс CStdioFile включает два новых метода: ReadString и WriteString. Первый метод позволяет прочитать из файла строку символов, а второй метод - записать.
Примеры записи и чтения из файла
Приведем фрагменты кода, в которых демонстрируется использование стандартных диалоговых панелей выбора файла и процедуры чтения и записи в файл.
Открытие файла и чтение из него
Запустите программу - Build / Rebuild all ( будут ошибки ), выберите Build / Set active configuration - Win 32 Realise, выберите пункт меню "Project", далее "Settings. ", закладку "C/C++", Category - Code Generation и в пункте "Use run-time library" выберите "Multithreaded". После этого сделайте опять Build / Rebuild all и программа будет работать.
БлогNot. Лекции по C/C++: работа с файлами (stdio.h)
Лекции по C/C++: работа с файлами (stdio.h)
В лекции рассмотрен классический способ работы с файлами в C/C++, основанный на библиотеке stdio.h (она же cstdio ) и доступе к данным через структуру FILE . Альтернативный современный механизм работы с файлами в языке C++ на основе потоков и библиотек <fstream> , <ifstream> , <ofstream> будет изучен в следующей лекции.
Базовые функции для работы с файлами описаны в библиотеке stdio.h . Вся работа с файлом выполняется через файловую переменную - указатель на структуру типа FILE , определённую в стандартной библиотеке:
Открыть файл можно функцией fopen , имеющей 2 параметра:
Параметр имя_файла может содержать относительный или абсолютный путь к открываемому файлу:
1) "data.txt" - открывается файл data.txt из текущей папки
Важно: при запуске exe-файла "текущая папка" – та, где он находится; при отладке в IDE папка может быть иной, например, в Visual Studio при открытом консольном решении с именем Console файл следует разместить в папке Console/Console , а при запуске исполняемого файла не из IDE – в папке Console/Debug .
2) "f:\\my.dat" - открывается файл my.dat из головной папки диска f:
3) имя файла запрашивается у пользователя:
Параметр режим_доступа определяет, какие действия будут разрешены с открываемым файлом, примеры его возможных значений:
1) "rt" - открываем для чтения текстовый файл;
2) "r+b" - открываем для произвольного доступа (чтение и запись) бинарный файл;
3) "at" – открываем текстовый файл для добавления данных в конец файла;
4) "w" - открываем файл для записи без указания того, текстовый он или бинарный.
Фактически, указание "r" или "t" не накладывает каких-либо ограничений на методы, которые мы будем применять для чтения или записи данных.
После открытия файла следует обязательно проверить, удалась ли эта операция. Для этого есть 2 основных подхода:
1) стандартный обработчик ferror (см. пособиe, п.8.7);
2) сравнить указатель, который вернула fopen , с константой NULL ( nullptr ) из стандартной библиотеки:
Пример. Приложение проверяет, удалось ли открыть файл из текущей папки, имя файла запрашивается у пользователя (Visual Studio)
Важно! Функции, возвращающие указатель, в том числе, fopen , считаются небезопасными в ряде новых компиляторов, например, Visual Studio 2015. Если их использование приводит не просто к предупреждению, а к генерации ошибок, есть 2 основных способа решения проблемы:
1) в соответствии с рекомендациями компилятора, заменить старые названия функций на их безопасные версии, например, strcpy на strcpy_s и fopen на fopen_s . При этом может измениться и способ вызова функций, например,
Если используется предкомпиляция, то можно определить этот макрос в заголовочном файле stdafx.h .
Выбор способа чтения или записи данных зависит от того, какой должна быть структура файла.
- fscanf - для чтения
- fprintf - для записи
Первым параметром этих функций указывается файловая переменная, в остальном работа совпадает со стандартными scanf и printf .
Пример. Файл text.txt в текущей папке приложения имеет следующий вид:
Прочитаем его как последовательность вещественных чисел.
1. Функции семейства scanf возвращают целое число - количество значений, которые успешно прочитаны в соответствии с указанным форматом. В реальных приложениях эту величину следует проверять в коде:
2. На "восприятие" программой данных может влиять установленная в приложении локаль. Например, если до показанного кода выполнен оператор результат работы кода может измениться (для русской локали разделителем целой и дробной части числа является запятая, а не точка).
3. Очередное чтение данных изменяет внутренний файловый указатель. Этот указатель в любой момент времени, пока файл открыт, показывает на следующее значение, которое будет прочитано. Благодаря этому наш код с "бесконечным" while не зациклился.
4. Код показывает, как читать из файла заранее неизвестное количество значений – это позволяет сделать стандартная функция feof (проверка, достигнут ли конец файла; вернёт не 0, если прочитано всё).
5. Распространённый в примерах из Сети код вида
в ряде компиляторов может породить неточности при интерпретации данных. Например, этот код может прочитать как последнее значение завершающий перевод строки в файле, благодаря чему последнее прочитанное значение "удвоится".
В качестве примера форматной записи в файл сохраним массив a из 10 целочисленных значений в файле с именем result.txt по 5 элементов в строке:
Важно! Ввод/вывод функциями библиотеки stdio.h буферизован, то есть, данные "пропускаются" через область памяти заданного размера, обмен данными происходит не отдельными байтами, а "порциями". Поэтому перед чтением данных желательно очищать буфер от возможных "остатков" предыдущего чтения методом fflush , а после записи данных следует обязательно закрывать файл методом fclose , иначе данные могут быть потеряны. Заметим, что консольный ввод/вывод "обычными" методами scanf и printf также буферизован.
- fgetc и fputc - для посимвольного чтения и посимвольной записи данных;
- fgets и fputs - для чтения и записи строк с указанным максимальным размером.
Как и в случае с функциями для чтения форматированных данных, у всех этих методов имеются аналоги для работы со стандартным вводом/выводом.
Пример. Читая файл, определить длину каждой строки в символах. Для решения задачи воспользуемся тем фактом, что строки завершаются символом "перевод строки" ( '\n' ). Предполагается, что файл уже открыт для чтения.
Важно! Из-за особенностей реализации fgetc , без последней проверки за телом цикла код мог "не обратить внимания", например, на последнюю строку файла, состоящую только из пробелов и не завершающуюся переводом строки.
Пример. Читаем построчно файл с известной максимальной длиной строки. Предполагается, что файл уже открыт для чтения.
Важно! Без дополнительной обработки прочитанные из файла строки при выводе будут содержать лишние пустые строки между строками данных. Это происходит потому, что функция fgets читает строку файла вместе с символом перевода строки (точней, под Windows - с парой символов \r\n , интерпретируемых как один), а puts добавляет к выводимой строке ещё один перевод строки.
- void *buffer - нетипизированный указатель на место хранения данных;
- size_t (unsigned) size - размер элемента данных в байтах.
- size_t count - максимальное количество элементов, которые требуется прочитать (записать);
- FILE *stream - указатель на структуру FILE
Пример. Целочисленный массив a запишем в двоичный файл.
Учитывая, что данные массива хранятся в последовательно идущих адресах памяти, цикл for для записи мы могли заменить одним оператором:
Подход к чтению данных с помощью fread аналогичен. Например, если файл уже открыт для чтения в режиме "rb":
- функции fgetpos и ftell позволяют выполнить чтение текущей позиции указателя в файле;
- функции fseek и fsetpos позволяют осуществить переход к нужной позиции в файле.
Пример. Определить размер файла в байтах, предположим, что файл уже открыт в режиме чтения или произвольного доступа.
Материал для чтения из пособия: пп. 8.6-8.11. Обратите внимание на таблицы с описанными прототипами функций ввода/вывода.
Рекомендуемые задачи: базовое задание включает две задачи, первая из которых предполагает обработку файла как текстовых данных, вторая – как бинарных. В качестве дополнительной третьей задачи может быть предложена реализация одной из задач 1, 2, содержащая консольный интерфейс и меню.
На этом уроке мы рассмотрим работу классов с заголовочными файлами в языке С++.
Отделение объявления от реализации
Все классы, которые мы использовали до сих пор, были достаточно простыми, поэтому мы записывали методы непосредственно внутри тела классов, например:
К счастью, язык C++ предоставляет способ отделить «объявление» от «реализации». Это делается путем определения методов вне тела самого класса. Для этого просто определите методы класса, как если бы они были обычными функциями, но в качестве префикса добавьте к имени функции имя класса с оператором разрешения области видимости ( :: ).
Вот наш класс Date с конструктором Date() и методом setDate(), определенными вне тела класса. Обратите внимание, прототипы этих функций все еще находятся внутри тела класса, но их фактическая реализация находится за его пределами:
Просто, не так ли? Поскольку во многих случаях функции доступа могут состоять всего из одной строки кода, то их обычно оставляют в теле класса, хотя переместить их за пределы класса можно всегда.
Вот еще один пример класса с конструктором, определенным извне, со списком инициализации членов:
Классы и заголовочные файлы
На уроке о заголовочных файлах в языке С++ мы узнали, что объявления функций можно поместить в заголовочные файлы, чтобы затем иметь возможность использовать эти функции в нескольких файлах или даже в нескольких проектах. Классы в этом плане ничем не отличаются от функций. Определения классов могут быть помещены в заголовочные файлы для облегчения их повторного использования в нескольких файлах или проектах. Обычно, определение класса помещается в заголовочный файл с тем же именем, что у класса, а методы, определенные вне тела класса, помещаются в файл .cpp с тем же именем, что у класса.
Вот наш класс Date, но уже разбитый на файлы .cpp и .h:
Методы, определенные внутри тела класса, считаются неявно встроенными. Встроенные функции освобождаются от правила одного определения. А это означает, что проблем с определением простых методов (таких как функции доступа) внутри самого класса возникать не должно.
Методы, определенные вне тела класса, рассматриваются, как обычные функции, и подчиняются правилу одного определения, поэтому эти функции должны быть определены в файле .cpp, а не внутри .h. Единственным исключением являются шаблоны функций (но об этом чуть позже).
Параметры по умолчанию
Параметры по умолчанию для методов должны быть объявлены в теле класса (в заголовочном файле), где они будут видны всем, кто подключает этот заголовочный файл с классом.
Библиотеки
Разделение объявления класса и его реализации очень распространено в библиотеках, которые используются для расширения возможностей вашей программы. Вы также подключали такие заголовочные файлы из Стандартной библиотеки С++, как iostream, string, vector, array и другие. Обратите внимание, вы не добавляли iostream.cpp, string.cpp, vector.cpp или array.cpp в ваши проекты. Ваша программа нуждается только в объявлениях из заголовочных файлов, чтобы компилятор смог проверить корректность вашего кода в соответствии с правилами синтаксиса языка C++. Однако реализации классов, находящихся в Стандартной библиотеке С++, содержатся в предварительно скомпилированном файле, который добавляется на этапе линкинга. Вы нигде не встречаете этот код.
Вне программ с открытым исходным кодом (где предоставляются оба файла: .h и .cpp), большинство сторонних библиотек предоставляют только заголовочные файлы вместе с предварительно скомпилированным файлом библиотеки. На это есть несколько причин:
На этапе линкинга быстрее будет подключить предварительно скомпилированную библиотеку, чем выполнять перекомпиляцию каждый раз, когда она нужна.
Наличие собственных файлов, разделенных на объявление (файлы .h) и реализацию (файлы .cpp), является не только хорошей формой содержания кода, но и упрощает создание собственных пользовательских библиотек.
Заключение
Возможно, у вас возникнет соблазн поместить все определения методов класса в заголовочный файл внутри тела класса. Хотя это скомпилируется, но здесь есть несколько нюансов:
Во-первых, как упоминалось выше, это приведет к загромождению определения вашего класса.
Во-вторых, функции, определенные внутри класса, являются неявно встроенными. Большие функции, которые вызываются из многих файлов, могут способствовать, таким образом, «раздуванию» вашего кода.
Поэтому рекомендуется следующее:
Классы, используемые только в одном файле, и которые повторно не используются, определяйте непосредственно в файле .cpp, где они используются.
Классы, используемые в нескольких файлах или предназначенные для повторного использования, определяйте в заголовочном файле с тем же именем, что у класса.
Тривиальные методы (обычные конструкторы или деструкторы, функции доступа и т.д.) определяйте внутри тела класса.
Нетривиальные методы определяйте в файле .cpp с тем же именем, что у класса.
На следующих уроках большинство наших классов будут определены в файле .cpp со всеми методами, реализованными непосредственно в теле класса. Это делается для удобства и лаконичности примеров. В реальных проектах лучше, когда классы помещаются в отдельные файлы .cpp и .h.
Привет! Я время от времени рассказываю на Хабре о решениях распространённых задач на C++ и вообще люблю делиться опытом. Поэтому даже написал целую книгу, которая называется «Разработка приложений на С++ с использованием Boost». Она может быть интересна разработчикам, которые уже немного знакомы со стандартной библиотекой языка, хотят глубже изучить Boost, упростить и повысить качество разработки приложений. Уверен, что информация, которую я собрал в книге, будет полезна — всё больше библиотек Boost становятся частью стандарта. Сегодня предлагаю прочитать главу, посвящённую работе с файлами. В ней я рассказываю о перечислении файлов в каталоге, стирании и создании файлов и каталогов, а также о самом быстром способе чтения. Надеюсь, будет интересно. И, пожалуйста, не забывайте делиться впечатлениями в комментариях.
UPD: добавил в конец поста бонус для читателей Хабра.
Перечисление файлов в каталоге
Существуют функции и классы стандартной библиотеки для чтения и записи данных в файлы. Но до появления C++17 в ней не было функций для вывода списка файлов в каталоге, получения типа файла или получения прав доступа к файлу.
Знание основ C++ более чем достаточно для использования этого рецепта. Этот рецепт требует линковки с библиотеками boost_system и boost_filesystem.
Как это делается.
Этот и последующий рецепты посвящены переносимым оберткам для работы с файловой системой.
-
Нам нужно подключить следующие два заголовочных файла:
FILE W "./main.o"
FILE W "./listing_files"
DIRECTORY W "./some_directory"
FILE W "./Makefile"
Как это работает.
Функции и классы Boost.Filesystem просто оборачивают системные вызовы для работы с файлами.
Обратите внимание на использование знака "./" на этапе 2. Системы POSIX используют косую черту для указания путей; Windows по умолчанию использует обратную косую черту. Тем не менее Windows также понимает косую черту, а даже если бы не понимала, то библиотека Boost позаботилась бы о неявном преобразовании формата пути.
Посмотрите на этап 3, где мы вызываем конструктор по умолчанию для класса boost::filesystem::directory_iterator. Этот конструктор работает по аналогии с конструктором по умолчанию класса std::istream_iterator, – создает итератор конца диапазона.
Этап 4 сложен не потому, что эту функцию трудно понять, а из-за того, что происходит много преобразований. Разыменование итератора begin возвращает boost::filesystem::directory_entry, который неявно преобразуется в boost::filesystem::path, использующийся в качестве параметра для функции boost::filesystem::status. На самом деле можно написать намного лучше:
Совет:
Внимательно прочитайте справочную документацию, чтобы избежать ненужных неявных преобразований.
Этап 5 очевиден, поэтому мы переходим к этапу 6, где неявное преобразование в boost::filesystem::path происходит снова. Более явное решение выглядит так:
Здесь begin->path() возвращает константную ссылку на переменную
boost::filesystem::path, которая содержится в boost::filesystem::directory_entry.
Дополнительно.
Boost.Filesystem является частью C++17. Все содержимое в C++17 находится в одном заголовочном файле в пространстве имен std::filesystem. Версия стандартной библиотеки несколько отличается от Boost-версии, в основном за счет использования перечислений с областью видимости (enum class) там, где Boost.Filesystem использовала просто перечисление без области видимости.
Совет:
Есть класс directory_entry, который обеспечивает кеширование информации о файловой системе. Так что если вы много работаете с файловой системой и запрашиваете различную информацию, попробуйте использовать directory_entry для лучшей производительности.
Как и в случае с другими библиотеками Boost, Boost.Filesystem работает с компиляторами для стандарта, предшествующего C++17, и даже с компиляторами для стандарта, предшествующего C++11.
См. также
Стирание и создание файлов и каталогов
Давайте рассмотрим следующие строки кода:
В этих строках мы пытаемся записать что-то в файл file.txt в каталоге dir/ subdir. Если такой директории нет, эта попытка будет неудачной.
Давайте посмотрим, как можно сделать это элегантно, используя Boost.
Подготовка
Для этого рецепта требуются базовые знания C++ и класса std::ofstream.
Boost.Filesystem не является библиотекой header-only, поэтому код в этом ре-
цепте требуется линковать с библиотеками boost_system и boost_filesystem.
Как это делается.
Мы продолжаем работать с переносимыми обертками для файловой системы и в этом рецепте посмотрим, как изменить содержимое каталога.
-
Как всегда, нам нужно подключить несколько заголовочных файлов:
Как это работает.
boost::system::error_code может хранить информацию об ошибках и широко используется во всех библиотеках Boost.
Если вы не предоставите экземпляр boost::system::error_code для функций Boost.Filesystem, код будет компилироваться. В этом случае при возникновении ошибки будет выброшено исключение boost::filesystem::filesystem_error.
Внимательно посмотрите на этап 3. Мы использовали функцию boost::filesystem::create_directories вместо boost::filesystem::create_directory, потому что последняя не может создавать вложенные подкаталоги. Та же самая история с boost::filesystem::remove_all и boost::filesystem::remove. Первая удаляет каталоги, которые могут содержать файлы и подкаталоги. Вторая удаляет один файл.
Остальные шаги просты для понимания и не должны вызывать проблем.
Дополнительно.
Класс boost::system::error_code является частью C++11. Его можно найти в заголовочном файле <system_error> в пространстве имен std. Классы Boost.Filesystem являются частью C++17.
Наконец, небольшая рекомендация для тех, кто собирается использовать Boost.Filesystem. Когда ошибки при работе с файловой системой являются частым явлением, или приложение требует высокой отзывчивости/производительности, используйте класс boost::system::error_codes. В противном случае для обработки ошибок перехват исключений будет предпочтительнее и надежнее.
См. также
Самый быстрый способ чтения файлов
В интернете люди спрашивают: «Какой самый быстрый способ чтения файлов?» Давайте усложним задачу для этого рецепта: какой самый быстрый и переносимый способ чтения двоичных файлов?
Подготовка
Для этого рецепта требуются базовые знания C++ и std::fstream.
Как это делается.
Техника из этого рецепта широко используется приложениями, чувствительными к производительности ввода-вывода. Это самый быстрый способ чтения файлов.
-
Нам нужно подключить два заголовка из библиотеки Boost.Interprocess:
Как это работает.
Все популярные операционные системы имеют возможность отображать файл в адресное пространство процессов. После того как такое отображение было выполнено, процесс может работать с этими адресами так же, как с обычной памятью. Операционная система сама заботится обо всех файловых операциях, таких как кеширование и упреждающее чтение.
Почему это быстрее, чем традиционные операции чтения и записи? Это связано с тем, что в большинстве случаев чтение и запись реализуются как отображение в память и копирование данных в указанный пользователем буфер. Таким образом, чтение обычно делает немного больше, чем отображение.
Как и в случае с std::fstream из стандартной библиотеки, мы должны передать режим открытия файла. См. этап 2, где мы предоставили режим boost::interprocess::read_only.
См. этап 3, где мы отобразили весь файл сразу. Эта операция действительно очень быстрая, потому что ОС не читает все данные с диска, а сразу возвращает нам управление и ожидает запросов к части отображаемой области. После запроса ОС загружает запрошенную часть файла с диска в память. Как мы видим, операции отображения в память являются ленивыми, а размер отображаемой области не влияет на производительность.
Однако 32-разрядная ОС не может отображать в память большие файлы, поэтому вам придется отображать их по частям. Операционные системы POSIX (Linux) требуют определения макроса _FILE_OFFSET_ BITS=64 для всего проекта, чтобы работать с большими файлами на 32-битной платформе. В противном случае ОС не сможет отобразить части файла, размер находится за границей первых 4 ГБ.
Теперь пришло время измерить производительность:
Как и ожидалось, отображенные в память файлы немного быстрее по сравнению с традиционными операциями чтения. Также видно, что чистые методы C имеют такую же производительность, что и класс C++ std::ifstream, поэтому по возможности не используйте FILE* функции в C++. Они предназначены только для C, а не для C++!
Для обеспечения оптимальной производительности std::ifstream не забудьте открыть файлы в двоичном режиме и читать данные блоками:
Дополнительно.
К сожалению, классы для отображения файлов в память не являются частью C++20, и, похоже, их не будет и в C++23.
Запись в области с отображением в память также является очень быстрой операцией. Операционная система кеширует записи и не сбрасывает изменения на диск немедленно. Существует разница между кешированием данных в ОС и std::ofstream. В случае std::ofstream данные кешируются приложением, и если работа приложения аварийно завершается, закешированные данные могут быть потеряны. Когда данные кешируются ОС, завершение работы приложения не приводит к их потере. Сбои питания и сбои ОС приводят к потере данных в обоих случаях.
Если несколько процессов отображают один файл и один из процессов изменяет отображаемую область, то изменения сразу видны другим процессам (даже без фактической записи данных на диск! Современные ОС очень умные! Только не забывайте про синхронизацию доступа).
См. также
Читайте также: