Рейтинг@Mail.ru

 

 

 

 

 

 

.: Учебник по практическому программированию ( Бейсик, Си, Паскаль ) :.

<< НазадСодержаниеВперед >>

маркированный список Глава 7. Работа с дисковыми файлами
маркированный список Основные типы файлов в системе QBasic
маркированный список Основные типы файлов в Паскале
маркированный список Основные типы файлов в Си
маркированный список Задачи, советы и ответы

Глава 7.

Работа с дисковыми файлами

Наиболее распространенным видом файлов являются именованные области внешней (дисковой) памяти, с которыми программы могут обмениваться информацией. Необходимость в таких обменах возникает, когда объем оперативной памяти недостаточен для хранения нужной информации. В другой ситуации, например, программа может воспользоваться данными, полученными ранее другой программой и предусмотрительно записанными на диск. К числу абонентов, участвующих в обмене данными, относятся и файлы-устройства: дисплей, принтер, графопостроитель, сканер, клавиатура, каналы связи и т. п. Данный раздел посвящен работе с дисковыми файлами, хотя технология обслуживания файлов-устройств принципиально мало отличается.

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

  • выделение ресурсов и приведение файла в состояние готовности к обмену (именно это скрывается за термином "открыть файл");
  • чтение (ввод из файла) или запись (вывод в файл) очередной порции данных;
  • возврат выделенных ресурсов и завершение неоконченных операций (этому соответствует термин "закрыть файл").

Несмотря на кажущуюся простоту процесса обмена данными, файловые операции достаточно сложны в освоении. Ну как, например, не спасовать перед системной библиотекой Си, которая насчитывает более 85 функций, обслуживающих дисковые файлы, и свыше 60 констант, задающих режимы работы файловых процедур. В QBasic и Паскале количество соответствующих процедур существенно меньше, однако подводные камни встречаются и там.

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

  • "S1S2S3.. .Sk" (переменное число символов, заключенных в одинарные или двойные кавычки);
  • kS1S2.. .Sk (k — однобайтовый или двухбайтовый указатель числа символов, предшествующий тексту);
  • S1S2...Sk\0 (\0 — однобайтовый признак конца строки, расположенный вслед за последним символом текста);
  • S1S2...Sk OD ОА (двухбайтовый признак конца строки, OD - "возврат каретки", ОА - "перевод строки").

Числовая информация может быть записана в дисковый файл либо в машинном формате (а в Си и Паскале количество разных типов числовых данных достигает десятка), либо с предварительным преобразованием из машинного представления в символьное.

Кроме числовых и текстовых данных в файлах может храниться информация и другого происхождения. Например, графические изображения, которые в процессах обмена данными выступают как двоичные коды, условно разделенные на байты. Естественно, что на содержимое этих байтов нельзя реагировать так же, как на некоторые управляющие коды типа "возврат каретки", "Перевод строки", "Признак конца файла", влияющие на передачу числовой и текстовой информации.

Кроме того, существует несколько способов доступа к файловым данным, из которых на практике чаще всего используют два — последовательный и произвольный. Последний иногда называют прямым (DIRECT ACCESS) или случайным (RANDOM ACCESS). Последовательный доступ при записи на диск характерен тем, что очередная записываемая порция пристраивается в хвост к предыдущей. Размеры смежных порций при этом, могут оказаться разными по длине. При чтении такой набор данных начинает извлекаться с самой первой порции и очередность считываемых данных повторяет их последовательность во время записи.

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

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

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

Основные типы файлов в системе QBasic

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

Строковые файлы в системе QBasic

В строковом файле условной порцией хранения данных является строка переменной длины, завершаемая двумя управляющими байтами с кодами OD ("возврат каретки") и OA ("перевод строки"). Строка файла может быть либо пустой, либо содержать одно или несколько числовых и символьных значений. Символьное значение либо завершается запятой, либо заключается в двойные кавычки. Числовые значения представлены в символьном формате и завершаются либо запятой, либо пробелом. Последнее значение в файловой строке завершается парой управляющих кодов ODOA.

Строковый файл последовательного доступа для вывода открывается с помощью следующего оператора:

OPEN имя_файла FOR OUTPUT AS #k

Если мы собираемся добавлять информацию в уже существующий строковый файл, то его открывают с указанием FOR APPEND. Файл, из которого информация должна считываться, открывается с указанием FOR INPUT.

Открываемому файлу программист присваивает числовой номер к из диапазона [1,255], который впоследствии заменяет имя файла в операторах ввода (INPUT) или вывода (PRINT, WRITE). Символ tt может опускаться.

Закрывается строковый файл оператором CLOSE tk.

Вывод в строковый файл, как правило, осуществляется по оператору WRITE Ik. При таком выводе каждое текстовое значение автоматически заключается в кавычки и все данные разделяются запятыми. Чтение данных из строкового файла производится с помощью оператора INPUT #k и ничем принципиально не отличается от ввода данных из строки, набираемой пользователем на клавиатуре.

В принципе, в строковый файл можно произвести запись и по оператору PRINT#k. Однако при этом выводимый текст в кавычки не заключается и разделительные запятые между отдельными значениями не вставляются. Если в строке файла оказывается несколько текстовых и числовых значений, то их потом будет трудно извлечь. Коллизии подобного рода демонстрирует программа 7_01.bas.

Программа 7_01.bas

CLS : DEFINT A-Z: А$="Строка" OPEN "bas_txt"

FOR OUTPUT AS #1 FOR J=l TO 10

PRINT #1,A$;J,J*2

PRINT A$;J,J*2

NEXT J

CLOSE #1

PRINT

OPEN "bas_txt"

FOR INPUT AS #2

FOR J=l TO 10

INPUT #2,B$,K1,K2

PRINT B$;K1,K2

NEXT J

CLOSE #2

END

Если ограничиться только первой половиной программы, которая записывает в дисковый файл bas_txt 10 строк и попутно выдает на экран содержимое этих строк, то кажется, что все в порядке. Содержимое файла bas_txt, которое можно увидеть, нажав клавишу F3, в точности повторяет ту информацию, которая отражена на экране:

Строка 1 2

Строка 2 4

Строка 3 6

Строка 4 8

Строка 5 10

Строка 6 12

Строка 7 14

Строка 8 16

Строка 9 18

Строка 10 20

Один пробел после слова строка образовался потому, что в теле оператора PRINT разделитель "точка с запятой" после текста не изменяет текущую позицию выводной строки, но записываемые числа положительны и вместо знака "+" мы видим пробел. Вторые числа в каждой строке начинаются с 15-й позиции (там тоже находится пробел вместо знака "+"). Этот переход в начало очередной зоны вывода вызван разделителем "запятая" в списке оператора PRINT. Если переключиться в режим просмотра шестнадцатеричной информации (F3 -> F4), то можно заметить дополнительные детали: однозначные числа представлены однобайтовыми кодами ASCII (1 -> 31, 2 -> —> 32, ...), а двузначные — двухбайтовыми (10 —> 3130, 12 -> 3132, ...). Кроме того, каждая строка завершается двухбайтовым признаком конца строки -ODOA. Это означает, что при выводе числовые данные были переведены в символьный формат, а каждая порция вывода была дополнена управляющими кодами Возврат каретки + Перевод строки.

Действие второй половины программы вызывает недоумение. Во-первых, на экране появляется сообщение input paste end of file, которое свидетельствует о попытке чтения после исчерпания данных в файле. Во-вторых, переключившись на экран пользователя, вместо ожидавшихся строк с одним словом и двумя числами в каждой мы видим совсем не то:

Строка1 2 0 2

4 0 3

6 0 4

..............

Тем не менее все объясняется достаточно просто. При считывании самой первой порции в переменную в$ заносятся первые 16 символов из строки дискового файла, завершаемые признаком конца строки (BASIC-система не настолько умна, чтобы остановиться после извлечения первых шести символов). При этом управляющие байты ODOA в переменную в$ не записываются, но пропускаются. Для формирования значения числовой переменной KI данные начинают извлекаться уже из второй строки дискового файла. Но там до первого разделителя числовых значений (пробела) расположены только нечисловые символы (слово Строка), которые игнорируются, и в переменную KI ничего не поступает (KI = о). В переменную К2 попадает первое числовое значение из второй строки (к2 = 2). При следующем повторении цикла в переменную в$ заносится остаток второй файловой строки, но лидирующие пробелы при этом игнорируются (в$ = "4"). Таким образом, к десятому повторению цикла образуется недостача одного числового значения.

Однако из приведенного примера вы должны сделать правильный вывод -нельзя просто так, без всяких разделителей, смешивать в одной строке символьные и числовые данные.

На самом деле, описанные выше проблемы снимаются, если в дисковый файл числовые и символьные данные выводятся по оператору WRITE #k. Проделайте эксперимент с программой 7_02.bas и поинтересуйтесь содержимым дискового файла bas_txt:

Программа 7_02.bas

CLS : А$ = "Строка"

OPEN "bas_txt" FOR OUTPUT AS #1

FOR J%=1 TO 10

WRITE #1,A$,J%,SQR(J%)

PRINT A$,J%,SQR(J%) NEXT J%

CLOSE #1

PRINT

OPEN "bas_txt"

FOR INPUT AS #2

FOR J%=1 TO 10

INPUT #2,B$,I%,R

PRINT B$,I%,R NEXT j% CLOSE #2

END

Записеориентированные файлы в системе QBasic

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

Описание структуры записи располагается между служебными словами TYPE — END TYPE. В профамме 7_03.bas через qq обозначено наименование шаблона записи, состоящей из трех полей с именами а (6-байтовое символьное поле), п (2-байтовое поле для хранения короткого числа) и г (4-байтовое поле для хранения короткого вещественного числа). С помощью оператора DIM объявлена структурированная переменная ь типа qq, содержащая три поля с именами b.а, b.n и b.r. Каждому из этих полей можно присвоить соответствующее значение и вывести запись ь в дисковый файл:

PUT #l,,b 'Вывод значения b в текущую запись

PUT #l,5,b 'Вывод значения b в запись с номером 5

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

OPEN "bas_rec" FOR RANDOM AS #1 LEN=12

Последнее указание (LEN=12) задает длину записи в байтах, и естественно, что она должна быть равна суммарной длине всех полей структур, участвующих в обмене.

Программа 7_03.bas формирует в цикле значения полей записи ь и выводит их на диск последовательно, а затем в цикле считывает эти записи в обратном порядке, демонстрируя тем самым произвольный доступ к записям.

Программа 7_03.bas

CLS TYPE qq

a AS STRING *6

n AS INTEGER

r AS SINGLE END TYPE DIM b AS qq b.а="Строка"

OPEN "bas_rec" FOR RANDOM AS §1 LEN=12

FOR J%=1 TO 10

b.n=J% : b.r=SQR(J%)

PUT #l,,b

PRINT b.a,b.n,b.r NEXT J% CLOSE #1

OPEN "bas_rec" FOR RANDOM AS #1

FOR J%=10 TO 1 STEP -1

GET #l,J%,b

PRINT b.a,b.n,b.r

NEXT J%

CLOSE #1

END

Двоичные файлы в системе QBasic

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

OPEN имя_файла FOR BINARY AS #k

Доступ к данным в двоичном файле производится с помощью операторов PUT (вывод в файл) и GET (чтение из файла). При этом второй параметр в этих операторах обозначает номер байта дискового файла, с которого начинается обмен. Количество байтов, участвующих в обмене, определяется длиной третьего аргумента. С двоичным файлом можно работать, используя как последовательный (второй аргумент в операторах GET/PUT опущен), так и прямой (произвольный) доступ.

Программа 7_04.bas формирует в цикле и последовательно записывает в двоичный файл текст "строка", целочисленное значение счетчика цикла j%

и вещественное значение квадратного корня из j%, а затем в цикле считывает эти данные в обратном порядке.

Программа 7_04.bas

CLS : А$="Строка"

OPEN "bas_bin" FOR BINARY AS #1

FOR J%=1 TO 10

B=SQR(J%)

PUT #1,,A$: PUT #1,,J% : PUT #1,,B

PRINT A$, J%, В NEXT J% CLOSE #1 PRINT

OPEN "bas_bin" FOR BINARY AS #1

FOR J%=10 TO 1 STEP -1

GET #1,(J%-1)*12+1,A$

GET #1,(J%-1)*12+7,K%

GET #1,(J%-1)*12+9,B

PRINT A$,K%,B

NEXT J%

CLOSE #1

END

Основные типы файлов в Паскале

Подобно BASIC, в Паскале поддерживаются такие же три типа файлов -текстовые (строковые), типизированные (записеориентированные) и нетипизированные (двоичные).

Текстовые (строковые) файлы в Паскале

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

Для инициализации текстового файла необходимо объявить переменную соответствующего типа (f1:text;), связать ее с именем дискового файла (assign(f1, 'pas_txt') ;) и открыть для записи (rewrite(fl);), дозаписи (append(f1);) или для чтения (reset(f1),-). Закрывается текстовый файл Процедурой close (f1);.

Наиболее естественный профаммный способ создания текстового файла заключается в последовательной записи в него тем или иным способом сформированного значения какой-либо переменной типа string по оператору writein. На самом деле, текущую строку текстового файла можно формировать в несколько приемов, записывая туда данные последовательностью операторов write, завершая их оператором writein, который собственно и заносит признак конца строки.

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

Программа 7_01.pas демонстрирует ошибку, напоминающую сбои в работе программы 7_01.bas. Отсутствие явных разделителей между символьной и числовой информацией приводит к непредусмотренному сдвигу данных (в переменную ь считывается первая файловая строка целиком) и попытке чтения числового значения ki из начала второй строки файла. Но Паскаль в отличие от Basic жестко контролирует соответствие типов и выдает сообщение invalid numeric format (Неправильный числовой формат) на первом же операторе readin.

Программа 7_01.pas

program bad_file;

uses Crt;

var

j,kl,k2:integer;

f:text;

b:string; begin

clrscr;

assign(f,'pas_txt') ;

rewrite(f);

for j :=1 to 10 do

begin

writeln('Строка',j:4,j*2:4);

writeln(f, 'Строка', j:4,j *2:4) ;

{или write(f,'Строка');

write(f,j:4);

writeln(j*2:4};

} end;

close(f);

writeln;

reset(f);

for j:=1 to 10 do begin

readln(f,b,kl,k2);

writeln(b,kl:4,k2:4);

end;

close(f);

readln;

end.

В программе 7_02.pas ошибка исправлена за счет изменения порядка числовых и символьных данных, но два текстовых значения в пределах одной строки ничем разделить нельзя. Заключение текста в одинарные кавычки ситуацию тоже не спасает.

Программа 7_02.pas

program txt_file;

uses Crt;

var

j,k1,k2:integer;

f:text;

b:string; begin

clrscr;

assign(f,'pas_txt');

rewrite(f);

for j:=1 to 10 do

begin

writeln(f,j:4,j*2:4,'Строка':8);

writeln(j:4,j*2:4,'Строка':8);

end;

close (f) ;

writeln;

reset(f);

for j:=1 to 10 do begin

readln(f,kl,k2,b);

writeln(kl:4,k2:4,b:8);

end;

close(f);

readln;

end.

Типизированные (записеориентированные) файлы в Паскале

Шаблон записи объявляется в разделе описания типов:

type

qq=record

a:string[6];

n:integer;

r:real;

end;

Для работы с типизированным файлом вводится файловая переменная (fl:fiie of qq;), которая связывается с дисковым файлом (assign(f1, 'pas_rec') ,-). Файл для ввода открывается оператором reset (f1), а для вывода — оператором rewrite (f1),. Обмен с записеориен-тированным файлом производится операторами read/write, в списках которых можно указывать только переменные типа "запись". Записи в типизированном файле нумеруются от 0 и имеется возможность прямого доступа к любой записи. Для этого перед операцией обмена указатель файла перемещается на начало нужной записи:

seek(f1,номер_записи);

Программа 7_03.pas формирует поля записи ь (строка из 6-ти символов, короткое целое число, вещественное число) и в цикле последовательно переписывает их на диск. Затем этот же файл открывается для ввода и его содержимое считывается в обратном порядке.

Программа 7_03.pas

program rec_file;

uses Crt;

type

qq=record

a:string[6];

n:integer;

r:real; end; var

f1:file of qq; j,kl: integer;

rec:qq; d:real;

begin clrscr;

assign (fl, 'pas__rec') ;

rewrite(fl);

rec.a:='Строка';

for j:=1 to 10 do begin

rec.n:=j;

rec.r:=sqrt(j);

write(fl,rec);

writeln(rec.a,rec.n:4,rec.r:10:4);

end;

close(fl);

writeln; reset(fl);

for j:=9 downto 0 do begin

seek(fl,j);

readffl,rec);

writeln(rec.a,rec.n:4,rec.r:10:4);

end;

readln;

end.

Нетипизированные(двоичные) файлы в Паскале

Для работы с нетипизированным (двоичным) файлом необходимо объявить файловую переменную (f1:fiдe;), связать ее с именем дискового файла и открыть его для записи (rewrite (f1,n);) или для чтения (reset(f1,n);). Параметр п здесь является необязательным, по умолчанию его значение равно 128. Задает он размер порции данных, участвующих в обмене, в байтах. Для обмена с двоичными файлами используются специальные процедуры blockread И blockwrite:

blockwrite(f1,buf,n_rec,vl);

blockread(f1,buf,n_rec,vl);

Здесь buf — массив (обычно типа byte), в котором находятся данные, либо подготовленные для записи на диск, либо считанные с диска. Параметр п_гес задает количество порций, участвующих в обмене. В переменную vl заносится фактическое количество записанных или считанных порций. Несовпадение между n_гес и значением vl при чтении обычно связано с некратностью длины файла и объемом считываемых данных. При выводе такое событие обычно возникает при исчерпании дисковой памяти. Чтобы обеспечить доступ к любому фрагменту данных в двоичном файле, необходимо воспользоваться процедурой перемещения указателя файла в нужную позицию:

seek(f1,n_rec);

В двоичном файле "записью" считается порция, размер которой был установлен в момент открытия файла, и нумеруются эти записи от 0.

Программа 7_04.pas выполняет в цикле вывод 10-ти порций данных, заготавливаемых в массиве buf (размер каждой порции — 15 байт). Обратите внимание на то, каким образом в этом массиве выделяются участки памяти для хранения разнотипных данных. Нетипизированный указатель р допускает присваивание адреса любого типа, а его значение может быть присвоено любому типизированному указателю. Для текста "строка", содержащего шесть символов, в массиве buf отводится семь байт с учетом указателя длины, предшествующего строковому значению.

Программа 7_04.pas

program bin_file;

uses Crt;

var

buf:array [1..15] of byte;

f1:file;

р:pointer;

рs:^string;

pk:^integer;

pr:^rеа1;

j:integer;

begin clrscr;

p:=@buf[l];

ps:=p;

p:=@buf[8];

pk:=p;

p:=@buf[10J;

pr:=p;

assign(f1,'pas_bin') ;

rewrite(fl,15);

ps^:='Строка';

for j:=1 to 10 do begin

pk^:=j;

pr^:=sqrt(j) ;

blockwrite(f1,buf,1);

writeln (рs^,р^: 4,рг^ : 10: 4) ;

end;

close(f1);

writeln;

reset(f1,15);

for j:=9 downto 0 do begin

seek(f1,j);

blockread(f1,buf,1);

writeln(рs^,рk^:4,pr^:10:4);

end;

close(f1);

readln;

end.

Основные типы файлов в Си

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

Текстовые (строковые) файлы в Си

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

Для инициализации текстового файла необходимо завести указатель на структуру типа FILE и открыть файл в одном из нужных режимов ("rt" для ввода, "wt" - для вывода, "at" — для дозаписи в уже существующий набор данных) по оператору f open:

FILE *f1;

f1=fopen(имя_файла, "режим");

Формат оператора вывода данных в текстовый файл таков:

fprintf(f1,"список_форматов \n",список_вывода);

Если очередная строка текстового файла формируется из значения элементов символьного массива str, то вместо оператора fprintf (fi, "%s\n",str) проще воспользоваться оператором fputs (f1, str).

Чтение данных из текстового файла осуществляется с помощью оператора fscanf(f1,"список_форматов", список_ввода) или fgets(str,n,f1). В последней функции параметр n означает максимальное количество считываемых символов, если раньше не встретится управляющий байт ОА.

В Си существуют и другие возможности для работы с текстовыми файлами — функции open, creat, read, write,

Программа 7_02.с

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

main( ) {

FILE *f;

int j,k;

float d;.

char s[7]="Строка";

f=fopen("c_txt","wt");

for(j=l;j<11;j++)

{

fprintf(f,"%s %d %f\n",s,j,sqrt(j));

fprintf("\n Строка %d %n",j,sqrt(j)); }

fclose(f); fprintf("\n");

f=fopen("c_txt","rt");

for(j=10; j>0; j--) {

fscanf(f,"%s %d %f",s,&k,&d);

fprintf("\n%s %d %f",s,k,d); }

getch () ; }

Обратите внимание на возможную ошибку при наборе программы 7_01.с. Если между форматными указателями %з и %d не сделать пробел, то в файле текст "строка" склеится с последующим целым числом. После этого при чтении в переменную s будут попадать строки вида "строка!", "строка2", ..., "строкаю", в переменную k будут считываться старшие цифры корня из j (до символа "точка"), а в переменной d скажутся дробные разряды соответствующего корня.

Записеориентированные файлы в Си

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

Для инициализации записеориентированного файла необходимо завести указатель на структуру типа FILE и открыть файл в одном из нужных режимов ("rb" — для ввода, "wb" — для вывода, "ab" — для дозаписи в уже существующий набор данных) по оператору f open:

FILE *f11;

f1=fopen(имя_файла, "режим");

Формат оператора вывода данных в записеориентированный файл таков:

fwrite(buf, size_rec, n_rec, f1) ;

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

fread(buf, size_rec, n_rec, f1);

Программа 7_02.с в цикле формирует значение записи b, состоящей из символьного (b.s, 7 байт, включая нулевой байт — признак конца строки), целочисленного (b.n, 2 байта) и вещественного (b.r, 4 байта) полей, и выводит содержимое полей на диск последовательными порциями. Затем файл открывается для ввода и содержимое записей извлекается в обратном порядке. Второй аргумент функции fseek определяет величину смещения указателя файла в байтах относительно точки, заданной третьим параметром. Параметр SEEK_SET означает, что сдвиг указателя в файле производится от его начала. Для сдвига от конца файла используется константа SEEK_END, а для сдвига относительно текущей позиции — константа SEEK_CUR.

Программа 7_03.с

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <string.h>

main( ) {

FILE *fl; int j, k;

struct { char s [7]; int n; float r; } b;

clrscr();

strcpy(b.s,"Строка");

fl=fopen("c_rec","wb");

for(j=l;j<ll;j++)

{

b.n=j; b.r=sqrt(j);

fwrite(&b,sizeof(b),l,fl);

printf("\n%s %d %f",b.s,b.n,b.r);

}

fclose(fl); printf("\n");

fl=fopen("c_rec","rb");

for(j=10; j>0; j —) {

fseek(fl,(j-1)*sizeof(b),SEEK_SET);

fread(&b,sizeof(b),l,f1);

printf("\n%s %d %f",b.s,b.n,b.r);

}

getch();

}

Двоичные файлы в Си

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

Кроме набора функций {fopen/fciose, fread/fwrite}, для работы с двоичными файлами в библиотеке Borland С предусмотрены и другие средства -

{_dos_open/_dos_close, _dos_read/_dos_write}, (_creat/_close, _read/_write}.

Однако знакомство со всеми возможностями этой библиотеки в рамках настоящего пособия не предусмотрено.

Программа 7_04.с

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

main ( ) {

FILE *f1; int j, k;

char s[7]="Строка";

int n;

float r; clrscr. () ;

fl=fopen("c_bin","wb");

for(j=l;j<11;j++) {

r=sqrt(j);

fwrite(s,sizeof(s),1,f1);

fwrite(&j,sizeof(int),1,f1);

fwrite(&r,sizeof(float),1,f1);

printf("\n%s %d %f",s,j,r); }

fclose(f1l); printf("\n"); fl=fopen("c_rec","rb"); for(j=10; j>0; j--) {

fseekffl,(j-1)*(sizeof(s)+sizeof(int)+sizeof(float)),

SEEK_SET);

fread(&s,sizeof(s),1,fl);

fread(&n,sizeof(int),1,f1);

fread(&r,sizeof(float),1,f1);

printf("\n%s %d %f",s,n,r); }

getch(); }

Задачи, советы и ответы

Задание 7.05. Перекодировка текстов из MS-DOS в Windows

Составить программу перекодировки текстового файла, подготовленного редактором MS-DOS, в кодовую страницу 1251 для Windows. Следует попытаться, по возможности сохранить вид таблиц, сформированных с помощью символов псевдографики.

Совет 1 (общий)

Практически все программы перекодировки символов типа байт-в-байт используют 256-байтный словарь соответствий. По коду j перекодируемого символа из j-гo байта словаря извлекается код, заменяющий исходный символ.

Совет 2 (общий)

Для максимального сохранения вида разделительных линий таблиц предлагаются следующие замены:

  • символ одинарных горизонтальных линий на знак "минус";
  • символ двойных горизонтальных линий на знак "равно";
  • символы одинарных и двойных вертикальных линий на знак " | ";
  • все остальные символы псевдографики на знак "плюс".

Совет 3 (общий)

Вообще говоря, словари перекодировки формируются в виде массива из 256 констант. Но для того, чтобы сделать структуру словаря более прозрачной, мы сформируем его программным путем с помощью процедуры to_win.

Совет 4 (Си, Паскаль)

Имя перекодируемого файла может быть указано в качестве параметра командной строки. Если этот параметр при запуске программы перекодировки не был задан, то программа должна запросить имя файла. Имя выходного файла для простоты можно зафиксировать, например tmpwin. txt.

Программа 7_05.bas

DECLARE SUB TOWIN(T01251() AS INTEGER)

DIM T0125K256) AS INTEGER

INPUT "Задайте имя файла - ",SOURCE$

TOWIN T01251()

OPEN SOURCE$ FOR INPUT AS #1

OPEN "TMPWIN.TXT" FOR OUTPUT AS #2

DO WHILE NOT EOF(l)

LINE INPUT #1, A$

FOR J=l TO LEN(A$)

MID$ (A$, J, 1) =CHR$ (T01251 (ASC (MID$ (A$, J, 1) ) ) )

NEXT J

PRINT #2, A$

LOOP

CLOSE 1,2

END

SUB TOWIN(T012510 AS INTEGER)

' Сохраняем первую половину таблицы ASCII

FOR J=0 TO 127: T01251(J)=J: NEXT J

' Увеличиваем на 64 коды букв от "А" до "n"

FOR J=128 TO 175: Т01251{J)=J+64: NEXT J

' Заменяем все символы псевдографики знаком "+"

FOR J=176 TO 223: Т01251(J)=ASC("+"): NEXT J

' Заменяем одинарную вертикальную черту

t01251(179)=ASC("|")

' Заменяем двойную вертикальную черту

t01251(186)=ASC("|")

' Заменяем одинарную горизонтальную черту

t01251(196)=ASC("-")

' Заменяем двойную горизонтальную черту

t01251(205)=ASC("=")

' Увеличиваем на 16 коды букв от "р" до "я"

FOR J=224 TO 239: Т01251(J)=J+16: NEXT J

t01251(240)=168: ' Заменяем код буквы "ё"

t0125К241) =184: ' Заменяем код буквы "Ё"

END SUB

Программа 7_05.с

#include <stdio.h>

#include <conio.h>

#include <string.h>

void to_win(void);

unsigned char to!251[256];

main(int narg, char **argv) {

FILE *fin,*fout;

unsigned char str[80],source[80];

int j,len;

if(narg==2) strcpy(source,argv[l]);

else {

printf("\n Задайте имя исходного файла - ");

scanf("%s",source); }

to_win();

fin=fopen(source,"rt");

fout=fopen("tmpwin.txt","wt");

while (Ifeof(fin)) {

fgets(str,80,fin);

len=strlen(str);

for(j=0; j<len; j++)

str[j]=to!251[str[j]];

fputs(str,fout);

}

fcloseall() ;

}

/*------------------------------------*/

void to_win(void) {

/* Формирование словаря перекодировки из MS-DOS в Windows */

int i ; /* Сохраняем первую половину таблицы ASCII */

for (i=0; i<128; i++) to1251[i]=i;

/* Увеличиваем на 64 коды букв от "А" до "n" */

for(1=128; i<176; i++)

to1251[i]=i+64;

/* Заменяем все символы псевдографики знаком "+" */

for(1=176; i<224; i++) to1251 [!] = ' + ';

и

/* Заменяем одинарную вертикальную черту */

to1251[179]='|'; /* Заменяем двойную вертикальную черту */

to1251[186]='|'; /* Заменяем одинарную горизонтальную черту */

to1251[196]='-'; /* Заменяем двойную горизонтальную черту */

to1251[205]='='; /* Увеличиваем на 16 коды букв от "р" до "я" */

for(i=224; К256; i++)

to1251[i]=i+16;

to1251[2401=168; /* Заменяем код буквы "ё" */

to1251[241]=184; /* Заменяем код буквы "Ё" */

return; }

Программа 7_05.pas

program translate; var

to1251:array [0..255] of char;

fin,fout:text;

str:string[803;

source:string[80];

j,len:integer; procedure to_win;

var

i:integer; begin

for i:=0 to 127 do

to1251[i]:=chr(i);

for i:=128 to 175 do

to1251[i]:=chr(1+64);

for i:=176 to 223 do

to1251[i]:='+';

to1251[179]:='|';

to1251[186]:='|';

to1251[196]:= '-';

to1251[205]:= '=';

for i:=224 to 239 do

to1251[i]:=chr(i+16);

to1251[240]:=chr(168);

to1251[241]:=chr(184);

end;

begin

if(ParamCount=2) then source:=ParamStr(1)

else begin

write('Задайте имя исходного файла - ');

readln(source);

end; to_win;

assign(fin,source);

reset(fin);

assign(fout, 'tmpwin.txt');

rewrite(fout);

while (not eof(fin)) do begin

readln(fin,str);

len:=length(str);

for j:=0 to len do

str[j]:=to1251[ord(str[j])];

writeln(fout,str);

end;

close (fin);

close(fout);

end.

Задание 7.06. Телефонный справочник

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

Совет 1 (QBasic)

Для записи двух символьных значений в одну строку текстового файла разумно воспользоваться оператором WRITE #, т. к. в этом случае мы получим доступ к каждой компоненте строки дискового файла.

Совет 3 (Паскаль)

Паскаль не позволяет открыть несуществующий файл на дозапись. Поэтому перед обращением к процедуре append приходится отключать системный контроль за ошибками ввода/вывода. Если открытие на дозапись не состоялось, то в первый раз приходится открывать файл notebook на запись. При чтении два текстовых значения в одной файловой строке здесь, конечно, можно разделить — телефон занимает фиксированное количество позиций в конце строки, между фамилией и телефоном находится какое-то количество пробелов. Однако для вывода на экран это не требуется.

Программа 7_06.bas

CLS

OPEN "notebook" FOR APPEND AS #1

'Ввод данных с клавиатуры

DO

INPUT "ФАМИЛИЯ: ",Name$

INPUT "ТЕЛЕФОН: ",Phone$

WRITE #1, Name$,Phone$

INPUT "Добавим ";R$

LOOP WHILE LEFT$(R$,l)="f1"

CLOSE #1

CLS

OPEN "notebook" FOR INPUT AS #1

PRINT "Список абонентов в файле:"

DO WHILE NOT EOF(l)

'LINE INPUT #1, NP$ ' Чтение строки целиком

'PRINT NP$ ' Вывод данных на экран

INPUT #1, А$, В$ ' Чтение строки по компонентам

PRINT А$, В$' ' Вывод данных на экран

LOOP

CLOSE #1

END

Программа 7_06.с

#include <conio.h>

#include <stdio.h>

main() {

FILE *f;

int k;

char r,Name[20],Phone[10]; clrscr();

'f=fopen("notebook","at");

m: /* Ввод данных с клавиатуры */

printf("\n ФАМИЛИЯ: ");

scanf("%s",Name); printf("ТЕЛЕФОН: ");

scanf("%s",Phone);

fprintf(f,"%-20s %10s\n",Name,Phone);

printf("Добавим (д/н) - ");

r=getche(); if(г=='д')

goto m;

fclose(f);

clrscr();

f=fopen("notebook","rt");

printf("\пСписок абонентов в файле:\n") ;

while(!feof (f)) {

fscanf(f,"%20s %10s\n",Name,Phone);

printf("%-20s %10s\n",Name,Phone); }

fclose(f);

getch(); }

Программа 7_06.pas

program notebook;

uses Crt;

var

f:text;

R,Name,Phone:string; begin

clrscr;

assign(f, 'notebook ');

{$!-} append(f); {$!+}

if IOResult <> 0 then rewrite(f);

{ Ввод данных с клавиатуры }

repeat

write('ФАМИЛИЯ: ');

readln(Name);

write('ТЕЛЕФОН: ');

readln(Phone);

writeln(f,Name, ' ':15-length(Name), Phone:10);

write{'Добавим ');

readln(R);

until r[l] <> 'д ';

close(f);

clrscr;

reset(f);

writeln('Список абонентов в файле: ');

repeat

readln(f,r);

{ Чтение строки целиком } writeln(r);

{ Вывод данных на экран } until eof(f);

close(f);

readln;

end.

Задание 7.07. Создание резервной копии файла

Написать программу, которая извлекает из командной строки имя файла и создает в том же каталоге резервную копию файла с расширением bak.

Совет 1 (общий)

Чтобы избежать нежелательного влияния управляющих символов с кодами о о ("Возврат каретки"), ОА ("Перевод строки"), 1А ("Признак конца файла") целесообразно рассматривать резервируемые данные независимо от их происхождения как двоичный файл.

Совет 2 (QBasic)

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

Совет 3 (Си)

Функция strchr (namel,'.') определяет позицию точки в имени исходного файла. Точнее, ее значение равно указателю на позицию точки, если таковая в имени namel содержится. В противном случае функция strchr возвращает NULL. В первом случае с помощью функции strncpy в строку name2 копируется начало имени до символа '.'и скопированная часть имени принудительно завершается признаком конца строки (символом 0x0).

Совет 4 (Паскаль)

Отключение системного контроля за ошибками ввода/вывода ({$!-}) перед открытием исходного файла сделано для того, чтобы взять на себя проверку несостоявшейся операции и выдать пользователю более осмысленное сообщение. Функция fsplit расчленяет полную спецификацию файла на путь до

каталога, содержащего исходный файл (Dir), собственно имя файла (Name) и его расширение (Ext). Программа будет работать быстрее, если размер буфера для копирования очередной порции увеличить до 32 768 (кратность 512 здесь желательна, т. к. это число совпадает с длиной физического сектора).

Программа 7_07.bas

CLS : DIM k AS STRING*!

INPUT "Задайте имя файла - ", NAME1$

OPEN NAME1$ FOR BINARY AS #1

К = INSTR(NAME1$, ".")

IF К = 0 THEN NAME2$ = NAME1$

ELSE NAME2$ = LEFT$(NAME1$, К - 1)

NAME2$ = NAME2$ + ".ВАК"

PRINT NAME2$, К

OPEN NAME2$ FOR BINARY AS §2

DO

GET fl, , k

PUT #2, , k

LOOP UNTIL (EOF(l))

END

Программа 7_07.с

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define N 512

main(int narg, char **argv) {

FILE *f1,*f2;

int rd,wr;

char buf[N];

char namel[80],name2[80];

char *point;

if(narg < 2)

{ printf("\n He задано имя файла"); exit(0); }

strcpy(namel,argv[l]);

fl=fopen(namel,"rb");

if(fl==NULL)

{ printf("\n Файл %s не найден",namel); exit(0); }

point=strchr(namel, '. ');

if(point) {strncpy(name2,namel,point-namel};

name2[point-namel]=0x0; }

else strcpy(name2,namel); strcat(name2,".bak");

f2=fopen(name2,"wb");

do {

rd=fread(buf,l,N,fl);

wr=fwrite(buf,1,rd,f2); }

while (rd!=0 && rd == wr) ;

fclose(fl);

fclose(f2); }

Программа 7_07.pas

program reserve; uses Dos; const

N=512; var

fl,f2 : file;

rd,wr : word;

buf : array [1..N] of byte;

namel, name2 : PathStr;

dir : DirStr;

name : NameStr;

ext : ExtStr; begin

if ParamCount=0 then

begin writeln('He задано имя файла');

exit;

end;

namel:=ParamStr(1);

assign(f1,name1);

{$!-} reset(f1,l); {$I+}

if IOResult <> 0 then

begin writeln('Файл ',namel, ' не найден');

exit;

end;

fsplit(namel,dir,name,ext);

name2:=dir+name+'.bak';

assign(f2,name2);

rewrite(f2,1); repeat

blockread(fl,buf,N,rd);

blockwrite(f2,buf,rd,wr);

until (rd=0) or (rd <> wr) ;

close(fl);

close(f2);

end.

Задание 7.08. Выдача каталога на экран

Составить программу, которая извлекает из текущего каталога имена файлов с заданным расширением (например, *.pas) и выводит их на экран подобно директиве MS-DOS (dir *.pas), суммируя количество и длину обнаруженных файлов.

Совет 1 (QBasic)

Система QBasic не включает в свой состав функции или процедуры для работы с файловой системой MS-DOS, присущие алгоритмическим языкам более высокого уровня типа Си или Паскаля. Поэтому мы ограничимся тем, что из программы на Basic можно выполнить любую директиву операционной системы по оператору SHELL. Программа 7_07.bas решает поставленную задачу, однако имена файлов из текущего или указанного каталога остаются недоступными для последующей программной обработки.

Совет 2 (Си)

Для извлечения файлов из текущего или указанного каталога можно воспользоваться функциями findfirst(char *path, struct ffblk sr, int attr) и findnext (struct ffblk sr}. Аргумент path является указателем на маску отбора файлов (например — "*.с"), расширенную спецификацию маски (например — "с: \bc\source\* .с") или просто путь (например — "c:\bc"). Аргумент sr представляет собой структуру типа ffblk, на поля которой заносятся атрибуты найденного набора. Эта структура описана в файле dir. h и имеет следующие поля:

struct ffblk {

char ff_reserved[21]; //резерв на будущее

char ff_attrib; //атрибут набора данных

unsigned ff_ftime; //упакованное время создания

unsigned ff_fdate; //упакованная дата создания

long ff fsize; //длина набора в байтах

char ff_name[13]; //имя набора

};

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

  • FA_RDONLY отбирать файлы с признаком только для чтения;
  • FA_HIDDEN — отбирать файлы с признаком "Скрытый";
  • FA_SYSTEM —отбирать системные файлы;
  • FA_LABEL — отбирать наборы с меткой "Идентификатор тома";
  • FA_DIREC — отбирать каталоги;
  • FA_ARCH — отбирать файлы с признаком "Архивировать".

Цикл поиска нужных наборов данных начинается с обращения к процедуре f indf irst (поиск первого объекта) и повторяется многократными обращениями к процедуре findnext (поиск следующего объекта). Аргумент sr в обоих обращениях должен быть один и тот же. Цикл поиска продолжается до тех пор, пока функция поиска возвращает нулевое значение. Как только очередная попытка окажется неудачной, функция поиска возвращает значение -1.

Совет 3 (Си)

Самый кропотливый момент в программе связан с выводом упакованных значений времени и даты последнего обновления файла, т. к. в системной библиотеке Си подходящей функции распаковки нет. Поэтому в программе 7_07.с введены две структуры битовых полей, эквивалентные способам упаковки даты (struct dat) и времени (struct tim). Обратите внимание на последовательность описания битовых полей — от младших разрядов слова к старшим (а не наоборот). С помощью объединения union данные указанных структур накладываются на беззнаковые целочисленные (unsigned) переменные, в которые предварительно переписываются упакованные данные. Последующее извлечение содержимого битовых полей использует соответствующие составные имена, например из упакованного значения даты v.d извлекаются разряды, в которых находится номер дня v.c.day. Единственное изменение содержимого поля v.с.year связано с тем, что функции поиска в MS-DOS возвращают значение года, уменьшенное на 1980.

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

Совет 4 (Паскаль)

Для извлечения файлов из текущего или указанного каталога можно воспользоваться процедурами FindFirst(path, .attr, sr) И FindNext(sr), вклю

ченными в состав модуля DOS. Аргумент path задает маску отбора файлов (например, "*.pas"), расширенную спецификацию маски (например, "с: \tp\source\* .раs") или просто путь (например, "c:\tp"). Второй аргумент определяет атрибут, которым должен быть снабжен отбираемый набор данных, и может принимать логическую сумму из следующих значений, задаваемых в Паскале мнемоническими константами:

  • Readonly - отбирать файлы с признаком "Только для чтения";
  • Hidden - отбирать файлы с признаком "Скрытый";
  • SysFile - отбирать системные файлы;
  • VolumeID - отбирать наборы с меткой "Идентификатор тома";
  • Directory - отбирать каталоги;
  • Archive - отбирать файлы с признаком "Архивировать";
  • AnyFile - брать любые файлы.

Аргумент sr представляет собой запись типа searchRec, на поля которой заносятся атрибуты найденного набора. Этот тип описан в модуле DOS и имеет следующую структуру:

type

SearchRec=record

Fill:array [1..21] of byte;

Attr:byte; {атрибут набора данных}

Time:longint; {упакованные дата и время создания}

Size:longint; {длина набора в байтах}

Name:string[12]; (имя набора} end;

Цикл поиска нужных наборов данных начинается с обращения к процедуре FindFirst (поиск первого объекта) и повторяется многократными обращениями к процедуре FindNext (поиск следующего объекта). Аргумент sr в обоих обращениях должен быть один и тот же. Цикл поиска продолжается до тех пор, пока системная переменная DosError принимает нулевое значение. Как только очередная попытка окажется неудачной, в DosError заносится 18.

Совет 5 (Паскаль)

Распаковка даты и времени осуществляется с помощью системной процедуры UnpackTime, первым аргументом которой является упакованный набор, возвращаемый процедурами поиска, а вторым — запись с распакованными полями типа DateTime, описанная в модуле Dos. Для придания единообразия колонкам с числовыми данными полезно малые (k < 10) числа дней, месяцев, часов, минут и секунд предварять лидирующим нулем. В случае необходимости такая добавка выполняется функцией Zero.

Программа 7_08.bas

SHELL "dir *.bas" END

Программа 7_08.с

#include <stdio.h>

#include <conio.h>

#include <dir.h>

#include <dos.h>

main() {

struct ffblk sr;

int k,nf=0;

long lf=0;

struct tim

{

unsigned sec:5;

unsigned min:6;

unsigned hour:5;

};

struct dat {

unsigned day:5;

unsigned month:4;

unsigned year:7;

};

union {struct tim a;

unsigned b;}u;

union {struct dat c;

unsigned d; }v;

clrscr();

printf("Имя файла Длина Дата Время");

k=findfirst("*.c",&sr,FA_ARCH);

while (k==0) {

u.b=sr.ff_ftime; v.d=sr.ff_fdate; nf++;

lf+= sr.ff_fsize;

printf("\n%12s %61d ",sr.ff_name,sr.ff_fsize) ;

printf(" %02d/%02d/%4d ",v.c.day,v.c.month,1980+v.c.year);

printf(" %02d:%02d",u.a.hour, u.a.min);

k=findnext(Ssr); }

printf("\n%d файлов занимают %ld байт",nf,If);

getch(); }

Программа 7_08.pas

program dir;

uses Crt,Dos; var

dt:DateTime;

sr:SearchRec;

const

nf:integer=0;

If:longint=0;

function Zero(k:byte):string;

var

s: string;

begin

Str(k,s);

if k>9 then Zero:=s else Zero:= '0 '+s; end;

begin clrscr;

writeln('Имя файла Длина Дата Врекя');

FindFirst('*.pas',AnyFile,sr);

while DosError=0 do

begin with sr,dt do

begin inc(nf);

If:=lf+Size;

UnpackTime(Time, dt);

write (Name:12, ' ',Size:6, ' ');

write(Zero(Day), '/',Zero(Month), '/',Year:4, ' ');

writeln(Zero(Hour), ': ',Zero(Min), ': ',Zero (Sec)); end;

FindNext(sr); end;

writeln(nf, ' файлов занимают ',1f, ' байт');

readln; end.

Задание 7.09. Сдвиг содержимого текстового файла

Составить программу сдвига содержимого каждой строки текстового файла на заданное число позиций вправо. Это может оказаться полезным для формирования левого поля нужной ширины перед выводом содержимого файла на принтер по командам операционной системы PRINT или COPY.

Совет 1 (QBasic)

Для считывания строк из текстового файла нужно использовать оператор LINE INPUT, иначе головные пробелы в считываемых строках будут игнорироваться и каждая строка будет обрезана по первому же -пробелу после значащих символов.

Совет 2 (Си)

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

Совет 3 (Паскаль)

В приведенных ниже двух вариантах программы использованы два разных подхода к чтению данных из текстового файла. В программе 7_08.pas очередная строка исходного файла считывается целиком и также целиком переписывается в выходной файл. Предварительно в ту же запись заносится п пробелов за счет указателя ширины поля после текстового операнда, содержащего единственный пробел. В программе 7_08a.pas, алгоритм которой менее рационален, строка исходного файла читается посимвольно до тех пор, пока функция eoln (f1) не обнаруживает признак конца строки. Такой прием может оказаться полезным, если строка текстового файла содержит несколько разнотипных компонент и разбираться с ними приходится, анализируя каждый символ. Однако и в этой ситуации целесообразно прочитать строку целиком и устроить аналогичную разборку в оперативной памяти.

Программа 7_09.bas

INPUT "Задайте имя исходного файла - ", NAME1$

INPUT "Задайте имя выходного файла - ", NAME2$

INPUT "Задайте величину сдвига - ", N%

OPEN NAME1$ FOR INPUT AS #1

OPEN NAME2$ FOR OUTPUT AS #2

DO WHILE NOT EOF(l)

LINE INPUT #1,A$: A$=SPACE$(N%)+A$

PRINT #2,A$

LOOP

CLOSE II: CLOSE #2

END

Программа 7_09.c

#include <stdio.h>

#include <stdlib.h>

void ind_copy(FILE *fl,FILE *f2,int n);

main(int narg, char **argv) {

FILE *fl,*f2; int n;

if(narg < 4) {

printf("\n Ошибка. Должно быть :"};

printf("\n7_09.exe файл1 файл2 n");

exit(0); }

f1l=fopen(argv[l],"rt");

f2=fopen(argv[2],"wt");

n=atoi(argv[3]};

ind_copy(fl,f2,n);

fcloseall();

}

/*-------------------------------------*/

void ind_copy(FILE *fl,FILE *f2,int n) {

char str[80]; int j;

for(j=0; j<n; j++) str[j]=' ';

while (Ifeof(fl)) {

fgets(&str[n],80,fl);

fputs(str,f2); }

return; }

Программа 7_09.pas

program indent;

var

f1,f2:text;

namel,name2:string;

n,k:integer;

procedure ind_copy(var fl,f2:text;n:integer);

var

str:string; begin

while not eof(fl) do

begin

readln(fl,str);

writeln(f2, ' ':n,str);

end;

end;

begin

if ParamCount < 3 then begin

writeln('Параметры заданы неверно. Должно быть: ');

writeln('7_09.exe файл1 файл2 сдвиг ');

exit;

end;

namel:=ParamStr(1);

name2:=ParamStr(2);

Val(ParamStr(3),n,k);

assign(fl,namel);

reset (fl);

assign(f2,name2);

rewrite(f2);

ind_copy(f1,f2,n);

close(f1);

close(f2); end.

Программа 7_09a.pas

program indent1; var

f1,f2:text; namel,name2:string;

n,k:integer;

procedure ind_copy(var f1,f2:text;n:integer) ;

var

ch:char;

begin

while not eof(fl) do begin

write(f2, ' ':n);

while not eoln(f1) do begin

read(fl,ch); write(f2,ch);

end;

readln(f1); writeln(f2);

end;

end;

begin

if ParamCount < 3 then begin

writeln("Параметры заданы неверно. Должно быть: ');

writeln('7_09a.exe файл! файл2 сдвиг ');

exit;

end;

narnel:=ParamStr(1);

name2:=ParamStr(2);

Val(ParamStr(3),n,k);

assign(fl,namel);

reset(f1);

assign(f2,name2);

rewrite(f2);

ind_copy(f1,f2,n);

close(fl);

close(f2);

end.

 

<< НазадСодержаниеВперед >>

 

®Сайт разработал: Nek по вопросам пишите сюда NekSuper@yandex.ru
 
Hosted by uCoz