Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.207.160.209] |
|
Сообщ.
#1
,
|
|
|
Работа с файлами это объемная тема. И совсем не просто охватить ее целиком и полностью в одном топике. Однако я попытаюсь.
Для того, чтобы работать с файлами в Паскале мы сначала должны определить тип информации, с которым мы собираемся работать. То есть, мы должны сразу же решить, какого типа информацию содержит (или будет содержать) файл. Это нам нужно сделать потому, что Паскаль поддерживает три разных подхода для работы с файлами (забегая далеко вперед - для знающих Паскаль - я уточню, что я намерено опускаю работу с потоками, поскольку это данном случае только усложнит ситуацию, и потоки целесообразнее рассмотреть в отдельном топике). И для разного типа информации желательно использовать наиболее подходящие подходы. Паскаль умеет рассматривать информацию, находящуюся в файлах с таких точек зрения: Итак, когда Вы определитесь, каким образом Вы собираетесь работать с файлом, тогда можно начинать работу. В самом начале нужно знать, что Паскаль не работает с файлом напрямую. Он работает с так называемым дескриптором файла. Дескриптор файла - это переменная, которая описывает конкретный файл, который Вы собираетесь обрабатывать. Дескриптор Вы должны создать сами, точно так же как Вы описываете любую другую переменную. Тип дескриптора будет определять, каким именно образом Вы собираетесь работать с файлом (один из трех способов, описанных выше). Есть три типа (в соответствии с тремя способами): Когда мы опишем такой дескриптор, у нас появится переменная, которую мы сможем указывать при работе с файлом, и она будет однозначно определять конкретный файл. Вот примеры описания файловых дескрипторов: type OnePhoneData=record { Запись-описание абонента } Number:longint; { Его телефон } Name:string[100]; { Его ФИО } Address:string[200]; { Его адрес } end; var TextFile:text; { Файл с текстовой информацией } PhoneFile: file of OnePhoneData; { Файл с записями типа OnePhoneData } SomeData: file; { Файл с неопределённой информацией } Values: file of longint; { Файл, состоящий из чисел (в их машинном представлении) } В этом примере описаны четыре различных файловых дескриптора. Каждый из них определяет различные типы информации, содержащиеся в файле. После того, как мы написали такие дескрипторы мы можем начинать работать с файлом. Для любого файла нужно выполнять последовательность действий в процессе работы. Вот эта последовательность: Менять местами пункты нельзя. Рассмотрим каждый по очереди, но прежде я хочу сделать небольшое отступление по поводу работы с информацией в файле: Для каждого файла при чтении или записи информации есть такое понятие, как текущее положение в файле. Текущее положение в файле это то положение, в котором на данный момент идет обработка информации. То есть Вы же понимаете, что вся информация состоит из частей, и все их сразу обрабатывать невозможно. Мы разбиваем всю информацию файла на одинаковые по размеру блоки, и далее работаем с этими блоками. Например если файл текстовый - то по буквам. Если файл типизированный - то по записям. Если нетипизированный - то по группам байт (нетипизированным записям). Так вот к примеру в типизированном файле мы можем прочесть первую запись, затем вторую, затем третью и так далее. И тогда текущим положением в файле сначала будет первая запись, потом вторая (когда мы первую уже прочтем), затем третья. В большинстве случаев за этим положением следить нет необходимости. То есть, после чтения из файла текущее положение автоматически изменяется на следующее. И так можно читать последовательно каждую запись, не заботясь о текущем положении. Но забегая вперед скажу, что иногда полезно бывает изменить текущее положение в файле, для того чтобы переписать к примеру запись в середине файла, когда мы уже находимся в конце. Чтобы исчерпать вопрос скажу, что текущее положение может быть началом файла, или самым концом файла. Если текущее положение уже конец файла, то последующее чтение из него ни к чему не приведет. А вот запись в него увеличит размер файла на записываемый кусок информации, после чего текущее положение изменится, и будет опять указывать в конец файла. Ну а теперь по пунктам работы с файлом: 1. Связь дескриптора и конкретного файла на компьютере После выполнения этих действий дескриптор уже не будет абстрактной переменной. Он будет обозначать конкретный файл на компьютере. А тип этого дескриптора будет определять, каким образом интерпретировать (понимать и обрабатывать) информацию в этом файле. Для дескрипторов любого типа связь с файлом осуществляется одной командой: Assign(<имя_переменной_дескриптора>, <строка_имя_файла>); <имя_переменной_дескриптора> - это, собственно, и есть дескриптор, который мы объявили ранее; <строка_имя_файла> - это либо константа, либо переменная-строка, содержащая в себе имя файла, с которым мы хотим осуществить связь. В этой строке может также указываться и путь к файлу. Если он не указан, то считается, что файл находится в той же папке, что и программа. Вот несколько разных примеров: const filename1='myfile.txt'; type Rec = record tel: longint; name: string; addr: string; end; var f:file of byte; tel:file of Rec; dat:text; s:string; begin s := 'd:\telbase'; assign(f,'ByteData.dat'); { Теперь переменная f - это файл bytedata.dat, и состоит из байт } assign(tel,s+'\Phones.db'); { tel это файл с телефонами и обонентами. Находится в d:\teldata\phones.dat } assign(dat,filename1); { dat - это текстовый файл myfile1.txt } ... {Тут идёт работа с файлами} end. Надеюсь у вас не возникло вопросов. Потому что мы переходим к следующей более сложной части: 2. Открытие файла на чтение и/или запись После того, как мы связали дескриптор файла с конкретным файлом этот дескриптор у нас уже считается тем самым файлом. Для того, чтобы работать с информацией, содержащейся в нем, нам нужно его открыть. Без этого ни запись, ни чтение информации из файла невозможно, и если забыть этот пункт, то ваша программа скорее всего завершится внутренней ошибкой. Файлы нужно открывать для того, чтобы указать системе, что Вы в данный момент используете файл. И тогда другие программы (если у вас windows) или резиденты (если DOS) не смогут менять содержимое файла, так как после его открытия к содержимому файла имеете доступ только вы. Именно когда Вы открываете файл, Вы определяете, каким образом Вы собираетесь манипулировать с информацией. Например Вам может быть нужно писать в файл, или читать из файла. А может быть и читать и писать сразу. Любая команда открытия файла открывает его, после чего в него можно писать и читать информацию. Но правда есть исключения - в некоторых случаях можно только читать, в некоторых только писать. Всего существует три команды открытия файлов. И хотя каждая позволяет и читать и писать, но чаще всего в силу их специфики одни используются исключительно для записи, другие исключительно для чтения, хотя я хочу сделать акцент на том, что это не является обязательным правилом, а во многих случаях правильнее и писать и читать из файла поочередно, не переоткрывая его разными командами. Один и тот же файл нельзя открыть одновременно двумя разными способами. Первая команда: Rewrite Эта команда создает (если файла с таким именем еще нет) и открывает файл. Синтаксис: Rewrite(<название_дескриптора_файла>[, <размер_записи>]); Как видно из формата, в этой команде следует указать дескриптор файла, который мы хотим открыть. Параметр <размер_записи> можно задавать только если дескриптором файла является дескриптор нетипизированного файла (то есть если дескриптор задан как f: file). В дескрипторе файла такого типа размер информации не указан. Но считается что файл состоит из байтов. Так вот этот параметр указывает, сколько байт можно прочесть либо записать в файл за один раз. Например: Если указать там цифру 10, то из нетипизированного файла командами чтения или записи будет читаться или записываться сразу по 10 байт. Меньше уже прочесть или записать за раз нельзя будет. Если нужно меньше - придется открывать файл заново. Если дескриптор файла является нетипизированным, и этот параметр не указан, то считается, что размер записи - 128 байт. Это значит, что читать или писать в этот файл можно только по 128 байт сразу. (Примечание: Для ТМТ паскаля размер записи если его не указывать будет 1 (один) байт. Хотя в справке указано 128. Это ошибка. Имейте в виду). Команда rewrite Открывает файл связанный с указанным дескриптором. Если такого файла еще нет, то она создает его на носителе информации. После открытия она устанавливает позицию, в которой идет обработка данных в самое начало. То есть, после каждой команды rewrite работа с информацией в файле будет идти с самого начала. Также (важно, запомните!!!) эта команда напрочь удаляет любую информацию, которая была раннее в файле. То есть, если этот файл существовал в момент его открытия таким образом, то вся информация, которая в нем была, будет безжалостно стерта, и он станет пустым. Командой следует пользоваться для создания новых файлов, или если Вы хотите заново заполнить какой либо информацией старый файл. После открытия файла в файл можно записывать любую информацию. А так же ее читать (только если файл открыт как типизированный или нетипизированный (из текстовых файлов после открытия таким образом читать нельзя)). Вам может показаться абсурдной мысль о чтении информации из файла, открытого таким образом, Вы можете спросить: "Как же можно читать информацию, если файл становится чистым и пустым, и в нём уже нет ничего?" Я сразу вас успокою, и скажу, что далее мы рассмотрим перемещение (навигацию) по файлам, и дам ответ на этот вопрос. Кратко скажу, что можно к примеру записать в файл некоторый блок информации, затем вернуться назад, в какую либо часть этого блока, и прочитать или переписать часть этого блока (или весь блок) новой информацией. Следующая команда: Reset Эта команда открывает файл, связанный с дескриптором, и позволяет читать или писать в него информацию (на запись устанавливаются ограничения - см. далее). Файл должен уже существовать. Иначе может возникнуть ошибка (подробнее об отслеживании существования файла читайте ниже). Синтаксис: Reset(<название_дескриптора_файла>[, <размер_записи>]); Как видите, полная аналогия с командой rewrite. Скажу вам сразу, что тут не будет ничего нового, так как в любой команде открытия файлов в паскале будет именно такой формат. Те же параметры: <название_дескриптора> - это дескриптор файла, который следует открыть для чтения. <размер_записи> - это размер записи для файлов, чей дескриптор описан как File (нетипизированный). Этот параметр подчиняется тем же правилам что и в команде rewrite. Позиция в файле открытом таким образом будет установлена на начало файла. Поэтому если мы открыли файл этой командой, то начнем читать всю информацию с самого начала (об этом часто забывают на первых парах. Например если мы сначала открыли файл командой reset, прочли часть информации, а затем переоткрыли его этой же командой, то мы при последующем чтении будем читать не информацию, идущую далее, а, опять же, с самого начала). Запись в файл, открытый таким образом можно производить только в том случае, если файл открыт как типизированный или нетипизированый. Записи в текстовый файл, открытый этой командой произвести нельзя. Теперь на очереди еше одна команда: Append Формат такой же как и у двух предыдущих команд с соблюдением всех правил и указаний. Открыв файл этой командой мы получаем возможность писать в него как и в команде reset. Разница между ними в двух вещах: после открытия файла таким образом читать информацию из него нельзя (есть исключения, читай далее), и второе - последующая запись в файл будет писать информацию после уже существующей в нем. Ну если конечно файл был пустым, то команда будет работать аналогично rewrite и reset. В процессе тестирования я выяснил, что эта команда в Borland Pascal'е к моему великому сожалению работает только с текстовыми файлами. Это значит что типизированные или нетипизированные файлы при помощи нее открыть нельзя. В отличие от этого паскаля компилятор ТМТ Pascal 3.9Lite позволяет открывать данной командой любой из трех типов файлов. Поэтому пользуясь компилятором ТМТ можно открыв файл таким образом производить как запись, так и чтение записей, перемещаясь по файлу (если файл не текстового типа) командой seek. Следует сказать, что файл должен существовать на носителе информации. В противном случае может возникнуть ошибка, а может и не возникнуть (читайте далее). Так-же есть команда, о которой я не могу не сказать, так как она очень тесно связана с открытием файлов. Truncate Синтаксис: Truncate(<название_дескриптора_файла>); Ее нельзя вызывать для файлов, не открытых ранее какой либо вышеописанной командой. Эта команда обрезает всю информацию в файле после текущей позиции. Например если мы находимся в середине файла, то все, что после того места где мы находимся будет удалено. И текущим положением в файле станет конец файла. Эта команда не работает на файлах, открытых как текстовые. Теперь поговорим об очень важных директивах: {$I+} и {$I-} Про директивы паскаля можно почитать тут: Директивы транслятора BP7 Эта директива включает или выключает автоматическую проверку ошибок ввода/вывода. Когда такая проверка включена, если возникает ошибка ввода/вывода, то выполнение программы завершается с сообщением о произошедшей ошибке. Но можно и отключить автоматическую проверку. И тогда на нас ляжет необходимость обрабатывать и корректировать соответствующие ошибки самим. Для чего нам это нужно? А очень просто. Типичной ошибкой ввода/вывода является то, что файл не найден. И если мы отключим автоматическое определение ошибок, то мы можем сами конкретно обработать вариант, когда файл, который мы пытаемся открыть не найден. Включить автоматическую проверку можно написав в коде программы так: {$I+}. Отключить, соответственно, можно так: {$I-} Если автоматическая проверка отключена, то результат операции ввода/вывода помещается в переменную IOResult (Input/Output), которая имеет тип Integer. Например при открытии файла туда помещается какое либо значение в зависимости от того, удалось ли открыть файл или нет. Если открытие произошло успешно, то в этой переменной будет находиться 0. Иначе в эту переменную будет записан код ошибки ввода/вывода, которая произошла. К сожалению я не нашел значений, и соответствующие им ошибки. Но нам этого и не нужно. Достаточно сравнить переменную IOResult с нулем после попытки открыть файл, чтобы узнать, открылся ли файл, или нет. Вот пример: var f:text; name:string; begin write('Введите имя файла, который мы попытаемся открыть: '); readln(name); { В данный момент если файла нет, то ошибки не произойдет. Ведь файл еще не пытались открыть } assign(f, myname); {$I-} { - отключим стандартную проверку ошибок ввода/вывода} reset(f); {попробуем открыть файл} {$I+} if ioresult = 0 then writeln('Все в порядке. Файл нашелся и открылся нормально') else writeln('Файл с именем ',myname,' не найден. Попробуйте другое имя'); ... {ну тут остальные действия. Какие - читайте далее} end; Как следует вникните в этот код. Продумайте все вышесказанное. Возможно вам следует сделать перерыв. Так как дальнейшая часть хоть и самая интересная, но не менее сложная. И возможно вам следует укрепить все что Вы прочли в сознании. Я по собственному опыту знаю, что большое количество сложной информации доходит до подсознания не сразу. Поэтому лучше сделайте перерыв. Поэкспериментируйте с тем, что прочли выше. Это будет полезным опытом, ведь я мог что-либо забыть сказать. |
Сообщ.
#2
,
|
|
|
3. Запись или чтение информации из файла После того как вы описали дескриптор файла, после того как вы его связали с реальным файлом, после того как вы открыли это файл наконец можно начать запись или чтение из него. В паскале есть два стандартных набора команд для работы с информацией в файлах. Это: 1. Write и Writeln, Read и Readln 2. BlockRead и BlockWrite То, как они работают целиком и полностью зависит от типа дескриптора файлов. Либо дескриптор файла текстовый, либо содержит типизированную информацию, либо нетипизированную. Для каждого из видов я опишу специфику работы этих команд. Начнём по порядку: 1. Write и Writeln, Read и Readln 1.1. Текстовые файлы В файлах, открытых как текстовые можно применять эти четыре команды с таким же результатом как и их применение для вывода (либо ввода) на экран. Применение команд Write и Writeln рассмотрено в статье нашего FAQ: Как вывести текст (строку) ? В синтаксисе следует учесть, что перед тем, как указывать что выводить, или куда вводить информацию нужно указать дескриптор файла, с которым мы работаем. Вот пример: Writeln(f1,'Эта строка окажется внутри файла с дескриптором f1'); Точно так же как и на экране эта команда занесет данную строку в файл, после чего перейдет на новую строку файла. При чтении из файла так же как и при чтении с клавиатуры следует соблюдать, чтобы тип информации совпадал с типом переменной, в которую мы хотим считать информацию. Например если в файле находится три строки, то прочесть их в переменную типа "число" скорее всего будет нельзя (если конечно в строках не содержатся числа). Например в файле такие данные: Это строка 12321 Это было число А вот это будет три символа: №%@ Можно прочесть такую информацию вот так: var f:text; s1,s2,s3:string; v1:longint; c1,c2,c3:char; begin assign(f,<тут имя файла>); reset(f); readln(f,s1); {s1 <- "Это строка"} readln(f,v1); {v1 <- 12321} readln(f,s2); {s2 <- "Это было число"} readln(f,s3); {s3 <- "А вот это будет три символа:"} readln(f,c1,c2,c3); {c1 <- "№", c2 <- "%", c3 <- "@"} ...{остальные действия} end. При чтении как и с клавиатуры можно комбинировать. Например такая инфа 1234 Я -3.14 var v:word; c:char; r:real; ... read(f1,v,c,r); ... При работе с файлом таким образом следует помнить, что это не экран, и применять команды gotoxy, clreol, clrscr нельзя. Они действуют только для информации на экране. Есть одна особенность при чтении из текстовых файлов: Признаком конца обычных файлов является достижение реального конца файла. Признаком достижения текстового файла могут быть две вещи: либо достижение реального конца файла, либо чтение из файла символа #26. Дело в том, что этот символ (стрелочка вправо) для текстовых файлов считается признаком конца файла. И если при чтении информации из файла (открытого как текстовый) встретится этот символ, то паскаль решит, что файл кончился, хотя на самом деле после этого символа может быть и остальная информация, и дальнейшее чтение уже не будет давать никакого результата. И даже более того: Если попытаться прочесть из файла этот символ не проверив перед этим, кончился ли файл, то это повлечет за собой такой глюк: в паскале прекратится выполнение каких либо действий с файлом, и вывод любой информации на экран. Следовательно старайтесь проверять перед чтением текстового файла, не кончился ли он. А что же делать, если нам нужно читать из файла текстовую информацию, включая и символ #26? определение символа #26 как конца файла происходит только в том случае, если данные из файла читаются в числовую переменную. к примеру в файле первый символ - #26 var v1:byte; v2:longint; v3:real; c:char; begin ... read(f,v1); или read(f,v2); или read(f,v3); {Даст глюк с концом файла} read(f,c); или read(f,s1); read(f,s2); {Глюка не будет. Символ #26 займет свое место} 1.2. Типизированные файлы Это файлы с типизированным форматом данных. При объявлении дескриптора таких файлов указывается тип данных, содержащихся в файле. Для файлов такого типа возможно использование только команд read и write из данной категории. (ReadLn и WriteLn нельзя). Такое ограничение очевидно. Поскольку данные в файле это не строки текста, то нет и линий. А значит нельзя переходить по линиям. Команда Read читает одну или несколько записей из файла в переменную (или переменные). Тип переменных, в которые читается запись должен совпадать с типом типизированного дескриптора файла. Если вы объявили файл как file of char, то читать из файла в переменные типа string нельзя Можно только в char. Команда write напротив записывает в файл одну или несколько переменных. Так же как и в команде read типы переменных должны быть такими же как и тип данных файла. Данные в файле хранятся в внутреннем машинном виде. Поэтом раскрыв файл, в котором записаны переменные типа word (к примеру) вы не увидите привычных цифр. Вместо них в файле будет всякая абракадабра. Это и есть внутреннее представление переменных типа word. То, в каком виде они записаны в памяти. В данном примере на каждую переменную типа word будет отведено по два значка (байта). Количество байт, которое переменная занимает в памяти можно прочесть в хелпе по переменным паскаля, вызвав его из паскаля. Например введите слово Real, наведите на него курсор и нажмите Ctrl+F1. Вы увидите описание переменных с плавающей запятой. Там будет небольшая табличка для различных переменных, их максимальных и минимальных значений, а так же в столбце под названием Bytes указано количество байт, которое занимает каждый из типов таких чисел в памяти. Для числа Real это 6 и значит если файл будет описан как file of real, то каждая запись в файл одной переменной типа Real будет добавлять в него 6 байт, соответствующих значению этой переменной. При чтении такой записи из файла будет взято 6 байт, и занесены в то место памяти, где находится переменная, в которую мы читаем. Таким образом в этой переменной окажется то самое число типа Real. Теперь важная информация: Если текущим местом в файле не является конец, а к примеру середина, то с чтением у нас нет вопросов. При чтении мы будем читать текущую запись, следующую после нее, и так далее до конца файла. А что же будет если мы попробуем записать запись в файл? Если вы подумали, что информация после текущего места сдвинется дальше, чтобы дать место для записи, которую мы пытаемся записать, то вы ошибаетесь. Если попытаться записать запись не в конец файла, а в какое-либо другое место, то текущая запись будет затерта той, которую мы записываем. Поясню на примере. Допустим у нас такой файл: В файле в машинном виде содержится четыре числа типа Byte - 10 20 30 40 На каждое из этих чисел в памяти приходится по одному байту. Поэтому в файле будет 4 байта. Допустим мы прочли из такого файла две переменные типа byte. Значит сейчас мы находимся на 3 переменной, но еще не читали ее. Если мы сейчас запишем в файл скажем вот такой командой: write(f,0) то в файле не станет 5 чисел. В нем их останется по прежнему 4, но третья текущая запись будет заменена новой, то есть числом типа byte равным нулю (как у нас написано). И в файле получится вот такая информация: 10 20 0 40 (Естественно в машинном виде) то есть тоже 4 байта, но один из них будет уже другим. Для того, чтобы вставлять в середину файла какую либо информацию сдвигая всю следующую дальше следует использовать более сложные уловки, о которых я (может быть) расскажу позже. Приведу пример: Создадим файл, в котором будет список студентов и результаты их зачетов по 5 предметам. И напишем программу, которая бы выводила эти данные. Вот программа, которая создает файл с описанием студентов: type TStudentInfo=record name:string[30]; kurs:string[20]; ekz:array[1..5] of byte; end; var f:file of TStudentInfo; st:TStudentInfo; p:byte; begin assign(f,'students.dat'); {$I-} {Будем сами отслеживать ошибки} reset(f); {Откроем файл. Позиция на данный момент в самом начале} {$I+} if ioresult<>0 then rewrite(f); {Если ошибка, занчит файла нет, и значит откоем его подругому} seek(f,filesize(f)); {Если мы открыли файл первым способом, то значит он уже был и значит там есть записи. Следовательно переместимся в его конец чтобы дописывать студентов} with st do repeat write('Введите имя студента (пустую строку для выхода): '); readln(name); if name='' then break; write('Введите курс:'); readln(kurs); for p:=low(ekz) to high(ekz) do begin write('Введите оценку по экзамену №',p,': '); readln(ekz[p]); end; write(f,st); {Вот эта строка и записывает информацию о студенте в файл} until false; close(f); {Эту команду мы ещё не рассматривали, но об этом я расскажу в конце} end. А вот программа, которая читает и выводит эту информацию из файла: type TStudentInfo=record name:string[30]; kurs:string[20]; ekz:array[1..5] of byte; end; var f:file of TStudentInfo; st:TStudentInfo; p:byte; begin assign(f,'students.dat'); {$I-} {Будем сами отслеживать ошибки} reset(f); {Попробуем открыть файл} {$I+} if ioresult<>0 then begin writeln('Файл с данными не найден. Запустите сначала первую программу'); exit; {Прервём выполнение программы} end; with st do repeat read(f,st); {Вот эта строка читает информацию о студенте из файла в st} writeln('Имя студента: ',name); write('Курс:',kurs); for p:=low(ekz) to high(ekz) do write('Оценка по экзамену №',p,': ',ekz[p]); until eof(f); {Эта команда ещё не рассмотрена, она будет возвращать неправду пока файл не кончится} close(f); {Эту команду мы ещё не рассматривали, но об этом я расскажу в конце} end. Поэкспериментируйте с программами, найдите их слабые места. Попробуйте от них избавитья %) 1.3. Нетипизированные файлы С файлами такого рода нельзя использовать первую группу команд. То есть, команды write, writeln, read и readln. Такое ограничение происходит потому что не известен тип записи в файле, а значит и неизвестен тип переменной в которую нужно было бы указывать в этих командах. На самом деле я кривлю душой, говоря о том, что неизвестен тип, и поэтому нельзя использовать эти команды. Потому что в принципе тип - это байт, или несколько байт. И чтение либо запись в файлы такого типа запросто можно было бы осуществить. Но... но увы в паскале видимо программисты не додумались, либо не захотели думать об этом. Посему данное ограничение пусть остаётся на их совести. |
Сообщ.
#3
,
|
|
|
2. BlockRead и BlockWrite
1.1. Текстовые файлы Нельзя использовать. 1.2. Типизированые файлы Также нельзя использовать. 1.3. Нетипизированые файлы Только файлы такого типа можно использовать с этими двумя командами. Команда BlockRead позволяет читать информацию из нетипизированного файла указанного размера. Вся прочтённая информация записывается в указанный буффер. Синтаксис: procedure BlockRead(<дескриптор_нетипизированного_файла>,<буффер>,<количество_записей>:word[,<результат>:word]); Рассмотрим параметры по порядку: После чтения очередной записи (или группы записей) команда BlockRead смещает указатель текущей позиции файла на количество_прочтённых_записей дальше. Суммарный размер информации, которую можно прочесть за раз этой командой не должен привышать 65535 байт, иначе возникнет ошибка. Это значит, что Размер_записи*<количество_записей> должно быть меньше чем 65536. Пример: В файле содержится 13 байт: 1112223334445 Смотрите, что мы получим читая его. var f:file; {Объявим дескриптор нетипизированного файла} b:array[1..100] of char; r:word; begin assign(f,'123.dat'); reset(f,3); {Установим размер записи равным 3} blockread(f,d,2); {сейчас мы прочли две первые записи, тоесть 3*2=6 байт} {Эти 6 байт занеслись в первые 6 ячеек массива d - "111222"} blockread(f,d[7],10,r); {Теперь мы попытались прочесть ещё 10 записей из файла} {Но в файле осталось только 2 полные записи, и ещё одна неполная} {Поэтому переменная r будет содержать число 2} {А в массив начиная с 7 ячейки будут записаны ещё 7 байт} {Обратите на это внимание. Именно ещё 7 а не 6, как можно было бы предположить} ... Этой командой довольно удобно читать целые массивы каких либо типизированных данных. Нам нужно только знать, сколько байт в файле занимает информация об одном из типизированных данных. Помните раньше для типизированных файлов я давал пример - файл с данными о студентах и их оценках. Тип данных там был таким: type TStudentInfo=record name:string[30]; kurs:string[20]; ekz:array[1..5] of byte; end; Пример: type TStudentInfo=record name:string[30]; kurs:string[20]; ekz:array[1..5] of byte; end; var f:file; st:array[1..10] of TSudentInfo; res:word; begin assign(f,'students.dat'); {$I-} {Будем сами отслеживать ошибки} {Попробуем открыть файл} {Обратите внимание - здесь принципиально важное место. Мы открываем файл с размером записи, равным размеру записи TStudentInfo, что позволит читать нам за раз одну запись} reset(f,sizeof(TStudentInfo)); {$I+} if ioresult<>0 then begin writeln('Файл с данными не найден. Запустите сначала первую программу'); exit; {Прервём выполнение программы} end; {Теперь прочтём сразу 10 описаний студентов в массив st} blockread(f,st,10,res); {Читаем 10*размер_записи_о_студенте байт} {После выполнения blockread в переменной res находится количество удачно прочтённых записей} {Поскольку в файле может быть меньше 10 описаний, для нас это принципиальные данные} writeln('Из файла удалось прочесть информацию о ,'res,' студенте(ах)'); close(f); {Эту команду мы ещё не рассматривали, но об этом я расскажу в конце} {Ну а теперь можно вывести информацию о res прочтённых студентах на экран} end. Ну и наконец команда BlockWrite Синтаксис: procedure BlockWrite(<дескриптор_нетипизированного_файла>,<буффер>,<количество_записей>:word[,<результат>:word]); Эта команда имеет идиентичный синтаксис с командой BlockRead. Команда записывает данные из памяти указанной параметром <буффер> в файл, связанный с дескриптором <дескриптор_нетипизированного_файла>. Как и в команде BlockRead тут есть параметр <количество_записей> типа word, который указывает, сколько записей переписать из памяти в файл этой командой. Параметр <результат> после выполнения процедуры содержит количество удачно записанных записей. Это значит, что в процессе записи в файл носитель информации может быть переполнен, и дальнейшая запись будет невозможной. Так вот в этом параметре будет содержаться количество целых записей, которые удалось занести в файл до того, как заполнился носитель информации. Тут следует учесть, что носитель информации может заполниться на середине очередной записи. Недописаная запись не будет удалена из файла, однако в параметре <результат> она учтена не будет. Как и в команде BlockRead параметр <результат> не является обязательным, и его можно не указывать. Обе команды можно комбинировать, то читая, то записывая записи в файл. 4. Закрытие файла Ну вот мы и добрались наконец к завершающей стадии работы с файлом. Это закрытие файла. После того, как информация, находящаяся в файле была должным образом обработана (или сформирована) файл обязательно следует закрыть. После закрытия файла все действия, проведённые нами с файлом вступят в силу. Не следует забывать этого действия, так как это может повлечь некоторые неприятные последствия. Во первых те изменения, которые вы поизвели в структуре файла могут не полностью сохраниться, и во вторых в системе файл останется открытым. А у каждой системы есть ограничение на возможное количество одновременно открытых файлов. Для ДОСа это 7 файлов (можно установить больше добавив в файл config.sys строку files=20). Для Windows больше, но всё же совершенно очевидно, что файл следует закрыть после работы с ним. Иначе после определённого количества запусков вашей программы в системе будет максимум открытых файлов, и в конце концов наступит момент, что система не сможет открыть ещё один. Файл любого типа закрывается командой Close, которая имеет простой синтаксис: procedure Close(<дескриптор_открытого_файла>); Попытка закрыть уже закрытый (либо ещё не открытый) файл не приводит ни к каким последствиям. Это бывает в некоторых случаях удобно. |
Сообщ.
#4
,
|
|
|
Далее нам следует рассмотреть другие функции, которые определены в Паскале для удобства работы с файлами.
Это функции: Seek, FilePos, FileSize, Eof, SeekEof, EoLn, SeekEoLn, Delete, Rename. Вроде все. Будем расматривать по порядку. Seek Одна из наиболее часто используемых при работе с файлами комманда. Эта команда меняет в открытом файле текущее месторасположение. Работает только для файлов, открытых как типизированые или нетипизированные. Не работает в текстовых файлах. Файл должен быть открыт. Синтаксис: procedure Seek(<Имя_дескриптора_файла>,<порядковый_номер_записи>); Эта процедура перемещает текущее положение в файле в запись с номером <порядковый_номер_записи>. Положение в файле считается начиная с 0. Тоесть если мы хотим переместиться в начало файла f, нам следует написать Seek(f,0); Если хотим переместиться в 10 запись, то должны будем написать Seek(f,9); Когда файл типизированный, записью считается тип файла, когда файл нетипизированный, то размер записи в байтах указывается при открытии файла (второй параметр команды открытия файла). Использование этой команды раскрывает очень широкие возможности в редактировании файла. При помощи неё стало возможным одновременное чтение и запись файла - то есть изменение его содержимого, и чтение в одном и том же файле. Вот классический пример: Пусть у нас файл состоит из 20 записей, каждая из которых - число. Нужно заменить нулём все записи, которые меньше нуля на ноль. Тоесть убрать отрицательные записи из файла. Можно конечно сделать так: Но вот вам куда более удобный способ: Такой подход даст значительно более быстро работающий результат. У вас может возникнуть один вопрос - откуда мы берём значение текущей записи, для того чтобы выполнить действие номер 2? Ведь нам чтобы сдвинуть номер записи назад, нужно знать номер текущей записи, иначе мы не сможем указать номер предыдущей записи. Можно конечно завести переменную, в которой содержать номер текущей записи, и изменять его в соответствии с алгоритмом. Однако это совершенно излишне, потому что именно для этого используется следующая команда. FilePos Эта команда используется для того чтобы узнать номер текущей записи в файле. Может быть использована только в типизированных и нетипизированных файлах. В текстовых файлах использовать нельзя (потому что в текстовых файлах нет понятия - запись). Записи в файле всегда нумеруются с нуля. Эта особенность очень часто у новичков вызывает ошибки. Допустим в файле всего находится 10 записей. Тогда первой записью всегда будет запись номер 0, а последней не 10, как можно было бы подумать, а 9. Тоесть записи будут нумероваться - 0, 1, 2...9 Всего получится 10 номеров (Вместе с 0). Учтите это и при использовании Seek. Синтаксис: function FilePos(<переменная_дескриптор_файла>):longint; Как видите эта команда - функция. Возвращаемым ею значением и будет текущее положение в файле. Тут также следует учесть, что текущим положением в файле будет номер текущей записи в файле, а не порядковый номер байта. Когда я начинал работать, я долго не мог понять разницы. Байты - это вся информация в файле. А записи - это блоки, из которых он состоит. Одна запись может быть размером к примеру 5 байт. И если эта команда вернёт цифру 10, то это будет значить, что сейчас в файле активна 11 запись (смотрите, мы учитываем ноль, поэтому 11). А это получится 11*5=55. Значит в файле эта запись будет начинаться с 55 байта (если их считать начиная с 1). Теперь немного практики: Файл состоит из записей типа String[100]. Найдём и заменим в нём все записи, в которых встречается слово "ФИО" На наше ФИО. Вот код: type sof100=string[100]; {Это тип информации в файле - обязательно нужно объявить (см. пояснение)} var f: File of sof100; s: sof100; {запись из файла будет здесь} fio: string; {Наше ФИО} begin write('Введём наше ФИО: '); readln(fio); if length(fio)>100 then fio:=copy(fio,1,100); {Обрежем лишние символы} assign(f,'fio.dat'); reset(f); while not eof(f) do {Этого мы ещё не прошли - цикл будет выполняться пока не окончится файл} begin read(f,s); if s='ФИО' then {Если текущая запись - слово ФИО} begin s:=fio; {Нужно преобразовать из строки в sof100, для этого присвоим s значение fio} seek(f,filepos(f)-1); {переместим позицию файла в позицию на один меньше текущей} write(f,s); {Перезапишем в файл наше ФИО} end; end; close(f); end. type sof100=string[100]; Дело в том, что если тип информации в файле описать как string[100], то это будет работать нормально но(!) описав переменную s:string[100] мы не сможем ни записать её в файл, ни прочесть в неё из него. Это происходит из-за того, что Паскаль считает, что тип данных в файле (string[100]) это один тип данных, а тип переменной s - (тоже string[100]) это совсем другой тип данных. Тоесть для нас конечно же очевидно, что оба типа одинаковые, но для Паскаля это совсем не очевидно. Для него переменная s, и данные в файле f имеют различные типы, и следовательно в файл f нельзя будет записать такую переменную s. Поэтому нам необходимо объявить один общий тип - type sof100=string[100]; и создать типизированый файл такого типа, и переменную s тоже такого типа. Тогда паскаль понимает, что и то, и то имеют один тип. Конечно же эта функция может быть использована только на открытых файлах. FileSize Эта команда позволяет определить количество записей в файле. Тоесть это можно назвать размером файла (в записях). Работает только для типизированных или нетипизированных файлов (не работает для текстовых) и файл должен быть обязательно открыт. Синтаксис: function FileSize(<переменная_дескриптор_файла>):longint; Как и FilePos эта команда является фукнцией. Значение, которое она возвращает является количеством записей в файле на данный момент. Если в файле при открытии уже есть записи, то эта команда вернёт их количество. Если в течении работы с файлом это количество изменится (например мы добавим новые, или удалим старые) то при последующем вызове этой команды это изменение будет учтено. Поэтому можно сказать, что эта команда возвращает количество записей в файле на данный момент. Если при октрытии файла в нём находится информация не кратная размеру записей (ну например размер записи - 5, а в файле 27 байт - 5 записей и 2 байта), то функция вернёт количество полных записей в файле (тоесть в данном примере - 5). Количество записей считается с 1. Поэтому проблемм с нулями тут обычно не возникает (как в командах FilePos и Seek). Не путайте - команда возвращает не размер файла в байтах, а количество записей (разве что если размер записи - 1 байт %)) На первых парах начинающие программисты часто забывают о разнице. Для этой команды я приведу переведённый пример из стандартного паскалевского хелпа: var f: file of Byte; size : Longint; begin Assign(f, ParamStr(1)); {Имя файла узнаем из командной строки программы} Reset(f); {Откроем файл с размером записи - 1 байт} size := FileSize(f); {В переменной будет находиться текущий размер открытого файла} Writeln('Размер файла в байтах: ',size); {Выведем его} Writeln('Перейдём в середину файла...'); Seek(f,size div 2); {Переходим} Writeln('Position is now ',FilePos(f)); {Выведем номер текущего положения в файле} Close(f); {Закроем файл} end. EOF Это важная команда, которая повседневно применяется при работе с файлами (Я даже не смог обойтись без неё в предыдущих примерах, поэтому возможно вы уже догадались, что она делает). Расшифровывается так: End Of File - EOF (конец файла) При помощи этой команды можно узнать, достигли ли мы конца файла. Допустим мы должны вывести всю информацию из файла на экран. Мы будем читать её из файла пока он не кончится. Для того, чтобы определить - когда-же у нас кончился файл мы должны использовать эту команду. Синтаксис такой: function EOF(<переменная_дескриптор_файла>):boolean; Всё просто. Команда возвращает true, если файл кончился, и возвращает false, если ещё не кончился. Eof может использоваться для определения достижения конца любого файла: будь то типизированный файл, нетипизированный файл, или даже текстовый файл(учтите что и текстовый тоже). Команда не реагирует на символ #26 в текстовых файлах. Поэтому определить наличие этого символа при помощи Eof нельзя. Однако если паскаль среагирует на этот символ, то произойдёт глюк (описаный в команде write для текстового файла). Для файлов нетекстового типа проблемм с этим символом нет. Для типизированных и нетипизировнных файлов эта команда возвращает признак окончания файла, если текущее положение находится в конце файла. Если переместить текущее положение (командой seek) в другое место файла, то после этого команда Eof вернёт false Простейшим примером для команды будет программа, читающая и выводящая текстовый файл на экран, пока он не кончится. var f: text; s: string; begin. assign(f,paramstr(1)); reset(f); while not eof(f) do begin readln(f,s); writeln(s); end; close(f); end. SeekEOF Команда является почти полным аналогом предыдущей. Однако есть и различия. Вопервых она может быть использована только для текстовых файлов. И во вторых - при вызове этой команды не только возвращается признак окончания файла, а так-же пропускаются все символы, меньше #33 (пробелы и управляющие символы). Если в файле встречается символ #26, то эта функция возвращает true - тоесть это признак конца файла. При помощи этой команды удобно читать из файла только буквы или цифры, пропуская пробелы, знаки табуляции, символы перехода строки. В результате можно получить текст "сплошняком". Синтаксис аналогичен команде Eof. Вот пример: var f: text; c: char; begin assign(f,'text.txt'); reset(f); while not seekeof(f) do begin read(f,c); write(c); end; close(f); end; EoLn Далее следует. Временное сохранение. |
Сообщ.
#5
,
|
|
|
Типизированные файлы
Новая версия находится в энциклопедии: Операции над типизированными файлами в Турбо Паскале. Типичные операции с записями - это добавление,изменение,удаление и вставка записей в файлах. Давайте рассмотрим структуру записей файла. Позиция при открытии файла ========= |*| | | | | ========= 1) Чтобы добавить запись в файл, необходимо сначала установить позицию в конец файла командой Seek ,а затем производить запись с помощью Write. Позиция в конце файла ========= | | | | | |* ========= Производим запись. Теперь добавилась ещё одна. =========== | | | | | | |* =========== type TStudentInfo=record Name: string[30]; Kurs: string[20]; Exams: array[1..5] of byte; end; var F: file of TStudentInfo; st: TStudentInfo; begin ....... Seek(F,FileSize(F)); { становимся в конец файла } Write(F,st); { записываем запись в файл } ....... end. 2) Чтобы подправить запись, надо стать на позицию этой записи Seek(F,recordN), а затем производить запись. Аналогично предыдущему примеру, только вместо Seek(F,FileSize(F)); пишем Seek(F,recordN);. Не забывайте, что нумерация записей начинается с нуля. ========= | | |*| | | ========= После того, как изменили запись, позиция продвинулась вперёд. ========= | | | |*| | ========= 3) Если надо найти запись и исправить ее, надо: Пример поиска и замены записи type TStudentInfo=record Name: string[30]; Kurs: string[20]; Exams: array[1..5] of byte; end; var f: file of TStudentInfo; st: TStudentInfo; who: string[30]; found: boolean; begin write('Кого ищем? '); readln(who); if who='' then exit; assign(f,'students.dat'); {$I-} reset(F); {$I+} found:=false; if IOresult=0 then with st do while Not EOF(F) do begin read(F,st); if name=who then { нашли такого/ую } begin write('Заменить на фамилию: '); readln(name); found:=true; seek(F,FilePos(F)-1); { вернуться на 1 позицию обратно, т.е. на позицию того, что надо заменять } write(f,st); break; { убрать это, если известно, что таких несколько } end; end; close(f); if Not Found then writeln(Who,' не найден. Ха-ха') else writeln(Who,' найден и заменен.'); writeln(#13#10'Жми Enter'); readln; end. 4) Вы спросите: "А как же вставить или удалить запись в файле?" Ответ: Файлы не имеют гибкой структуры данных, т.е. нельзя "раздвинуть" записи и вставить ещё одну, или удалить среди записей ненужную, подвинув остальные на место удалённой. То есть можно, но так не делается. В этом случае вам придётся создать второй файл, а прежний удалить. 0..i-1 , i , i+1..N |