Read — Процедура Delphi

Содержание

Процедуры Read и ReadLn

Процедуры Read и ReadLn выполняют чтение информации из устройства стандартного ввода. В консольных приложениях этим устройством может быть, например, клавиатура (точнее — введённые с клавиатуры данные), в графических приложениях — файл на диске.

То есть эти процедуры являются “антиподами” процедур Write и WriteLn — выполняют противоположные им действия.

Процедуры Read и ReadLn выполняют схожие действия. Основное отличие между ними следующее: процедура ReadLn после завершения ввода выполняет перевод строки (а в случае с файлами читает файл строка за строкой). А процедура Read читает данные подряд — без перевода строки.

В Турбо Паскале я не помню такого (может просто забыл), но в FreePascal ввод с клавиатуры можно выполнять только процедурой ReadLn, а процедура Read почему-то не работает.

Синтаксис для вывода на консоль:

procedure Read(Args: Arguments);

Синтаксис для вывода в файл:

procedure Read(var F: Text; Args: Arguments);

Аргументами (Arguments) могут быть переменные разных типов. Если используется несколько переменных, то они перечисляются через запятую. Например:

Как уже было сказано, при вводе с консоли эти переменные могут быть разных типов. Но, в отличие от процедур Write/WriteLn использовать константы не допускается (и это логично))).

ВАЖНО!
При вводе данных следует учитывать, что в случае, если введённое пользователем значение имеет тип, отличный от типа переменной, в которую вводится это значение, то возникнет ошибка времени выполнения. Если, например, в указанном выше примере пользователь в качестве первого числа введёт вещественное значение (такое как 3.14), то программа завершится аварийно, так как переменная х имеет целый тип.

При чтении из файла можно работать как с типизированными файлами, так и с текстовыми файлами.

Если F (см. синтаксис) — это типизированный файл, то переменные, передаваемые как параметры (Args) должны иметь такой же тип, какой указан для файла F. Нетипизированные файлы использовать не допускается. Если параметр F не указан, то предполагается, что чтение выполняется из стандартного устройства ввода.

Если файл F имеет тип Text, то переменные должны иметь тип Char, Integer, Real или String.

Если при чтении файла нет данных, доступных для чтения, то в переменную F возвращается пустое значение (0 — для порядковых типов, пустая строка — для строковых).

В случае использования процедуры ReadLn, то есть при построковом чтении данных, конец строки обозначается определённой последовательностью символов (какими именно — зависит от операционной системы, для DOS/Windows это два символа — #10 и #13).

Маркер конца строки не является частью прочитанной строки и игнорируется.

Если во время выполнения процедуры Read/ReadLn происходит ошибка, то генерируется ошибка времени выполнения. Такое поведение не всегда приемлемо (например, во время чтения файла). Поэтому в каких-то случаях генерацию ошибок отключают. Сделать это можно с помощью директивы компилятора .

В разных отладочных и учебных программах процедуру ReadLn часто используют для того, чтобы консольное приложение не закрылось автоматически после выполнения. Для этого в конце программы просто записывают (как в примере выше):

То есть просто имя процедуры без параметров. В этом случае программа будет ожидать нажатия клавиши ENTER. Следовательно, программа не завершится, пока не будет нажата клавиша ENTER, и это позволит увидеть результат работы программы. Разумеется, в операционной системе DOS (и подобных) такой проблемы нет. Это актуально только для Windows.

Read — Процедура Delphi

Delphi 4 для начинающих. Часть 3
Разгребаясь с письмами читателей, я решил начать следующую статью с работы с файлами и строковыми переменными. На такой ход меня натолкнуло следующее письмо: «Я только начал программировать на Delphi. У меня к вам большая просьба: при создании различных программ часто приходится работать с файлами (создавать, удалять, копировать, переписывать файлы), не могли бы вы в ближайшем номере «Компьютерной газеты» мне рассказать о методах работы с файлами». Что ж, желание просящих выполняю.

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

Для использования файлов в приложении разработчику приходится решать множество задач. Основные из них – это поиск необходимого файла и выполнение с ним операций ввода/вывода.

Основные принципы и структура файловой системы мало изменились еще со времен MS-DOS. Если не принимать во внимание способы защиты файлов и организацию их хранения на уровне кластеров, то все остается без изменений вот уже скоро двадцать лет. Новые варианты файловых систем (FAT32, NTFS) не изменяют главного – понятия файла и способов обращения к нему. Поэтому современный программный код Delphi, например, для чтения данных из файла, удивительно похож на аналогичный, написанный, к примеру, на Turbo Pascal 4.0.

При организации операций файлового ввода/вывода в приложении большое значение имеет тип используемых данных. В основном это строки, но встречаются числовые данные или структурированная информация, например, записи или массивы данных. О типе хранимых в файле данных необходимо знать заранее. Для работы с файлами необходимо для начала описать тип файла. Для этого используются специальные файловые переменные. Они делятся на нетипизированные и типизированные. В Delphi имеется одна нетипизированная переменная. Для ее обозначения используется ключевое слово file :

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

Типизированные файловые переменные обеспечивают ввод/вывод с учетом конкретного типа данных. Для их объявления используется ключевое слово file of, к которому добавляется конкретный тип данных. Например, для работы с двоичным файлом файловая переменная будет иметь вид:

v ar ByteFile: file of byte;

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


type Country = record

var CountryFile: file of Country;

Для работы с текстовыми файлами используется специальная файловая переменная TextFile или Text :

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

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

  1. Объявить файловую переменную;
  2. При помощи функции AssignFile связать эту переменную с требуемым файлом;
  3. Открыть файл при помощи функций Append, Reset или Rewrite;
  4. Выполнить операции чтения и/или записи. При этом, в зависимости от сложности задачи и структуры данных, может использоваться целый ряд вспомогательных функций.
  5. Закрыть файл при помощи функции CloseFile .

В качестве примера рассмотрим небольшой фрагмент исходного кода программы:

If OpenDlg1.Execute then AssignFile(F, OpenDlg1.fileName) else exit;

While Not EOF(F) do

Если в диалоге OpenDlg1 был выбран файл, то его имя связывается с файловой переменной F при помощи процедуры AssignFile . В качестве имени файла всегда рекомендуется передавать полное имя файла (включая путь к нему). Как раз в таком виде возвращают результат выбора файла диалоги работы с файлами, описанные в материале прошлого номера. Затем при помощи процедуры Reset этот файл открывается для чтения и записи. В цикле осуществляется чтение из файла текстовых строк и запись их в компонент TMemo . Процедура Readln осуществляет чтение текущей строки файла и переходит на следующую строку. Цикл выполняется до тех пор, пока функция EOF не сообщит о достижении конца. По завершении цикла файл закрывается.

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


procedure Reset(var F [: File; RecSize: Word ] );
— открывает существующий файл для чтений и записи, текущая позиция устанавливается на первой строке файла;


procedure Append(var F: Text);
— открывает файл для записи информации после его последней строки, текущая позиция устанавливается на конец файла;

procedure Rewrite(var F: File [; Recsize: Word ] ); — создает новый файл и открывает его, текущая позиция устанавливается в начало файла. Если файл с таким именем уже существует, то он перезаписывается.

Чтение данных из файла выполняют процедуры Read и Readln.


procedure Read( [ var F: Text; ] V1 [, V2. Vn ] ); —
для текстовых файлов;


Procedure Read(F, V1 [, V2,…,Vn ] );
— для типизированных файлов.

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

procedure Readln([ var F: Text; ] V1 [, V2, . Vn ]); — считывает одну строку текстового файла и устанавливает текущую позицию на следующую строку. Если использовать процедуру без переменных, то она просто передвигает текущую позицию на следующую строку.

Процедуры для записи данных в файл Write и Writeln работают аналогично.

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


procedure Seek(var F; N: Longint); —
обеспечивает смещение текущей позиции на N элементов. Размер одного элемента в байтах зависит от типа данных файла (от типизированной переменной).

Рассмотрим теперь режим блочного чтения и записи в файл. Он обеспечивает ввод/вывод данных между файлом и областью адресного пространства (буфером). Этот режим отличается значительной скоростью, причем скорость пропорциональна размеру одного передаваемого блока – чем больше блок, тем больше скорость.

Для реализации этого режима необходимо использовать только нетипизированные файловые переменные. Размер блока определяется в процедуре открытия файла ( Reset, Rewrite, Append ). Непосредственно для выполнения операций используются процедуры BlockRead и BlockWrite . Процедура


procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred: Integer]);

выполняет запись блока из памяти в файл. При этом параметр F содержит нетипизированную файловую переменную, связанную с нужным файлом, параметр Buf определяет переменную (число, строку, массив, структуру), в которую читаются байты из файла, параметр Count определяет число считываемых блоков, а параметр AmtTransferred возвращает число реально считанных блоков.

В блочном режиме чтения/записи размер блока необходимо выбирать таким образом, чтобы он был кратен размеру одному значению типа, который хранится в файле. Например, если в файле хранятся значения типа Double (8 байт), то размер блока может быть равен 8, 16, 24, 32 и т.д. Фрагмент исходного кода блочного чтения из такого файла выглядит так:

DoubleArray: array [0..255] of Double;

If OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

BlockRead(F, DoubleArray, 32, Transfered);

ShowMessage(‘Считано ‘+IntToStr(Transfered)+’ блок(-а,-ов)’);

Как видно из примера, размер блока установлен в процедуре Reset и кратен размеру элемента массива DoubleArray, в который считываются данные. В переменную Transfered возвращается число считанных блоков. Если размер файла меньше заданного в процедуре BlockRead числа блоков, ошибка не возникает, а в переменную Transfered передается число реально считанных блоков.

Обратите внимание, что для процедуры BlockRead организовывать цикл для чтения нескольких блоков нет необходимости.


procedure BlockWrite(var f: File; var Buf; Count: Integer [; var AmtTransferred: Integer]);

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

Эта запись обеспечивает хранение характеристик файла при удачном поиске. Дата и время создания файла хранятся в формате MS-DOS, поэтому для получения этих параметров в формате TDateTime необходимо использовать функцию


function FileDateToDateTime(FileDate: Integer): TDateTime;

Обратное преобразование выполняет функция


function DateTimeToFileDate(DateTime: TDateTime): Integer;

Свойство Attr может содержать комбинацию следующих значений:

faReadOnly – только для чтения;

Для определения параметров файла используется оператор AND :


If SearchRec.Attr AND faReadOnly) > 0 then ShowMessage(‘
файл только для чтения’);

Непосредственно для поиска файлов используются функции FindFirst и FindNext .

function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer;

находит первый файл, заданный полным маршрутом Path и параметрами Attr . Если заданный файл найден, функция возвращает 0, иначе – код ошибки. Параметры найденного файла возвращаются в записи F типа TSearchRec .

function FindNext(var F: TSearchRec): Integer;

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

Для освобождения ресурсов, выделенных для выполнения поиска, применяется функция

procedure FindClose(var F: TSearchRec);

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

FindFirst(DirEdit.Text, faArchive + faHidden, SearchRec);

While FindNext(SearchRec) = 0 do

Напоследок, как и обещал в прошлом номере, немного о создании дополнительных форм. Итак, для создания дополнительной формы необходимо воспользоваться пунктом меню New Form ( File/New Form ) или кнопкой New Form на панели инструментов. Перед Вами появится новая форма проекта с названием Form2 . При запуске программы эта форма не будет активизирована. Для ее запуска необходимо воспользоваться процедурой Show из кода исходной программы. Список существующих форм и возможность активизация форм проекта доступны при нажатии на кнопку ViewForm на панели инструментов или при нажатии на пункт Forms… ( View/Forms… ). Таким образом, необязательно загромождать экран бесконечными формами проекта, когда на данный момент из них необходима только одна. Достаточно закрыть ненужные формы, а при необходимости активизировать их при помощи диалога ViewForm .

Read — Процедура Delphi

Is used to read text values from the console.

Is used to read text values from a text file with the given FileHandle.

Is used to read data from a binary file with the given FileHandle.

You must use AssignFile to assign a file to the FileHandle and open the file with Reset before using Read .

For text files, each line of text is parsed into the given variables. These variables may be text or number types.

For strings, the whole line is read, unless it exceeds the string variable capacity — only that amount of text that fits is passed.

When parsing for numbers, white space characters and line ends are seen as separator values. If a number value exceeds the capacity of the variable, it is cast to the variable without raising an exception.

When reading for strings or characters, a ReadLn must be performed when Eoln (end of the line) is reached. Otherwise, subsequent Read calls repeatedly return a null value.

For binary files, data values Value1, Value2 etc, are read from the data the file. If the file given by FileHandle is a typed file (one defined to contained defined records), then these values must be of the same type (record). Notes You cannot use Read to read from an untyped binary file (one declared as File with no following of type).

To read from an untyped binary file, use BlockRead.

Read does not use buffering — BlockRead is more efficient.

Delphi. Когда использовать read / write а когда get set ?

Озаботился тут этим вопросом теоретического интереса ради и получил для себя удивительный ответ!

Как известно есть 2 способа задать свойства объекту

А вот и интересный ответ

Выходит вместо 4 раз написания Get / Set мы просто обошлись одной парой. В каких случаях это применимо, когда Get / Set ничего другого не содержат кроме присваивания или что-то другое, я пока не понял и не потестил в реальных проектах – но я взял это себе на заметку

Read — Процедура Delphi

Изучив основные «кирпичики», из которых составляются программные инструкции, а именно — переменные и операторы, мы можем приступить к исследованию вопросов их эффективного расположения в теле программы. Для этих целей рассмотрим вопрос использования подпрограмм.

О подпрограммах в Object Pascal

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

Структура подпрограммы похожа на программу в миниатюре: она содержит заголовок, блок объявления переменных и блок инструкций. Из отличий можно выделить лишь невозможность подключать модули (блок uses), а так же ограничения на объявления типов данных: если локальные простые и даже составные типы в подпрограммах вполне допустимы, то более сложные типы — объекты, классы и интерфейсы, локальными быть не могут, а потому в подпрограммах их объявлять нельзя.

Использование подпрограммы состоит из 2 этапов: сначала подпрограмму описывают, а затем, уже в блоке инструкций программы, вызывают. Отметим, что в библиотеке Delphi имеется описание тысяч готовых подпрограмм, описывать которые, разумеется, уже не надо. А их вызовом мы уже неоднократно занимались — достаточно взглянуть на любой пример, где мы встречали инструкции, подобные таким:

write(‘Hello, world!’); readln;

Здесь и write, и readln — стандартные подпрограммы Object Pascal. Таким образом, с вызовом подпрограмм мы уже знакомы. Осталось узнать, как создавать собственные, или пользовательские, подпрограммы. Но прежде отметим, что все подпрограммы делятся на 2 лагеря: процедуры и функции. Мы уже использовали эти термины, и даже давали им описание, однако повторимся: процедуры — это такие подпрограммы, которые выполняют предназначенное действие и возвращают выполнение в точку вызова. Функции в целом аналогичны процедурам, за тем исключением, что они еще и возвращают результат своего выполнения. Результатом работы функции могут быть данные любого типа, включая объекты.

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

Как процедурам, так и функциям могут передаваться данные для обработки. Делается это при помощи списка параметров. Список параметров в описании подпрограммы и список аргументов, указываемых при ее вызове должен совпадать. Иначе говоря, если в описании определено 2 параметра типа Integer, то, вызывая такую подпрограмму, в качестве аргументов так же следует указать именно 2 аргумента и именно типа Integer или совместимого (скажем, Word или Int64).

ПРИМЕЧАНИЕ
На самом деле, Object Pascal позволяет довольно гибко обращаться с аргументами, для чего имеются различные методы, включая «перегружаемые» функции, значения параметров по умолчанию и т.д. Тем не менее, в типичном случае, количество, тип, и порядок перечисления аргументов при объявлении и при вызове процедуры или функции, должны совпадать.

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

Процедуры

Итак, начнем исследование подпрограммы с процедур. Как уже было отмечено, процедуру надо описать. Описание процедуры состоит из заголовка и тела процедуры.

Заголовок состоит из ключевого слова procedure, за которым следует имя процедуры и, при необходимости, список параметров, заключенных в круглые скобки:

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

ПРИМЕЧАНИЕ
Вопросы локальных и глобальных переменных, и вообще видимости в программах, будет рассмотрен позже в этой главе.

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

procedure TriplePrint(str: string); var i: integer; begin for i := 1 to 3 do begin writeln(‘»‘+str+'»‘); end; // конец цикла for end; // конец процедуры TriplePrint

Здесь мы определили процедуру TriplePrint, которая будет трижды выводить переданную ей в качестве аргумента строку, заключенную в двойные кавычки. Как видно, данная процедура имеет все составные части: ключевое слово procedure, имя, список параметров (в данном случае он всего один — строковая переменная str), блок объявления собственных переменных (целочисленная переменная i), и собственное тело, состоящее из оператора цикла for.

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

Отметим так же, что рассмотренная нами процедура сама содержит вызов другой процедуры — writeln. Процедуры могут быть встроенными. Иначе говоря, объявление одной процедуры можно помещать в заголовочную часть другой. Например, наша процедура TriplePrint может иметь вспомогательную процедуру, которая будет «подготавливать» строку к выводу. Для этого перед объявлением переменной i, разместим объявление еще одной процедуры. Назовем ее PrepareStr:

procedure PrepareStr; begin str := ‘»‘+str+'»‘; end;

Отметим, что переменная str, хотя и не передается этой процедуре в качестве параметра, тем не менее может в ней использоваться, поскольку данная процедура является составной частью процедуры TriplePrint, внутри которой данная переменная доступна для использования.

Таким образом, мы получаем две процедуры, одна из которых (TriplePrint) может использоваться во всей программе, а другая (PrepareStr) — только внутри процедуры TriplePrint. Чтобы преимущество использования процедур было очевидно, рассмотрим их на примере программы, которая будет использовать ее неоднократно, для чего обратимся к листингу 6.1 (см. так же пример в Demo\Part1\Procs).

Листинг 6.1. Использование процедур

program procs; <$APPTYPE CONSOLE>procedure TriplePrint(str: string); procedure PrepareStr; begin str := ‘»‘+str+'»‘; end; var i: integer; begin PrepareStr; for i := 1 to 3 do begin writeln(str); end; end; // конец процедуры TriplePrint begin // начало тела основной программы TriplePrint(‘Hello. ‘); // первый вызов TriplePrint TriplePrint(‘How are you. ‘); // 2-й вызов TriplePrint(‘Bye. ‘); // 3-й readln; end.

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

Функции

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

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

Рассмотрим пример функции, которая будет возвращать куб числа, переданного ей в качестве аргумента:

function cube(value: integer) : integer; result := value * value * value; >

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

В результате выполнения этого выражения переменной x будет присвоено значение 27. Данный пример иллюстрирует использование функций в классическом случае — для явного вычисления значения переменной. Однако функции могут использоваться в выражениях и напрямую. Например, можно поставить вызов функции cube в каком-либо месте арифметического выражения подобно обычной переменной:

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

procedure TriplePrint(str: string); function PrepareStr(s: string) : string; begin result := ‘»‘+s+'»‘; end; var i: integer; begin for i := 1 to 3 do begin writeln(PrepareStr(str)); // функция использована как переменная end; end;

Как уже отмечалось, помимо специальной переменной result, в функциях можно использовать другую автоматически объявляемую переменную, имя которой соответствует имени функции. Так, для функции cube имя переменной также будет cube:

function cube(value: integer) : integer; cube := value * value * value; >

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

Рекурсия

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

Рассмотрим вопрос рекурсии на следующем примере:

function recfunc(x: integer) : integer begin dec(x); // функция декремента, уменьшает целое на 1 if x > 5 then x := recfunc(x); result := 0; // возвращаемое значение тут не используется end;

Здесь мы объявили функцию recfunc, принимающую один аргумент, и вызывающую саму себя до тех пор, пока значение этого аргумента больше 5. Хотя на первый взгляд может показаться, что такое поведение функции похоже на обычный цикл, на самом деле все работает несколько по-иному: если вы вызовите ее со значением 8, то она выдаст вам 3 сообщения в следующей последовательности: 5, 6, 7. Иначе говоря, функция вызывала саму себя до тех пор, пока значение x было больше 5, и собственно вывод сообщений начала 3-я по уровню получившейся вложенности функция, которая и вывела первое сообщение (в данном случае им стало 5, т.е. уменьшенное на единицу 6).

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

Листинг 6.2. Рекурсия с комментариями

program recurse; <$APPTYPE CONSOLE>function recfunc(x, depth: integer) : integer; begin dec(x); if x > 5 then begin write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); inc(depth); depth:=recfunc(x, depth); end else writeln(‘End of recursive calls. ‘); write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); dec(depth); result := depth; end; begin recfunc(8,0); readln; end.

Исходный код находится в Demo\Part1\Recurse, там же находится и исполняемый файл recurse.exe, результат работы которого вы можете увидеть на своем экране.

Использование параметров

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

procedure Circle (square: real; var radius, length: real);

Данная процедура принимает «на обработку» одно значение — площадь (square), а возвращает через свои параметры два — радиус (radius) и длину окружности (length). Практическая ее реализация может выглядеть таким образом:

procedure Circle (square: real; var radius, length: real); begin radius := sqrt(square / pi); // функция pi возвращает значение числа ? length := pi * radius * 2; end;

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

var r,l: real; . Circle(100,r,l);

После вызова функции Circle, переменные r и l получат значения радиуса и длины окружности. Остается их вывести при помощи writeln. Исходный код программы приведен в листинге 6.3.

Листинг 6.3. Процедура с параметрами

program params; <$APPTYPE CONSOLE>procedure Circle (square: real; var radius, length: real); begin //функция sqrt извлекает корень, а функция pi возвращает значение числа ? radius := sqrt(square / pi); length := pi * radius * 2; end; var r,l: real; begin Circle(100,r,l); writeln(r); writeln(l); readln; end.

Запустив такую программу, можно убедиться, что она работает и выводит верные результаты, однако вид у них получается довольно-таки неудобочитаемый, например, длина окружности будет представлена как «3,54490770181103E+0001». Чтобы сделать вывод более удобным для восприятия, нам понадобится функция FloatToStrF. С ее помощью мы можем определить вывод числа на свое усмотрение, например:

Кроме того, не помешало бы указать, где радиус, а где — длина окружности. Для этого модернизируем строки вывода результатов следующим образом:

writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8));

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

. var s,r,l: real; begin write(‘Input square: ‘); readln(s); Circle(s,r,l); writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8)); readln; end.

В принципе, это уже лучше, однако не помешало бы добавить обработку возможных ошибок ввода. Скажем, площадь должна быть больше 0. Проверку на то, является ли значение s больше нуля, можно производить непосредственно в основном коде программы, но в целях создания более универсального кода, вынесем ее в подпрограмму. Для этого первой инструкцией процедуры Circle должна быть проверка значения площади:

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

function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

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

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

if Circle(s,r,l) then begin // вывод end else // сообщить об ошибке

Результатом проделанной работы будет программа, приведенная в листинге 6.4. Она же находится в Demo\Part1\Params.

Листинг 6.4. Функция с параметрами

program params; <$APPTYPE CONSOLE>uses sysutils; //этот модуль соджержит функцию FloatToStrF function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

Итак, при помощи ключевого слова var в списке параметров подпрограммы мы можем добиться использования передаваемых аргументов в том блоке, где был произведен вызов данной подпрограммы. В несколько другом аспекте используется ключевое слово const. Фактически, оно объявляет локальную константу, т.е. значение, которое нельзя изменять внутри данной процедуры или функции. Это бывает полезным в том случае, когда такое изменение недопустимо по логике программы и служит гарантией того, что такое значение не будет изменено.

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

function MyBetterFunc(val1: integer; const val2: integer = 2); begin result := val1*val2; end;

Обращение же к такой функции может иметь 2 варианта: с указанием только одного аргумента (для параметра val1), или же с указанием обоих:

x := MyBetterFunc(5); // получим 10 x := MyBetterFunc(5,4); // получим 20

Оба вызова будут верными, просто в первом случае для второго параметра будет использовано значение, заданное по умолчанию.

Области видимости

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

program Project1; procedure Proc1; var a: integer; begin a := 5; //верно. Локальная переменная a здесь видна end; begin a := 10; //Ошибка! Объявленная в процедуре Proc1 переменнаая здесь не видна end.

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

program Project2; var a: integer; // глобальная переменная a procedure Proc1; begin a := 5; // верно b := 10; // Ошибка! Переменая b на этот момент еще не объявлена end; var b: integer; // глобальная переменная b begin a := 10; // верно b := 5; // тоже верно. Здесь видны все г var a: integer; // глобальная переменная end.

Теперь рассмотрим такой вариант, когда у нас имеются 2 переменных с одним и тем же именем. Разумеется, компилятор еще на стадии проверки синтаксиса не допустит, чтобы в программе были объявлены одноименные переменные в рамках одного диапазона видимости (скажем, 2 глобальных переменных X, или 2 локальных переменных X в одной и той же подпрограмме). Речь в данном случае идет о том, что произойдет, если в одной и той же программе будет 2 переменных X, одна — глобальная, а другая — локальная (в какой-либо подпрограмме). Если с основным блоком программы все ясно — в нем будет присутствовать только глобальная X, то как быть с подпрограммой? В таком случае в действие вступает правило близости, т.е. какая переменная ближе (по структуре) к данному модулю, та и есть верная. Применительно к подпрограмме ближней оказывается локальная переменная X, и именно она будет задействована внутри подпрограммы.

program Project3; var X: integer; procedure Proc1; var X: integer; begin X := 5; // Здесь значение будет присвоено локальной переменной X end; begin X := 10; // Здесь же значение будет присвоено голобальной переменной X end.

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

program Project1; procedure Proc1; procedure SubProc; begin end; begin SubProc; // Верно. Вложенная процедура здесь видна. end; begin Proc1; // Верно. Процедура Proc1 объявлена в зоне глобальной видимости SubProc; // Ошибка! Процедура SubProc недоступна за пределами Proc1. end.

Наконец в том случае, когда имена встроенной и некой глобальной процедуры совпадают, то, по аналогии с переменными, в области видимости встроенной процедуры, именно она и будет выполнена.

Видимость в модулях

Все то, что мы уже рассмотрели, касалось программ, умещающихся в одном единственном файле. На практике же, особенно к тому моменту, когда мы перейдем к визуальному программированию, программы будут включать в себя множество файлов. В любом случае, программа на Object Pascal будет иметь уже изученный нами файл проекта — dpr, или основной модуль программы. Все прочие файлы будут располагаться в других файлах, или модулях (units), с типичным для Pascal расширением pas. При объединении модулей в единую программу возникает вопрос видимости переменных, а так же процедур и функций в различных модулях.

Для начала вернемся к рассмотрению структуры модуля, которая имеет ряд отличий от структуры программы. Итак, в простейшем случае, модуль состоит из названия, определяемого при помощи ключевого слова unit, и 2 секций — interface и implementation. Так вот как раз первая секция, interface, и служит для определения (декларации) типов данных, переменных, функций и процедур данного модуля, которые должны быть доступны за пределами данного модуля.

Чтобы лучше в этом разобраться, создадим программу, состоящую из 2 модулей — основного (dpr) и дополнительного (pas). Для этого сначала создайте новый проект типа Console Application, а затем добавьте к нему модуль, для чего из подменю File ‘ New выберите пункт Unit. После этого сохраните проект, щелкнув по кнопке Save All (или File ‘ Save All). Обратите внимание, что первым будет предложено сохранить не файл проекта, а как раз файл дополнительного модуля. Назовем его extunit.pas, а сам проект — miltiunits (см. Demo\Part1\Visibility). При этом вы увидите, что в части uses файла проекта произошло изменение: кроме постоянно добавляемого модуля SysUtils, появился еще один модуль — extunit, т.е. код стал таким:

uses SysUtils, extunit in ‘extunit.pas’;

Мы видим, что Delphi автоматически добавила пояснение, в каком файле находится подключаемый модуль. Это вызвано тем, что если о расположении собственных модулей Delphi все известно, то пользовательские модули могут находиться где угодно на жестком диске ПК. Но в данном случае мы сохранили и файл программы, и подключаемый модуль в одном каталоге, следовательно, их пути совпадают, и данное указание можно было бы опустить:

uses SysUtils, extunit;

Тем не менее, оставим код как есть, и приступим к разработке модуля extunit. В нем, в части implementation, напишем 2 процедуры — ExtProc1 и ExtProc2. Обе они будут делать одно и то же — выводить строку со своим названием. Например, для первой:

Теперь вернемся к главному модулю программы и попробуем обратиться к процедуре ExtProc1:

. begin ExtProc1; end.

Попытка компиляции или запуска такой программы приведет к ошибке компилятора «Undeclared identifier», что означает «неизвестный идентификатор». И действительно, одного лишь описания процедуры недостаточно, чтобы она была доступна вне своего модуля. Так что перейдем к редактированию extunit и в секции interface напишем строку:

Такая строка, помещенная в секцию interface, является объявлением процедуры ExtProc1, и делает ее видимой вне данного модуля. Отметим, что в секции interface допускается лишь объявлять процедуры, но не определять их (т.е. тело процедуры здесь будет неуместно). Еще одним полезным эффектом от объявления процедур является то, что таким образом можно обойти такое ограничение, как необходимость определения подпрограммы до ее вызова. Иначе говоря, поскольку в нашем файле уже есть 2 процедуры, ExtProc1и ExtProc2, причем они описаны именно в таком порядке — сначала ExtProc, а потом ExtProc2, то выведя объявление ExtProc2 в interface, мы сможем обращаться к ExtProc2 из ExtProc1, как это показано в листинге 6.5:

Листинг 6.5. Объявление процедур в модуле

unit extunit; interface procedure ExtProc1; procedure ExtProc2; implementation procedure ExtProc1; begin writeln(‘ExtProc1’); ExtProc2; // Если объявления не будет, то компилятор выдаст ошибку end; procedure ExtProc2; begin writeln(‘ExtProc2’); end; end.

Отметим, что теперь процедуры ExtProc2, так же, как и ExtProc1, будет видна не только по всему модулю extunit, но и во всех использующей этот модуль программе multiunits.

Разумеется, все, что было сказано о процедурах, верно и для функций. Кроме того, константы и переменные, объявленные в секции interface, так же будут видны как во всем теле модуля, так и вне него. Остается лишь рассмотреть вопрос пересечения имен, т.е. когда имя переменной (константы, процедуры, функции) в текущем модуле совпадает с таковым в подключенном модуле. В этом случае вновь вступает в силу правило «кто ближе, тот и прав», т.е. будет использоваться переменная из данного модуля. Например, если в extunit мы объявим типизированную константу Z, равную 100, а в multiunits — одноименную константу, равную 200, то обратившись к Z из модуля extunit, мы получим значение 100, а из multiunits — 200.

Если же нам в multiunits непременно понадобится именно та Z, которая находится в модуле extunit, то мы все-таки можем к ней обратиться, для чего нам пригодится точечная нотация. При этом в качестве имени объекта указывают название модуля:

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

Некоторые стандартные функции

В Object Pascal, как уже отмечалось, имеются огромное количество стандартных процедур и функций, являющихся составной частью языка, и с некоторыми мы уже знакомы (например, приведенные в табл. 5.1 и 5.2 функции преобразования). Детальное описание всех имеющихся в Object Pascal процедур и функций можно получить в справочной системе Delphi, однако мы все-таки рассмотрим здесь некоторые из них, чтобы составить общее представление — см. таблицу 6.1.

Таблица 6.1. Некоторые стандартные процедуры и функции Delphi

Синтаксис Группа Модуль Описание
function Abs(X); арифметические System Возвращает абсолютное значение числа
procedure ChDir(const S: string); управления файлами System Изменяет текущий каталог
function Concat(s1 [, s2. sn]: string): string; строковые System Объединяет 2 и более строк в 1
function Copy(S; Index, Count: Integer): string; строковые System Возвращает часть строки
function Cos(X: Extended): Extended; тригонометрические System Вычисляет косинус угла
procedure Delete(var S: string; Index, Count: Integer); строковые System Удаляет часть строки
function Eof(var F): Boolean; ввод-вывод System Проверяет, достигнут ли конец файла
procedure Halt [ ( Exitcode: Integer) ]; управления System Инициирует досрочное прекращение программы
function High(X); диапазона System Возвращает максимальное значение из диапазона
procedure Insert(Source: string; var S: string; Index: Integer); строковые System Вставляет одну строку в другую
function Length(S): Integer; строковые System Возвращает длину строки или количество элементов массива
function Ln(X: Real): Real; арифметические System Возвращает натуральный логарифм числа (Ln(e) = 1)
function Low(X); диапазона System Возвращает минимальное значение из диапазона
procedure New(var P: Pointer); размещения памяти System Создает новую динамическую переменную и назначает указатель для нее
function ParamCount: Integer; командной строки System Возвращает количество параметров командной строки
function ParamStr(Index: Integer): string; командной строки System Возвращает указанный параметр из командной строки
function Pos(Substr: string; S: string): Integer; строковые System Ищет вхождение указанной подстроки в строку и возвращает порядковый номер первого совпавшего символа
procedure RmDir(const S: string); ввод-вывод System Удаляет указанный подкаталог (должен быть пустым)
function Slice(var A: array; Count: Integer): array; разные System Возвращает часть массива
function UpCase(Ch: Char): Char; символьные System Преобразует символ в верхний регистр
function LowerCase(const S: string): string; строковые SysUtils Преобразует ASCII-строку в нижний регистр
procedure Beep; разные SysUtils Инициирует системный сигнал
function CreateDir(const Dir: string): Boolean; управления файлами SysUtils Создает новый подкаталог
function CurrentYear: Word; даты и времени SysUtils Возвращает текущий год
function DeleteFile(const FileName: string): Boolean; управления файлами SysUtils Удаляет файл с диска
function ExtractFileExt(const FileName: string): string; имен файлов SysUtils Возвращает расширение файла
function FileExists(const FileName: string): Boolean; управления файлами SysUtils Проверяет файл на наличие
function IntToHex(Value: Integer; Digits: Integer): string; форматирования чисел SysUtils Возвращает целое в шестнадцатеричном представлении
function StrPCopy(Dest: PChar; const Source: string): PChar; строковые SysUtils Копирует Pascal-строку в C-строку (PChar)
function Trim(const S: string): string; строковые SysUtils Удаляет начальные и конечные пробелы в строке
function TryStrToInt(const S: string; out Value: Integer): Boolean; преобразования типов SysUtils Преобразует строку в целое
function ArcCos(const X: Extended): Extended; тригонометрические Math Вычисляет арккосинус угла
function Log2(const X: Extended): Extended; арифметические Math Возвращает логарифм по основанию 2
function Max(A,B: Integer): Integer; арифметические Math Возвращает большее из 2 чисел
function Min(A,B: Integer): Integer; арифметические Math Возвращает меньшее из 2 чисел

Те функции, которые имеются в модуле System, являются основными функциями языка, и для их использования не требуется подключать к программе какие-либо модули. Все остальные функции и процедуры можно назвать вспомогательными, и для их использования следует подключить тот или иной модуль, указав его в uses, например, как это делает Delphi уже при создании новой программы с SysUtils:

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

Функции в действии

В целом мы уже ознакомились с несколькими десятками предопределенных процедур и функций, а так же умеем создавать собственные. Пора применить полученные знания на практике, для чего вновь вернемся к программе, рассмотренной в главе, посвященной операторам — игре «Угадай-ка». В ней, по сути, был реализован только один из рассмотренных в самом начале книги алгоритмов — угадывания числа. Что касается алгоритма управления, то на тот момент мы оставили его без внимания.

Но прежде, чем вносить в программу изменения, определимся с тем, что мы все-таки хотим получить в итоге. Допустим, что мы хотим сделать следующие вещи:

  1. Реализовать-таки возможность повторного прохождения игры без перезапуска программы;
  2. Добавить немного «геймплея». Иначе говоря, введем уровни сложности и подсчет очков. Новые уровни можно реализовать как повторное прохождение игры с увеличением сложности (скажем, за счет расширения диапазона загадываемых значений);
  3. В продолжение п. 2 добавить еще и таблицу рекордов, которая будет сохраняться на диске.

Поскольку часть работы уже выполнена, то для того, чтобы приступить к разработке новой версии игры (назовем ее «Угадай-ка 2.0»), мы не будем как обычно создавать новый консольный проект в Delphi, а откроем уже существующий (Ugadaika) и сохраним его под новым именем, скажем, Ugadaika2, и в новом каталоге. Таким образом, мы уже имеем часть исходного кода, отвечающую за угадывание, в частности, цикл while (см. листинг 4.5). Этот фрагмент логичнее всего выделить в отдельную процедуру, вернее даже функцию, которая будет возвращать число попыток, сделанное пользователем. Для этого создадим функцию, которая будет принимать в качестве аргумента число, которое следует угадать, а возвращаемым значением будет целое, соответствующее числу попыток. Ее объявление будет таким:

function GetAttempts(a: integer):integer;

Данная функция так же должна иметь в своем распоряжении переменную, необходимую для ввода пользователем своего варианта ответа. Еще одна переменная нужна для подсчета результата, т.е. количества попыток. В качестве первой можно было бы использовать глобальную переменную (b), однако во избежание накладок, для локального использования в функции следует использовать локальную же переменную. Что касается переменной-счетчика, то для нее как нельзя лучше подходит автоматическая переменная result. Еще одним изменением будет использование цикла repeat вместо while. Это вызвано тем, что с одной стороны, тем, что хотя бы 1 раз пользователь должен ввести число, т.е. условие можно проверять в конце цикла, а с другой мы можем избавиться от присвоения лишнего действия, а именно — присвоения заведомо ложного значения переменной b. Ну и еще одно дополнение — это второе условие выхода, а именно — ограничение на число попыток, которое мы установим при помощи константы MAXATTEMPTS:

const MAXATTEMPTS = 10;

В результате код функции получится таким, как представлено в листинге 6.6.

Листинг 6.6. Функция GetAttempts

function GetAttempts(a: integer):integer; var b: integer; begin Result:=0; repeat inc(Result); // увеличиваем счетчик числа попыток write(#13+#10+’?:’); read(b); if (b>a) then begin write(‘Too much!’); continue; end; if (b

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

var level, score, attempt: integer; f: TextFile; s: string;

Теперь инициализируем счетчик псевдослучайных чисел (т.е. оставим randomize на месте) и инициализируем нулем значения счета и уровня:

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

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

repeat writeln(‘Level ‘+IntToStr(level)+’:’); writeln(‘From 0 to ‘+IntToStr(level*100)); attempt:=GetAttempts(random(level*100+1)); score:=score+(MAXATTEMPTS-attempt)*level; writeln(#10+’You current score is: ‘+IntToStr(score)); inc(level); until attempt>MAXATTEMPTS;

После завершения работы цикла, т.е. когда пользователь хоть раз истратит на отгадывание все 10 попыток, следует сообщить итоговый результат и сравнит его с предыдущим значением, которое следует считать из файла. Файл мы назовем records.txt, и сопоставим с переменной f:

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

if not FileExists(‘record.txt’) then begin Rewrite(f); writeln(f,’0′); // первая строка содержит число-рекорд writeln(f,’None’); // а вторая — имя последнего победителя CloseFile(f); end;

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

Reset(f); readln(f, attempt); readln(f,s); writeln(#10+’BEST SCORE: ‘+IntToStr(attempt)+’ by ‘+s); CloseFile(f);

Ну и последнее, чего нам остается — это проверить, является ли новое значение выше рекорда, и если да — то записать новый рекорд в файл, не забыв спросить имя игрока:

Вот, собственно, и все. Полный код получившейся программы можно увидеть на листинге 6.7, или же в файле проекта в каталоге Demo\Part1\Ugadaika2.

Листинг 6.7. Программа угадай-ка, окончательный вариант

В завершение отметим, что эта программа использует использование не только функций, но и констант, глобальных и локальных переменных, а так же циклов и операций файлового ввода-вывода. Таким образом, на текущий момент мы познакомились со всеми основами обычного, процедурного программирования. Пора двигаться дальше — к объектно-ориентированному программированию в Object Pascal!

Процедуры Delphi

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

Главное отличие процедуры от функции состоит в том, что есть связь между именем функции и значением. Таким образом, возможно использование имени функции в разнообразных выражениях. Организация процедуры Delphi по сути ничем не отличается от организации функции.

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

Общий вид процедуры Delphi:

Общий вид процедуры Delphi выглядит следующим образом:

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

Если процедура обладает какими-либо параметрами, то программист их указывает в скобках, сразу после имени процедуры. В конце заголовка процедуры ставится символ «;». В случае, когда в процедуре имеются именованные константы, программист объявляет их в разделеconst.

Далее за этим разделом идет раздел type, использующийся для объявления типов. После располагается раздел var, содержащий все переменные, которые впоследствии программист использует в своей программе. Затем следует раздел инструкций , которые включает в себя исполняемые инструкции данной процедуры.

Примеры процедуры Delphi:

Приведем пример процедуры Delphi, вычисляющей стоимость некоторой покупки:

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

19 апреля 2009 г.

Настройки проектов в Delphi с точки зрения поиска ошибок

О чём идёт речь

Сначала, давайте посмотрим на них: открываем Project/Options. Нас будут интересовать вкладки Compiling и Linking (в старых версиях Delphi они назывались Compiler и Linker):

На вкладке «Compiler» нас будут интересовать опции «Stack Frames», группа «Debug information», «Local Symbols» и «Symbol reference info», «I/O Checking», «Overflow checking» и «Range checking». На «Linking» — «Map file», «Debug Information» (известная в предыдущих версиях Delphi как «Include TD32 debug info») и «Include remote debug symbols».

Давайте посмотрим, за что отвечают эти опции. А затем — как их лучше бы всего расставить. При этом мы будем рассматривать такие ситуации: обычное приложение и приложение с механизмом диагностики исключений.
Кроме того, настройки проекта могут отличаться, компилируете ли вы приложение для себя или для распространения. В новых версиях Delphi появились профили настроек (Debug и Release, соответственно). Вы можете задать свой набор настроек в каждом профиле, а затем переключаться между ними. В старых Delphi есть только один профиль настроек и вам нужно менять каждую настройку вручную.

Напомним, что при смене любой из опций необходимо сделать полный Build проекту (а не просто Compile).

Что означают эти опции?

Самыми важными настройками являются группа опций «Debug information», «Local Symbols» и «Symbol reference info».

Программа представляет собой набор машинных команд. Текст программы представляет собой текстовый файл. Вопрос: как отладчик узнаёт, когда надо остановиться, если вы поставили бряк на строку в тексте? Где же соответствие между текстовым файлом и набором байт в exe-файле? Вот для такой связи и служит отладочная информация. Это, грубо говоря, набор инструкций типа: «машинные коды с 1056 по 1059 относятся к строке 234 модуля Unit1.pas». Вот с помощью такой информации и работает отладчик. Указанные выше опции отвечают за генерацию отладочной информации для ваших модулей.

Отладочная информация сохраняется вместе с кодом модуля в dcu-файле. Т.е. один и тот же Unit1.pas может быть скомпилирован как с отладочной информацией, так и без неё — в разные dcu файлы. Отладочная информация увеличивает время компиляции, размер dcu-файлов, но не влияет на размер и скорость работы полученного exe-файла (т.е. отладочная информация не подключается к exe-файлу) (*).

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

Подключение отладочной информации к приложению осуществляется несколькими способами: либо это опции проекта (а именно: «Map File», «Debug information» (Linker)/«Include TD32 Debug info» или «Include remote debug symbols»), либо это возможности всевозможных экспертов (типа EurekaLog, JCL или madExcept), которые добавляют отладочную информацию в программу в своём формате.

Итак, опции отладочной информации:

  • «Debug information» (директива <$D+>или <$D->) – это собственно и есть отладочная информация. Т.е. соответствие между текстом программы и её машинным кодом. Вы должны включить эту опцию, если хотите ставить бряки, выполнять пошаговую отладку, а также иметь стек с именами для своего кода. Часто эту опцию включают автоматически различные эксперты типа EurekaLog.
  • «Local symbols» (директива <$L+>или <$L->) – является дополнением к отладочной информации. Она отвечает за соответствие между данными в exe-файле и именами переменных. Если вы включаете эту опцию, то отладчик позволит вам просматривать и изменять переменные. Также окно «Call Stack» будет способно отражать переданные в процедуры параметры.
  • «Reference info» – это дополнительная информация для редактора кода, которая позволяет ему отображать более подробную информацию об идентификаторах. Например, где была объявлена переменная.

Эти три опции тесно связаны и обычно нет смысла включать/выключать их по одной.

  • «Use Debug DCUs» — эта опция переключает сборку вашей программы с отладочными модулями Delphi или с обычными. Если вы внимательно посмотрите на установку Delphi, то увидите, что pas файлы из папки Source никогда не используются для компиляции — а только для отладки. Для компиляции же используются уже готовые dcu-файлы из папки Lib. Это уменьшает время компиляции. Поскольку dcu могут быть скомпилированы с отладочной информацией и без неё, то в папке Lib есть два набора dcu-файлов: обычные и отладочные. Переключая эту опцию, вы указываете, какие из них использовать. Если вы выключите эту опцию, то не сможете заходить по F7 в стандартные функции и методы Delphi (т.к. для них будет отсутствовать отладочная информация). Также при выключенной опции вы не будете видеть информацию о стандартном коде в стеке вызовов.
  • «Stack Frames» — эта опция отвечает за генерацию стековых фреймов. Если опция выключена, то стековый фрейм не генерируется без необходимости. Если она включена -то фрейм генерируется всегда. Стековые фреймы используются при построении стека вызовов по фреймам (построение методом raw-сканирование не нуждается в стековых фреймах). В обычном приложении стековые фреймы генерируются практически всегда (**).
  • «Range checking» — служит помощником в поиске проблем при работе, например, с массивами. Если её включить, то для любого кода, который работает с массивами и строками, компилятор добавляет проверочный код, который следит за правильностью индексов. Если при проверке обнаруживается, что вы вылезаете за границы массива, то будет сгенерировано исключение класса ERangeError. При этом вы можете идентифицировать ошибку обычной отладкой. Если же опция выключена, то никакого дополнительного кода в программу не добавляется. Включение опции немного увеличивает размер программы и замедляет её выполнение. Рекомендуется включать эту опцию только в отладочной версии программы.
  • «Overflow checking» — похожа на опцию «Range checking», только проверочный код добавляется для всех арифметических целочисленных операций. Если результат выполнения такой операции выходит за размерность (происходит переполнение результата), то возбуждается исключение класса EIntOverflow. Пример – к байтовой переменной, равной 255, прибавляется 2. Должно получиться 257, но это число больше того, что помещается в байте, поэтому реальный результат будет равен 1. Это и есть переполнение. Эта опция используется редко по трём причинам. Во-первых, самый разный код может рассчитывать на то, что эта опция выключена (часто это различного рода криптографические операции, подсчёт контрольной суммы и т.п., но не только). В связи с этим при включении этой опции могут начаться совершенно различные проблемы. Во-вторых, в обычных ситуациях работают с четырёхбайтовыми знаковыми величинами, и работа около границ диапазонов представления происходит редко. В-третьих, арифметические операции с целыми – достаточно частый код (в отличие от операций с массивами), и добавление дополнительной работы на каждую операцию иногда может быть заметно (в смысле производительности).
  • «I/O Checking» — эта опция используется только при работе с файлами в стиле Паскаля, которые считаются устаревшими. По-хорошему, вы не должны использовать их и, соответственно, эту опцию.

Замечу также, что эти опции можно выставлять и локально — как для целого модуля, так и для отдельной функции/процедуры (а для некоторых опций — даже для участка кода). Делается это обычными директивами компилятора, узнать которые вы можете, нажав F1 в окне настроек. Например, «Stack Frames» регулируется <$W+>и <$W->.

Кроме того, помимо настроек компилятора (Compiling) есть ещё настройки компоновщика (Linking):

  • «Map file» — включение опции заставляет линкёр Delphi создавать вместе с проектом map-файл. Различные установки опции отвечают за уровень детализации и обычно имеет смысл ставить только Off или Detailed. Map файл обычно используется всевозможными утилитами типа EurekaLog, JCL или madExcept в качестве первичного источника для создания отладочной информации в своём формате. Поэтому руками устанавливать эту опцию вам придётся крайне редко — эксперты включают её самостоятельно по необходимости.
  • «Debug Information» (Linker)/«Include TD32 debug info» — внедряет в приложение отладочную информацию для внешнего отладчика в формате TD32. Обычно эта опция включается, если вы отлаживаете проект через Attach to process и Delphi не может найти отладочную информацию. При включении этой опции размер самого приложения увеличивается в 5-10 раз (при условии, что опция «Place debug information in separate TDS file» выключена). Поэтому, если вам нужна отладочная информация в распространяемом приложении — лучше рассмотреть другие варианты (лучше всего подходит отладочная информация в специализированных форматах — EurekaLog, JCL, madExcept).
  • «Include remote debug symbols» — заставляет линкёр создать rsm-файл вместе с проектом, в который записывается информация для удалённого отладчика Delphi. Вам нужно включать эту опцию, если вы хотите выполнить удалённую отладку. Полученный rsm-файлик нужно копировать вместе с приложением на удалённую машину.

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

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

Обычное приложение без механизма диагностики исключений

Общие настройки для любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») вообще на готовый модуль не влияют, т.к. отладочная информация в exe/DLL не хранится, ну и нам жить не мешают => поэтому смысла их выключать я не вижу («Use Debug DCUs» — устанавливайте по вкусу, смотря по тому, хотите вы отлаживаться в стандартных модулях или нет).

«Stack Frames» вообще включать незачем.

Генерацию map-файла выключаем.

Профиль Debug

Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» — включаем только для отладки внешнего процесса.

Соответственно, «Include remote debug info» — включаем только для удалённой отладки.

Профиль Release

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Приложение с механизмом диагностики исключений (типа EurekaLog, JCL или madExcept)

Общие настройки любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») держать включёнными, т.к. в противном случае не будет доступна отладочная информация. Соответственно, ваши информационные механизмы пойдут лесом.

«Stack Frames» — вообще вЫключать незачем.

Генерацию map-файла включаем, если об этом не позаботился эксперт (маловероятно).

Профиль Debug

«Use Debug DCUs» — по вкусу.
Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» — включаем только для отладки внешнего процесса.

«Include remote debug info» — включаем только для удалённой отладки.

Профиль Release

Включаем «Use Debug DCUs».

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Примечание: если вы используете мало операций с индексами в своей программе (так что дополнительные проверки не замедлят её), то будет хорошей идеей всегда держать опцию «Range checking» включённой.

Что может пойти не так, если настройки будут заданы неверно?

Ну, во-первых, это невозможность отладки (например, отсутствие информации для удалённого отладчика или выключенная опция «Debug information» (Compiler)), большой размер приложения (например, случайно забыли выключить «Debug information» (Linker)/«Include TD32 debug info»), медленная работа (например, компиляция с отладочным кодом), отсутствие или неполный стек вызовов в средствах диагностики исключений (например, выключили «Debug information» (Compiler)). В очень редких и запущенных случаях переключение опций может сказаться на работоспособности программы (например, установка Stack frames может снизить максимально возможную глубину рекурсии). Ну и недочёты по мелочи.

Кстати, если вы разрабатываете компоненты, то надо учитывать, что у Delphi есть именно два набора DCU-файлов, которые отличаются настройками компиляции. Вообще говоря, простое переключение опций, ответственных за генерацию отладочной информации, никак не влияет на интерфейс и реализацию модуля. Но в общем случае код может использовать условные директивы. Поэтому может быть ситуация, когда два набора DCU файлов (скомпилированных с разными опциями) не совместимы друг с другом — потому что они использовали какую-либо директиву и содержат разный код (а вот и пример).
Поэтому вам тоже надо иметь два набора DCU-файлов: один — скомпилированный с опцией «Use Debug DCUs», другой — без. Причём, не важно включена или выключена опция «Debug information» (Compiler) в ваших настройках в обоих случаях.

Примечания:
(*) Слова «отладочная информация увеличивает время компиляции, размер dcu-файлов, но не влияет на размер и скорость работы полученного exe-файла» некоторые понимают неправильно. В частности, многие замечают, что если переключить профиль приложения с Debug на Release (или наоборот), то размер приложения изменится (незначительно или же намного — зависит от настроек проекта и его исходного кода). Позвольте, но ведь я говорю об отладочной информации в чистом виде (мы говорим про опции «Debug Information», «Local Symbols» и т.п. с вкладки «Compiling»), а вы меняете профиль компиляции целиком. Это не только смена опций отладочной информации (которые, кстати, могут даже вообще не меняться при смене профиля), а также и множество других настроек.

Например, в вашем коде может быть тьма конструкций вида <$IFDEF DEBUG>какой-то код <$ENDIF>. Разумеется, когда вы собираете программу в Release, этот код в программу не попадёт, а когда вы собираете его в Debug, то — попадёт. Потому что по умолчанию профиль Debug содержит символ условной компиляции «DEBUG». В результате размер .exe действительно получится разный. Но повлияла ли на этот размер отладочная информация? Неа. Фактически, вы собрали в разных профилях разных код — неудивительно, что он разный по размеру.

Более того, вы можете удалить из конфигурации Debug символ условной компиляции «DEBUG» — и тогда вы будете собирать один и тот же код в обоих профилях. Ну, по крайней мере, свой код. Сторонний, уже собранный код, конечно же, никак не будет затронут. Например, код RTL/VCL уже скомпилирован с фиксированными настройками и не меняется при пересборке проекта. Вон там чуть выше я упомянул, что в некоторых версиях Delphi отладочный и релизный варианты кода RTL/VCL незначительно отличаются — опять же, благодаря условной компиляции. Но никак не отладочной информации.

Кроме того, к иному коду приводит и включение/выключение опций вида Optimization, Stack Frames, Range Check Errors и др. Действительно, оптимизация может удалять код (оптимизировать ненужный) или увеличивать (вставлять inline вместо вызова), Stack Frames очевидно добавляет код (код установки фрейма), равно как и Range Check Errors (код проверки диапазонов). В результате, вы меняете профиль — вы меняете и код (при условии, что разные профили имеют разные настройки вышеупомянутых опций — что так и есть по умолчанию). Меняете код — меняете и размер. Имеет ли хоть какое-то отношение к этому изменению размера отладочная информация? Нет.

Далее, другой пример. Несмотря на то, что по умолчанию отладочная информация в .exe файл не попадает — никто же не запрещает её вам туда добавить самому! Например, если вы будете использовать трейсер исключений, то он самостоятельно добавит в .exe часть отладочной информации, которая нужна ему для построения читабельных стеков вызовов.

Также о внедрении отладочной информации можно попросить и среду. Например, вы можете включить «Include TD32 Debug Info» (эта опция называется «Debug Information» на вкладке «Linking» в последних версиях Delphi) и выключить опцию «Place debug information in separate TDS file». Тогда вся отладочная информация из .dcu файлов будет собрана в один большой файл и этот файл будет внедрён в .exe. Тогда да, отладочная информация повлияет на размер файла, но это происходит не из-за её свойств, а потому что мы явно попросили такое поведение: «добавьте отладочную инфу в мою программу».

Кстати говоря, в последних версиях Delphi здорово поменяли настройки профилей компиляции. Если раньше Release и Debug мало чем отличались, то теперь отличия существенны. Профиль Debug выключает Optimization, но включает Stack Frames (а профиль Release — делает наоборот). Профиль Debug включает отладочную информацию, а Release — отключает. Но оба профиля включают Assert-ы. Также оба профиля включают I/O checks, но выключают overflow и range. В настройках компоновщика (linking) профиль Debug включает TD32 («Debug information») и Remote debug symbols (не для всех платформ). Release, соответственно, выключает эти же опции. И оба профиля не включают Map file и отдельный файл для TD32.

(**) Например: Button1Click состоит всего из двух инструкций: «call A; ret;». Она очень короткая и не использует аргументы или локальные переменные. Поэтому, очевидно, что ей не нужен стековый фрейм. Когда опция «Stack frames» выключена, то для Button1Click стековый фрейм не создаётся (но он создаётся, если опция «Stack frames» будет включена).

Но, для более сложных процедур стековые фреймы будут генерироваться вне зависимости от установки опции «Stack frames».

Например, тоже очень короткая процедура B всегда имеет фрейм. Причина: использование типа String в ShowMessage. Компилятору нужно вставить неявную строковую переменную и неявный try/finally для её освобождения, поэтому процедуре нужен фрейм.

В реальных приложениях фреймы генерируются для 99% процедур. Подробнее: Фреймы на стеке.

Канал в Telegram

Вы здесь

Работа с INI файлами в Delphi

Практически любое программное обеспечение способно запоминать (сохранять) настройки установленные пользователем. К примеру, внешний вид окна, язык и мн. др. Для сохранения настроек приложения есть несколько способов:
1. Реестр — все настройки и установки программы хранятся в системном реестре Windows.
2. INI файлы — настройки сохраняются в файл с расширением .ini
3. Создание файла с собственной структурой и способом хранения настроек.

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

Структура INI файлов

INI файлы в системе Windows выглядят следующим образом:

т.е. фактически это текстовый (txt) документ и легко редактируется приложением блокнот. Структура INI файла так же проста, она имеет категорию(секцию), которая указывается в квадратных скобках ([menu]) и значения(идентификатор) Value = 256, где Value имя некоторого параметра в программе, например ширина окна, а 256-собственно значение этого параметра. Общий вид структуры выглядит так:

[menu]
f.heigth = 1440
f.w >[language]
default = russian

Ну все. Достаточно теории. приступим к практике.
Создайте форму со следующими компонентами: две кнопки Button, Edit(Вкладка Standard), SpinEdit(вкладка Samples) и checkbox (вкладка Standard). Примерный вид формы такой:

Нам необходимо по нажатию кнопки «Сохранить в INI» сохранить в example.ini (распологается в папке с программой) текстовое значение Edit, числовое значение Spin и логическое значение checkbox. При нажатии кнопки «Считать INI«, соответственно прочитать эти значения.
Структура файла example.ini следующая:

[TEST]
EditVal =
SpinVal =
CheckboxVal =

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

Приступим к реализации.
Для работы с INI в Delphi имеется стандартная библиотека inifiles. Пропишем ее в uses.
Нам понадобятся две переменные pathINI (будет хранить путь к файлу example.ini) и sIniFile типа TIniFile. Эти переменные должны быть общими для всех процедур в модуле, поэтому их необходимо прописать в разделе var перед implementation

При загрузке нашего приложения, переменная pathINI должна получить полный путь к файлу ini. Для того чтоб произошли какие либо действия при загрузке формы, у объекта Form есть событие OnCreate. Кликните два раза по свободному месту на форме, после чего в редакторе кода появится процедура TForm1.FormCreate , добавьте в нее следующую строчку:

Код для кнопок будет следующим:

procedure TForm1.Button1Click(Sender: TObject);
begin
//создаем ссылку на объект INI
sIniFile := TIniFile.Create(pathINI);

// запись в INI строки
sIniFile.WriteString(‘TEST’, ‘EditVal’, edit1.Text);

// запись в INI целого числа
sIniFile.WriteInteger(‘TEST’, ‘SpinVal’, SpinEdit1.Value);

// запись в INI логического значения boolean
sIniFile.WriteBool(‘TEST’, ‘CheckboxVal’, CheckBox1.Checked);

//запись значений позиции формы
sIniFile.WriteInteger(‘FormPosition’, ‘fTop’, form1.Top);
sIniFile.WriteInteger(‘FormPosition’, ‘fLeft’, form1.Left);
// очистка переменной объекта
sIniFile.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin

if FileExists(pathINI) then //проверяем есть ли файл INI
begin
sIniFile := TIniFile.Create(pathINI);
edit1.Text:=sIniFile.ReadString(‘TEST’, ‘EditVal’, ‘Раздел не найден!’);
spinedit1.Value:=sIniFile.readInteger(‘TEST’, ‘SpinVal’, 0);
checkbox1.Checked:=sIniFile.readBool(‘TEST’, ‘CheckboxVal’, false);
form1.Top:= sIniFile.readInteger(‘FormPosition’, ‘fTop’, 10);
form1.Left:= sIniFile.readInteger(‘FormPosition’, ‘fLeft’, 10);
sIniFile.Free;
end
else showmessage(‘Файл example.ini не найден!’);

end;

Код не сложный, но при чтении значений из INI файла, есть один параметр (третий после указания секции и идентификатора), этот параметр будет присвоен в случае если идентификатор не будет найден т.е. значение по умолчанию. В таблице ниже приведен полный список функций записи и чтения в INI файл:

Использование процедур и функций в Delphi

Скобки

Добавление скобок при вызове процедур и функций без параметров уже давно не является новинкой в Delphi, тем не менее, эта возможность мало известна. Эту возможность оценят по достоинству те программисты, которым приходится работать на двух языках (C++ и Delphi), так как им не нужно будет постоянно помнить о разнице в синтаксисе при вызове процедур и функций в разных языках. В Delphi оба варианта, приведенные ниже, считаются корректными.

Возможность перегрузки

Впервые концепция перегрузки процедур и функций была реализована в Delphi 4. Она позволяет иметь несколько различных процедур и функций с одинаковыми именами, но с разными списками параметров. Такие процедуры и функции должны быть описаны с применением директивы overload.

procedure Test (I: integer); overload;
procedure Test (S: string); overload;
procedure Test (D: double); overload;

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

Передача параметров

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

Передача параметров по значению

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

procedure Test(s: string);

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

Однако это не относится к объектам. Например, если в функцию передается переменная (а точнее экземпляр объекта) TStringList, то в данном случае произойдет передача по ссылке (даже если это не указано явно). Этот способ передачи является у большинства самым излюбленным, но в тоже время является и самым не практичным, т.к. для выполнения метода выделяется дополнительная память для создания точной копией передаваемой переменой. Для решения этой проблемы следует использовать один из способов описанных ниже.

Передача параметров по ссылке

Pascal позволяет также передавать параметры в функции или процедуры по ссылке — такие параметры называются параметрами-переменными. Передача параметра по ссылке означает, что функция или процедура сможет изменить полученные значения параметров. Для передачи параметров по ссылке используется ключевое слово var, помещаемое в список параметров вызываемой процедуры или функции.

procedure ChangeMe(var x: longint);
begin
x := 2; // Параметр х изменен вызванной процедурой
end;

Вместо создания копии переменной x, ключевое слово var требует передачи адреса самой переменной x, что позволяет процедуре непосредственно изменять ее значение.

Передача параметров констант

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

procedure Test(const s: string );

Передача открытых массивов

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

function AddEmUp(A: array of integer): integer;

В открытом массиве можно передавать переменные, константы или выражения из констант.

Для получения информации о фактически передаваемом массиве параметров в функции или процедуре могут использоваться функции High, Low и SizeOf.

Object Pascal также поддерживает тип array of const, который позволяет передавать в одном массиве данные различных типов. Синтаксис объявления функций или процедур, использующих такой массив для получения параметров, следующий:

procedure WhatHaveIGot( A: array of const );

Вызвать объявленную выше функцию можно, например, с помощью такого оператора:

procedure WhatHaveIGot( [‘Text’, 10, 5.5, @WhatHaveIGot, 3.14, true, ‘c’] );

При передаче функции или процедуре массива констант все передаваемые параметры компилятор неявно конвертирует в тип TVarRec. Тип данных TVarRec объявлен в модуле System следующим образом:

PVarRec = ^TVarRec;
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

Поле VType определяет тип содержащихся в данном экземпляре записи TVarRec данных и может принимать одно приведенных значений.

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

procedure WhatHaveIGot( A: array of const );
var
i: integer;
TypeStr: string;
begin
for i := Low(A) to High(A) do
begin
case A[i].VType of
vtInteger : TypeStr := ‘Integer’;
vtBoolean : TypeStr := ‘Boolean’;
vtChar : TypeStr := ‘Char’;
vtExtended : TypeStr := ‘Extended’;
vtString : TypeStr := ‘String’;
vtPointer : TypeStr := ‘Pointer’;
vtPChar : TypeStr := ‘PChar’;
vtObject : TypeStr := ‘Object’;
vt ;
vtW ;
vtPW ;
vtAnsiString : TypeStr := ‘AnsiString’;
vtCurrency : TypeStr := ‘Currency’;
vtVariant : TypeStr := ‘Variant’;
vtInterface : TypeStr := ‘Interface’;
vtW ;
vtInt64 : TypeStr := ‘Int64’;
end;
ShowMessage( Format( ‘Array item %d is a %s’, [i, TypeStr] ) );
end;
end;

Значения параметров по умолчанию

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

procedure HasDefVal( s: string; i: integer = 0 );

Подобное объявление означает, что процедура HasDefVal может быть вызвана двумя путями. В первом случае — как обычно, с указанием обоих параметров:

procedure HasDefVal( ‘Hello’, 26 );

Во втором случае можно задать только значение параметра s, а для параметра i использовать значение, установленное по умолчанию:

procedure HasDefVal( ‘Hello’ );

При использовании значении параметров по умолчанию следует помнить о нескольких приведенных ниже правилах:

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

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

function Add( I1, I2: integer ): integer;
begin
Result := I1 + I2;
end;

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

function Add( I1, I2: integer; I3: integer = 0 ): integer;
begin
Result := I1 + I2 + I3;
end;

Директива

Директива <$X->запрещает вызов функций как процедур (с игнорированием возвращаемого результата). По умолчанию этот режим включен (<$X+>). Так вот, запомните, использование переменной Result недопустимо при сброшенном флажке опции Extended Syntax, расположенном во вкладке Compiler диалогового окна Project Options, или при указании директивы компилятора <$X->.

В каждой функции языка Objecl Pascal существует локальная переменная с именем Result, предназначенная для размещения возвращаемого значения. Кроме того, вернуть значение из функции можно также путем присвоения значения переменной, имеющей то же имя, что и данная функция. Это стандартный синтаксис языка Pascal, сохранившийся от его предыдущих версий. При использовании в теле функции переменной с ее именем не забывайте, что существуют большие отличия в обработке этого имени — все зависит от того, где она расположена — в левой части оператора присвоения или же в любом другом месте текста функции. Если имя функции указано в левой части оператора присвоения, то предполагается, что назначается возвращаемое функцией значение. Во всех других случаях предполагается, что осуществляется рекурсивный вызов этой функции.

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

Delphi: Alternative to using Reset/ReadLn for text file reading

i want to process a text file line by line. In the olden days i loaded the file into a StringList :

Problem with that is once the file gets to be a few hundred megabytes, i have to allocate a huge chunk of memory; when really i only need enough memory to hold one line at a time. (Plus, you can’t really indicate progress when you the system is locked up loading the file in step 1).

The i tried using the native, and recommended, file I/O routines provided by Delphi:

Problem with Assign is that there is no option to read the file without locking (i.e. fmShareDenyNone ). The former stringlist example doesn’t support no-lock either, unless you change it to LoadFromStream :

So now even though i’ve gained no locks being held, i’m back to loading the entire file into memory.

Is there some alternative to Assign / ReadLn , where i can read a file line-by-line, without taking a sharing lock?

i’d rather not get directly into Win32 CreateFile / ReadFile , and having to deal with allocating buffers and detecting CR , LF , CRLF ‘s.

i thought about memory mapped files, but there’s the difficulty if the entire file doesn’t fit (map) into virtual memory, and having to maps views (pieces) of the file at a time. Starts to get ugly.

Илон Маск рекомендует:  Что такое код mb_get_info
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL