Лекция 5. Работа с текстовыми и двоичными файлами средствами языка С++. Типы данных, определяемые программистом: переименование типов, перечислимые типы, структуры, объединения Файлы Под файлом понимается именованная область внешней памяти ЭВМ (жесткого диска, дискеты, диска CD-ROM). Для работы с файлами в С++ определён особый тип данных FILE. Переменные типа FILE представляют собой структуры, содержащие информацию о файлах, позволяющую осуществлять доступ и операции над ними. Файлы становятся доступными программе только после выполнения особой процедуры открытия файла. Эта процедура заключается в создании переменной типа FILE и её связывании с именем существующего или вновь создаваемого файла, а также в указании направления обмена информацией: чтение из файла или запись в него. Файловая переменная создаётся и связывается с именем файла в результате обращения к стандартной функции fopen( ): FILE *f1; f1=fopen("data.txt", "rt"); Первым параметром процедуры fopen( ) является указатель на
строку символов,
содержащую имя файла. Второй параметр содержит строку-спецификатор, задающую тип доступа к файлу и его свойства. Второй параметр может содержать следующие символы: r – открыть файл для чтения; w – открыть пустой файл для записи (если открывается существующий файл, его содержимое теряется); a – открыть для записи в конец файла; файл создаётся, если он не существует; r+ - открыть для чтения и записи (файл должен существовать); w+ - открыть пустой файл для чтения и записи, если файл существует, его содержимое теряется; a+ - открыть для чтения и добавления, файл создаётся, если он не существует; b – открывает файл в двоичном режиме; t – открывает файл в текстовом режиме.
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
Возвращаемым значением функции fopen() является указатель на файловую переменную. При её выполнении дисковый файл подготавливается к обмену данными. В результате специальная переменная – файловый указатель места
(не путать с указателем на
файловую переменную) инициализируется нужным значением. Файловым указателем называется переменная, содержащая адрес байта в файле, начиная с которого в него будут помещены данные или откуда они будут прочитаны. Например, при открытии файла со спецификатором “r” файловый указатель помещается в начало файла, а при открытии со спецификатором “a” – в конец. Текстовые и двоичные файлы, доступ к файлам в языке С++ Текстовые файлы Текстовые файлы предназначены для хранения текстовой информации. Текстовый файл трактуется в С++ как совокупность символьных строк переменной длины. Доступ к каждой строке возможен лишь последовательно, начиная с первой. При создании текстовых файлов в конце каждой строки ставится специальный признак EOL (кодируется последовательностью кодов 13 и 10), а в конце всего файла – признак конца файла (код 26). Для доступа к информации в текстовом файле применяются процедуры fprintf( ), fgets(), и fgetc( ). Они имеют следующий формат и смысл. fprintf(f1, "My Text"); Функция fprintf() аналогична по действию функции printf() (то есть записывает строку), но имеет первым параметром указатель на файловую переменную (в примере f1), связанный с текстовым файлом, куда функция выводит данные. char s[256]; fgets(s, 256, f1); Функция fgets( ) аналогична по действию функции gets( ), то есть читает из файла строку символов, пока не будет прочитан признак конца строки. Первым параметром указывается буфер,
куда
помещается
прочитанная
строка,
вторым
параметром
указывается
увеличенное на 1 число символов, по прочтении которого функция прекращает свою © В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
работу, третий параметр – указатель на файловую переменную. Функция возвращает значение 0 в случае невозможности чтения данных (например, если достигнут конец файла). char i; i=fgetc(f1); Функция fgetc(f1) читает один символ из файла, связанного с указателем, передаваемым ей в качестве параметра. После завершения функции fprintf(), fgets(), и fgetc() сдвигают файловый указатель на число прочитанных или записанных байт. Таким образом, следующее к ним обращение приведёт к чтению или записи файла начиная с нового места. Двоичные файлы Двоичные файлы предназначены для хранения информации в двоичном виде. В них можно хранить данные любого формата и любой длины. Для доступа к информации в двоичном файле применяются функции fread() и fwrite().Они имеют следующий формат и смысл. char i; fread(&i, sizeof(char), 1, f1); Функция fread() читает данные из файла. Её первый параметр – указатель на переменную, в которую будут считываться данные; второй параметр – размер одной порции читаемых данных; третий – число таких порций, читаемых за одно обращение к функции; последний параметр – указатель на файловую переменную. Функция возвращает число фактически прочитанных порций (размер каждой из которых указан вторым параметром), или 0, если произошла ошибка (например, достигнут конец файла). char i; fwrite(&i, sizeof(char), 1, f1);
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
Функция fwrite( ) записывает данные в файл. Её первый параметр – указатель на переменную, откуда будут записываться данные; второй параметр – размер одной порции записываемых данных, третий – число таких порций, записываемых за одно обращение к функции; последний параметр – указатель на файловую переменную. Функция возвращает число фактически записанных порций или 0 в случае ошибки. После завершения функций fread( ) и fwrite( ) файловый указатель смещается на число прочитанных или записанных байт. Кроме строго последовательных чтения и записи в языке С++ можно обеспечить доступ к любому месту файла с помощью функции fseek( ). fseek(f1, 10, SEEK_CUR); Первый параметр функции fseek() – указатель на файловую переменную; второй параметр – число байт, на которое нужно сдвинуть файловый указатель; третий параметр – это место, с которого нужно начинать сдвиг. Он может принимать следующие значения: SEEK_SET – начало файла, SEEK_CUR – текущее положение файлового указателя, SEEK_END – конец файла. Есть ещё несколько полезных функций для работы с файлами: getcurdir() – возвращает текущий каталог; chdir() – устанавливает каталог по умолчанию. Перечисленные
функции
не
исчерпывают
файловый
инструментарий
С++.
О
дополнительных возможностях и специфических приёмах работы с файлами (например, с помощью потоковых команд) можно узнать из справочной системы языка С++. Средства windows для работы с файлами Операционная система Windows имеет собственные средства для работы с файлами, которые становятся доступными после ссылки на заголовок <windows.h>. Перечислим основные из них (подробную информацию по этим функциям можно узнать из справочной системы WIN32.HLP или WIN32S.HLP). CreateFile() – создаёт новый или открывает существующий файл. © В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
ReadFile() – читает данные из файла. WriteFile() – записывает данные в файл. Главное причина, по которой программисту иногда приходится обращаться напрямую к функциям WinAPI при работе с файлами – возможность реализовать особые режимы доступа, чтения и записи (например, асинхронные чтение и запись). Типы данных, определяемые программистом Встроенные типы данных, массивы и указатели образуют основу для представления и обработки
информации
на
языке
С++.
Однако
C++
позволяет
пользователю
дополнительно определять и собственные типы данных, наиболее удобные для решения той или иной задачи.
Средствами такого определения, обсуждаемыми в настоящей
лекции, являются: оператор переименования типов, перечислимые типы, структуры и объединения. Переименование типов Любому типу в С++ можно присвоить простое имя или переименовать его. Это делается с помощью ключевого слова typedef: typedef тип новое_имя_типа; или typedef тип новое_имя_типа [число элементов]; для типов-массивов. Пример: typedef char byte; //Вводится новый тип - byte, эквивалентный char typedef int myarray[100];//Вводится новый тип myarray – массив целых из 100 элементов Таким образом, ключевое слово typedef является средством упрощения объявления переменных.
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
Перечислимые типы Перечислимые типы служат для представления переменных, которые могут принимать значения из заданного набора целых именованных констант. Определение перечислимого типа выглядит так: enum имя_типа {список констант}; Пример: enum color {red, green, blue}; По умолчанию, первая из именованных констант представляется значением 0, вторая – 1 и т.д. При объявлении можно указать значения констант и явным образом: enum color {white=-1,red, green, blue}; В этом случае любая из перечисленных констант, для которой значение не указано, принимается равной значению предыдущей константы плюс 1. После объявления переменной, имеющей перечислимый тип, ей можно присваивать значения: color x; x=red; Структуры Массивы позволяют обращаться с набором логически связанных однотипных элементов. Если же требуется хранить набор разнородных, но логически связанных данных, используются структуры. Объявление типа-структуры имеет вид: struct имя_типа {список элементов};
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
где список элементов состоит из объявлений, аналогичных обычным объявлениям переменных. Пример структуры, хранящей сведения о человеке: struct humen { char name[20]; char sirname[20]; int birthyear; }; Таким образом, в данном примере структура как бы группирует различные данные, относящиеся к конкретному человеку. При размещении в памяти компьютера переменной, имеющей тип структуры, её элементы располагаются строго последовательно, один за другим, согласно порядку, указанному при объявлении структуры. На рисунке показана схема расположения элементов структуры в памяти.
Указатель на структуру Поле 1
Поле 2
Поле 3
Для доступа к отдельным элементам структуры имеются две операции: точка и стрелка. Точка применяется для доступа к элементам структуры в том случае, если имеется сама переменная. Если же имеется лишь указатель на переменную, применяется стрелка. Пример: //--------------------------------------------------------humen a1, *a2; sprintf(a1.name, “Oleg”); //Помещаем в элемент a1.name строку © В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
sprintf(a1.sirname,”Ivanov”); //Помещаем в элемент a1.sirname строку a1.birthyear=1985; //Присваиваем элементу a1.birthyear значение a2=new humen; //Выделяем память под переменную типа humen sprintf(a2->name, “Victor”); //Помещаем в элемент a2->name строку sprintf(a2->sirname,”Petrov”); //Помещаем в элемент a2->sirname строку a2->birthyear=1987; //Присваиваем элементу a2->birthyear значение //------------------------------------------------------------------------------------Таким образом, запись a2->birthyear эквивалентна (*a2).birthyear и стрелка служит лишь для упрощения записи. Объединения Еще одним типом, определяемым пользователем, являются объединения. Они похожи по своему виду на структуры, и объявляются следующим образом: union имя_типа {список_элементов}; Пример: union realnumber { float flo; double dbl; long double ldb; }; Отличие объединений от структур состоит в том, что все элементы объединения занимают одно и то же место в памяти, они перекрываются. Компилятор отводит под объединение память, достаточную для размещения наибольшего элемента. На рисунке изображена схема расположения элементов объединения в памяти.
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС
Поле 2
Указатель на объединение
Поле 3
Поле 1 Доступ к элементам объединения осуществляется так же, как и в структурах – посредством точки или стрелки, за которыми следует имя элемента. Смотри также пример к лекции 5, расположенный на портале
© В.М. Гриняк, доц. каф. ИСПИ ВГУЭС