Пути к файлам
Казалось бы — что может быть проще, чем работа с файлами в C++. Но отдельные личности поражают своей находчивостью в поиске наихудшего подхода.
Не стоит делать так:
std::string filepath(«C:\\тест»);
std::ofstream file(filepath.c_str());
Если кратко, то использование не ASCII символов в строковых константах char может привести к печальным последствиям. Я уже обсуждал этот вопрос в посте о кодировках. В данном случае название файла напрямую зависит от кодировки исходника и если кто-то напишет подобное в utf-8, в windows-xp можно получить файл с запрещенными символами, с которым невозможно будет ничего сделать. Можно не использовать не ASCII. Но вы же не можете запретить это пользователю (потоку или БД из которого получен путь). Это же дискриминация по национальному признаку! Срочно исправляемся:
std::wstring filepath= L»C:\тест»
std::ofstream file(filepath.c_str());
Несведущие в стандарте пользователи Visual Studio могут успокоиться, пока нужда не заставит сменить компилятор (точнее STL). И тут начинается…
Дело в том, что текущий стандарт и не предполагает его наличия (не трогаем пока C++0x). Это в чистом виде энтузиазм мелкомягких.
Что же делать?
- Использовать стороннюю библиотеку для работы с путями (к примеру boost::filesystem)
- Использовать std::locale
- Придумывать свой
С третьим вариантом все ясно, с первым тоже ничего сложного:
- #include <boost/filesystem/fstream.hpp>
- #include <string>
- namespace fs = boost :: filesystem ;
- int main ( int argc, char * argv )
- <
- std :: wstring filepath ( L «C:\тест» ) ;
- fs :: ofstream ( filepath ) ;
- return 0 ;
- >
А вот по поводу второго у тех, кто не учил матчасть, могут возникнуть проблемы.
Пользуемся std::locale
Отступление.
Огорчу пользователей mingw: вам придется использовать стороннюю реализацию STL (к примеру stlport) из-за отсутствия в родной правильной поддержки локализации. А точнее, функция std::locale(«») всегда возвращает std::locale(«C»), что бы там у вас не стояло. Тот же stlport лишен подобного недостатка. О том как слепить связку mingw+stlport+boost я отписал тут.
Все что нам нужно сделать это следовать простым правилам — с не ASCII работаем в «расширенном» виде. То есть, читаем путь в std::wstring, используя соответствующим образом локализованный поток, а при использовании, сужаем по пользовательской локализации. Эта идея основана на том, что раз пользователь правильно видит символы своего языка в консоли, то его пользовательская локализация знает в какую кодировку надо сузить широкую строку, чтобы правильно интерпретировать путь. Итак, пример. Допустим у нас есть файл в кодировке cp866, содержащий путь. Нам необходимо создать файл по этому пути. Что мы делаем:
- #include <iostream>
- #include <string>
- #include <fstream>
- #include <locale>
- #include <memory>
- #include «facet/codecvt/codecvt_cp866.hpp»
- /**@brief Сужает широкую строку, используя локализацию loc
- @return Возвращает суженную строку или пустую суженную строку, в
- случае. если возникла ошибка*/
- std :: string narrow ( const std :: wstring & wstr, const std :: locale & loc )
- <
- const size_t sz = wstr. length ( ) ;
- if ( sz == 0 )
- return std :: string ( ) ;
- mbstate_t state = 0 ;
- char * cnext ;
- const wchar_t * wnext ;
- const wchar_t * wcstr = wstr. c_str ( ) ;
- char * buffer = new char [ sz + 1 ] ;
- std :: uninitialized_fill ( buffer, buffer + sz + 1 , 0 ) ;
- typedef std :: codecvt < wchar_t , char , mbstate_t > cvt ;
- cvt :: result res ;
- res = std :: use_facet < cvt > ( loc ) . out ( state, wcstr, wcstr + sz, wnext,
- buffer, buffer + sz, cnext ) ;
- std :: string result ( buffer ) ;
- if ( res == cvt :: error )
- return std :: string ( ) ;
- return result ;
- >
- /**@brief Расширяет строку, используя локализацию loc
- @return Возвращает расширенную строку или пустую расширенную строку, в
- случае, если возникла ошибка.*/
- std :: wstring widen ( const std :: string & str, const std :: locale & loc )
- <
- const size_t sz = str. length ( ) ;
- if ( sz == 0 )
- return std :: wstring ( ) ;
- mbstate_t state = 0 ;
- const char * cnext ;
- wchar_t * wnext ;
- const char * cstr = str. c_str ( ) ;
- wchar_t * buffer = new wchar_t [ sz + 1 ] ;
- std :: uninitialized_fill ( buffer, buffer + sz + 1 , 0 ) ;
- typedef std :: codecvt < wchar_t , char , mbstate_t > cvt ;
- cvt :: result res ;
- res = std :: use_facet < cvt > ( loc ) . in ( state, cstr, cstr + sz, cnext,
- buffer, buffer + sz, wnext ) ;
- std :: wstring result ( buffer ) ;
- delete [ ] buffer ;
- if ( res == cvt :: error )
- return std :: wstring ( ) ;
- return result ;
- >
- int main ( int argc, char * argv [ ] )
- <
- //Пусть имеется cp866 файл с путем
- std :: ofstream ofile ( «input.txt» , std :: ios :: binary ) ;
- if ( ! ofile )
- <
- std :: cerr << «Error open file» << std :: endl ;
- return 0 ;
- >
- std :: ostreambuf_iterator < char > writer ( ofile ) ;
- * ( writer ) = 0xe2 ; // т
- * ( ++ writer ) = 0xa5 ; // е
- * ( ++ writer ) = 0xe1 ; // с
- * ( ++ writer ) = 0xe2 ; // т
- ofile. close ( ) ;
- //Читаем путь
- std :: locale cp866 ( std :: locale ( ) , new codecvt_cp866 ) ;
- std :: wifstream ifile ( «input.txt» , std :: ios :: binary ) ;
- ifile. imbue ( cp866 ) ;
- std :: wstring wpath ;
- ifile >> wpath ;
- ifile >> wpath ;
- ifile. close ( ) ;
- //Создаем по этому пути файл
- std :: ofstream file ( narrow ( wpath, std :: locale ( «» ) ) . c_str ( ) ) ;
- file << «testing» ;
- file . close ( ) ;
- >
Фасеты можно взять на git-hub.
SUMMARY
Если вы используете путь из argv — можете смело с ним работать (пользователь знает что делает). Из «внешней среды» путь получайте с помощью правильно локализованного потока как широкую строку и сужайте ее с помощью пользовательской локализации.
С вопросами можно обращаться:
0. К стандарту
1. К книге Страуструпа (3-е специальное издание, приложение)
2. К документации по mingw.
3. К документации по boost.
4. К посту о фасетах и кодировках.
Всем прямых путей!
UPD: Ну и как правильно заметили Gorthauer87,Migun и naryl в комментариях, обратные слеши и платформо-специфичные пути тоже плохая идея.
Работа с путями
В этой и последующих статьях мы объясним, как ASP.NET Framework использует пути для сопоставления URL, на который указывает запрос, с файлом, применяемым для генерации ответа. Различают два типа путей — виртуальные и физические — и мы расскажем о роли каждого из них, а также о том, как они связаны друг с другом. Кроме того, мы продемонстрируем ряд базовых приемов управления путями, которые позволяют получить контроль над URL, поддерживаемыми приложением.
В качестве примера мы создаем новый проект под названием PathsAndURLs, используя шаблон ASP.NET Empty Web Application (Пустое веб-приложение ASP.NET). В проект мы добавили веб-форму по имени Default.aspx с разметкой, приведенной в примере ниже:
Эта базовая веб-форма отображает простое сообщение, содержащее ее имя. Это упростит освоение примеров по мере исследования отношений между URL и файлами, на которые они нацелены. Файл отделенного кода остается без изменений.
Щелкните правой кнопкой мыши на элементе Default.aspx в окне Solution Explorer и выберите в контекстном меню пункт Set As Start Page (Установить в качестве стартовой страницы). Если вы не сделаете этого, то получите результаты, отличающиеся от приведенных далее.
Мы добавили в проект файл класса C# по имени SimpleModule.cs и поместили в него реализацию интерфейса IHttpModule, показанную в примере ниже. Этот модуль обрабатывает событие BeginRequest, вызывая метод ProcessRequest(), который принимает объект HttpApplication:
Начальная реализация модуля реагирует на событие BeginRequest выводом сообщения в окно Output среды Visual Studio. Это сообщение отражает URL для текущего запроса, который извлекается из свойства HttpRequest.RawUrl. Перед использованием модуль должен быть зарегистрирован, в примере ниже представлено добавление в файл Web.config:
Чтобы протестировать приложение, просто запустите его. Браузер запросит URL вида /Default.aspx, который сгенерирует ответ с помощью веб-формы Default.aspx и выведет в окно Output среды Visual Studio следующее сообщение:
Если вы видите три запроса к корневому URL (/), значит, вы забыли установить Default.aspx в качестве стандартной стартовой страницы. Множество наблюдаемых запросов отражают попытку сервера IIS найти стандартный документ для обслуживания вместе с малоизвестным средством ASP.NET, которое называется обработкой URL без расширений: оба эти аспекта будут объясняться позже.
Понятие путей
— это строка, применяемая для уникального указания ресурса. В ASP.NET приходится иметь дело с двумя видами путей — физические пути и виртуальные пути. уникально идентифицирует файл в рамках файловой системы. Например, на нашей машине разработки физический путь к созданной ранее веб-форме выглядит следующим образом:
уникальным образом идентифицирует файл, доступный через веб-приложение, и представляет собой часть URL, идущую за сервером и портом. Если файл Default.aspx запрашивается посредством следующего URL:
то виртуальный путь будет таким:
Отображение между физическими и виртуальными путями является важным аспектом приложения Web Forms из-за особенностей использования отдельных файлов, таких как aspx, ashx и ascx, для генерации ответов. Когда мы создаем простую веб-форму или элемент управления, то обычно не заботимся о запрашиваемом файле, т.к. отображение виртуального пути на физический обеспечивают встроенные обработчики веб-форм.
Понимание отношений между виртуальными и физическими путями становится важным, когда мы начинаем добавлять функциональность для расширения способа обработки запросов средой ASP.NET. Обработчики, модули, специальные страницы ошибок, зависимости кеша, перенаправления требуют знания того, какой файл запрашивается данным URL — и это означает отображение между путями двух разных типов. В последующих разделах мы объясним, как получать информацию о путях, относящихся к конкретному запросу, и рассмотрим средства, которые помогают при отображении между ними.
Использование символа тильды (
Вы часто будете видеть, что виртуальные пути ASP.NET выражаются с применением символа тильды (
). Такие пути создают URL, которые являются относительными корневой папки приложения и называются URL, относящимися к приложению.
Можно создавать приложения ASP.NET, доступные через один и тот же корневой URL, так что URL вида http://site.com, http://site.com/hr и http://site.com/sales будут представлять разные веб-приложения. Веб-форма внутри приложения hr может использовать URL наподобие
/Default.aspx для ссылки на виртуальный путь /hr/Default.aspx, и не запрашивать файл /Default.aspx, являющийся частью совершенно другого приложения. Приложения развертываются таким образом, что они могут разделять общее имя хоста и порт, а символ «
» означает, что приложение не должно иметь жестко закодированную информацию о том, как оно разворачивается.
Выполнение такого вида развертывания требует высокой дисциплины, поскольку нужно помнить о применении символа «
» при каждом определении виртуального пути для ссылки на другую веб-форму или перенаправления запроса. Вследствие этого мы считаем, что развертывание подобным образом обычно заканчивается проблемами, т.к. всегда находится как минимум один URL, который был указан неправильно, и в результате запрос отправляется к совершенно другому приложению.
Мы рекомендуем развертывать приложения изолированно, чтобы не нужно было постоянно помнить о символе «
«. В случае платформы на основе облака или хостинга это делается легко, тогда как ситуация с хостингом на собственных серверах требует использования нескольких портов HTTP или множества серверов.
Получение информации о путях
В классе HttpRequest определен ряд удобных методов и свойств, которые можно применять для получения информации о путях, с которыми связаны запросы; все эти методы и свойства описаны в таблице ниже:
Получает корневой виртуальный путь приложения, которым будет /, если только в одну и ту же структуру каталогов не было развернуто несколько приложений
Возвращает виртуальный путь с применением нотации тильды (
Получает виртуальный путь текущего запроса. Это значение обновляется, когда используются методы Transfer() или Execute(), определенные в классе HttpServerUtility
Возвращает расширение файла, возвращаемого свойством CurrentExecutionFilePath, включая точку. Это свойство чаще всего используется фабриками обработчиков, которые поддерживают множество типов обработчиков и должны определять, какой тип обработчика необходимо создавать
Получает виртуальный путь текущего запроса, не включая информацию пути. Это значение не обновляется при вызове методов Transfer() или Execute()
Возвращает физический путь для указанного виртуального пути
Получает виртуальный путь текущего запроса, включая информацию пути. Это значение не обновляется при вызове методов Transfer() или Execute()
Возвращает дополнительную информацию пути для текущего запроса
Получает корневой физический путь для местоположения приложения; в нашей системе для рассматриваемого примера это С:\Projects\PathsAndURLs
Получает физический путь к файлу, на который указывает текущий запрос. Это свойство возвращает путь к файлу из исходного запроса и не обновляется в случае вызова методов Transfer() или Execute()
Свойства, связанные с путями, которые определены в классе HttpRequest, могут быть сгруппированы с использованием двух характеристик — всегда ли они возвращают исходный путь, связанный с запросом, и как обрабатывается дополнительная информация пути.
Получение фиксированной и динамической информации пути
Большинство значений, которые возвращают свойства, связанные с путями, относятся к запросу в том виде, в каком он был впервые получен и не обновлялся в результате вызова методов Execute() или Transfer(), определенных в классе HttpServerUtility.
Исключением является свойство CurrentExecutionFilePath, которое обновляется при переопределении обычного запроса. Такая комбинация статической и динамической информации пути удобна, когда определяются обработчики, которым необходимы какие-то сведения о том, что изначально запрашивалось. В качестве простого примера мы добавили в папку Content веб-форму по имени ReguestReporter.aspx:
Свойства, описанные в таблице выше, применяются для отображения исходного виртуального и физического пути запроса, а также текущего виртуального пути. Свойства для получения текущего физического пути не предусмотрено, поэтому мы вызываем метод MapPath(), который преобразует виртуальный путь в физический. Имя для свойства CurrentExecutionFilePath выбрано неудачно, поскольку оно возвращает виртуальный путь, а не физический, как можно было предположить.
Для демонстрации отличий между исходными и текущими путями мы модифицировали метод ProcessRequest() в файле класса SimpleModule.cs, вызвав в нем метод HttpServerUtility.Transfer(), когда виртуальным путем для запроса является /Test.aspx, как показано в примере ниже:
Чтобы увидеть отличия между путями, запустите приложение и запросите URL вида /Test.aspx. Веб-формы Test.aspx не существует, но запрос будет перехвачен и перемещен модулем, давая в результате вывод, представленный на рисунке ниже. (Конкретные физические пути зависят от того, где был создан проект Visual Studio.)

В этом примере раскрыты два важных момента. Первый заключается в том, что физические пути вовсе не должны ссылаться на существующие файлы — они генерируются на основе запрашиваемого URL и сведений о местоположении файлов приложения на диске, которыми располагает среда ASP.NET.
Второй момент связан с тем, что важно обращать максимальное внимание на то, где получается информация пути. Следует иметь четкое представление о том, что требуется — пути, которые были запрошены, или пути, которые ASP.NET использует для генерации ответа.
При построении приложения об этом отличии легко забыть — вплоть до того момента, когда вы вносите изменение, которое требует применения методов, определенных в классе HttpServerUtility, и обнаруживаете неожиданные проблемы. Тревожными признаками служат ссылки с URL, которые ведут на неправильную веб-форму, и сообщения об ошибках, связанные с бесконечными циклами (из-за нескончаемой передачи управления тому же самому пути).
Обработка дополнительной информации пути
Когда среда ASP.NET производит разбор URL, она по очереди просматривает каждый его сегмент (текст между парой символов /), пока не найдет такой, который содержит точку. Она предполагает, что это запрашиваемый файл. Среда ASP.NET обрабатывает URL слева направо.
В URL вида /Content/RequestReporter.aspx сегментами являются Content и RequestReporter.aspx. Комбинация сегментов слева от имени файла называется виртуальным каталогом, а сегмент, содержащий точку — именем файла.
Вдобавок URL могут содержать сегменты, которые находятся после имени файла, например:
Комбинация таких сегментов называется дополнительной информацией пути, или просто информацией пути. В приведенном выше URL информация пути выглядит как «/One/Two/Three». Эту информацию пути можно использовать для предоставления значений данных веб-формам и другим обработчикам или же игнорировать ее. Выбор влияет на то, какое свойство класса HttpRequest будет применяться для получения сведений о пути.
В таблице ниже перечислены свойства класса HttpRequest и значения, которые они возвращают для показанного выше URL:
/Content/RequestReporter.aspx (но будет изменяться в результате вызова методов Transfer() или Execute())
.aspx (но может измениться в результате вызова методов Transfer() или Execute())
Обратите внимание, что информация пути генерируется только для URL, которые обрабатываются ASP.NET, т.е. для URL, запрашивающих файлы типов, которые обслуживает ASP.NET, к примеру, aspx. В случае запроса URL вроде http://localhost:32404/Content/Colors.html/One/Two/Three виртуальным путем является /Content/Colors.html/One/Two/Three, a информация пути отсутствует.
Как показано в таблице, для получения требуемой информации пути важно выбирать правильные свойства, учитывая использование класса HttpServerUtility и наличие в URL дополнительной информации пути.
Манипулирование путями
В классе System.Web.VirtualPathUtility определен набор статических методов, с помощью которых можно манипулировать с путями. Использование этих методов предпочтительнее самостоятельной обработки строк путей, поскольку структура URL может оказаться довольно сложной.
Возвращает указанный путь, добавляя завершающий символ /, если он отсутствует
Комбинирует базовый и относительный путь, обеспечивая корректное количество символов /
Возвращает часть, представляющую каталог, из виртуального пути
Возвращает расширение файла из указанного пути, включая ведущую точку
Возвращает имя файла из указанного пути
Возвращает true, если указанный путь начинается с /
Возвращает true, если указанный путь начинается с
Возвращает первый путь, выраженный относительно второго пути
Возвращает указанный путь, удаляя завершающий символ /, если он существовал
Преобразует указанный относительный путь в абсолютный
Преобразует указанный абсолютный путь в относительный
Некоторые из перечисленных методов позволяют упростить работу с путями, относящимися к приложению (которые начинаются с символа
). Мы предпочитаем не пользоваться ими, однако считаем методы, предназначенные для извлечения определенных элементов и комбинирования путей, довольно удобными. Эти методы не отличаются особой сложностью, и вы легко можете воссоздать их в собственном коде. Тем не менее, следует отметить, что выполняемая ими работа является рутинной и требует многочисленных действий, связанных с разбором.
Работа с файлами в C
В языке Си, файлы рассматриваются как место, куда можно сохранять информацию и читать данные, выводя их в другой файл, в консоль и другие места. Язык C предоставляет все необходимое, для выполнения операций с файлами. Запись или чтение из файла является потоком байтов. Прежде, чем осуществлять какую-либо манипуляцию с файлом, необходимо открыть поток.
Как работать с файлами
Для открытия потока передачи байтов следует писать следующую конструкцию:
FILE * переменная fopen(имя файла, режим открытия);
Переменная имеет тип структуры FILE . В нее сохраняется результат работы функции fopen, который открывает поток. Первый параметр указывает имя файла и путь к нему, с которым будет происходить манипуляция, второй параметр указывает режим открытия — для записи, чтения, записи и чтения.
Если открытый файл уже не нужен, то его следует закрыть. В противном случае он будет потреблять лишнюю память, что может сказаться на производительности программы. Для закрытия файла применяется функция fclosе(переменная потока). Параметр функции указывает на переменную, в которую был сохранен открытый поток. Если поток был успешно закрыт, то функция возвращает 0, в противном случае EOF, указывающее на ошибку.
Пример открытия и закрытия потока:
int main(void)
<
FILE * f = fopen(«D:\text.txt», «w»); \\ Доступ к файлу, который находится по адресу D:\text.txt c параметром «W», который указывает на запись.
fclose(f); \\ Удаление потока
return 0;
>
Открытие потоков может сопровождаться ошибками. К примеру, при открытии файла для чтения, окажется, что такового не существует. Может быть недостаточно памяти для открытия файла и тому подобное. Если ошибка присутствует, то функция fopen() вернет NULL. Поэтому, чтобы такого не происходило, можно обработать ошибку с помощью функции perror.
Пример проверки на ошибку:
int main(void)
<
FILE * f; \\ Объявление переменной типа FILE
if((f= fopen(«D:\text25.txt», «r»))==NULL) \\ Проверка на наличие ошибки при открытии потока.
<
perror(«Error:»); Если есть ошибка, то будет выведен ее текст, в нашем случае это «Error No such file or directory».
exit(0); \\Принудительный выход из программы.
>fclose(f); \\ Если файл открыт, то функция тут же его закроет.
return 0;
>
Для вывода ошибки в консоль, применяется функция perror(string), которая в качестве параметра принимает текст ошибки. В нашем примере не имеет смысла продолжать выполнение программы, поэтому ее принудительно останавливают с помощью функции exit(0).
Текст ошибки из параметра функции perror сопровождается и сообщением компилятора в виде No such file or directory, которое говорит о отсутствия такого файла в указанной директории. Однако некоторые параметры функции fopen позволяют создать несуществующий файл. Если файл с конкретным именем не находится, то он будет создан заново.
Опции-параметры для открытия файлов
Второй параметр функции fopen задает ограничения на действия открытого файла. От режима зависит как файл может быть обработан. Каждый режим представляет собой символ или их набор. Существуют следующие режимы открытия файловых потоков в языке Си:
- wb — запись бинарного файла.
- rb – чтение бинарного файла.
- ab – продожение записи.
- w+b – создается бинарный файл для чтения и записи.
- r+b – чтение или запись бинарного файла.
- a+b – если файл отсутствует, то создается, если присутствует и заполнен, то продолжение записи.
Те же параметры применяются и для текстовых файлов, только без символа «b». Режимы задаются чтобы расширить возможности манипуляций с файлом и ограничить их до чтения или только записи. Также режимы задают формат файла — бинарный или текстовый. Неправильно установленный режим может привести либо к ошибке, либо к некорректной интерпретации файла.
Запись в текстовый файл
Для записи символов или строки в текстовый файл используется функция fputs(). Синтаксис данной функции следующий: int fputs( const char ch, FILE f); Здесь первый параметр ch принимает записываемую строку или символ. Второй параметр указывает на открытый файловый поток. Если при записи произошла ошибка, то функция возвращает значение ошибки EOF.
Пример записи теста в файл:
int main(void)
<
char * mes = «Hello, my name is Vova»; \\Сохраняем строку для записи в файл
char * fName = «D://text5.txt»; \\Сохраняем путь к файлу
char c[256];
FILE * f;if((f= fopen(fName, «w»))==NULL) \\Если возникла ошибка при открытии файла, то вывести сообщение «Error» и выйти из программы
<
perror(«Error»);
return 1;
>fputs(mes, f); \\ Запись в файл строки в переменной mes
fclose(f);
return 0;>
Так как в данном примере был использован режим открытия «W», то нам открывается пустой файл для записи. Если после выполнения данного кода открыть файл text5.txt, то в его содержании будет строка «Hello, my name is Vova».
Позиционирование указателя при записи файла
Режим «r» или «w» устанавливает каретку записи в начало файла. Режим «a» устанавливает каретку в конец имеющихся данных. Таким образом первый вариант перезаписывает файл, второй дописывает. При записи или чтении каретка передвигается на позицию, равную числу прочитанных или записанных байтов, или символов.
Однако часто необходимо выполнять поток байтов с конкретной позиции в файле, к примеру, со средины или с 53-й позиции. Для этого необходимо воспользоваться функцией fseek(), которая перемещает каретку в указанную позицию.
У данной функции следующий синтаксис: fseek(файл, количество байтов для смещения, начальная позиции);
Пояснение к функции:
- Параметр «файл» является переменной типа FILE, указывающая на директорию его расположения.
- Параметр «количество байтов для смещения» обладает типом long и в числовом формате указывает на сколько символов нужно переместить каретку.
- Параметр «начальная позиция» имеет числовой тип и указывает на каком по порядку от начала байте нужно установить каретку файла.
- В третий параметр можно вписать одну из встроенных в структуру FILE констант: SEEK_SET — устанавливает позицию на начало файла; SEEK_CUR — сохраняет текущую позицию в файле, если она была смещена; SEEK_END — устанавливает каретку на конечную позицию.
Пример использования функции fseek():
int main(void) <
int i; \\ Переменная для сохранения символов в цикле
char * str = «Hello»; \\ Первая строка записываемая в файл
char * final_str; \\ Строка для дописывания в файл
File * f = fopen(«text.txt», w);
fputs(str, f); \\ Записываем первую строку в файлfseek(f, 6, SEEK_SET); \\ Устанавливаем каретку на 6-ю позицию
fputs(«World!», f); \\ С шестой позиции записываем в файл строку «World!»
fseek(f, 0, SEEK_SET); \\ Устанавливаем позицию каретки в началоwhile ((i = getc(f))!=EOF) \\ Получаем каждый символ файла и сохраняем его в переменную «final_str»
<
*final_str +=(char *)i;
>printf(«Содержание файла:», final_str); \\ Выводим содержимое файла на консоль
fclose(f);
>
Основные функции для работы с файлами в языке Си
Есть ряд функций в языке Си, который позволяет манипулировать с файлами и потоками передачи данных. Основные из них были рассмотрены выше. Однако необходимо ознакомиться с их полным списком.
Открытие и закрытие файлового потока
- File fopen(f, option) — принимает в качестве аргумента путь к фалу и режим открытия потока, создает поток для записи или чтения текстовых или бинарных данных. Если нет возможности открыть файл, а режим не предусматривает его создания, то возвращает NULL.
- File freopen(dir, option, f) — аналогичная функция предыдущей, однако первый параметр указывает только путь к фалу, а последний имя файла.
- fclose(str) — закрывает поток для освобождения памяти от потока, в качестве параметра принимает путь к фалу или объект FILE.
Чтение из файлов
- fgets(f) — читает символ из файла и возвращает его числовой формат int, если результат успешный, то возвращает следующий файл. Если возникает ошибка то возвращает EOF.
- getc(f) — аналогичная предыдущей функции, но возвращает только один символ в числовом формате.
Запись в файл
- fwrite(chararr, sizeb, sizec, f) — функция записывает массив символов chararr в файл. Второй параметр указывает размер каждого байта, третий параметр указывает количество байтов, четвертый является объектом File или строковой директорией к нему. Если запись осуществилась успешно, то функция вернет sizec в формате int.
- fputc(number, f) — первый параметр конвертирует числовой код символа в сам символ и записывает его в поток f. Если запись прошла успешно, то возвращается записанный символ первого параметра, в случае неудачи возвращается EOF.
Считывание русских символов из файла
setlocale(LC_ALL, «Russian») — вызов данной функции позволяет настроить поток на работу с кириллицей.
Первый параметр является языковой константой, которая активирует выбор языка символов, второй параметр устанавливает язык для чтения и записи. Прежде, чем использовать данную функцию, необходимо в начале программы подключить директиву #include .
Отличие работы с потоками в Си от других языков
Как видно, язык Си не использует обращение к объектам для оперирования с файлами. Для открытия потока здесь даже не применяется конструкция new IOstream(). Ведь Cи является процедурным языком и в нем не используется парадигма ООП. Тем не менее, если открывать поток без сохранения в переменную, то дальнейшее применение функций должно сопровождаться записью в их параметры директории к файлу, какая была указана при открытии.
Ранее мы создавали переменную “f” для сохранения потока. Без нее всегда придется писать строку на подобие “C\:files\index.txt”. Данный подход достаточно неудобный и требует постоянной записи строки. При этом, все равно придется закрывать поток. Проще сохранить его в переменную – это дает гарантию тог, что работа всегда осуществляется с данным потоком.
Заключение
Работа с файлами является мощным инструментом каждого языка программирования. Ведь можно не только изменять содержание текстовых файлов, но и аудио, видео и и даже изображений через запись и чтение байтов. Так как в файлы записываются строки и символы, то перед записью их можно обрабатывать различными функциями или с помощью событий.
Большинство сложных приложений — аудиоконверторы, проигрыватели, графические редакторы и другие, работают с файлами. Важно уметь использовать возможности Си для работы с файловыми потоками, чтобы писать ложные автоматизированные сервисы.
Работу с файлами изучить достаточно просто. Ведь Си — это не объектно-ориентированный язык и обладает достаточно малым набором простых функций для обработки файлов. Режимы открытия файлов также не сложно запомнить. Ведь они практически идентичны. А благодаря наличию в языке встроенных функций по предоставлению информации об ошибках, можно гарантированно узнать, что файл так и не был создан, или в него ничего не было записано.
Работа с файлами
Для удобства обращения информация в запоминающих устройствах хранится в виде файлов.
Файл – именованная область внешней памяти, выделенная для хранения массива данных. Данные, содержащиеся в файлах, имеют самый разнообразный характер: программы на алгоритмическом или машинном языке; исходные данные для работы программ или результаты выполнения программ; произвольные тексты; графические изображения и т. п.
Каталог ( папка , директория ) – именованная совокупность байтов на носителе информации, содержащая название подкаталогов и файлов, используется в файловой системе для упрощения организации файлов.
Файловой системой называется функциональная часть операционной системы, обеспечивающая выполнение операций над файлами. Примерами файловых систем являются FAT (FAT – File Allocation Table, таблица размещения файлов), NTFS, UDF (используется на компакт-дисках).
Существуют три основные версии FAT: FAT12, FAT16 и FAT32. Они отличаются разрядностью записей в дисковой структуре, т.е. количеством бит, отведённых для хранения номера кластера. FAT12 применяется в основном для дискет (до 4 кбайт), FAT16 – для дисков малого объёма, FAT32 – для FLASH-накопителей большой емкости (до 32 Гбайт).
Рассмотрим структуру файловой системы на примере FAT32.
Файловая структура FAT32
Устройства внешней памяти в системе FAT32 имеют не байтовую, а блочную адресацию. Запись информации в устройство внешней памяти осуществляется блоками или секторами.
Сектор – минимальная адресуемая единица хранения информации на внешних запоминающих устройствах. Как правило, размер сектора фиксирован и составляет 512 байт. Для увеличения адресного пространства устройств внешней памяти сектора объединяют в группы, называемые кластерами.
Кластер – объединение нескольких секторов, которое может рассматриваться как самостоятельная единица, обладающая определёнными свойствами. Основным свойством кластера является его размер, измеряемый в количестве секторов или количестве байт.
Файловая система FAT32 имеет следующую структуру.
Нумерация кластеров, используемых для записи файлов, ведется с 2. Как правило, кластер №2 используется корневым каталогом, а начиная с кластера №3 хранится массив данных. Сектора, используемые для хранения информации, представленной выше корневого каталога, в кластеры не объединяются.
Минимальный размер файла, занимаемый на диске, соответствует 1 кластеру.
Загрузочный сектор начинается следующей информацией:
- EB 58 90 – безусловный переход и сигнатура;
- 4D 53 44 4F 53 35 2E 30 MSDOS5.0;
- 00 02 – количество байт в секторе (обычно 512);
- 1 байт – количество секторов в кластере;
- 2 байта – количество резервных секторов.
Кроме того, загрузочный сектор содержит следующую важную информацию:
- 0x10 (1 байт) – количество таблиц FAT (обычно 2);
- 0x20 (4 байта) – количество секторов на диске;
- 0x2С (4 байта) – номер кластера корневого каталога;
- 0x47 (11 байт) – метка тома;
- 0x1FE (2 байта) – сигнатура загрузочного сектора ( 55 AA ).
Сектор информации файловой системы содержит:
- 0x00 (4 байта) – сигнатура ( 52 52 61 41 );
- 0x1E4 (4 байта) – сигнатура ( 72 72 41 61 );
- 0x1E8 (4 байта) – количество свободных кластеров, -1 если не известно;
- 0x1EС (4 байта) – номер последнего записанного кластера;
- 0x1FE (2 байта) – сигнатура ( 55 AA ).
Таблица FAT содержит информацию о состоянии каждого кластера на диске. Младшие 2 байт таблицы FAT хранят F8 FF FF 0F FF FF FF FF (что соответствует состоянию кластеров 0 и 1, физически отсутствующих). Далее состояние каждого кластера содержит номер кластера, в котором продолжается текущий файл или следующую информацию:
- 00 00 00 00 – кластер свободен;
- FF FF FF 0F – конец текущего файла.
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:
- 8 байт – имя файла;
- 3 байта – расширение файла;
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:
- 8 байт – имя файла;
- 3 байта – расширение файла;
- 1 байт – атрибут файла:

- 1 байт – зарезервирован;
- 1 байт – время создания (миллисекунды) (число от 0 до 199);
- 2 байта – время создания (с точностью до 2с):

- 2 байта – дата создания:

- 2 байта – дата последнего доступа;
- 2 байта – старшие 2 байта начального кластера;
- 2 байта – время последней модификации;
- 2 байта – дата последней модификации;
- 2 байта – младшие 2 байта начального кластера;
- 4 байта – размер файла (в байтах).
В случае работы с длинными именами файлов (включая русские имена) кодировка имени файла производится в системе кодировки UTF-16. При этого для кодирования каждого символа отводится 2 байта. При этом имя файла записывается в виде следующей структуры:
- 1 байт последовательности;
- 10 байт содержат младшие 5 символов имени файла;
- 1 байт атрибут;
- 1 байт резервный;
- 1 байт – контрольная сумма имени DOS;
- 12 байт содержат младшие 3 символа имени файла;
- 2 байта – номер первого кластера;
- остальные символы длинного имени.
Далее следует запись, включающая имя файла в формате 8.3 в обычном формате.
Работа с файлами в языке Си
Для программиста открытый файл представляется как последовательность считываемых или записываемых данных. При открытии файла с ним связывается поток ввода-вывода . Выводимая информация записывается в поток, вводимая информация считывается из потока.
Когда поток открывается для ввода-вывода, он связывается со стандартной структурой типа FILE , которая определена в stdio.h . Структура FILE содержит необходимую информацию о файле.
Открытие файла осуществляется с помощью функции fopen() , которая возвращает указатель на структуру типа FILE , который можно использовать для последующих операций с файлом.
- "r" — открыть файл для чтения (файл должен существовать);
- "w" — открыть пустой файл для записи; если файл существует, то его содержимое теряется;
- "a" — открыть файл для записи в конец (для добавления); файл создается, если он не существует;
- "r+" — открыть файл для чтения и записи (файл должен существовать);
- "w+" — открыть пустой файл для чтения и записи; если файл существует, то его содержимое теряется;
- "a+" — открыть файл для чтения и дополнения, если файл не существует, то он создаётся.
Возвращаемое значение — указатель на открытый поток. Если обнаружена ошибка, то возвращается значение NULL .
Функция fclose() закрывает поток или потоки, связанные с открытыми при помощи функции fopen() файлами. Закрываемый поток определяется аргументом функции fclose() .
Возвращаемое значение: значение 0, если поток успешно закрыт; константа EOF , если произошла ошибка.
Чтение символа из файла:
Аргументом функции является указатель на поток типа FILE . Функция возвращает код считанного символа. Если достигнут конец файла или возникла ошибка, возвращается константа EOF .
Запись символа в файл:
Аргументами функции являются символ и указатель на поток типа FILE . Функция возвращает код считанного символа.
Функции fscanf() и fprintf() аналогичны функциям scanf() и printf() , но работают с файлами данных, и имеют первый аргумент — указатель на файл.
Функции fgets() и fputs() предназначены для ввода-вывода строк, они являются аналогами функций gets() и puts() для работы с файлами.
Символы читаются из потока до тех пор, пока не будет прочитан символ новой строки ‘\n’ , который включается в строку, или пока не наступит конец потока EOF или не будет прочитано максимальное количество символов. Результат помещается в указатель на строку и заканчивается нуль- символом ‘\0’ . Функция возвращает адрес строки.
Результат выполнения — 2 файла
Работа с файлами в C++ описана здесь.