Кривая Гильберта
Следующая программа вычерчивает в диалоговом окне кривую Гильберта. На рис. 12.7 приведены кривые Гильберта первого, второго и третьего порядков. Если присмотреться, то видно, что кривая второго порядка получается путем соединения прямыми линиями четырех кривых первого порядка. Аналогичным образом получается кривая третьего порядка, но при этом в качестве "кирпичиков" используются кривые второго порядка. Таким образом, чтобы нарисовать кривую третьего порядка, надо нарисовать четыре кривых второго порядка. В свою очередь, чтобы нарисовать кривую второго порядка, надо нарисовать четыре кривых первого порядка. Таким образом, алгоритм вычерчивания кривой Гильберта является рекурсивным.
Диалоговое окно программы Кривая Гильберта, в котором находится кривая пятого порядка, приведено на рис. 12.8, текст программы — в листинге 12.4.
Рис. 12.7. Кривые Гильберта первого, второго и третьего порядков
Рис. 12.8. Кривая Гильберта пятого порядка
Листинг 12.4. Кривая Гильберта
unit gilbert_;
interface
uses
Windows, Messages, SysUtils,
Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ComCtrls;
type
TForml = class(TForm)
procedure FormPaint(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation {$R *.dfm}
var
p: integer =5; // порядок кривой
u: integer =7; // длина штриха
{ Кривую Гильберта можно получить путем
соединения элементов а,b,с и d.
Каждый элемент строит
соответствующая процедура. }
procedure a(i:integer; canvas: TCanvas); forward;
procedure b(i:integer; canvas: TCanvas); forward;
procedure с(i:integer; canvas: TCanvas); forward;
procedure d(i:integer; canvas: TCanvas); forward;
// Элементы кривой
procedure a(i: integer; canvas: TCanvas);
begin
if i > 0 then begin
d(i-l, canvas);
canvas.LineTo(canvas.PenPos.X+u,canvas.PenPos.Y);
a(i-l, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y+u);
a(i-l, canvas);
canvas.LineTo(canvas.PenPos.X-u,canvas.PenPos.Y);
с (i-1, canvas);
end;
end;
procedure b(i: integer; canvas: TCanvas);
begin
if i > 0 then begin
c(i-l, canvas);
canvas.LineTo(canvas.PenPos.X-u,canvas.PenPos.Y);
b(i-1, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y-u);
b(i-l, canvas);
canvas.LineTo(canvas.PenPos.X+u, canvas.PenPos.Y);
d(i-l, canvas);
end;
end;
procedure c(i: integer; canvas: TCanvas);
begin
if i > 0 then begin
b(i-1, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y-u);
с (i-1, canvas);
canvas.LineTo(canvas.PenPos.X-u,canvas.PenPos.Y);
c(i-1, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y+u);
a(i-1, canvas);
end;
end;
procedure d(i: integer; canvas: TCanvas);
begin
if i > 0 then begin
a(i-1, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y+u);
d(i-1, canvas);
canvas.LineTo(canvas.PenPos.X+u,canvas.PenPos. Y) ;
d(i-1, canvas);
canvas.LineTo(canvas.PenPos.X,canvas.PenPos.Y-u);
b(i-1, canvas);
end;
end;
procedure TForml.FormPaint(Sender: TObject);
begin
Form1.Canvas.MoveTo(u, u) ;
a(5,Form1.Canvas); // вычертить кривую Гильберта
end;
end.
Следует обратить внимание на следующую особенность реализации программы. Процедура, которая вычерчивает элемент а, помимо самой себя (для вычерчивания элемента а кривой более низкого порядка) вызывает процедуры d и ь, описание (текст) которых в тексте программы находится после процедуры а. Чтобы компилятор не вывел сообщение об ошибке, в текст программы помещено объявление процедуры с ключевым словом forward, означающим, что это только объявление, а описание (реализация) находится дальше. Таким образом, уже в процессе компиляции процедуры а, компилятор "знает", что имена ь и d означают процедуры.
Поиск файлов
В качестве примера использования рекурсии рассмотрим задачу поиска файлов. Пусть нужно получить список всех файлов, например, с расширением bmp, которые находятся в указанном пользователем каталоге и во всех подкаталогах этого каталога.
Словесно алгоритм обработки каталога может быть представлен так:
1. Вывести список всех файлов удовлетворяющих критерию запроса.
2. Если в каталоге есть подкаталоги, то обработать каждый из этих каталогов.
Приведенный алгоритм (его блок-схема представлена на рис. 12.4) является рекурсивным: для того чтобы обработать подкаталог, процедура обработки текущего каталога должна вызвать сама себя.
Рис. 12.4. Рекурсивный алгоритм поиска файлов
Вид диалогового окна программы приведен на рис. 12.5, текст — в листинге 12.3.
Поле Файл (Edit1) используется для ввода имени искомого файла или маски (для поиска файлов одного типа). Имя каталога, в котором нужно выполнить поиск, можно ввести непосредственно в поле Папка или выбрать из стандартного диалогового окна Обзор папок, которое появляется в результате щелчка на кнопке Папка. Окно Обзор папок (рис. 12.6) выводит на экран стандартная функция Seiectoirectory. Следует обратить внимание, что имя каталога, который используется в диалоговом окне Обзор папок в качестве корневого, должно передаваться функции SeiectDirectory как Строка WhideChar. Для Преобразования обычной строки в строку WideChar использована функция StringToWhideChar.
Рис. 12.5. Окно программы Поиск файлов
Рис. 12.6. Диалоговое окно Обзор папок появляется в результате щелчка на кнопке Папка
Основную работу выполняет рекурсивная функция Find. У функции Find один-единственный параметр — структура searchRec, которая используется функциями FindFirst и FindNext для поиска соответственнопервого и следующего файла, удовлетворяющего критерию поиска. Следует обратить внимание на то, как осуществляется перебор каталогов в текущем каталоге. Если текущий каталог не корневой, то помимо обычных, то есть имеющих имя, в каталоге есть еще два каталога: .. и ., которые обозначают каталог предыдущего уровня. Эти два каталога не обрабатываются, так как при входе в эти каталоги фактически выполняется выход (переход) в родительский каталог. Если этого не учесть, то программа зациклится.
Листинг 12.3. Программа поиск файлов
// поиск файла в указанном каталоге и его подкаталогах
// используется рекурсивная процедура Find
unit FindFile_;
interface
uses
Windows, Messages, SysUtils, Variants,
Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, FileCtr;
type
TForm1 = class(TForm)
Editl: TEdit; // что искать
Edit2: TEdit; // где искать
Memo1: TMemo; // результат поиска
Button1: TButton; // кнопка Поиск
Button2: TButton; // кнопка Папка
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
FileName: string; // имя или маска искомого файла
cDir: string;
n: integer; // кол-во файлов, удовлетворяющих запросу
// поиск файла в текущем каталоге
procedure Find;
var
SearchRec: TSearchRec; // информация о файле или каталоге
begin
GetDir(0,cDir); // получить имя текущего каталога
if cDir [length (cDir) ] <> 'V then cDir := cDir+'\';
if FindFirst(FileName, faArchive,SearchRec) = 0
then repeat
if (SearchRec.Attr and faAnyFile) = SearchRec.Attr
then begin
Form1.Memo1.Lines.Add(cDir + SearchRec.Name);
n := n + 1; end; until FindNext(SearchRec) <> 0;
// обработка подкаталогов текущего каталога
if FindFirst('*', faDirectory, SearchRec) = 0 then repeat
if (SearchRec.Attr and faDirectory) = SearchRec.Attr then begin
// каталоги .. и . тоже каталоги,
// но в них входить не надо .'.'.'
if SearchRec.Name[1] <> '.' then begin
ChDir(SearchRec.Name);// войти в каталог
Find; // выполнить поиск в подкаталоге
ChDir('..');// выйти из каталога
end;
end;
until FindNext(SearchRec) <> 0;
end;
/ возвращает каталог, выбранный пользователем
function GetPath(mes: string):string;
var
Root: string; // корневой каталог
pwRoot : PWideChar; Dir: string;
begin
Root := '';
GetMem(pwRoot, (Length(Root)+1) * 2);
pwRoot := StringToWideChar(Root, pwRoot, MAX_PATH*2);
if SelectDirectory(mes, pwRoot, Dir) then
if length(Dir) =2 // пользователь выбрал корневой каталог
then GetPath := Dir+'\' else GetPath := Dir else
GetPath := '';
end;
щелчок на кнопке Поиск
procedure TForml.ButtonlClick(Sender: TObject);
begin
Memo1.Clear; // очистить поле Memol
Label4.Caption := '';
FileName := Edit1.Text; // что искать.
cDir := Edit2.Text; // где искать
n:=0; // кол-во найденных файлов
ChDir(cDir); // войти в каталог начала поиска
Find; // начать поиск
if n = 0 then
ShowMessage('Файлов, удовлетворяющих критерию поиска нет.')
else Label4.Caption := 'Найдено файлов:' + IntToStr(n);
end;
// щелчок на кнопке Папка
procedure TForml.Button2Click (Sender: TObject);
var
Path: string; begin
Path := GetPath('Выберите папку');
if Path <> ''
then Edit2.Text := Path;
end;
end.
Поиск кратчайшего пути
Обычно задача поиска пути на графе формулируется следующим образом: найти наилучший маршрут. Под наилучшим маршрутом, как правило, понимают кратчайший. Найти кратчайший маршрут можно выбором из всех найденных. Однако совсем не обязательно искать все маршруты.
Можно поступить иначе: во время выбора очередной точки проверить, не превысит ли длина формируемого маршрута длину уже найденного пути, если эта точка будет включена в маршрут; если превысит, то эту точку следует пропустить и выбрать другую.
Таким образом, после того как будет найден первый маршрут, программа будет вести поиск только по тем ветвям графа, которые могут улучшить найденное решение, отсекая пути, делающие формируемый маршрут длиннее уже найденного.
В листинге 12.6 приведена процедура, которая использует процедуру step, выполняющую выбор очередной точки маршрута таким образом, что обеспечивается поиск пути минимальной длины.
Листинг 12.6. Поиск кратчайшего пути
procedure TForm1.Button1Click(Sender: TObject);
const
N=10;{ кол-во вершин графа} var
map:array[1..N,1..N]of integer;
// Карта.map[i,j] не 0,если
// точки i и j соединены
road:array[1..N]of integer;
// Дорога — номера точек карты
incl:array[1..N]of boolean; // incl[1]равен TRUE,если точка
// с номером i включена в road
start, finish:integer;
// Начальная и конечная точки
found:boolean; len:integer; // длина найденного (минимального)
// маршрута } c_len:integer; // длина текущего (формируемого)
// маршрута i,j:integer;
// выбор очередной точки
procedure step(s,f,p:integer);
var
с:integer; { Номер точки, в которую делаем очередной шаг }
i:integer; begin
if s=f then begin
len:=c_len;{ сохраним длину найденного маршрута }
{ вывод найденного маршрута }
for i:=1 to p-1 do
Label1.caption:=Label1.caption+' '+IntToStr(road[i]);
Label1.caption:=Label1.caption
+', длина:'+IntToStr(len)+#13;
end
else
{ выбираем очередную точку }
for c:=l to N do { проверяем все вершины }
if(map[s,c]<> 0)and(NOT incite])
and((len=0)or(c_len+map[s,c]< len)) then begin
// точка соединена с текущей, но не включена в
// маршрут
roadtp]:=c;{ добавим вершину в путь }
incl[c]:=TRUE;{ пометим вершину как включенную }
c_len:=c_len+map[s,с];
step(c,f,p+l);
incite]:=FALSE; roadtp]:=0;
c_len:=c_len-map[s,с];
end;
end;
{ конец процедуры step }
begin
Labell.caption:='';
{ инициализация массивов }
for i: =1 to N do road [ i ] : =0;
for i:=l to N do incl[i]:=FALSE;
{ ввод описания карты из SrtingGrid.Cells}
for i:=l to N do
for j:=1 to N do
if StringGridl.Cells[i, j] <> "
then mapti,j]:=StrToInt(StringGridl.Cells[i,j])
else mapti,j]:=0;
len:=0; // длина найденного (минимального) маршрута с
len:=0,- // длина текущего (формируемого) маршрута
start:=StrToInt(Edit1.text);
finish:=StrToInt(Edit2.text);
road[1]:=start;{ внесем точку в маршрут }
incl[start]:=TRUE;{ пометим ее как включенную }
step(start,finish,2);{ищем вторую точку маршрута }
// проверим, найден ли хотя бы один путь
if not found
then Label1.caption:='Указанные точки не соединены!';
end;
Диалоговое окно программы поиска кратчайшего пути и процедура обработки события OnActivate ничем не отличаются от диалогового окна и соответствующей процедуры OnActivate программы поиска всех возможных маршрутов, рассмотренной в предыдущем разделе.
Поиск пути
Механизм рекурсии весьма эффективен при программировании задач поиска. В качестве еще одного примера рассмотрим задачу поиска пути между двумя городами. Если несколько городов соединены дорогами, то очевидно, что попасть из одного города в другой можно различными маршрутами. Задача состоит в нахождении всех возможных маршрутов.
Карта дорог между городами может быть изображена в виде графа — набора вершин, означающих города, и ребер, обозначающих дороги (рис. 12.9).
Рис. 12.9. Представление карты дорог в виде графа
Процесс поиска может быть представлен как последовательность шагов. На каждом шаге с использованием некоторого критерия выбирается точка, в которую можно попасть из текущей. Если очередная выбранная точка совпала с заданной конечной точкой, то маршрут найден. Если не совпала, то делаем из этой точки еще шаг. Так как текущая точка может быть соединена с несколькими другими, то нужен какой-то формальный критерий выбора. В простейшем случае можно выбрать точку с наименьшим номером.
Пусть, например, надо найти все возможные пути из точки 1 в точку 5. Согласно принятому правилу, сначала выбираем точку 2. На следующем шаге выясняем, что точка 2 тупиковая, поэтому возвращаемся в точку 1 и делаем шаг в точку 3. Из точки 3 — в точку 4, из 4 — в 6 и из точки 6 — в точку 5. Один маршрут найден. После этого возвращаемся в точку 6 и проверяем, возможен ли шаг в точку, отличную от 5. Так как это возможно, то делаем шаг в точку 7, и затем — в 5. Найден еще один путь. Таким образом, процесс поиска состоит из шагов вперед и возвратов назад. Поиск завершается, если из узла начала движения уже некуда идти.
Алгоритм поиска имеет рекурсивный характер: чтобы сделать шаг, мы выбираем точку и опять делаем шаг, и так продолжаем до тех пор, пока не достигнем цели.
Таким образом, задача поиска маршрута может рассматриваться как задача выбора очередной точки (города) и поиска оставшейся части маршрута, т. е. имеет место рекурсия.
Граф можно представить двумерным массивом, который назовем тар (карта). Значение элемента массива map[i, j] — это расстояние между городами i и j, если города соединены дорогой, или ноль, если города не соединены прямой дорогой. Для приведенного графа массив тар можно изобразить в виде таблицы, представленной на рис. 12.10.
Рис. 12.10. Массив тар
Содержимое ячейки таблицы на пересечении строки i и столбца j соответcтвует значению map [ i, j ].
Помимо массива тар нам потребуются массив road (дорога) и массив incl(от include — включать). В road мы будем записывать номера пройденных городов. В момент достижения конечной точки он будет содержать номера всех пройденных точек, т. е. описание маршрута.
В inci [i] будем записывать true, если точка с номером i включена в маршрут. Делается это для того, чтобы не включать в маршрут уже пройденную точку (не ходить по кругу).
Так как мы используем рекурсивную процедуру, то надо обратить особое внимание на условие завершения рекурсивного процесса. Процедура должна прекратить вызывать сама себя, если текущая точка совпала с заданной конечной точкой.
На рис. 12.11 приведена блок-схема алгоритма процедуры выбора очередной точки формируемого маршрута, а диалоговое окно — на рис. 12.12.
Для ввода массива, представляющего описание карты, используется компонент stringGridl (значения его свойств приведены в таблице 12.1), для вывода результата (найденного маршрута) — поле метки Label 1. Начальная и конечная точки маршрута задаются вводом значений в поля редактирования Edit1 и Edit2. Процедура поиска запускается щелчком кнопки Поиск (Buttonl). Поля меток Label2, Label3 и Label4 используются для вывода поясняющего текста.
Рис. 12.11. Блок-схема процедуры выбора точки маршрута
Рис. 12.12. Окно программы Поиск маршрута
Таблица 12.1. Значения свойств компонента stringGrid1
Свойство |
Значение |
||
Name |
StringGrid1 |
||
ColCount |
11 |
||
RowCount |
11 |
||
FixedCols |
1 |
||
FixedRows |
1 |
||
Options . goEditing |
TRUE |
||
DefaultColWidth |
16 |
||
DefaultRowHeight |
14 |
||
Текст программы приведен в листинге 12.5.
Листинг 12.5. Поиск маршрута
unit road_;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids;
type
TForml = class(TForm)
StringGridl: TStringGrid;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Button1: TButton;
Label4: TLabel;
procedure FormActivate(Sender: TObject);
procedure ButtonlClickfSender: TObject);
private
{ Private declarations } public
{ Public declarations } end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForml.FormActivate(Sender: TObject);
var
i:integer; begin
// нумерация строк
for i:=1 to 10 do
StringGridl.Cells[0,i]:=IntToStr(i); // нумерация колонок
for i:=l to 10 do
StringGridl.Cells[1,0]:=IntToStr(i);
// описание предопределенной карты StringGridl.Cells[1,2]:='1' StringGridl.Cells[2,l]:='1'
StringGridl.Cells[1,3]:='1'
StringGridl.Cells[3,1]:='1'
StringGridl.Cells[1,4]:='1'
StringGridl.Cells[4,1]:='1'
StringGridl.Cells[3,7]:='1'
StringGridl.Cells[7,3]:='1'
StringGridl.Cells[4,6]:='1'
StringGridl.Cells[6,4]:='1'
StringGridl.Cells[5,6]:='1'
StringGridl.Cells[6,5]:='1'
StringGridl.Cells[5,7]:='1'
StringGridl.Cells[7,5]:='1'
StringGridl.Cells[6,7]:='1'
StringGridl.Cells[7,6]:='1'
end;
procedure TForml.ButtonlClick(Sender: TObject);
const
N=10;// кол-во вершин графа var
map:array[1..N,1..N]of integer; // Карта.map[i,j]ne 0,
// если точки i и j соединены
road:array[1..N]of integer;
// Дорога - номера точек карты
incl:array[1..N]of boolean; // incl[1]равен TRUE, если точка
// с номером i включена в road
start,finish:integer; // Начальная и конечная точки
found:boolean; i,j:integer;
procedure step(s,f,p:integer);
var
с:integer;// Номер точки, в которую делаем очередной шаг
i:integer;
begin
if s=f then begin
// Точки s и f совпали !
found:=TRUE;
Labell.caption:=Labell.caption+#13+'Путь:';
for i:=l to p-1 do
Labell.caption:=Labell.caption+' '
+IntToStr(road[i]); end
else begin
// выбираем очередную точку for c:=l to N do
begin // проверяем все вершины
if(map[s,c]<> 0)and(NOT incite1)
// точка соединена с текущей и не включена в маршрут
then begin
road[p]:=c;// добавим вершину в путь
incl[c]:=TRUE;// пометим вершину как включенную
step(c,f,p+l); incite]:=FALSE; road[p]:=0;
end;
end;
end;
end;// конец процедуры step
begin
Label1.caption: =' ' ;
// инициализация массивов
for i:=l to N do road[i]:=0;
for i:=l to N do incl[i]:=FALSE;
// ввод описания карты из SrtingGrid.Cells
for i:=l to N do
for j:=1 to N do
if StringGrid1.Cells[i,j] <> ''
then map[i,j]:=StrToInt(StringGridl.Cells[i, j] ;
else map[i,j]:=0;
start:=StrToInt(Editl.text);
finish:=StrToInt(Edit2.text);
road[l]:=start;// внесем точку в маршрут
incl[start]:=TRUE;// пометим ее как включенную
step(start,finish,2);//ищем вторую точку маршрута
// проверим, найден ли хотя бы один путь
if not found
then Labell.caption:='Указанные точки не соединены!';
end;
end.
При запуске программы в момент активизации формы приложения происходит событие onActivate, процедура обработки которого заполняет массив StringGridl.cells значениями, представляющими описание карты. Этаже процедура нумерует строки и столбцы таблицы, заполняя зафиксированные ячейки первого столбца и первой строки StringGridl.
Поиск маршрута инициирует процедура TFormi.Buttoniciick, которая запускается щелчком на кнопке Поиск. Данная процедура для поиска точки, соединенной с исходной точкой, вызывает процедуру step, которая после выбора первой точки, соединенной с начальной, и включения ее в маршрут вызывает сама себя. При этом в качестве начальной точки задается уже не исходная, а текущая, только что включенная в маршрут.
Понятие рекурсии
Рекурсивным называется объект, частично состоящий или определяемый с помощью самого себя. Факториал — это классический пример рекурсивного объекта. Факториал числа п — это произведение целых чисел от 1 до п. Обозначается факториал числа п так: n!.
Согласно определению
n! = 1 х 2 х 3 х ... х (п - 1) х п. Приведенное выражение можно переписать так:
n! = nх ((n - 1) х (n - 2) х ...х 3 х 2 х 1) = n х (n - 1)!
То есть, факториал числа п равен произведению числа п на факториал числа (п - 1). В свою очередь, факториал числа («-!) — это произведение числа (п - 1) на факториал числа (п - 2) и т. д.
Таким образом, если вычисление факториала п реализовать как функцию, то в теле этой функции будет инструкция вызова функции вычисления факториала числа (п - 1), т. е. функция будет вызывать сама себя. Такой способ вызова называется рекурсией, а функция, которая обращается сама к себе, называется рекурсивной функцией.
В листинге 12.1 приведена рекурсивная функция вычисления факториала.
Листинг 12.1. Рекурсивная функция вычисления факториала
function factorial(n: integer): integer;
begin
if n <> 1
then factorials n * factorial(n-1)
// функция вызывает сама себя
else factorial := 1; // рекурсивный процесс закончен
end;
Обратите внимание, что функция вызывает сама себя только в том случае, если значение полученного параметра k не равно единице. Если значение параметра равно единице, то функция сама себя не вызывает, а возвращает значение, и рекурсивный процесс завершается.
На рис. 12.1 приведен вид диалогового окна программы, которая для вычисления факториала числа использует рекурсивную функцию factorial. Текст программы приведен в листинге 12.2.
Рис. 12.1. Окно программы вычисления факториала
Листинг 12.2. Использование рекурсивной функции
unit factor ;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Edit1: TEdit;
Button1: TButton;
Label2: TLabel;
procedure ButtonlClick(Sender: TObject) ;
private
{ Private declarations } public
{ Public declarations } end;
var
Form1: TForm1;
implementation
{$R *.DFM}
// рекурсивная функция
function factorial(n: integer): integer;
begin
if n > 1
then factorial := n * factorial(n-1) // функция вызывает сама себя
else factorial:= 1; // факториал 1 равен 1
end;
procedure TForml.ButtonlClick(Sender: TObject);
var
k:integer; // число, факториал которого надо вычислить
f:integer; // значение факториала числа k
begin
k := StrToInt(Edit1.Text);
f := factorial(k);
label2.caption:='Факториал числа '+Edit1.Text
+ ' равен '+IntToStr(f);
end;
end.
На рис. 12.2 приведены два диалоговых окна. Результат вычисления факториала, представленный на рис. 12.2, а, соответствует ожидаемому.
Рис. 12.2. Примеры работы программы вычисления факториала
Результат, представленный на рис. 12.2, б, не соответствует ожидаемому. Факториал числа 44 равен нулю! Произошло это потому, что факториал числа 44 настолько велик, что превысил максимальное значение для переменной типа integer, и, как говорят программисты, произошло переполнение с потерей значения.
Delphi может включить в исполняемую программу инструкции контроля диапазона значений переменных. Чтобы инструкции контроля были добавлены в программу, нужно во вкладке Compiler диалогового окна Project Options (рис. 12.3) установить флажок Overflow checking (Контроль переполнения), который находится в группе Runtime errors (Ошибки времени выполнения).
Рис. 12.3. Вкладка Compiler диалогового окна Project Options