Работа с текстовыми файлами
Теги: Текстовые файлы, fopen, fclose, feof, setbuf, setvbuf, fflush, fgetc, fprintf, fscanf, fgets, буферизированный поток, небуферизированный поток.
Работа с текстовыми файлами
Р абота с текстовым файлом похожа работу с консолью: с помощью функций форматированного ввода мы сохраняем данные в файл, с помощью функций форматированного вывода считываем данные из файла. Есть множество нюансов, которые мы позже рассмотрим. Основные операции, которые необходимо проделать, это
- 1. Открыть файл, для того, чтобы к нему можно было обращаться. Соответственно, открывать можно для чтения, записи, чтения и записи, переписывания или записи в конец файла и т.п. Когда вы открываете файл, может также произойти куча ошибок – файла может не существовать, это может быть файл не того типа, у вас может не быть прав на работу с файлом и т.д. Всё это необходимо учитывать.
- 2. Непосредственно работа с файлом — запись и чтение. Здесь также нужно помнить, что мы работаем не с памятью с произвольным доступом, а с буферизированным потоком, что добавляет свою специфику.
- 3. Закрыть файл. Так как файл является внешним по отношению к программе ресурсом, то если его не закрыть, то он продолжит висеть в памяти, возможно, даже после закрытия программы (например, нельзя будет удалить открытый файл или внести изменения и т.п.). Кроме того, иногда необходимо не закрывать, а «переоткрывать» файл для того, чтобы, например, изменить режим доступа.
Кроме того, существует ряд задач, когда нам не нужно обращаться к содержимому файла: переименование, перемещение, копирование и т.д. К сожалению, в стандарте си нет описания функций для этих нужд. Они, безусловно, имеются для каждой из реализаций компилятора. Считывание содержимого каталога (папки, директории) – это тоже обращение к файлу, потому что папка сама по себе является файлом с метаинформацией.
Иногда необходимо выполнять некоторые вспомогательные операции: переместиться в нужное место файла, запомнить текущее положение, определить длину файла и т.д.
Для работы с файлом необходим объект FILE. Этот объект хранит идентификатор файлового потока и информацию, которая нужна, чтобы им управлять, включая указатель на его буфер, индикатор позиции в файле и индикаторы состояния.
Объект FILE сам по себе является структурой, но к его полям не должно быть доступа. Переносимая программа должна работать с файлом как с абстрактным объектом, позволяющим получить доступ до файлового потока.
Создание и выделение памяти под объект типа FILE осуществляется с помощью функции fopen или tmpfile (есть и другие, но мы остановимся только на этих).
Функция fopen открывает файл. Она получает два аргумента – строку с адресом файла и строку с режимом доступа к файлу. Имя файла может быть как абсолютным, так и относительным. fopen возвращает указатель на объект FILE, с помощью которого далее можно осуществлять доступ к файлу.
FILE* fopen(const char* filename, const char* mode);
Например, откроем файл и запишем в него Hello World
#include #include #include void main() < //С помощью переменной file будем осуществлять доступ к файлу FILE *file; //Открываем текстовый файл с правами на запись file = fopen("C:/c/test.txt", "w+t"); //Пишем в файл fprintf(file, "Hello, World!"); //Закрываем файл fclose(file); getch(); >
Функция fopen сама выделяет память под объект, очистка проводится функцией fclose. Закрывать файл обязательно, самостоятельно он не закроется.
Функция fopen может открывать файл в текстовом или бинарном режиме. По умолчанию используется текстовый. Режим доступа может быть следующим
| Тип | Описание |
|---|---|
| r | Чтение. Файл должен существовать. |
| w | Запись нового файла. Если файл с таким именем уже существует, то его содержимое будет потеряно. |
| a | Запись в конец файла. Операции позиционирования (fseek, fsetpos, frewind) игнорируются. Файл создаётся, если не существовал. |
| r+ | Чтение и обновление. Можно как читать, так и писать. Файл должен существовать. |
| w+ | Запись и обновление. Создаётся новый файл. Если файл с таким именем уже существует, то его содержимое будет потеряно. Можно как писать, так и читать. |
| a+ | Запись в конец и обновление. Операции позиционирования работают только для чтения, для записи игнорируются. Если файл не существовал, то будет создан новый. |
Если необходимо открыть файл в бинарном режиме, то в конец строки добавляется буква b, например “rb”, “wb”, “ab”, или, для смешанного режима “ab+”, “wb+”, “ab+”. Вместо b можно добавлять букву t, тогда файл будет открываться в текстовом режиме. Это зависит от реализации. В новом стандарте си (2011) буква x означает, что функция fopen должна завершиться с ошибкой, если файл уже существует. Дополним нашу старую программу: заново откроем файл и считаем, что мы туда записали.
#include #include #include void main() < FILE *file; char buffer[128]; file = fopen("C:/c/test.txt", "w"); fprintf(file, "Hello, World!"); fclose(file); file = fopen("C:/c/test.txt", "r"); fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); >
Вместо функции fgets можно было использовать fscanf, но нужно помнить, что она может считать строку только до первого пробела.
fscanf(file, «%127s», buffer);
Также, вместо того, чтобы открывать и закрывать файл можно воспользоваться функцией freopen, которая «переоткрывает» файл с новыми правами доступа.
#include #include #include void main() < FILE *file; char buffer[128]; file = fopen("C:/c/test.txt", "w"); fprintf(file, "Hello, World!"); freopen("C:/c/test.txt", "r", file); fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); >
Функции fprintf и fscanf отличаются от printf и scanf только тем, что принимают в качестве первого аргумента указатель на FILE, в который они будут выводить или из которого они будут читать данные. Здесь стоит сразу же добавить, что функции printf и scanf могут быть без проблем заменены функциями fprintf и fscanf. В ОС (мы рассматриваем самые распространённые и адекватные операционные системы) существует три стандартных потока: стандартный поток вывода stdout, стандартный поток ввода stdin и стандартный поток вывода ошибок stderr. Они автоматически открываются во время запуска приложения и связаны с консолью. Пример
#include #include #include void main() < int a, b; fprintf(stdout, "Enter two numbers\n"); fscanf(stdin, "%d", &a); fscanf(stdin, "%d", &b); if (b == 0) < fprintf(stderr, "Error: divide by zero"); >else < fprintf(stdout, "%.3f", (float) a / (float) b); >getch(); >
Ошибка открытия файла
Если вызов функции fopen прошёл неудачно, то она возвратит NULL. Ошибки во время работы с файлами встречаются достаточно часто, поэтому каждый раз, когда мы окрываем файл, необходимо проверять результат работы
#include #include #include #define ERROR_OPEN_FILE -3 void main() < FILE *file; char buffer[128]; file = fopen("C:/c/test.txt", "w"); if (file == NULL) < printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); >fprintf(file, "Hello, World!"); freopen("C:/c/test.txt", "r", file); if (file == NULL) < printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); >fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); >
Проблему вызывает случай, когда открывается сразу несколько файлов: если один из них нельзя открыть, то остальные также должны быть закрыты
. FILE *inputFile, *outputFile; unsigned m, n; unsigned i, j; inputFile = fopen(INPUT_FILE, READ_ONLY); if (inputFile == NULL) < printf("Error opening file %s", INPUT_FILE); getch(); exit(3); >outputFile = fopen(OUTPUT_FILE, WRITE_ONLY); if (outputFile == NULL) < printf("Error opening file %s", OUTPUT_FILE); getch(); if (inputFile != NULL) < fclose(inputFile); >exit(4); > .
В простых случаях можно действовать влоб, как в предыдущем куске кода. В более сложных случаях используются методы, подменяющиее RAII из С++: обёртки, или особенности компилятора (cleanup в GCC) и т.п.
Буферизация данных
- 1) Если он заполнен
- 2) Если поток закрывается
- 3) Если мы явно указываем, что необходимо очистить буфер (здесь тоже есть исключения:)).
- 4) Также очищается, если программа завершилась удачно. Вместе с этим закрываются и все файлы. В случае ошибки выполнения этого может не произойти.
Форсировать выгрузку буфера можно с помощью вызова функции fflush(File *). Рассмотрим два примера – с очисткой и без.
#include #include #include void main() < FILE *file; char c; file = fopen("C:/c/test.txt", "w"); do < c = getch(); fprintf(file, "%c", c); fprintf(stdout, "%c", c); //fflush(file); >while(c != 'q'); fclose(file); getch(); >
Раскомментируйте вызов fflush. Во время выполнения откройте текстовый файл и посмотрите на поведение.
Буфер файла можно назначить самостоятельно, задав свой размер. Делается это при помощи функции
void setbuf (FILE * stream, char * buffer);
которая принимает уже открытый FILE и указатель на новый буфер. Размер нового буфера должен быть не меньше чем BUFSIZ (к примеру, на текущей рабочей станции BUFSIZ равен 512 байт). Если передать в качестве буфера NULL, то поток станет небуферизированным. Можно также воспользоваться функцией
int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );
- _IOFBF — полная буферизация. Данные записываются в файл, когда он заполняется. На считывание, буфер считается заполненным, когда запрашивается операция ввода и буфер пуст.
- _IOLBF — линейная буферизация. Данные записываются в файл когда он заполняется, либо когда встречается символ новой строки. На считывание, буфер заполняется до символа новой строки, когда запрашивается операция ввода и буфер пуст.
- _IONBF – без буферизации. В этом случае параметры size и buffer игнорируются.
Пример: зададим свой буфер и посмотрим, как осуществляется чтение из файла. Пусть файл короткий (что-нибудь, типа Hello, World!), и считываем мы его посимвольно
#include #include #include void main() < FILE *input = NULL; char c; char buffer[BUFSIZ * 2] = ; input = fopen("D:/c/text.txt", "rt"); setbuf(input, buffer); while (!feof(input)) < c = fgetc(input); printf("%c\n", c); printf("%s\n", buffer); _getch(); >fclose(input); >
Видно, что данные уже находятся в буфере. Считывание посимвольно производится уже из буфера.
feof
Функция int feof (FILE * stream); возвращает истину, если конец файла достигнут. Функцию удобно использовать, когда необходимо пройти весь файл от начала до конца. Пусть есть файл с текстовым содержимым text.txt. Считаем посимвольно файл и выведем на экран.
#include #include #include void main() < FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) < printf("Error opening file"); _getch(); exit(0); >while (!feof(input)) < c = fgetc(input); fprintf(stdout, "%c", c); >fclose(input); _getch(); >
Всё бы ничего, только функция feof работает неправильно. Это связано с тем, что понятие «конец файла» не определено. При использовании feof часто возникает ошибка, когда последние считанные данные выводятся два раза. Это связано с тем, что данные записывается в буфер ввода, последнее считывание происходит с ошибкой и функция возвращает старое считанное значение.
#include #include #include void main() < FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) < printf("Error opening file"); _getch(); exit(0); >while (!feof(input)) < fscanf(input, "%c", &c); fprintf(stdout, "%c", c); >fclose(input); _getch(); >
Этот пример сработает с ошибкой (скорее всего) и выведет последний символ файла два раза.
Решение – не использовать feof. Например, хранить общее количество записей или использовать тот факт, что функции fscanf и пр. обычно возвращают число верно считанных и сопоставленных значений.
#include #include #include void main() < FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) < printf("Error opening file"); _getch(); exit(0); >while (fscanf(input, "%c", &c) == 1) < fprintf(stdout, "%c", c); >fclose(input); _getch(); >
Примеры
1. В одном файле записаны два числа — размерности массива. Заполним второй файл массивом случайных чисел.
#include #include #include #include //Имена файлов и права доступа #define INPUT_FILE "D:/c/input.txt" #define OUTPUT_FILE "D:/c/output.txt" #define READ_ONLY "r" #define WRITE_ONLY "w" //Максимальное значение для размера массива #define MAX_DIMENSION 100 //Ошибка при открытии файла #define ERROR_OPEN_FILE -3 void main() < FILE *inputFile, *outputFile; unsigned m, n; unsigned i, j; inputFile = fopen(INPUT_FILE, READ_ONLY); if (inputFile == NULL) < printf("Error opening file %s", INPUT_FILE); getch(); exit(ERROR_OPEN_FILE); >outputFile = fopen(OUTPUT_FILE, WRITE_ONLY); if (outputFile == NULL) < printf("Error opening file %s", OUTPUT_FILE); getch(); //Если файл для чтения удалось открыть, то его необходимо закрыть if (inputFile != NULL) < fclose(inputFile); >exit(ERROR_OPEN_FILE); > fscanf(inputFile, "%ud %ud", &m, &n); if (m > MAX_DIMENSION) < m = MAX_DIMENSION; >if (n > MAX_DIMENSION) < n = MAX_DIMENSION; >srand(time(NULL)); for (i = 0; i < n; i++) < for (j = 0; j < m; j++) < fprintf(outputFile, "%8d ", rand()); >fprintf(outputFile, "\n"); > //Закрываем файлы fclose(inputFile); fclose(outputFile); >
2. Пользователь копирует файл, при этом сначала выбирает режим работы: файл может выводиться как на консоль, так и копироваться в новый файл.
#include #include #include #define ERROR_FILE_OPEN -3 void main() < FILE *origin = NULL; FILE *output = NULL; char filename[1024]; int mode; printf("Enter filename: "); scanf("%1023s", filename); origin = fopen(filename, "r"); if (origin == NULL) < printf("Error opening file %s", filename); getch(); exit(ERROR_FILE_OPEN); >printf("enter mode: [1 - copy, 2 - print] "); scanf("%d", &mode); if (mode == 1) < printf("Enter filename: "); scanf("%1023s", filename); output = fopen(filename, "w"); if (output == NULL) < printf("Error opening file %s", filename); getch(); fclose(origin); exit(ERROR_FILE_OPEN); >> else < output = stdout; >while (!feof(origin)) < fprintf(output, "%c", fgetc(origin)); >fclose(origin); fclose(output); getch(); >
3. Пользователь вводит данные с консоли и они записываются в файл до тех пор, пока не будет нажата клавиша esc. Проверьте программу и посмотрите. как она себя ведёт в случае, если вы вводите backspace: что выводится в файл и что выводится на консоль.
#include #include #include #define ERROR_FILE_OPEN -3 void main() < FILE *output = NULL; char c; output = fopen("D:/c/test_output.txt", "w+t"); if (output == NULL) < printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); >for (;;) < c = _getch(); if (c == 27) < break; >fputc(c, output); fputc(c, stdout); > fclose(output); >
4. В файле записаны целые числа. Найти максимальное из них. Воспользуемся тем, что функция fscanf возвращает число верно прочитанных и сопоставленных объектов. Каждый раз должно возвращаться число 1.
#include #include #include #define ERROR_FILE_OPEN -3 void main() < FILE *input = NULL; int num, maxn, hasRead; input = fopen("D:/c/input.txt", "r"); if (input == NULL) < printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); >maxn = INT_MIN; hasRead = 1; while (hasRead == 1) < hasRead = fscanf(input, "%d", &num); if (hasRead != 1) < continue; >if (num > maxn) < maxn = num; >> printf("max number = %d", maxn); fclose(input); _getch(); >
Другое решение считывать числа, пока не дойдём до конца файла.
#include #include #include #include #define ERROR_FILE_OPEN -3 void main() < FILE *input = NULL; int num, maxn, hasRead; input = fopen("D:/c/input.txt", "r"); if (input == NULL) < printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); >maxn = INT_MIN; while (!feof(input)) < fscanf(input, "%d", &num); if (num >maxn) < maxn = num; >> printf("max number = %d", maxn); fclose(input); _getch(); >
5. В файле записаны слова: русское слово, табуляция, английское слово, в несколько рядов. Пользователь вводит английское слово, необходимо вывести русское.
Файл с переводом выглядит примерно так
солнце sun
карандаш pen
шариковая ручка pencil
дверь door
окно windows
стул chair
кресло armchair
и сохранён в кодировке cp866 (OEM 866). При этом важно: последняя пара cлов также заканчивается переводом строки.
Алгоритм следующий — считываем строку из файла, находим в строке знак табуляции, подменяем знак табуляции нулём, копируем русское слово из буфера, копируем английское слово из буфера, проверяем на равенство.
#include #include #include #include #define ERROR_FILE_OPEN -3 void main() < FILE *input = NULL; char buffer[512]; char enWord[128]; char ruWord[128]; char usrWord[128]; unsigned index; int length; int wasFound; input = fopen("D:/c/input.txt", "r"); if (input == NULL) < printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); >printf("enter word: "); fgets(usrWord, 127, stdin); wasFound = 0; while (!feof(input)) < fgets(buffer, 511, input); length = strlen(buffer); for (index = 0; index < length; index++) < if (buffer[index] == '\t') < buffer[index] = '\0'; break; >> strcpy(ruWord, buffer); strcpy(enWord, &buffer[index + 1]); if (!strcmp(enWord, usrWord)) < wasFound = 1; break; >> if (wasFound) < printf("%s", ruWord); >else < printf("Word not found"); >fclose(input); _getch(); >
6. Подсчитать количество строк в файле. Будем считывать файл посимвольно, считая количество символов ‘\n’ до тех пор, пока не встретим символ EOF. EOF – это спецсимвол, который указывает на то, что ввод закончен и больше нет данных для чтения. Функция возвращает отрицательное значение в случае ошибки.
ЗАМЕЧАНИЕ: EOF имеет тип int, поэтому нужно использовать int для считывания символов. Кроме того, значение EOF не определено стандартом.
#define _CRT_SECURE_NO_WARNINGS #include #include #include int cntLines(const char *filename) < int lines = 0; int any; //any типа int, потому что EOF имеет тип int! FILE *f = fopen(filename, "r"); if (f == NULL) < return -1; >do < any = fgetc(f); //printf("%c", any);//debug if (any == '\n') < lines++; >> while(any != EOF); fclose(f); return lines; > void main() < printf("%d\n", cntLines("C:/c/file.txt")); _getch(); >
ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

Всё ещё не понятно? – пиши вопросы на ящик
Как считать все строки из файла c
Доступ к диску (чтение/запись) гораздо (на несколько порядков) медленнее, чем доступ к данным в оперативной памяти. Кроме того, если мы читаем или записываем файл при помощи системных вызовов маленькими порциями (по 1-10 символов)
char c; while( read(0, &c, 1)) . ; /* 0 - стандартный ввод */
то мы проигрываем еще в одном: каждый системный вызов — это обращение к ядру операционной системы. При каждом таком обращении происходит довольно большая дополнительная работа (смотри главу «Взаимодействие с UNIX«). При этом накладные расходы на такое посимвольное чтение файла могут значительно превысить полезную работу.
Еще одной проблемой является то, что системные вызовы работают с файлом как с неструктурированным массивом байт; тогда как человеку часто удобнее представлять, что файл поделен на строки, содержащие читабельный текст, состоящий лишь из обычных печатных символов (текстовый файл).
Для решения этих двух проблем была построена специальная библиотека функций, названная stdio — «стандартная библиотека ввода/вывода» (standard input/output library). Она является частью библиотеки /lib/libc.a и представляет собой надстройку над системными вызовами (т.к. в конце концов все ее функции время от времени обращаются к системе, но гораздо реже, чем если использовать сисвызовы непосредственно).
Небезызвестная директива #include stdio.h> включает в нашу программу файл с объявлением форматов данных и констант, используемых этой библиотекой.
Библиотеку stdio можно назвать библиотекой буферизованного обмена, а также библиотекой работы с текстовыми файлами (т.е. имеющими разделение на строки), поскольку для оптимизации обменов с диском (для уменьшения числа обращений к нему и тем самым сокращения числа системных вызовов) эта библиотека вводит буферизацию, а также предоставляет несколько функций для работы со строчно-организованными файлами.
- дескриптор fd файла для обращения к системным вызовам;
- указатель на буфер, размещенный в памяти программы;
- указатель на текущее место в буфере, откуда надо выдать или куда записать очередной символ; этот указатель продвигается при каждом вызове getc или putc;
- счетчик оставшихся в буфере символов (при чтении) или свободного места (при записи);
- режимы открытия файла (чтение/запись/чтение+запись) и текущее состояние файла. Одно из состояний — при чтении файла был достигнут его конец **;
- способ буферизации;
Предусмотрено несколько стандартных структур FILE, указатели на которые называются stdin, stdout и stderr и связаны с дескрипторами 0, 1, 2 соответственно (стандартный ввод, стандартный вывод, стандартный вывод ошибок). Напомним, что эти каналы открыты неявно (автоматически) и, если не перенаправлены, связаны с вводом с клавиатуры и выводом на терминал.
Буфер в оперативной памяти нашей программы создается (функцией malloc) при открытии файла при помощи функции fopen(). После открытия файла все операции обмена с файлом происходят не по 1 байту, а большими порциями размером с буфер — обычно по 512 байт (константа BUFSIZ).
При чтении символа
int c; FILE *fp = . ; c = getc(fp);
getc выдает ее первый байт.
При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску уже не происходит! Лишь когда буфер будет исчерпан — произойдет очередное чтение с диска. Таким образом, информация читается из файла с опережением, заранее наполняя буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный вызов read — для чтения двух порций информации из файла, каждая — по 512 байт.
char c; FILE *fp = . ; putc(c, fp);
- буфер заполнен (содержит BUFSIZ символов).
- при закрытии файла (fclose или exit ***).
- при вызове функции fflush (см. ниже).
- в специальном режиме — после помещения в буфер символа ‘\n‘ (см. ниже).
- в некоторых версиях — перед любой операцией чтения из канала stdin (например, при вызове gets), при условии, что stdout буферизован построчно (режим _IOLBF, смотри ниже), что по-умолчанию так и есть.
Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из stdio (кто кого вызывает). Далее s означает строку, c — символ, fp — указатель на структуру FILE **** . Функции, работающие со строками, в цикле вызывают посимвольные операции. Обратите внимание, что в конце концов все функции обращаются к системным вызовам read и write, осуществляющим ввод/вывод низкого уровня.
Системные вызовы далее обозначены жирно, макросы — курсивом.
Открыть файл, создать буфер:
#include stdio.h> FILE *fp = fopen(char *name, char *rwmode); | вызывает V int fd = open (char *name, int irwmode); Если открываем на запись и файл не существует (fd < 0), то создать файл вызовом: fd = creat(char *name, int accessmode); fd будет открыт для записи в файл.
По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rwrw-rw-).
Соответствие аргументов fopen и open:
rwmode irwmode ------------------------ "r" O_RDONLY "w" O_WRONLY|O_CREAT |O_TRUNC "r+" O_RDWR "w+" O_RDWR |O_CREAT |O_TRUNC "a" O_WRONLY|O_CREAT |O_APPEND "a+" O_RDWR |O_CREAT |O_APPEND
Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его не было.
Если fopen() не смог открыть (или создать) файл, он возвращает значение NULL:
if((fp = fopen(name, rwmode)) == NULL)
Итак, схема:
printf(fmt. )--->--,----fprintf(fp,fmt. )->--* fp=stdout | fputs(s,fp)--------->--| puts(s)----------->-------putchar(c)-----,---->--| fp=stdout | fwrite(array,size,count,fp)->--| | Ядро ОС putc(c,fp) ------------------* | |файловая---write(fd,s,len)---------------read(fd,s,len)-* _flsbuf(c,fp) | | ! | |системные буфера ! | | | ! V ungetc(c,fp) |драйвер устр-ва ! | | |(диск, терминал) ! | _filbuf(fp) | | | ! *--------->-----БУФЕРgetc(fp) | rdcount=fread(array,size,count,fp)--<--| gets(s)-------<---------c=getchar()------,----<--| fp=stdout | | fgets(sbuf,buflen,fp)-<--| scanf(fmt. /*ук-ли*/)--<-,--fscanf(fp,fmt. )-* fp=stdin
Закрыть файл, освободить память выделенную под буфер:
fclose(fp) ---> close(fd);
И чуть в стороне — функция позиционирования:
fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);
Функции _flsbuf и _filbuf — внутренние для stdio, они как раз сбрасывают буфер в файл либо читают новый буфер из файла.
По указателю fp можно узнать дескриптор файла:
int fd = fileno(fp);
Это макроопределение просто выдает поле из структуры FILE. Обратно, если мы открыли файл open-ом, мы можем ввести буферизацию этого канала:
int fd = open(name, O_RDONLY); /* или creat() */ . FILE *fp = fdopen(fd, "r");
(здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму открытия open-ом). Теперь можно работать с файлом через fp, а не fd.
В приложении имеется текст, содержащий упрощенную реализацию главных функций из библиотеки stdio.
4.11.
Функция ungetc(c,fp) «возвращает» прочитанный байт в файл. На самом деле байт возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам. Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при getc()) на 1 позицию назад. Вернуть можно только один символ подряд (т.е. перед следующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно сдвинуть указатель за начало буфера и, записывая туда символ c, разрушить память программы.
while((c = getchar()) != '+' ); /* Прочли '+' */ ungetc(c ,stdin); /* А можно заменить этот символ на другой! */ c = getchar(); /* снова прочтет '+' */
4.12.
Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так ничего не стоит написать:
FILE *fp = . ; fputc( fp, '\n' );
Запомните навсегда!
int fputc( int c, FILE *fp );
указатель файла идет вторым! Существует также макроопределение
putc( c, fp );
Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в функцию:
#include putNtimes( fp, c, n, f ) FILE *fp; int c; int n; int (*f)(); < while( n >0 )< (*f)( c, fp ); n--; >> возможен вызов putNtimes( fp, 'a', 3, fputc ); но недопустимо putNtimes( fp, 'a', 3, putc );
Тем не менее всегда, где возможно, следует пользоваться макросом — он работает быстрее. Аналогично, есть функция fgetc(fp) и макрос getc(fp).
Отметим еще, что putchar и getchar это тоже всего лишь макросы
#define putchar(c) putc((c), stdout) #define getchar() getc(stdin)
4.13.
Известная вам функция printf также является частью библиотеки stdio. Она входит в семейство функций:
FILE *fp; char bf[256]; fprintf(fp, fmt, . ); printf( fmt, . ); sprintf(bf, fmt, . );
Первая из функций форматирует свои аргументы в соответствии с форматом, заданным строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посимвольно (вызывая putc) в файл fp. Вторая — это всего-навсего fprintf с каналом fp равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в массив bf. В конце строки sprintf добавляет нулевой байт ‘\0’ — признак конца.
Для чтения данных по формату используются функции семейства
fscanf(fp, fmt, /* адреса арг-тов */. ); scanf( fmt, . ); sscanf(bf, fmt, . );
Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми файлами (содержащими изображение данных в виде печатных символов).
4.14.
Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные массивы байт. Для разделения строк в них используется символ ‘\n‘. Так, например, текст
стр1 стрк2 кнц
хранится как массив
с т р 1 \n с т р к 2 \n к н ц длина=14 байт ! указатель чтения/записи (read/write pointer RWptr) (расстояние в байтах от начала файла)
При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последовательность \r\n, которая возвращает курсор в начало строки (‘\r‘) и опускает курсор на строку вниз (‘\n‘), то есть курсор переходит в начало следующей строки.
В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе на экран никаких преобразований не делается ***** . Зато библиотечные функции языка Си преобразуют эту последовательность при чтении из файла в \n, а при записи в файл превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для работы с файлом без таких преобразований, его надо открывать как «бинарный»:
FILE *fp = fopen( имя, "rb" ); /* b - binary */ int fd = open ( имя, O_RDONLY | O_BINARY ); '\n' - '\012' (10) line feed '\r' - '\015' (13) carriage return '\t' - '\011' (9) tab '\b' - '\010' (8) backspace '\f' - '\014' (12) form feed '\a' - '\007' (7) audio bell (alert) '\0' - 0. null byte
Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти разные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов с данными и программ совершенно недопустимо!).
Задание: напишите программу подсчета строк и символов в файле. Указание: надо подсчитать число символов ‘\n‘ в файле и учесть, что последняя строка файла может не иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы прочитаете самым последним) не есть ‘\n‘, то добавьте к счетчику строк 1.
4.15.
Напишите программу подсчета количества вхождений каждого из символов алфавита в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив из 256 счетчиков. Для больших файлов счетчики должны быть типа long).
4.16.
Почему вводимый при помощи функций getchar() и getc(fp) символ должен описываться типом int а не char?
Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF (end of file), равное целому числу (-1). Это НЕ символ кодировки ASCII, поскольку getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы с кодами 0. 255), а специальный признак не должен совпадать ни с одним из хранимых в файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя бы еще 1 бит). Проверка на конец файла в программе обычно выглядит так:
. while((ch = getchar()) != EOF )< putchar(ch); . >
- Пусть ch имеет тип unsigned char. Тогда ch всегда лежит в интервале 0. 255 и НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно будет приведено к типу unsigned char обрубанием и станет равным 255. При сравнении с целым (-1) оно расширится в int добавлением нулей слева и станет равно 255. Таким образом, наша программа никогда не завершится, т.к. вместо признака конца файла она будет читать символ с кодом 255 (255 != -1).
- Пусть ch имеет тип signed char. Тогда перед сравнением с целым числом EOF байт ch будет приведен к типу signed int при помощи расширения знакового бита (7ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании значения байту ch обрублено до типа char: 255; но в сравнении с EOF значение 255 будет приведено к типу int и получится (-1). Таким образом, истинный конец файла будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также воспринят как конец файла. Таким образом, если в нашем файле окажется символ с кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь остаток файла необработанным (а в нетекстовых файлах такие символы — не редкость).
- Пусть ch имеет тип int или unsigned int (больше 8 бит). Тогда все корректно.
Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система в любой момент времени знает длину файла с точностью до одного байта; признак EOF вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чтения достиг конца файла (то есть позиция чтения стала равной длине файла — последний байт уже прочитан).
В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину файла символ CTRL/Z, то некоторые программы перестанут «видеть» остаток файла после этого символа!
Наконец отметим, что разные функции при достижении конца файла выдают разные значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read — выдает 0, а gets, fgets — NULL.
4.17.
Напишите программу, которая запрашивает ваше имя и приветствует вас. Для ввода имени используйте стандартные библиотечные функции
gets(s); fgets(s,slen,fp);
В чем разница?
Ответ: функция gets() читает строку (завершающуюся ‘\n‘) из канала fp==stdin. Она не контролирует длину буфера, в которую считывается строка, поэтому если строка окажется слишком длинной — ваша программа повредит свою память (и аварийно завершится). Единственный возможный совет — делайте буфер достаточно большим (очень туманное понятие!), чтобы вместить максимально возможную (длинную) строку.
Функция fgets() контролирует длину строки: если строка на входе окажется длиннее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет оставлен «на потом». Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того fgets, в отличие от gets, не обрубает символ ‘\n‘ на конце строки, что доставляет нам дополнительные хлопоты по его уничтожению, поскольку в Си «нормальные» строки завершаются просто ‘\0‘, а не «\n\0«.
char buffer[512]; FILE *fp = . ; int len; . while(fgets(buffer, sizeof buffer, fp))< if((len = strlen(buffer)) && buffer[len-1] == '\n') /* @ */ buffer[--len] = '\0'; printf("%s\n", buffer); >
Здесь len — длина строки. Если бы мы выбросили оператор, помеченный ‘@’, то printf печатал бы текст через строку, поскольку выдавал бы код ‘\n‘ дважды — из строки buffer и из формата «%s\n».
Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочитан, эти функции возвращают свой первый аргумент — адрес буфера, в который была записана очередная строка файла.
Фрагмент для обрубания символа перевода строки может выглядеть еще так:
#include stdio.h> #include string.h> char buffer[512]; FILE *fp = . ; . while(fgets(buffer, sizeof buffer, fp) != NULL)< char *sptr; if(sptr = strchr(buffer, '\n')) *sptr = '\0'; printf("%s\n", buffer); >
4.18.
В чем отличие puts(s); и fputs(s,fp); ?
Ответ: puts выдает строку s в канал stdout. При этом puts выдает сначала строку s, а затем — дополнительно — символ перевода строки ‘\n‘. Функция же fputs символ перевода строки не добавляет. Упрощенно:
fputs(s, fp) char *s; FILE *fp; < while(*s) putc(*s++, fp); >puts(s) char *s;
4.19.
Найдите ошибки в программе:
#include main()
Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций.
* Это не та «связующая» структура file в ядре, про которую шла речь выше, а ЕЩЕ одна — в памяти самой программы.
** Проверить это состояние позволяет макрос feof(fp); он истинен, если конец был достигнут, ложен — если еще нет.
*** При выполнении вызова завершения программы exit(); все открытые файлы автоматически закрываются.
**** Обозначения fd для дескрипторов и fp для указателей на файл прижились и их следует придерживаться. Если переменная должна иметь более мнемоничное имя — следует писать так: fp_output, fd_input (а не просто fin, fout).
***** Управляющие символы имеют следующие значения:
© Copyright А. Богатырев, 1992-95
Си в UNIX
Как считать все строки из файла c
Потоки для работы с текстовыми файлами представляют объекты, для которых не задан режим открытия ios::binary .
Запись в файл
Для записи в файл к объекту ofstream или fstream применяется оператор
#include #include int main() < std::ofstream out; // поток для записи out.open("hello.txt"); // открываем файл для записи if (out.is_open()) < out out.close(); std::cout
Здесь предполагается, что файла «hello.txt» располагается в одной папке с файлом программы. Данный способ перезаписывает файл заново. Если надо дозаписать текст в конец файла, то для открытия файла нужно использовать режим ios::app :
std::ofstream out("hello.txt", std::ios::app); if (out.is_open()) < out out.close();
Чтение из файла
Если надо считать всю строку целиком или даже все строки из файла, то лучше использовать встроенную функцию getline() , которая принимает поток для чтения и переменную, в которую надо считать текст:
#include #include #include // для std::getline int main() < std::string line; std::ifstream in("hello.txt"); // окрываем файл для чтения if (in.is_open()) < while (std::getline(in, line)) < std::cout > in.close(); // закрываем файл >
Также для чтения данных из файла для объектов ifstream и fstream может применяться оператор >> (также как и при чтении с консоли):
#include #include #include struct Point < Point(double x, double y): x, y <> double x; double y; >; int main() < std::vectorpoints< Point, Point, Point>; std::ofstream out("points.txt"); if (out.is_open()) < // записываем все объекты Point в файл for (const Point& point: points) < out > out.close(); std::vector new_points; std::ifstream in("points.txt"); // окрываем файл для чтения if (in.is_open()) < double x, y; while (in >> x >> y) < new_points.push_back(Point); > > in.close(); for (const Point& point: new_points) < std::cout >
Здесь вектор структур Point записывается в файл.
for (const Point& point: points)
При чем при записи значений переменных файл они отделяются пробелом. В итоге будет создаваться файл в формате
0 0 4 5 -5 7
Используя оператор >>, можно считать последовательно данные в переменные x и y и ими инициализировать структуру.
double x, y; while (in >> x >> y) < new_points.push_back(Point); >
Но стоит отметить, что это ограниченный способ, поскольку при чтении файла поток in использует пробел для отделения одного значения от другого и таким образом считывает эти значения в переменные x и y. Если же нам надо записать и затем считать строку, которая содержит пробелы, и какие-то другие данные, то такой способ, конечно, не сработает.
Как считать все строки из файла c
--- tags: си, прога --- # Чтение из файлов в си Как читать файлы в C и обработать все ошибки? ## `fgetc` ```c if (fgetc(f) == '#') < while (fgetc(f) != '\n') <>> ``` Вот тут мы скипаем по одному байту, пока не наткнёмся на `\n`. Его тоже съедим и поедем дальше. Ошибки все игнорируются. Это. страшно. Какие они могут быть? - `EOF` — страшная аббриевиатура означает `end of file`. `fgetc` возвращает не `char`, а `int`. Потому что она может вернуть и `EOF`. Это будет означать, что файл закончился. - Другие ошибки чтения. Жёсткий диск умер внезапно, прочесть не смогли, система кидает ошибку, потоп, пожар, всё что ни есть на свете может разные ошибки заставить вернуть `fgetc`. В общем, если какая-то ошибка есть, получим вместо символа `EOF`. Это надо проверить. Раздуваем код выше: ```c int skip_comment_if_any(FILE *f) < int ch = fgetc(f); if (ch == EOF) < return -1; >if (ch != '#') < // коммента нет, всё ок return 0; >while (1) < ch = fgetc(f); if (ch == EOF) < // файл скончался преждевременно return -1; >if (ch == '\n') < // кончилась строка return 0; >> > ``` Вроде такого. Прочесть что-то из файла — полбеды. Другая половина — обработать все ошибки. В си вынуждены мы страдать. Чтобы страдать меньше, всё растаскиваем по функциям. Скипать коммент — функция. Прочесть хедер в файле — функция. Раны (от слова run) из самих данных поля скушать — функция. ## `fgets` Итак, это был `fgetc`. Заменим `c` на `s`: `fgets` кушает несколько байт из файла и сохраняет их в буфер. Отличие от `fread`: в конец пихается `\0`. Удобно. ```c char *str = calloc(10, sizeof(char)); fgets(str, 10, f); printf("%s", str); ``` Она прочтёт **девять** байт, а десятый будет `\0`. Или не десятый, если не хватит в файле байт. Какие тут ошибки? Ну да всё те же самые. Если будет ошибка, `fgets` вернёт `NULL` для разнообразия. ```c char *str = calloc(10, sizeof(char)); if (fgets(str, 10, f) == NULL) < // ошибка чтения return -1; >printf("%s", str); ``` Так-то лучше. ## `fscanf` Есть ещё одна полезная функция для чтения из файла. Это уже formatted input/output называется. Для файлов `fscanf` нас интересует. Это. вариант для ленивых, на самом деле. Как известно, `fscanf` делает всё на свете: и числа читает, и строки, и байты. За удобство платим, конечно. Ошибки обрабатывать сложно. Смотрим сюда. В файле записано `test`. ```c int count = 0; fscanf(f, "%d", &count); print(count); ``` Я без понятия, что этот код выведет, потому что в файле даже цифры нет. Это плохо. Нам надо будет парсить RLE — это, если забыть про комменты и хедер, набор данных рода `<число>`. Число — это количество символов ``, которые мы схлопнули здесь. ``` 2ob4o ``` Это закодирована строка `ooboooo`. Вот мы начали писать функцию `read_run`. ```c int read_run(FILE *f, /* какие-то ещё параметры */) < int count = 0; fscanf(f, "%d", &count); // Про остальное пока забудем. >``` Мы ленивы, юзаем `fscanf`. Попробуем поломать нашу наивную функцию. Дадим ей `test`. Будет ошибка. Узнать об ошибке мы можем, если посмотрим, что вернёт `fscanf`. Она плюётся числом — количеством **скушанных процентиков**. В коде выше с `test` вернётся `0`, то есть числа там не прочёл `fscanf`. Хорошо: ```c int count = 0; // В строке формата у нас один процент, // так что fscanf должен вернуть 1. if (fscanf(f, "%d", &count) != 1) < // Сисла нет, дефолтимся в 1. count = 1; >``` Почему `1`? Ну, единичный символ в RLE пишет без числа. Просто `o` кодирует просто `o`, без лишних заворотов. То есть один символ. Замечательно. `fscanf` может ещё заменить `fgetc` волшебным заклинанием `%c`: ```c // Продолжаем: char ch; if (fscanf(f, "%c", &ch) != 1) < // Что-то символ не смогли прочесть. // Похоже, что файл скончался. return -1; >``` Наверное, это удобнее. Теперь мы прочли число и символ, можем обрабатывать его. А все ли ошибки учли? Ноп. Пусть в файле лежит `-2o`. Первый `fscanf`, который с `%d`, радостно сожрёт `-2`. Ну, отрицательный размер — это странно. Вернёмся к коду тому и добавим: ```c int count = 0; if (fscanf(f, "%d", &count) != 1) < // Числа нет, дефолтимся в 1. count = 1; >if (count < 0) < // Отрицательное число — ошибка. return -1; >``` Тяк, всё обработали? - [x] Отрицательные числа - [x] Положительные числа - [x] Ноль - [ ] Слишком большие числа А, не, погодите. Пусть в файле лежит `123456789012345678901234567890`. В `int` такое число не влезет. Что вернёт `fscanf`? Ща погуглим. . Гугл что-то молчит, тогда проверим сами: ```c #include int main() < int number; printf("Enter a number: "); scanf("%d", &number); printf("Your input: %d\n", number); return 0; >``` Компилируем и запускаем: ``` $ gcc main.c -o ./prog $ ./prog Enter a number: 1234567898012345678901234567890 Your input: -1 ``` Нехорошо, нехорошо. Окей, короче, я всё это говорил к тому, что `fscanf` в жизни мы сможем юзать только для чтения одного байта, наверное. Потому что нельзя нормально обработать ошибки. ## Как прочесть число Забудем про `fscanf` окончательно и будем сами красиво парсить числа. Немного вводных данных. В `` есть функция под именем [`strtoull`](https://en.cppreference.com/w/c/string/byte/strtoul). Она берёт строку и преобразует её в число. Пытается, как минимум. Если не может, то функция не взрывается. Значит, можно использовать. ```c #include // Возвращаем int — это код ошибки. // Сам результат будет в *result. int read_count(FILE *f, unsigned long long *result) < // 1. В unsigned long long влезет до 18446744073709551615. Это 20 цифр. // Поэтому создаём буфер на 21 символ, которые мы будем читать. // двадцать первый — это `\0`. char str[21] = ; size_t len = 0; bool significant_digits_started = false; // 2. Читаем по одной цифре, пока число не закончится. for (int i = 0; i < 20; ++i) < int ch = fgetc(f); if (ch != EOF && !isdigit(ch)) < // Мы сожрали один байт, и это не цифра. // Вернём байт назад. // ungetc также может завершиться с ошибкой. if (ungetc(ch, f) == EOF) < return -1; >> if (ch == EOF || !isdigit(ch)) < // Не цифра или конец файла: завершаем строку, брякаемся. str[i] = '\0'; break; >// Вот этот иф будет скипать нули в начале числа. if (ch != '0' || significant_digits_started) < str[i] = (char) ch; len++; >> // 3. Вызываем strtoull if (len == 0) < // Если цифру не встретили, выходим. return -2; >char *end; *result = strtoull(str, &end, 10); // end после вызова указывает на последний байт, который strtoull обработал. // Точнее, на следующий за последним обработанным байт. // Если он завершится посередине, не на конце строки, то он столкнулся с ошибкой. if (end != &str[len]) < // Ошибка. 111111 return -3; >// 4. Ошибок нет. Число в *result. return 0; > ``` Фух. Сколько раз я мог здесь прострелить свою бедную ногу, боюсь считать. А я это ещё не дебажил. Не буду лишать веселья этим заниматься. А, да, к слову. `ungetc` там есть — это значит взять символ и засунуть его обратно в файл. Если прочесть его позже, то мы его получим назад. Такое можно сделать только один раз. Сразу 10 символов обратно не запихаем, к сожалению. ## Итог Итак, в конце этого документа надо что-то заключительное рассказать. Ну, мы узнали, что `fgetc` нужен, чтобы читать один байт. `fgets` нужен, чтобы прочесть сразу несколько. `fscanf` нужен почти никогда, потому что он игнорирует ошибки. Только для прототипов хороша функция эта. Наконец, есть код, которым можно прочесть одно число из файла. И в нём узнали про `strtoull` и `ungetc`. ## Конец (файла). Это как постскриптум. Не всегда конец файла — это ошибка. Например, если данные закончились, то конец файла закономерен. Нужно тщательно подумать в коде о том, когда `EOF` ошибка, а когда нет. По-моему, я писал выше где-то, что `EOF` на самом деле возвращается при всём подряд: конце файла, другой ошибке чтения. Поэтому нужно знать ещё 2 функции для полного счастья. - `feof(f)` — кончился ли файл. - `ferror(f)` — встретилась ли какая-то другая ошибка. Жонглируя этими функциями, мы можем правильно обработать ошибки. ```c while (true) < unsigned long long count = 0; int status = read_count(file, &count); if (status == -1) < // EOF или ошибка чтения: и то, и другое для нас ошибка, // потому что файлу рано кончаться здесь return -1; >else if (status == -2) < // Не встретили цифру: будем считать, что дали 1 count = 1; >else if (status == -3) < // Слишком огромное число. Выходим. return -1; >if (count == 0) < // Дали `0o` какой-нибудь. Это невалидно. Длины больше нуля должны быть. return -1; >int ch = fgetc(file); if (ch == EOF) < if (status == -2 && feof(file)) < // Не встретили цифру, но дошли до EOF. // Файл, то бишь, кончился. // Значит, всё кончилось, выходим. Это не ошибка. return 0; >// Иначе, значит, ошибка. return -1; > // Теперь можно что-то делать с count и ch. > ``` Вот заготовка для чтения данных поля. Чтобы понять, что файл кончился по-хорошему, используем `feof`. Теперь точно всё.число>
Last changed by
The first at being the last
Add a comment
Read more
Java 1
Конспект по первому семинару по Джаве. Как вообще запускаются программы на Java? ИЕ просил перед парой установить JDK, чтобы радотали команды javac, javap, java. javac компилирует .java в .class, синтаксис (базовый) такой: javac Main.java После этой операции полявляется файл Main.class javap -- дизассемблер для файлов .class, синтаксис:
Задачи с зачёта (2020-12-10, четверг)
АХТУНГ: копипаст кода отсюда тождественно равно получению пермабана. Семинарист мой код спалит моментально — быстрее, чем свет до него дойдёт, от листочка отразившись. Никто так не упарывается с красотой и декомпозицией. Весь код даётся только в образовательных целях, чтобы понять, куда рыть. Алгоритмы вы будете писать на нелинованных листочках A4 ручкой, простыню убьётесь копировать. ДИСКЛЕЙМЕР: Ответы могут быть несущественно, заметно, разительно или полностью и в корне неверными. Если ошибки есть, то виноваты все, кроме меня. Но можете написать, и я исправлю. Задача 1. Область видимости и время жизни Классифицируйте виды переменных по области видимости и по времени жизни. Приведите примеры. Времена жизни переменных