Метод_материалы / Учебники / Программирование_С
.pdfstruct Person
{
char * name; int age;
};
Покажем обязательные синтаксические конструкции в прототипах функций, которые работают со структурами:
1) |
параметр функции — объект структурного типа: |
|
|
void |
F1 (struct Person Имя_объекта); |
2) |
параметр функции — указатель на объект структурного типа: |
|
|
void |
F2 (struct Person * Имя_объекта); |
3) |
функция возвращает структуру: |
|
|
struct Person F3 (Список_параметров); |
|
4)функция возвращает указатель на структуру: struct Person * F4 (Список_параметров) ;
Механизмы передачи параметров и возвращения значения функцией подробно рассмотрены в первой главе.
1.3.7.Ввод-вывод данных в С
Вязыке С нет средств ввода-вывода. Этим обеспечивается аппаратная независимость языка. Ввод-вывод реализуется посредством библиотек стандартных функций языка С (стандарт ANSI C). Библиотека stdio.h содержит средства ввода-вывода (обмена с устройствами), в том числе с файлами на диске.
Сточки зрения языка нет разницы, с устройством или файлом происходит обмен. Существует три уровня ввода-вывода:
•верхнего уровня (потоковый),
•записями (низкоуровневый),
•для консоли и портов. это системно-зависимый обмен.
Вданном пособии мы рассмотрим только работу с файлами.
1.3.7.1. Файлы
Определение. Файл — именованная область внешней памяти, в которой содержится некоторая информация. Например, тексты программ хранятся в виде текстовых файлов. Файлы также могут хранить данные, а программы могут обращаться к файлам данных для чтения информации или записи.
Файлы удобно использовать, когда программа обрабатывает большой объем информации, или когда данные должны храниться на внешних устройствах, или когда данные могут быть исходными для нескольких программ обработки.
Программы, которые работают с данными, хранящимися в файле, чаще всего, временно размещают их в оперативной памяти. Этот прием позволяет увеличить скорость обработки данных и снизить сложность программ. Данные из файлов,
111
будучи прочитаны программой, становятся значениями объектов программы, например, массивами, матрицами и пр.
1.3.7.2. Типы файлов
По механизму хранения данных и обращения к ним файлы разделяются на файлы последовательного доступа и файлы прямого доступа.
Файлами последовательного доступа являются текстовые файлы. Такие файлы подготавливаются в текстовом редакторе и хранят данные в символьном представлении. Их можно легко просматривать и редактировать
Файл прямого доступа — это двоичный файл. Хранит данные одного типа, не обязательно базового. Каждое данное хранится во внутреннем представлении, размер определен типом данного. Чтобы получить какое-нибудь данное, можно переместить указатель файла непосредственно на это данное, и выполнить операцию обмена.
1.3.7.3. Использование файлов
1.3.7.4.1. Объявление файловой переменной
Для объявления логического имени файла используется тип FILE, описание которого находится в библиотеке stdio.h. Это структура данных, которая хранит данные о файле, такие как размер буфера и прочие.
Объявление файла: FILE *имя_файла;
//имя_файла: имя переменной, связаннойс файлом
//указатель на стандартную структуру данных FILE
Это объявление аналогично объявлению обычных переменных, так же как:
int |
*a; |
|
FILE |
*имя; |
|
Например: |
|
|
FILE |
*in, *out; |
// файл in для ввода, out для вывода |
FILE |
*my_file; *my_other_file; |
|
Этот указатель можно назвать логическим именем файла, под которым файл будет известен программе, и будет использоваться во всех последующих операциях.
1.3.7.4.2. Открытие файла Смысл этой операции в том, чтобы связать логическое имя файла с файлом,
физически существующим на диске. Логическому имени файла присваивается значение, возвращаемое функцией fopen. В случае ошибки открытия файла fopen возвращает NULL. Функция имеет два параметра, оба строкового типа.
Формат обращения к функции:
имя_файла = fopen("имя_физического_файла", "режим_открытия_файла"); например:
in = fopen("input.txt", "r"); out = fopen("output.txt", "wb");
112
1.3.7.4.3. Ошибки открытия файла Существуют некоторые обычные ошибки открытия файла, например, файл не
найден, диск заполнен, недостаточно динамической памяти для выполнения операции и прочие. При любой ошибке открытия файла fopen возвращает NULL. Это используется для того, чтобы обрабатывать возможные ошибки вводавывода. В случае возникновения одной из штатных ошибок ввода-вывода, ее код errno распознает и обрабатывает функция perror:
FILE *my_file;
…
if ((my_file = fopen("f.txt","wt")) = = NULL)// текстовый для записи
{
perror ("Ошибка открытия файла");
//если есть ошибка, ее код = errno
//perror анализирует номер ошибки
//и выводит поясняющий текст
} exit();
Прототип функции perror находится в stdio.h. Там же определена переменная int errno. Ею пользуются многие функции С, в том числе, функции ввода-вывода. fopen, обнаружив ошибку, заносит ее код в переменную errno. Функция perror выводит на экран текстовую строку (свой аргумент), затем двоеточие, пробел и сообщение об ошибке, содержимое и формат которого определены реализацией.
Если файл не найден, а ошибка не анализируется, поток ввода перенаправляется на стандартный поток ввода-вывода (консоль), с попыткой чтения из буфера обмена.
1.2.7.4.4. Закрытие файла Закрытие файла — это высвобождение логического имени файла.
Используется, чтобы отвязать логическое имя от физического файла. Логическое имя не перестает существовать, и может быть использовано повторно для других целей, например, для изменения режима работы с файлом.
Синтаксис:
fclose (имя_файла);
Пример: fclose (in); fclose (out);
При завершении работы программы все открытые в ней файлы будут закрыты, даже если операция закрытия не была выполнена программистом. Если файл закрывает программист, то все данные из буфера выводятся в файл перед его закрытием. Если же файл закрывается при завершении программы, то некоторые данные могут быть потеряны вследствие механизма буферизации обмена.
1.2.7.4.5. Конец файла
Признак конца файла (end-of-file) присутствует в конце файла всегда. Чтобы его распознать в потоке, используется макроопределение feof. С его помощью можно проверить, найден ли в потоке признак конца файла (end-of-file).
114
Объявление:
int feof (FILE *stream);
Как видим, тип возвращаемого значения int, следовательно, данная функция возвращает целое значение (точнее, логическое). feof проверяет данный поток на наличие end-of-file в текущем положении указателя потока. Возвращаемое значение отлично от нуля, если при выполнении последнего оператора ввода для потока найден end-of-file, и равно нулю, если end-of-file не обнаружен.
1.2.7.5. Чтение и запись для текстовых файлов
Все функции ввода-вывода, которые были рассмотрены ранее, работают по принципам потокового ввода-вывода, используя стандартные текстовые потоки stdin, stdout, srderr.
Для символов: |
|
int getc () |
// читает символ (int) c клавиатуры stdin |
int putc (int C) |
// выводит символ C на экран stdout |
Для строк: |
|
char *gets (char *S) |
// читает строку S (char *) c клавиатуры stdin |
int puts (char *S) |
// выводит строку S на экран stdout |
Для форматированных данных: |
|
int scanf (const char* format,…) |
// читает по форматному вводу из stdin |
int printf (const char* format,…) |
// выполняет форматный вывод в stdout |
При работе с файлами для текстовых файлов используются другие функции библиотеки <stdio.h>, отличительным признаком которых является буква f в начале имени функции. Каждая из них имеет, по сравнению с вышеперечисленными функциями, дополнительный параметр — это логическое имя файла, для которого выполняется данная операция. Остальные механизмы остаются неизменными.
При открытии файлов для ввода и вывода перехват возможной ошибки выполняет условный оператор. Функция perror возвращает сообщение об ошибке, а fprintf переназначает вывод строки сообщения об ошибке в стандартный поток stderr.
Пример. Посимвольное копирование данных из одного файла в другой.
#include <stdio.h>
#include <stdlib.h> void main (void)
{
FILE *in, *out;
// открыть файлы
if ((in = fopen ("prim_in.txt", "rt")) == NULL)
{
perror ("Ошибка открытия файла для ввода.");
fprintf (stderr, "Ошибка открытия файла для ввода.\n"); return; // программа завершит работу
115
}
if ((out = fopen("prim_out.txt", "wt")) == NULL)
{
fprintf (stderr, "Ошибка открытия файла для вывода.\n"); return; // программа завершит работу
}
//циклом копирования управляет макроопределение feof
//условие выхода «пока входной файл не закончился» while ( !feof(in) )
fputc (fgetc (in), out); // вВыводит в out символ, введенный fgetc(in) fclose(in);
fclose(out);
}
1.2.7.6. Чтение и запись для бинарных файлов
Двоичный файл хранит информацию в плотном упакованном двоичном представлении. Данные, хранящиеся в таком файле, могут быть произвольного типа, не обязательно базового. Все они имеют одинаковый размер, определенный размером типа данного, пересылаемого в файл. В качестве аргумента указывается void область памяти, обмен происходит сплошным потоком байт. Для обмена с такими файлами используются те же приемы, кроме операций ввода-вывода. Этот обмен не форматированный, поэтому используются функции fread и fwrite, прототипы которых:
size_t fread (void *buf, size_t size, size_t count, FILE *stream)
Функция прочитывает count элементов размером size байт каждый в область памяти, адрес которой определен указателем buf, из файла, определенного указателем stream. Возвращает количество прочитанных элементов, которое может быть меньше, чем count, если произошла ошибка ввода или встречен конец файла.
size_t fwrite (const void *ptr, size_t size, size_t count, FILE *stream)
Функция записывает count элементов размером size байт каждый из области памяти, адрес которой определен указателем buf, в файл, определенный указателем stream. Возвращает количество записанных элементов.
Упрощенно можно записать синтаксис этих функций так:
fread (Адрес_области_ввода, Размер_объекта, Количество_объектов, Имя_файла);
fwrite (Адрес_области_вывода, Размер_объекта, Количество_объектов, Имя_файла);
Например, для обмена двумерного массива данных типа float размером 10*10, можно использовать одно обращение к функции, в результате которого в поток будут переправлены 10*10*4 = 400 байт:
float |
a[10][10]; |
.. |
|
fread (a, sizeof (float), 10*10, in);
116
fwrite (a, sizeof (float), 10*10, out);
1.3.8.Конструкции псевдокода
Вэтом пособии не все тексты программ приведены на языке C. Значительная часть алгоритмов записана на уровне псевдокода, который представляет собой некое обобщение кодов различных языков программирования. В этом разделе приведены наиболее часто используемые в пособии конструкции псевдокода.
1.3.8.1.Ветвления — альтернатива и выбор
Альтернатива: if (условие)
действие;
или
if (условие) действие1;
else
действие2;
Выбор:
switch ( переменная)
{
case <константа> : < предложение >; [break;]
…
…
default : <предложение>;
}
1. 3.8.2. Блок
{
действие1; действие2;
…
действие n;
};
1. 3.8.3. Цикл с предусловием while (условие)
действие;
117
1. 3.8.4. Цикл с постусловием do
действие; while (условие);
1. 3.8.5. Цикл с параметром
for (условие для числа повторений) действие;
1. 3.8.6. Функции
Используется синтаксис языка C. В данном пособии псевдокод частично соответствует синтаксису языка C, однако полностью с ним не совпадает.
Условия для ветвлений и циклов мы будем записывать, не придерживаясь каких-либо правил, но в интуитивно понятной форме.
Наконец, заметим, что часто в приведенных ниже описаниях алгоритмов первый индекс массива есть 1 — хотя в языке C, разумеется, это 0. Предопределенные константы FALSE и TRUE — это соответственно 0 и –1, в соответствии с синтаксисом C.
118
2. СТРУКТУРЫ ДАННЫХ КАК ОСНОВА ДЛЯ РАЗРАБОТКИ ПРОГРАММ
2.1.Статические конструируемые типы данных
2.1.1.Стек (на основе массива)
Вэтой главе будет продолжен процесс конструирования новых структур данных из ранее построенных типов. Термин "статические" означает, что эти структуры размещаются в памяти фиксированного, заранее определенного размера (в отличие от изучаемых в главе 2.2 динамических структур).
Первой из таких структур является стек (stack), который можно себе представить в виде стакана, куда можно положить некий объект (на уже лежащие
встеке) и откуда можно достать объект (с самого верха). На рис. 2.1 проиллюстрирована работа со стеком, элементы которого имеют тип int. Сначала стек пуст, затем на его "дно" положен элемент 7, затем поочередно положено еще три элемента, затем верхний элемент взят из стека.
Основные функции:
•положить элемент,
•взять элемент.
Рис. 2.1. Стек на основе массива
Принцип очередности, реализованный в стеке, называют LIFO — Last In, First Out, то есть "первым зашел — последним вышел". Стрелкой на рис. 2.1 изображен так называемый указатель стека, который является индексом первой свободной ячейки стека (то есть ячейки над вершиной стека).
119
Стек как структура данных поддерживает только две функции для работы с ним: положить — Push ("протолкнуть") элемент и достать — Pop ("вытолкнуть") элемент. Однако для организации корректной работы со стеком нужно еще знать его состояние: не пуст ли он или не полон (в реальной ситуации размер стека ограничен). Поэтому можно ввести дополнительные функции.
Вспомогательные функции:
•стек_пуст: да/нет,
•стек_полон: да/нет.
Пример программы, реализующей основные функции стека на основе массива:
#include <stdio.h>
#include <conio.h>
# define ST_SIZE 10 |
// размер стека |
|
# define TRUE –1 |
|
|
# define FALSE 0 |
|
|
struct STACK { |
// структура стека |
|
int |
elem[ST_SIZE]; |
// массив элементов |
int |
top; |
// индекс вершины стека |
}; |
|
|
// прототипы функций стека |
|
|
void ClearStack( STACK * pt_st ); |
// очистка стека |
|
int StackIsFull( STACK * pt_st ); |
// проверка заполненности стека |
|
int StackIsEmpty( STACK * pt_st ) ; |
// проверка пустоты стека |
|
int Push( STACK * pt_st, int new_el ); |
// проталкивание элемента в стек |
|
int Pop( STACK * pt_st, int * new_el ); |
// выталкивание элемента из стека |
|
main() |
|
|
{ |
// создаем экземпляр стека |
|
STACK st_var; |
||
STACK * pt; |
// создаем указатель на стек |
|
int st_el; |
// целое число — элемент стека |
|
120
