Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 300070.doc
Скачиваний:
6
Добавлен:
30.04.2022
Размер:
295.42 Кб
Скачать

6. Лабораторная работа № 6

Тема. Файлы и структуры.

Цель. Закрепить основные характеристики и операции для неоднородных и файловых типов, уметь создавать и обрабатывать файлы с элементами простых и сложных типов.

Содержание работы

1. Изучить файловый и неоднородный типы.

2. Для поставленной задачи выбрать и обосновать представление данных задачи (исходных и результатов) в файле.

3. Для поставленной задачи подготовить файлы с исходными данными.

4. Спроектировать и отладить программу решения задачи.

Методические указания

  1. Тип структура

Неоднородный тип (структура, запись) позволяет конструировать структуры данных самой произвольной природы. Он используется для представления объектов, имеющих достаточно сложное, неоднородное строение и, как правило, используется при создании различного рода информационных систем. Значение неоднородного типа состоит из фиксированного количества элементов (полей) разных типов, поэтому каждый элемент должен иметь уникальное имя, которое используется для доступа к элементу. Программист сам описывает неоднородный (структурный) тип, задавая его “внутреннее строение”: количество элементов, их тип и имена.

Описание неоднородного типа:

struct <имя структурного типа>

{ <определения элементов> };

Здесь struct – служебное слово – спецификатор структурного типа,

<имя структурного типа> - идентификатор типа, произвольно выбираемый программистом (<имя структурного типа> может быть опущено),

<определения элементов> - совокупность одного или более описаний объектов, каждый из которых определяет тип элемента, вводимого структурного типа.

Пример задания типа структура:

struct book //описан именованный структурный

// тип с именем book, состоящий из

{char author[20]; //элементов: author – массив из 20

char title [80]; //символов, title – массив из 80

}; //символов

Определение объекта (например, переменной) именованного структурного типа имеет вид:

struct <имя структурного типа> < список структур>;

или

<имя структурного типа> < список структур> ;

где < список структур> - список, выбранных программистом, имен структур.

Пример определения переменных именованного структурного типа:

struct book tom1, tom2; //определены две структуры

// tom1 и tom2 типа book

или book tom1, tom2;

Определять переменные структурного типа можно одновременно с описанием типа:

struct point //описан именованный структурный тип

//с именем point, состоящий из трех

{ int num; //элементов: num – элемент целого типа,

float x; //x и y - элементы вещественного

float y; //типа и определены две структуры

} x1,x2; // х1 и х2 типа point

Определение объекта неименованного структурного типа имеет вид:

struct { <определения элементов> } < список структур>;

Пример определения переменных неименованного структурного типа:

struct {int day; //определены две структуры data и x

int mounth; // неименованного структурного типа,

int year; //состоящего из трех элементов

} data, x; //целого типа

Таким образом структуры неименованного структурного типа определяются одновременно с описанием самого типа. Такой вариант используется для однократного определения структур.

Так как элементы структур могут быть любых типов, то допускается вложение структур, т.е. элементом структуры может быть другая структура. В этом случае описание структурных типов должно быть сделано в такой последовательности, чтобы используемый в описании структурный тип был определен ранее:

struct fn //описан структурный тип с именем fn,

{ char fam[20]; //включающий фамилию

char name [15]; }; // и имя

struct student //описан тип student, включающий

{ fn namestud; //фамилию и имя студента,

int age ; //его возраст,

int mark [5] ; //оценки в сессию по пяти предметам

}stud [25], //определен массив stud из 25 элемен-

stud1; // тов, каждый элемент типа student

//и переменная stud1

При определении структура может быть инициализирована:

struct point z = {5, 2.37, 10.5 };

Элемент num структуры z получил значение 5, ее элемент x значение 2.37, а элемент y значение 10.5.

При определении объекта структурного типа ему выделяется память в таком количестве, чтобы могли разместиться данные всех элементов. Размер памяти в байтах, выделенный для структуры можно получить с помощью операции sizeof, например, sizeof (struct point).

Для обращения к элементам структуры используется уточненное имя вида:

<имя структуры> . <имя элемента структуры>

Здесь точка означает операцию доступа к элементу структуры, у нее самый высокий приоритет (см. таблицу 1.2). Уточненные имена элементов структур обладают всеми правами объектов соответствующих типов. Над элементами структуры можно выполнять операции, допустимые для их типа; присваивать им значения с помощью оператора присваивания, или в процессе ввода, или при использовании их в качестве фактического параметра.

Для примеров, приведенных выше:

Структура безымянного типа, задающая дату года:

data.day = 12; // элементу, определяющему день месяца,

//присвоено значение 12

data.month = 9; // элементу, определяющему месяц года

//присвоено значение 9

data.year = 2002; // элементу, определяющему год

//присвоено значение 2002

Массив с элементами структурного типа student, задающий информацию о студентах группы из 25 человек:

stud [1].namestud.fam = “Иванов”; // элементу,

//определяющему фамилию студента с номером 1

// в массиве stud присвоено значение строки “Иванов”

stud [1].namestud.name = “Алексей”; // элементу,

//определяющему имя студента с номером 1 в

//массиве stud присвоено значение строки “Алексей”

stud [1].age = 19; // элементу, определяющему возраст //студента с номером 1 в массиве stud присвоено

//значение 19

stud [1].mark[4] = 5; // элементу, определяющему оценку

//по пятому предмету студента с номером 1 в

// массиве stud присвоено значение 5

Для структур одного типа допустим оператор присваивания

V1 = V2;

где V1, V2 – переменные одного структурного типа. Например, для структур, определенных выше:

x = data;

Структура может быть параметром функции и возвращаемым функцией значением, например заголовок функции ff:

struct student ff (struct student x)

говорит, что формальный параметр х и возвращаемый функцией результат имeют структурный тип student. Фактический параметр функции ff структура типа student, связь между формальным и фактическим параметром устанавливается по значению, т.е. для переменной х выделяется память, куда копируется значение фактической структуры при вызове функции. Вызов функции (для приведенных выше описаний):

stud1 = ff ( stud[0] );

Кроме того, в обоих случаях могут использоваться указатели на объекты структурного типа:

student * ff ( student * x )

Вызов функции (для приведенных выше описаний):

pstud = ff ( &stud [j] ) ;

Здесь pstud – указатель на структуру, который должен быть определен до вызова ff:

student *pstud;

  1. Файл

Файловый тип – это тип, который связывает программу с внешними устройствами ЭВМ. Значение файлового типа представляет собой произвольной длины последовательность компонент:

Размер файла (т.е. длина последовательности) никак не оговаривается при объявлении файла и ограничивается только емкостью устройств внешней памяти. Для указания конца структуры используется признак конца файла (end of file).

В Си файл рассматривается как поток или последовательность символов (байтов), независящая от конкретного устройства, с которым ведется обмен данными. При обмене с потоком часто используется вспомогательный участок основной памяти, называемый буфер потока (буфер ввода, буфер вывода).

При работе с потоком можно производить следующие действия:

- открывать и закрывать потоки;

- вводить (читать) и выводить (записывать): символ, строку, форматированные данные, порцию данных произвольной длины;

- анализировать условие достижения конца потока (конца файла) и ошибки ввода-вывода;

- получать и устанавливать указатель текущей позиции в потоке;

  • управлять буферизацией потока и размером буфера.

Все операции ввода-вывода реализованы с помощью функций, находящихся в библиотеке языка Си. Чтобы использовать эти функции, необходимо включить в программу заголовочный файл stdio.h (директивой #include <stdio.h>), который содержит прототипы функций ввода-вывода, определения констант, типов и структур, необходимых для работы функций.

Работа с потоком начинается с его инициализации – открытия. При открытии происходит связывание указателя на поток с конкретным файлом. Этот указатель идентифицирует поток (файл) во всех последующих операциях. Указатель на поток должен быть определен следующим образом:

FILE *fp;

Здесь FILE – имя структурного типа, определение которого находится в заголовочном файле stdio.h. Указатель на поток должен получить значение в результате выполнения функции открытия потока:

fp = fopen (имя_файла, режим_открытия);

Функция fopen возвращает указатель на поток, являющийся указателем на объект структурного типа FILE (структура типа FILE содержит всю необходимую для работы с потоком информацию). Параметры функции являются указателями на массивы символов, содержащих соответственно имя файла, связываемого с потоком, и строку режимов его открытия. Они также могут задаваться непосредственно в виде строк, например:

fp = fopen ( “data.txt”, “r” );

где “data.txt” – имя некоторого файла, связываемого с потоком (путь к файлу должен быть известен в системе программирования);

“r” – обозначение одного из режимов работы с файлом (тип доступа к потоку).

Поток можно открыть в текстовом или двоичном режиме. В соответствии с этим различают файлы текстовые и двоичные.

При открытии файла в текстовом режиме прочитанная из потока последовательность символов преобразуется, если это необходимо из символьного представления во внутреннее представление. Например, если при форматном вводе читается числовая информация, то происходит преобразование прочитанной последовательности символов в двоичное целое или число с плавающей точкой в соответствии со спецификацией формата; при форматном выводе числовой информации происходит преобразование из внутреннего представления числа в последовательность символов, изображающих число. Последовательность символов, хранящаяся в текстовом файле, может быть разбита на строки. При записи в текстовый поток символа новой строки ‘\n’ он заменяется последовательностью символов CR (“возврат каретки”) и LR (“перевод строки”). При чтении из текстового файла последовательность символов CR и LR преобразуется в один символ новой строки ‘\n’.

Если в файле хранится не текстовая информация, а двоичная, то никакие преобразования не должны выполняться. Например, в файл записывается (а затем читается) числовая информация в своем внутреннем представлении. Такой файл надо открыть как двоичный.

Текстовый режим обозначается буквой t (она обычно опускается), двоичный режим буквой b.

В соответствии с основными действиями с файлами возможны следующие стандартные режимы открытия файла:

“w” – новый текстовый файл открывается для записи в него (вывода). Если файл уже существовал, то предыдущее содержимое стирается, т.е. файл создается заново;

“r” – существующий текстовый файл открывается только для чтения;

“a” – текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции информации. Добавляется информация всегда в конец файла. При открытии в этом режиме уже существующий файл не уничтожается;

“w+” – новый текстовый файл открывается для записи и последующих исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешена и в конец файла, т.е. файл может увеличиваться;

“r+” – существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла (т.е. доступен для исправлений), но запись в конец недопустима, т.е. нельзя увеличить размер файла;

“a+” – текстовый файл открывается или создается (если файла нет) и становится доступным для изменений, т.е. для чтения и записи в любом месте в том числе и в конце файла; при этом в отличие от режима “w” существующий файл не уничтожается.

Режимы “a”, “a+”, “w+”, “r+” позволяют обновлять ранее созданные файлы, изменяя значения элементов или добавляя новые (новые элементы можно добавлять только в конец файла).

При работе с файлом в двоичном режиме к соответствующему символу добавляется буква b, например, “wb”, “r+b” (при работе с текстовым файлом буква t, если она не опускается, например, “rt”, “a+t”).

С открытым файлом связано понятие текущей позиции и указателя на текущую позицию. Текущая позиция это байт, начиная с которого производится очередная операция чтения/записи. При открытии потока в режимах “r” и “w” указатель текущей позиции чтения/записи в потоке устанавливается на начальный байт потока, а при открытии в режиме “a”– в конец потока: за последним байтом.

При открытии файла могут произойти ошибки, в таких случаях fopen возвращает NULL, иначе значение отличное от NULL. Типичная последовательность операторов, которую рекомендуется использовать при открытии файла:

if (( fp = fopen (“data.txt”, “r” ) ) = = NULL)

{ perror (“ошибка при открытии файла”); exit (0); }

Здесь perror – библиотечная функция сообщений об ошибках.

Открытые на диске файлы надо явно закрыть после окончания работы с ними, используя функцию fclose:

fclose ( указатель_на_поток);

После того как файл открыт, в него можно записывать или читать информацию в зависимости от режима. При выполнении каждой операции ввода-вывода указатель текущий позиции в потоке перемещается на новую текущую позицию в соответствии с числом прочитанных (записанных) байтов. Операция чтения (или записи) для потока всегда производится с текущей позиции в потоке. Таким образом, естественный способ обращения к элементам файла – последовательный. Последовательно читая из файла, можно достигнуть конца файла. При попытке продолжать чтение операция не будет выполнена, операция чтения при этом будет возвращать константу EOF (константа определена в заголовочном файле). Для отслеживания ситуации «достигнут ли конец файла» можно использовать функцию feof, прототип функции:

int feof ( указатель_на поток);

Функция возвращает ненулевое значение, если достигнут конец файла, иначе нулевое значение.

Форматный обмен (обмен с текстовым файлом). Функции форматного обмена предназначены для ввода/вывода отдельных символов, строк, целых и вещественных чисел всех типов. При вводе данные помещаются в буфер, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они сначала накапливаются в буфере, а при заполнении буфера записываются в виде единого блока в файл за одно обращение к нему. Таким образом, использование буфера позволяет сократить число обменов с файлом. Буфер выделяется программе по умолчанию при открытии файла.

Операция чтения (ввода) выполняется функцией fscanf. Прототип функции:

int fscanf (указатель_на_поток,форматная_строка,список _аргументов);

Здесь форматная строка задает порядок ввода и преобразование вводимых значений. Она может включать произвольный текст, управляющие символы и спецификации преобразования данных. Список аргументов задает переменные, которым будут присвоены введенные значения, каждый аргумент задается адресом переменной.

Функция читает последовательность символов из входного потока, начиная с байта, на который показывает текущее положение указателя файла. Ввод прекращается, если встретился пробельный символ или прочитано количество символов, указанных в спецификации преобразования. Прочитанная последовательность символов интерпретируется в соответствии с форматной строкой (форматная строка просматривается последовательно от первого символа к последнему) как символьное представление целого числа или вещественного числа или один символ или строка символов. Затем преобразуется во внутреннее представление и записывается в область памяти очередной переменной из списка аргументов (указатель текущей позиции файла при этом перемещается на новую текущую позицию в соответствии с числом прочитанных байтов). Этот процесс продолжается пока не исчерпана форматная строка или не достигнут конец файла или не произошла ошибка. В первом случае функция возвращает количество объектов, получивших значение при вводе, при достижении конца файла – возвращает константу EOF, в случае ошибки – значение –1.

Пример:

while ( fscanf (fp, “%5d %f “, &x, &y) != EOF ) ;

или

while ( !feof (fp ) ) fscanf (fp, “%5d %f “, &x, &y);

Цикл будет продолжаться пока при чтении из потока с указателем fp функция fscanf не получить значение EOF. Функция читает пары чисел, разделенных пробелами, при этом первое число занимает не более 5 позиций (символов) в файле и интерпретируется как целое число, после преобразования во внутреннее представление оно будет записано в переменную x целого типа; второе число отделено от первого и следующего за ним пробелами, оно интерпретируется как число с плавающей точкой, после преобразования во внутреннее представление записывается в переменную y. Последняя пара чисел может быть не полной, т.е. не содержать вещественного числа.

Операция записи (вывода) выполняется функцией fprintf. Прототип функции:

int fprintf (указатель_на_поток,форматная_строка,список_аргументов);

Здесь форматная строка имеет тот же смысл, что и в fscanf. Список аргументов включает выражения, значения которых должны быть записаны в файл.

Функция просматривает форматную строку слева направо все встреченные произвольные символы выводит в файл, при встрече спецификации преобразования вычисляет значение соответствующего ему выражения из списка аргументов, преобразует его из внутреннего представления в последовательность символов в соответствии со спецификацией и выводит в текущую позицию файла. Для каждого аргумента должна быть указана одна спецификация преобразования. Если аргументов меньше , чем спецификаций, то результат зависит от реализации языка. Вывод заканчивается, когда исчерпана форматная строка или возникла ошибка. Функция возвращает число выведенных символов, а при ошибке отрицательное число.

Пример:

fprintf (fp, “ %3i %f “, a, c*a);

В файл с указателем fp будет выведено значение целой переменной а, преобразованное в последовательность из трех символов, и значение выражения с*а, преобразованное в последовательность символов вида: знак_числаdddd.dddd. Количество цифр перед точкой зависит от величины выводимого значения, а количество цифр после десятичной точки зависит от точности представления числа.

Количество аргументов и их типы в функциях fscanf и fprintf должны соответствовать последовательности спецификаций преобразования в форматной строке. При несовпадении типов и спецификаций будет введен или выведен «мусор».

Двоичный режим обмена с файлами (двоичный файл). В двоичном режиме в файл можно записать содержимое любой области памяти без преобразования из внутреннего представления. Таким образом, форма представления данных в памяти и в двоичном файле одинакова. Поэтому при чтении из двоичного файла преобразование во внутреннее представление не нужно. Для обмена с двоичным файлом используются функции fread и fwrite.

Прототип функции fread:

int fread (void * buf, int size, int count, FILE *fd);

Функция читает count элементов размерностью size байтов из входного потока fd и помещает их в область памяти, адрес которой находится в переменной buf типа указатель на тип void, т.е. на любой тип. Функция возвращает количество прочитанных элементов, оно может быть меньше count, если конец файла встретился раньше, чем было прочитано count элементов или, если произошла ошибка.

Прототип функции fwrite:

int fwrite (void *buf, int size, int count, FILE *fd);

Функция записывает в выходной поток fd count элементов по size байтов из области памяти, адрес которой находится в buf - переменной - указатель на тип void. Функция возвращает количество выведенных элементов.

Прямой доступ. Кроме последовательного обмена для файлов на диске возможен произвольный (прямой) доступ. Для этого предназначены функции:

  • fseek – устанавливает указатель текущей позиции в потоке на нужный байт;

  • ftell – возвращает значение указателя текущей позиции в потоке;

  • rewind – устанавливает указатель текущей позиции в потоке на начало потока.

Прототип функции fseek:

int fseek ( указатель_на_поток, смещение, начало_отсчета);

Смещение задается выражением типа long и может быть отрицательным, т.е. возможно перемещение к началу (отрицательное значение) и к концу (положительное значение) файла. Начало отсчета задается одной из предопределенных констант:

SEEK_SET – начало файла

SEEK_CUR – текущая позиция

SEEK_END – конец файла.

Пример:

fseek (fp, -(long)sizeof (book), SEEK_CUR);

Указатель текущей позиции будет перемещен на одну структуру типа book назад относительно текущей позиции.

Прототип функции ftell:

long ftell (указатель_на_поток);

Прототип функции rewind:

void rewind (указатель_на_поток);

Пример:

FILE *df;

double x [20], y;

double *p = &x[0];

df = fopen (“b.dat”, “wb+”); //Открыть (создать новый) файл как

//двоичный для записи и чтения

fwrite ((void *) p, sizeof (double), 20, df); //Записать массив х в файл

//начиная с текущей позиции

fseek (df, 80L, SEEK_SET); //Установить указатель текущей

//позиции на 80 байт от начала файла

fread ((void*) &y, sizeof y, 1, df ); //Прочитать 1 элемент размером

//8 байт в переменную y, начиная с текущей позиции,

// т.е. в у будет записано значение х[10]

Стандартные потоки. Любой программе доступны стандартные текстовые потоки:

stdin – доступен для чтения и связи с клавиатурой;

stdout – доступен для записи и связи с дисплеем.

Эти файлы открываются автоматически, когда начинается выполнение программы.

При обращении к функциям чтения/записи указатели на стандартные потоки stdin и stdout опускаются. При вводе с клавиатуры нажатие клавиши enter соответствует признаку конца строки, одновременное нажатие на клавиши ctrl и z- признаку конца файла.

Под выводимые в текстовый файл значения отводится поле, размер которого задается либо по умолчанию (под символ – одна позиция; под строку- число позиций, равное длине строки; под целое число- число позиций, равное числу значащих цифр плюс одна под знак отрицательного числа; под вещественное число – 17 позиций), либо явно – с помощью форматов. (О форматировании выводимых значений см. методические указания к лабораторной работе № 1.) Это необходимо учитывать, чтобы при выводе отдельные элементы не сливались друг с другом. Для разделения значений используются пробелы, разнесение информации по разным строкам и т.д.

Таблица 6.1

Основные функции ввода/вывода для стандартных потоков

Прототипы функций

Действия

int getchar (void)

Осуществляет ввод одного символа, возвращает введенный символ. При достижении конца файла или ошибке ввода возвращает целую константу EOF

int putchar (int c)

Выводит один символ, возвращает только что выведенный символ

int getc (FILE *stream)

Указатель на поток имеет предопределенное значение stdin. Ввод одного символа, возвращает введенный символ. При достижении конца файла или ошибке ввода возвращает EOF. Использует буфер ввода.

int putc ( int c, FILE *stream)

Указатель на поток имеет предопределенное значение stdout. Вывод одного символа, возвращает выведенный символ. Использует буфер вывода.

Продолжение Таблицы 6.1

char * gets ( char * s)

Ввод строки символов, завершенной символом ‘\n’, который появлятся при нажатии на Enter. Возвращает указатель на массив символов, куда производился ввод. Если произошла ошибка, возвращает NULL.

int puts ( char * s)

Вывод строки символов, завершенной символом ‘\0’. Возвращает последний выведенный символ - '\n’, куда производился ввод. Если произошла ошибка, возвращает EOF

int scanf (форматная_строка, список _аргументов)

Читает последовательности символов из входного потока и интерпретирует их в соответствии с форматной строкой, затем преобразует во внутреннее представление и присваивает переменным из списка аргументов. Ввод заканчивается, если исчерпана форматная строка. Возвращает количество введенных переменных, при достижении конца файла EOF, а при ошибке -1 .

Продолжение Таблицы 6.1

int printf (форматная_строка, список _аргументов)

Преобразует данные из списка аргументов из внутреннего представления в символьный вид в соответствии с форматной строкой и выводит их в поток. Возвращает число выведенных символов, в случае ошибки – отрицательное число.