Uploaded by Serge Pisarsky

5 op z

advertisement
Обмен данными в Cи
между оперативной и внешней памятью.
С точки зрения ОС минимальная единица
информации – файл.
Файл – именованная область памяти, которая
характеризуется:
- именем;
- типом (расширением);
- размером;
- временем создания (модификации).
В ОС с файлами
операции:
- создание;
- чтение;
- удаление;
- переименование;
- копирование.
допустимы
следующие
ОС умеет работать с файлом в целом.
Файл в Cи – это просто поток ввода-вывода и
идентифицируется указателем на переменную
базового типа FILE.
typedef struct
{
short
level;
/* уровень буфера ввода-вывода
(заполнен / пуст) */
unsigned
flags;
/* флаг статуса */
char
fd;
/* дескриптор файла */
unsigned char
hold;
/* символ который возвращается
в буфер */
short
bsize;
/* размер буфера в байтах */
unsigned char
*buffer;
/* указатель на начало буфера */
unsigned char
*curp;
/* текущий указатель в буфере */
unsigned
istemp;
/* индикатор временного файла */
short
token;
/* используется для проверки файла */
} FILE;
Различают два уровня ввода-вывода:
- физический;
- логический.
Физический уровень ввода-вывода обеспечивает
перемещение информации между внешней
памятью и буфером ввода-вывода.
На
логическом
уровне
ввода-вывода
перемещение информации выполняется между
буфером
ввода-вывода
и
переменными
программы.
1.txt
Оперативная память
Физический уровень
Буфер ввода-вывода
Логический уровень
char name[80]
…
В Cи можно работать с файлами 2-х типов:
- текстовыми (последовательность символов,
разбитая на строки символом ‘\n’);
- бинарными
(двоичными)
(внутреннее
представление этого файла – цепочка нулей и
единиц).
Текстовые файлы мобильны. Они содержат в
своих байтах коды символов, поэтому могут
быть прочитаны в любом текстовом редакторе.
У файлов различают 2 метода доступа:
- прямой;
- последовательный.
В последовательных файлах элемент с
номером n может быть обработан только после
обработки элементов с номером n-1 (системный
ввод и системный вывод).
В файлах прямого доступа любой элемент
доступен по своему «номеру».
Все
действия
поддерживаются
stdio.h.
связанные
с
файлами
стандартной
библиотекой
В stdio.h определены
(буферы, потоки):
- stdin – ввода;
- stdout – вывода;
- stderr – ошибки.
стандартные
файлы
Открытие файла:
fopen(p1,p2);
p1: строка = имя файла.
p2: строка = способ открытия файла:
“r’’ – для чтения (read);
“w’’ – для записи (write);
“a’’ – для добавления (append);
“r+w’’ (или “r+’’) - для чтения и записи;
“w+’’ – создать пустой файл для чтения и записи;
“a+’’ – для чтения и добавления данных.
В языке Си нет функции создания файла. Эту
работу выполняет функция fopen().
“r”, “r+” – открытие существующего файла.
“w”,
“w+”,
“a”
или
“a+”
–
открытие
существующего, если он был, или создание
нового, если такого файла не было.
“a+’’ – все операции записи выполняются в
конец файла, позицию для чтения можно
изменить с помощью FSEEK).
При открытии “w” или “w+” существующего
файла: запись в начало (создание нового с
удалением старой версии).
Функция fopen() возвращает:
- указатель на файл, с помощью которого
дальше можно ссылаться на этот файл.
- NULL, если файл не открыт.
FILE *input, *output;
input= fopen(“in.txt”, “r”);
if (input==NULL) printf(“файл не открыт\n“);
output= fopen(“out.txt”, “w”);
if (output==NULL) printf(“файл не открыт\n“);
!!! Возвращаемые значения следует проверять!
После работы с файлом связь между ОС и
программой следует разорвать.
Закрытие файла:
fclose(<указатель на файл>);
Возвращаемое значение:
0 – в случае успеха;
-1 – иначе.
!!! Обязательно нужно закрывать файлы,
открытые на запись, т.к. в этот момент
происходит перемещение последней порции
информации из буфера ввода-вывода во
внешнюю память.
Функции форматированного ввода/вывода :
fscanf() – ввод из файла.
fprintf() – вывод в файл.
Количество аргументов функций переменное:
- указатель на файл;
строка
формата
со
спецификацией
преобразования данных;
- сохраняемые (загружаемые) значения.
Число и типы элементов списка ввода-вывода
должны соответствовать спецификациям в
командной строке.
Формат – это некая инструкция преобразования
данных:
%[<выравнивание>][+| ] [<ширина>]
[<.точность>] <модификатор типа>
Прочитать строку из файла:
char str[100];
fscanf (input,”%[A-z .,;!]”,str);
Функции fscanf() и fprintf() применимы для
текстовых файлов.
Неформатированный ввод-вывод
Используется для последовательных файлов,
но только для символов и строк.
Ввод символа:
int fgetc(<указатель на файл>);
int getc(<указатель на файл>);
Из файла считывается один символ из потока и
переводится в int.
Если ошибка или конец файла - EOF.
Вывод символа:
int fputc(char ch, <указатель на файл>);
int putc(char ch, <указатель на файл>);
Символ ch записывается в выходной поток.
Если успешно, то возвращается значение ch
типа int, иначе – EOF.
Пример 76:
char c;
FILE *input, *output;
c = getc(input);
putc(c, output);
Ввод-вывод строки:
char* fgets(a1, a2, a3);
int fputs(a1, a3);
a1 – указывает на место ОЗУ, куда строка
считывается с помощью fgets или откуда она
записывается с помощью fputs.
a3 – указывает на файл, который читает
функция fgets или файл, в который пишет fputs.
a2 – максимальная длина считываемой строки.
Функция
после:
fgets
прекращает
ввод
символов
- после считывания символа новой строки
(эскейп-символ ‘\n’);
- после считывания символа конца файла
(EOF);
- после ввода строки длиной a2-1.
fgets сохраняет введенный символ ‘\n’.
fputs не ставит символ ‘\0’, записывает в файл
строку из a1.
Функция
char*
gets(s)
считывает
из
стандартного файла stdin строку в переменную
s.
Если строка завершается символом ‘\n’, то он
отбрасывается.
Функция int puts(s) выводит в стандартный файл
stdout строку s.
Пример 77. Перезапись содержимого одного
файла в другой:
#define LSTR 80
…
FILE *input, *output;
char string[LSTR+1];
…
if ((input = fopen(“bx”, “r”)) == NULL)
{
printf(“Файл bx не открыт!\n”);
exit(-1);
}
if ((output = fopen(“bix”, “w”)) == NULL)
{
printf(“Файл bix не открыт!\n”);
exit(-1);
}
while (fgets (string, LSTR, input) != EOF)
fputs (string, output);
…
fclose(input);
fclose(output);
Функция ungetc
int ungetc (char ch, <указатель на файл>);
Символ ch «выталкивается» во входной поток
=> будет считан первым следующей операцией
ввода.
Если нет ошибки => возращается значение ch
типа int. Если ошибка, то EOF.
Ввод-вывод и стандартные файлы
связан с клавиатурой
Не
требуют
stdin
определения в
stdout
программе
stderr
пользователя
связаны с терминалом
Стандартные файлы можно переназначить на
другие устройства – внешние. Это выполняется
в командной строке при вызове исполнимого
файла:
- stdin: связан с клавиатурой
my_prog < input_file
После этого все функции ввода программы,
которые вводили из stdin, будут вводить из
указанного файла.
- stdout:
my_prog > output_file
Все функции вывода программы, которые
работали с stdout будут выводить в output_file
(файл пользователя/принтер).
- stderr: не
терминал.
перенаправляется,
scanf – ввод со stdin
printf – вывод в stdout
всегда
–
Отличие от fscanf,
fprintf:
нет
имени
указателя на файл
Ввод-вывод с моделированием потока в
памяти (ОЗУ)
sscanf (char* s, char* format, <аргументы>);
sprintf (char* s, char* format, <аргументы>);
Аналоги fscanf, fprintf, но поток ввода-вывода
заменен строкой s.
#define N 100
#define MARKING "------------------------------------------------"
struct input
{
char district[20];
float year1,
year2;
};
int main()
{
int n = 0;
setlocale(LC_ALL, "Rus");
FILE* f;
fopen_s(&f, "input.txt", "r");
if (f == NULL)
{
printf("Файл базы данных \"input.txt\" не
найден.");
_getch();
return 1;
}
struct input *a = new input[N];
printf("%s\n| Запас газоносных районов
СССР в процентах |\n%s\n|
1940
|
1958
MARKING);
Район
|
|\n%s\n", MARKING, MARKING,
while (fscanf(f, "%s %f %f", a[n].district, &a[n].year1,
&a[n].year2) != EOF)
{
if (a[n].year1 <= 100 && a[n].year2 <= 100)
{
printf("| %-19s |%10.1f |%10.1f |\n",
a[n].district, a[n].year1, a[n].year2);
printf("%s\n", MARKING);
n++;
}
else
{
printf("!!! Далее таблица содержит
ошибочные значения: процент запаса отдельного
района от общего запаса СССР не может быть
больше 100 \n\n");
break;
}
}
fclose(f);
if (n < 2)
{
printf("Количество районов должно быть
не меньше 2. \nЗаполните базу данных и
повторите");
_getch();
}
fopen_s(&f, "output.txt", "w");
printf("Районы, процент запасов которых в 1958 не
снизился по отношению к 1940:\n");
fprintf(f, "Районы, процент запасов которых в 1958
не снизился по отношению к 1940:\n");
for (int i = 0; i < n; i++)
{
if (a[i].year1 <= a[i].year2)
{
printf("%s, с ростом показателей в
%0.1f\n", a[i].district, a[i].year2 - a[i].year1);
fprintf(f, "%s, с ростом показателей в
%0.1f\n", a[i].district, a[i].year2 - a[i].year1);
}
}
Файлы прямого доступа и бинарные файлы
Особенности файла в Си – бестиповый.
Единственный признак – свойство потока:
текстовый или двоичный.
Тип
файла
указывается
при
открытии
(создании) с помощью fopen() добавлением к
способу открытия буквы ‘t’ для текстовых
файлов и ‘b’ для бинарных.
FILE *input, *output;
input = fopen(“in.txt”, “r+t”);
output = fopen(“out.txt”, “wb”);
Открытие/закрытие файлов прямого доступа
выполняется теми же функциями, что и для
файлов последовательного доступа.
Функции ввода \ вывода
Запись
и
чтение
в
бинарный
файл
осуществляется с помощью функций fread() и
fwrite().
size_t fread (void *ptr, size_t size, size_t
FILE *stream);
n,
Читает из файла stream в массив ptr n
объектов-записей размера size.
Возвращает
количество
действительно
прочитанных
блоков
заданного
размера
(результат может быть отличен от n).
Состояние потока после завершения операции
чтения
необходимо
проверять
другими
функциями – feof() и ferror().
size_t fwrite(const void *ptr, size_t size, size_t
n, FILE *stream);
Записывает в файл stream из массива ptr n
объектов-записей размера size.
Возвращает
количество
действительно
записанных
блоков
заданного
размера
(результат может быть отличен от n)
Пример 78. Запись несимвольных данных в
файл и последующее их чтение.
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
double d = 12.23;
int i = 101;
long l = 123023L;
if((fp=fopen("test","wb+"))==NULL)
{
printf("Ошибка при открытии файла.\n");
exit(-1);
}
fwrite(&d, sizeof(double), 1, fp);
fwrite(&i, sizeof(int), 1, fp);
fwrite(&l, sizeof(long), 1, fp);
rewind(fp); // fp в начальное положение
fread(&d, sizeof(double), 1, fp);
fread(&i, sizeof(int), 1, fp);
fread(&l, sizeof(long), 1, fp);
printf("%f %d %ld", d, i, l);
fclose(fp);
return 0;
}
void rewind(FILE *stream);
Устанавливает указатель в начало файла.
int ftell(FILE *stream);
Возвращает текущую позицию stream. При
ошибке возвращает –1L.
fseek() и произвольный доступ
Можно выполнять операции произвольного
чтения и
записи с
помощью
fseek(),
устанавливающей текущую файловую позицию.
int fseek(FILE *fp, long offset, int wherefrom);
fp – это указатель на файл, возвращенный
fopen().
Аргументы offset и wherefrom зависят от того,
текстовый файл или двоичный.
Для двоичного файла:
offset – число байтов смещения от точки,
определяемой wherefrom:
- SEEK_SET – начало файла;
- SEEK_CUR – текущая позиция;
- SEEK_END – конец файл.
Двоичные файлы можно читать и в обратном
порядке.
Для текстового файла:
offset – может быть либо =0, либо результату,
который возвращает функция ftell.
wherefrom – SEEK_SET.
// Открыть существующий как двоичный для
чтения и записи
FILE *fd;
fd = fopen("a.dat","rb+wb");
// Создать новый как двоичный для записи и
чтения
fd = fopen("a.dat","wb+");
Пример 79. Получение клиента из списка по
номеру.
struct addr
{
char name[40], street[40], city[40];
char state[3];
char zip[10];
} info;
void find(long int client_num)
{
FILE *fp;
if((fp=fopen("mail", "rb")) == NULL)
{
printf("Не удается открыть файл.\n");
exit(1);
}
fseek(fp, client_num*sizeof(struct addr),
SEEK_SET);
fread(&info, sizeof(struct addr), 1, fp);
fclose(fp);
}
Функции обработки ошибок
int ferror(FILE *stream);
Возвращает 0, если нет ошибок, и число,
отличное от 0, если ошибки есть.
Вызывается после функции, которая может
вызвать ошибку, например, fread() и fwrite().
void clearerr(FILE *stream);
Очищает состояние признака ошибки в stream.
int feof(FILE *stream);
Возвращает EOF если конец файла, иначе 0.
Особенности обработки файлов прямого
доступа
Последовательный файл считывают с помощью
цикла с неизвестным числом повторений до
наступления условия «прочитан конец файла».
У
файлов
прямого
доступа
следует
использовать цикл с известным числом
повторений для обработки такого файла.
Можно получить размер файла:
1) С помощью fseek установить
позицию на конец файла:
fseek(stream, 0L, SEEK_END);
текущую
2) Запросить значение текущего указателя
(размер файла) с помощью ftell:
size = ftell(stream);
Далее в цикле позиционировать текущую
позицию файла с помощью fseek, пересчитывая
каждый раз величину смещения – второй
аргумент (позиционировать от начала).
Пример сортировки
struct Record
{
//структуры данных
char country[20]; //Название страны
float el55;
//Произведено
электроэнергии в 1955
float el58;
//в 1958
} a, b;
int cmp(const void * a, const void * b) {
return strcmp(((Record*)a)->country,
((Record*)b)->country);
int i=0, j, pos,n;
if ((fv = fopen(name,"rb+")) == NULL)
//Открытие файла для чтения
{
printf("Error open file!\n");
exit(-1);
}
fseek(fv,0,SEEK_END);
n = ftell(fv)/sizeof(a);
//Определение количества записей в файле
fseek(fv,0,SEEK_SET);
for (i=0; i<n; i++) {
fread(&rec[i],sizeof(Record),1,fv);
puts(rec[i].country);
}
qsort(rec,n,sizeof(Record),cmp());
fseek(fv,0,SEEK_SET);
for (i=0; i<n; i++) {
puts(rec[i].country);
fwrite(&rec[i],sizeof(Record),1,fv);
}
free(rec);
fclose(fv);
void qsort ( void * first, size_t number, size_t
size, int ( * comparator )
first указатель на 1й элемент сортируемого
массива.
number
количество
элементов
в
сортируемом массиве, на который ссылается
указатель first.
Size размер одного элемента массива в
байтах.
Comparator Функция, которая сравнивает
два элемента.
ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ
– это данные, размер которых может
увеличиваться
или
уменьшаться
при
исполнении программы.
Связанный список – это набор элементов
данных, в любом месте которых можно
производить вставки и удаления.
startPtr – указатель на голову (первый элемент)
списка
Информационная часть
17
29
…
Ссылочная часть
93
Связанный список – это линейный набор
ссылающихся на себя структур, называемых
узлами, и объединённых указателем-связкой.
Доступ к связанному списку обеспечивается
указателем на первый элемент списка (голову).
Каждый узел списка
необходимости.
создаётся
по
мере
Узел может содержать данные любого типа,
включая другие структуры.
Список – рекурсивная структура данных,
поскольку каждый элемент указывает на такой
же элемент.
Хвост
Хвост
Голова
Голова
Голова
1
10
…
10
Хвост
93
Элемент
списка
–
это
структура,
ссылающаяся на себя, т.е. одним из элементов
структуры
является
указатель,
который
ссылается на элемент того же типа.
struct node
{
int data;
struct node *next;
};
next содержит адрес следующего элемента
списка.
Поле next последнего элемента списка должно
быть равно NULL.
Создание и использование динамических
структур данных требует возможности выделять
и удалять память во время работы программы.
Выделение памяти:
void* malloc(size_t);
Освобождение памяти:
void free(void* memblock);
struct node *p = malloc(sizeof(struct node));
if (p==NULL)
printf(“Память не выделена!\n”);
…
free(p);
!!! Следует всегда освобождать память, которая
перестала использоваться, во избежание утечки
памяти.
Операции над списками
1. Добавление элемента в конец списка
{3, 10, -8} + 20 = {3, 10, -8, 20}
(5)
(1)
-8
10
3
(2)
(3)
(7)
(6)
20
(4)
2. Добавление элемента в начало списка
1 + {3, 10, -8} = {1, 3, 10, -8}
(3)
(5)
(1)
(4)
-8
10
1
3
(2)
3. Удаление элемента в конце списка
{3, 10, -8, 20} - 20 = {3, 10, -8}
(1)
(3)
(2)
3
(5)
10
-8
(4)
20
4. Удаление элемента в начале списка
{1, 3, 10, -8} - 1 = {3, 10, -8}
(3)
(2)
(1)
1
3
(4)
10
-8
Преимущества списка перед массивом:
- использует ровно столько памяти сколько
нужно в текущий момент;
- переполнение происходит только, если не
хватает ресурсов ОС.
Массив проецируется в ОП на вектор =>
необходимо изначально выделить непрерывную
область памяти заранее известного размера.
«Довыделение» памяти (расширение массива)
– невозможно.
Список – связная структура данных, элементы
списка расположены в памяти в произвольном
порядке
(связь
между
элементами
обеспечивается за счет указателя на следующий
элемент).
Расширение списка – динамически выделяется
память
под
новый
элемент
списка,
добавляемый элемент связывается с остальной
частью списка.
Недостатки списка по сравнению с массивом:
- так как элементы списка в памяти обычно
расположены в разброс, то доступ к элементу
списка осуществляется медленнее, чем к
элементу массива.
Обратиться к элементу массива – рассчитать
адрес этого элемента в памяти (адрес начала
массива + смещение элемента относительно
начала массива) – быстро.
Обратиться к элементу списка – выполнить
последовательный проход по элементам списка
(медленно).
Стеки – это списки, добавление и удаление
элементов в которых выполняются только с
начала (из головы).
Для стеков реализован принцип LIFO: Last Input
First Output.
Очереди – это списки, добавление элементов в
которых выполняется в конец (в хвост), а
удаление элементов - с начала (из головы).
Для очередей реализован принцип FIFO: First
Input First Output.
Реализация стека с помощью связанного
списка
Базовые операции:
- push (поместить элемент на верхушку стека);
- pop (извлечь элемент из верхушки стека и
получить его значение – выборка и
удаление);
- peek
(считать
значение
элемента,
расположенного на верхушке стека – без
извлечения).
Операция
push
Состояние стека
До операции
После операции
27
17
7
Порядок: 7, 17, 27
37
37
27
17
7
pop
37
27
17
7
37
37
27
17
7
peek
37
27
17
7
37
37
27
17
7
Пример 80. Реализация стека на языке Си
Будем использовать технологию раздельной
компиляции, программный комплекс будет
состоять из трех файлов:
- описание типа данных stack и прототипы
функций для работы со стеком (заголовочный
файл);
- определение функций для работы со стеком
(файл реализации);
- главный модуль программы, содержащий
функцию main (файл реализации).
/* Файл stack.h */
#define STACK struct stack
STACK
{
int info;
/* информационная часть */
STACK *next;
/* ccылочная часть */
}
extern void push(STACK **s, int item);
extern int pop(STACK **s, int *error);
extern int peek(STACK **s, int *error);
/* Файл stack.c */
#include <alloc.h>
#include “stack.h”
void push(STACK **s, int item)
/*(1)*/
{
STACK *new_item;
new_item = (STACK*)malloc(sizeof(STACK)); /*(2)*/
new_item -> info = item;
/*(3)*/
new_item ->next = *s;
/*(4)*/
*s = new_item;
/*(5)*/
}
int pop(STACK **s, int *error)
/* error = 1, если неуспех, иначе error = 0 */
{
STACK *old_item = *s;
int old_info = 0;
*error = 1;
if (*s)
/* Стек не пустой */
{
old_info = old_item -> info;
*s = (*s) -> next;
free(old_item);
*error = 0;
}
return old_info;
}
int peek(STACK **s, int *error)
{
if (*s)
/* Стек не пустой */
{
*error = 0;
return (*s) -> info;
}
else
/* Стек пуст */
{
*error = 1;
return 0;
}
}
/* файл program.c*/
#include <stdio.h>
#include "stack.h"
STACK *st = NULL;
/* st
NULL */
int main()
{
int err;
push(&st, -8);
/* st
*/
-8
printf("%d\n", peek(&st, &err)); /*-8 */ /* err = 0 */
push(&st, 10);
/* st
*/
10
-8
printf("%d\n", peek(&st, &err)); /* 10 */ /* err = 0 */
3
10
push(&st, 3); /* st
*/
-8
printf("%d\n", peek(&st, &err)); /* 3 */ /* err = 0 */
printf("%d\n", pop(&st, &err)); /* 3 */ /* err = 0 */
10
-8
/* st
*/
printf("%d\n", pop(&st, &err)); /* 10 */ /* err = 0 */
/* st
*/
-8
printf("%d\n", pop(&st, &err)); /* -8 */ /* err = 0 */
/* st
NULL */
printf("%d\n", pop(&st, &err)); /* 0 */ /* err = 1 */
return 0;
}
Реализация очереди с помощью связанного
списка
Базовые операции:
- insert (добавить в конец очереди);
- take_off (извлечь из начала очереди).
Операция
insert
take_off
Состояние очереди
До операции
После операции
3
10
3
10
-8
начало
конец
-8
3
3
10
-8
3
10
-8
Пример 81. Реализация очереди на языке Си
/* файл queue.h */
#define QUEUE struct queue
QUEUE
{
int info;
QUEUE *next;
};
extern void insert(QUEUE **q,int item);
extern int take_off(QUEUE **q,int *err);
/* файл queue.c */
#include <alloc.h>
#include <stdio.h>
#include "queue.h"
void insert(QUEUE **q, int item)
{
QUEUE *current=*q;
QUEUE *previous=NULL;
QUEUE *new_node;
while(current)
{
previous=current;
current=current->next;
}
new_node=(QUEUE*)malloc(sizeof(QUEUE));
new_node->info=item;
/* previous – указатель на последний элемент */
if (previous) /* Если очередь не пуста */
{
new_node->next=previous->next;
previous->next=new_node;
}
else
/* Иначе */
{
*q=new_node;
(*q)->next=NULL;
}
}
int take_off(QUEUE **q, int *err)
{
int value=0;
QUEUE *old_header=*q;
*err=1;
if (*q)
{
value=old_header->info ;
*q=(*q)->next;
free(old_header);
*err=0;
}
return value;
}
/* program.c */
#include <stdio.h>
#include "queue.h"
QUEUE *q=NULL;
/* q
NULL */
/* q
/* q
/* q
3
3
3
int main()
{
int err;
insert(&q,3);
insert(&q,10);
insert(&q,-8);
10
10
-8
*/
*/
*/
printf("%d\n",take_off(&q,&err)); /* 3 */ /*err = 0 */
/* q
*/
10
-8
printf("%d\n",take_off(&q,&err)); /* 10 */ /*err = 0 */
/* q
*/
-8
printf("%d\n",take_off(&q,&err)); /* -8 */ /*err = 0 */
/* q
NULL
*/
printf("%d\n",take_off(&q,&err)); /* 0 */ /*err = 1 */
return 0;
}
Вариант из методички
#include "stdafx.h"
#include "stdlib.h"
#include "conio.h"
struct list
{
int value;
struct list *next;
} list;
void add(list **head, int item)
{
list *new_item = (list*)malloc(sizeof(list));
new_item->value = item;
new_item->next = *head;
*head = new_item;
}
void print(const list* head)
{
while (head)
{
printf("%d->", head->value);
head = head->next;
}
printf("NULL");
}
void dubl(list *head)
{
if (!head)
return;
list *new_item, *temp = head;
while (temp)
{
new_item = (list*)malloc(sizeof(list));
new_item->value = temp->value;
new_item->next = temp->next;
temp->next = new_item;
temp = temp->next->next;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
list *head = NULL;
int key, item;
printf(«введите список.");
do
{
printf("\n\nэлемент = ");
while (scanf_s("%d", &item) != 1)
{
printf(«Ошибка ввода. повторите\n");
printf("Element = ");
fflush(stdin);
}
add(&head, item);
do
{
printf("\nхотите ввести еще раз? (y/n): ");
key = _getch();
_putch(key);
}
while (key != 'Y' && key != 'y' && key != 'N' && key != 'n')
}
while (key != 'N' && key != 'n');
printf("\n\n исходный список:\n");
print(head);
dubl(head);
printf("\n\n полученный список:\n");
print(head);
return 0;
Vvedite spisok.
Element = 5
Eshe vvesti? (y/n): y
Element = 3
Eshe vvesti? (y/n): y
Element = 8
Eshe vvesti? (y/n): y
Element = 10
Eshe vvesti? (y/n): n
Ishodniy spisok:
10->8->3->5->NULL
Polu4ennuy spisok:
10->10->8->8->3->3->5->5->NULL
Препроцессор Си
Препроцессор (preprocessor) – это программа,
которая выполняет предварительную обработку.
На вход компилятору подается
конструкций двух языков:
смесь
из
1) язык Си;
2) директив препроцессора.
Препроцессор обрабатывает директивы своего
языка и заменяет их инструкциями языка Си.
Поэтому на вход компилятору поступает только
текст на языке Си.
Директивы препроцессора
Язык препроцессора состоит из директив
(операторов) или команд:
- с первой позиции строки;
- начинаются с «#»;
- после «#» - непосредственно наименование
директивы;
- разделитель частей директивы – пробел;
- символ продолжения «\» используется для
переноса части директивы новую строку:
#include \
<conio.h>
- не заканчивается «;».
Виды директив:
- включения файлов;
- макроподстановка;
- управляемые компиляцией.
Включение файлов
Объединение нескольких исходных файлов в
один:
#include “<имя_файла>“ – поиск
выполняется в текущей директории;
имени
#include
<имя_файла>
–
поиск
имени
выполняется в сист. директории заголовочных
файлов.
Семантика:
- замена строки директивы текстом из
включаемого файла;
- директивы могут быть вложенными, но не
рекурсивно вложенными.
Макроподстановка
# define . . . . . . .
Два вида: простая и с аргументами.
Простая макроподстановка
#define <имя> <строка>
- <имя> - цепочка символов (заглавных букв);
- конец <имени> - пробел;
- <строка> - любая конструкция допустимая в Си.
Все
последующие
вхождения
<имени>
заменяется <строкой>, исключения:
1) если <имя> встречено внутри другой лексемы;
2) если <имя> встречается внутри символьной
или строковой константы.
Пример 88.
#define NULL 0
#define EOF (-1)
…
if (p==NULL)
x=y;
z=zNULL;
printf (“NULL\n”);
/* выполняется */
не
выполняется
Пример 89.
#define PROV (a>b && b>c && (a * b)!=0)
void main()
{
int a,b,c;
.......
if (PROV)
printf (b);
else
printf (c);
}
Макроподстановка с аргументами
#define <имя>(<имя_арг1>,...,<имя_аргn>) <строка>
Cинтаксис:
- Нельзя ставить пробел между <именем> и ( );
- Обычно в <строке> должны встретиться
<имена_арг>
Семантика:
- аналог функции.
- синонимы: псевдофункция, макро, макросы.
Могут быть вложенными, но не рекурсивно
вложенными.
Пример 90. Перевод в верхний регистр
заглавных букв
#define up(x) ((x)-”a”+”A”)
В
отличие
от
полноценных
функций
макроподстановки
с
аргументами
обрабатываются препроцессором.
Для полноценной функции настройка аппарата
формальных и фактических параметров, вызов
функции и возврат из нее результата в среднем
стоит порядка 60 машинных команд.
Побочные эффекты в макроподстановке
Если объявление макроподстановки выполнено
не аккуратно, то ее использование приводит к
побочным эффектам.
Побочными эффектами обладают процедуры и
функции: это тот случай, когда процедура или
функция изменяет и-или использует значения
внешних переменных.
2 вида побочных эффектов:
- периода лексического анализа;
- периода выполнения.
Побочные эффекты лексического анализа
Пример 91.
#define POW3(y) y*y*y
int a=3;
int b;
b=POW3(a+2);
/* a+2*a+2*a+2
b=3+2*3+2*3+2=17 */
2 способа исправить побочный эффект:
/* Способ 1 */
int POW3(int y)
{
return y*y*y;
}
/* Способ 2 */
#define POW3(y) ((y)*(y)*(y))
В первом случае мы имеем большие накладные
расходы при вызове функции.
Второй вариант – исправить макрос (каждое
вхождение аргумента в строке должно быть
заключено в круглые скобки и вся строка в
круглые скобки).
Побочные эффекты периода выполнения
Эти
ошибки
обусловлены
многократным
вычислением одних и тех же аргументов
макроса.
Пример 92.
#define max(x,y) ((x)<(y)?(y):(x))
int i=10, y;
y=max(i++,2);
Результат: i=12 и y=12.
Пример 93.
#define max(x,y) ((x)<(y)?(y):(x))
int c;
c=max(getchar(), ‘k’);
Функция getchar будет вызвана 2 раза, если
считанный символ >= ‘k’.
Директива #undef
Cинтаксис: #undef <имя>
Семантика: Отмена определения, связанного с
этим именем.
Пример 94.
#include <stdio.h>
#undef getchar
int getchar (void) {.....}
Условная компиляция
Директивы условной компиляции указывают
компилятору определенные фрагменты текста,
которые
должны
компилироваться
при
выполнении различных условий:
1)
2)
3)
4)
5)
6)
7)
#if;
#ifdef;
#ifndef;
#endif;
#elif;
#defined;
#else.
1) #if Условная подстановка фрагмента текста в
зависимости
от
значения
константного
выражения, записанного после #if;
2) #ifdef Условная подстановка
текста, если макрос определен;
фрагмента
3) #ifndef Условная подстановка фрагмента
текста, если макрос не определен;
4) #endif
Обозначение
конца
подстановки фрагмента текста;
условной
5) #elif Альтернатива для #ifdef или #ifndef после
которых записывается проверяемое условие
(константное выражение);
6) #defined Может использоваться вместе с #if,
она проверяет является ли имя именем макроса;
7) #else Альтернатива #if без дополнительных
условий.
Семантика:
- вычисляется константное целое выражение,
записанное в #if (оно не должно содержать ни
одного оператора sizeof или (тип) и
именованных констант);
- если выражение отлично от нуля, то в
компиляцию включаются все последующие
строки до #endif , или #elif , или #else;
- выражение
#defined
(<имя>),
которое
используется в #if или #elif есть 1, если <имя>
было определено в макросе.
Пример 95.
#if !defined (HDR)
#define HDR
.
.
/* Это определение HDR.h */
.
#endif
/* Исключаем повторное определение HDR */
Пример 96.
#ifndef HDR
#define HDR
.
.
/* Это определение hdr.h */
.
#endif
Пример 97.
/* Это пример цепочки проверенных систем для
выбора нужного заголовочного файла, для его
включения в программу */
#if SYSTEM == SYSV
#define HDR “sysv.h”
#elif SYSTEM == MSDOS
#define HDR “msdos.h”
#else
#define HDR “default.h”
#endif
#include HDR
Download