Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

DiVM / OOP / 12_116608_1_51491

.pdf
Скачиваний:
26
Добавлен:
11.05.2015
Размер:
6.45 Mб
Скачать

program Console;

{$APPTYPE CONSOLE}

uses SysUtils;

procedure Average(const X, Y: Double; out M, P: Double); begin

M := (X + Y) / 2;

P := Sqrt(X * Y); end;

var

M, P: Double;

begin

Average(10, 20, M, P);

Writeln('Среднее арифметическое = ', M); Writeln('Среднее геометрическое = ', P); Writeln('Press Enter to exit...'); Readln;

end.

Существует разновидность параметров без типа. Они называются нетипизированными и предназначены для передачи и для приема данных любого типа. Нетипизированные параметры описываются с помощью ключевых слов const и var, при этом тип данных опускается:

procedure JustProc(const X; var Y; out Z);

Внутри подпрограммы тип таких параметров не известен, поэтому программист должен сам позаботиться о правильной интерпретации переданных данных. Заметим, что при вызове подпрограмм на место нетипизированных параметров (в том числе и на место нетипизированных const-параметров) можно подставлять только переменные.

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

(передача значения), либо адрес аргумента (передача ссылки на значение). Конкретный способ передачи выбирается компилятором в зависимости от того, как объявлен параметр в заголовке подпрограммы. Связь между объявлением параметра и способом его передачи поясняет таблица 2.10:

Ключевое

Назначение

Способ передачи

слово

 

 

 

 

 

<отсутствует>

Входной

Передается копия значения

const

Входной

Передается копия значения либо ссылка на

 

 

значение в зависимости от типа данных

out

Выходной

Передается ссылка на значение

var

Входной и выходной

Передается ссылка на значение

Таблица 2.10. Способы передачи параметров

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

2.8.6. Опущенные параметры процедур и функций

71

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

procedure Initialize(var X; MemSize: Integer; InitValue: Byte = 0);

Для параметра InitValue задано стандартное значение, поэтому его можно опустить при вызове процедуры Initialize:

Initialize(MyVar, 10); // Эквивалентно Initialize(MyVar, 10, 0);

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

procedure Initialize(var X; InitValue: Byte = 0; MemSize: Integer); // Ошибка!

2.8.7. Перегрузка процедур и функций

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

procedure IncrementInteger(var Value: Integer); procedure IncrementReal(var Value: Real);

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

procedure

Increment(var

Value:

Integer); overload;

//

процедура

1

procedure

Increment(var

Value:

Real); overload;

//

процедура

2

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

var

X:Integer;

Y:Real;

begin X:=1; Y:=2.0;

Increment(X); // Вызывается процедура 1 Increment(Y); // Вызывается процедура 2

end.

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

procedure Print(X: Shortint); overload; // процедура 1 procedure Print(X: Longint); overload; // процедура 2

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

72

Print(5);

// Вызывается процедура 1

Print(150);

// Вызывается процедура 2

Print(-500); //

Вызывается

процедура

2

Print(-1);

//

Вызывается

процедура

1

Очевидно, что одно и то же число может интерпретироваться и как Longint, и как Shortint (например, числа 5 и –1). Логика компилятора в таких случаях такова: если значение фактического параметра попадает в диапазон значений нескольких типов, по которым происходит перегрузка, то компилятор выбирает процеудуру (функцию), у которой тип параметра имеет меньший диапазон значений. Например, вызов Print(5) будет означать вызов того варианта процедуры, который имеет тип параметра Shortint. А вот вызов Print(150) будет означать вызов того варианта процедуры, который имеет тип параметра Longint, т.к. число 150 не вмещается в диапазон значений типа данных Shortint.

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

procedure Print(X: Integer); overload;

procedure Print(X: Longint); overload; // Ошибка!

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

type

TMyInteger = Integer;

procedure Print(X: Integer); overload;

procedure Print(X: TMyInteger); overload; // Ошибка!

Что делать в тех случаях, когда такая перегрузка просто необходима? Для этого пользовательский тип данных необходимо создавать с использованием ключевого слова type:

type

TMyInteger = type Integer;

procedure Print(X: Integer); overload;

procedure Print(X: TMyInteger); overload; // Правильно

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

procedure

Increment(var

Value:

Real; Delta: Real = 1.0); overload; //

процедура

1

procedure

Increment(var

Value:

Real); overload;

//

процедура

2

Вызов процедуры Increment с одним параметром вызовет неоднозначность:

var

X: Real; begin

Increment(X, 10); // Вызывается процедура 1 Increment(X); // Ошибка! Неоднозначность

end.

Запрещается также перегружать функции, которые отличаются лишь типом возвращаемого значения.

function SquareRoot(X: Integer): Single; overload;

function SquareRoot(X: Integer): Double; overload; // Ошибка!

2.8.8. Соглашения о вызове подпрограмм

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

73

вызывать подпрограммы, написанные на других языках (и наоборот), в языке Delphi существуют директивы, соответствующие четырем известным соглашениям о вызове подпрограмм: register, stdcall, pascal, cdecl.

Директива, определяющая правила вызова, помещается в заголовок подпрограммы, например:

procedure Proc; register;

function Func(X: Integer): Boolean; stdcall;

Директива register задействует регистры процессора для передачи параметров и поэтому обеспечивает наиболее эффективный способ вызова подпрограмм. Эта директива применяется по умолчанию. Директива stdcall используется для вызова стандартных подпрограмм операционной системы. Директивы pascal и cdecl используются для вызова подпрограмм, написанных на языках Delphi и C/C++ соответственно.

2.8.9. Рекурсивные подпрограммы

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

Приведенная ниже программа содержит функцию Factorial для вычисления факториала. Напомним, что факториал числа определяется через произведение всех натуральных чисел, меньших либо равных данному (факториал числа 0 принимается равным 1):

X! = 1 * 2 * ... * (X – 2) * (X – 1) * X

Из определения следует, что факториал числа X равен факториалу числа (X – 1), умноженному на X. Математическая запись этого утверждения выглядит так:

X! = (X – 1)! * X, где 0! = 1

Последняя формула используется в функции Factorial для вычисления факториала:

program Console;

{$APPTYPE CONSOLE}

uses SysUtils;

function Factorial(X: Integer): Longint; begin

if X = 0 then // Условие завершения рекурсии

Factorial := 1 else

Factorial := Factorial(X - 1) * X;

end;

begin

Writeln('4! = ', Factorial(4)); // 4! = 1 * 2 * 3 * 4 = 24 Writeln('Press Enter to exit...');

Readln; end.

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

74

Бывает встречается такая рекурсия, когда первая подпрограмма вызывает вторую, а вторая

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

2.8.10. Упреждающее объявление процедур и функций

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

procedure Proc; forward;

function Func(X: Integer): Boolean; forward;

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

procedure Proc2(<формальные параметры>); forward;

procedure Proc1; begin

...

Proc2(<фактические параметры>);

...

end;

procedure Proc2; // Список формальных параметров опущен begin

...

Proc1;

...

end;

begin

...

Proc1;

...

end.

2.8.11. Процедурные типы данных

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

type

TProc = procedure (X, Y: Integer);

TFunc = function (X, Y: Integer): Boolean;

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

75

var

P: TProc; F: TFunc;

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

program Console;

{$APPTYPE CONSOLE}

uses SysUtils;

function Power(X, Y: Double): Double; begin

Result := Exp(Y * Ln(X)); end;

type

TFunc = function (X, Y: Double): Double;

var

F: TFunc;

begin

F := Power; // В переменную F заносится адрес функции Power Writeln('2 power 4 = ', F(2, 4)); // Вызов Power посредством F Writeln('Press Enter to exit...');

Readln; end.

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

2.9. Программные модули

2.9.1. Структура модуля

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

76

Заголовок модуля

unit <имя

модуля>;

Директивы компилятора

{$<директивы>}

Интерфейсная часть

interface

 

Подключение модулей

uses <имя>, ..., <имя>;

Константы

const ... ;

Типы данных

type

... ;

Переменные

var

... ;

Заголовки процедур

procedure

<имя> (<параметры>);

Заголовки функций

function

<имя> (<параметры>): <тип>;

Часть реализации

implementation

Подключение модулей

uses <имя>, ..., <имя>;

Константы

const ... ;

Типы данных

type

... ;

Переменные

var

... ;

Реализация процедур

procedure <имя>; begin ... end;

Реализация функций

function

<имя>; begin ... end;

Код инициализации

initialization <операторы>

Код завершения

finalization <операторы>

 

end.

 

 

После слова unit записывается имя модуля. Оно должно совпадать с именем файла, в котором находится исходный текст модуля. Например, если файл называется MathLib.pas, то модуль должен иметь имя MathLib. Заголовок модуля формируется автоматически при сохранении файла на диске, поэтому его не следует изменять вручную. Чтобы дать модулю другой заголовок, просто сохраните его на диске под другим именем.

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

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

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

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

Если модуль не нуждается в инициализации и завершении, блоки initialization и finalization можно опустить.

В качестве упражнения давайте создадим модуль и подключим его к основной программе (для этого сначала запустите среду Delphi):

1.Выберите в главном меню команду File | New..., в появившемся диалоговом окне активизируйте значок с подписью Unit и щелкните на кнопке OK (рисунок 2.6).

77

Рисунок 2.6. Окно среды Delphi для создания нового модуля

2.Вы увидите, что среда Delphi создаст в редакторе кода новую страницу с текстом нового модуля Unit1 (рисунок 2.7):

Рисунок 2.7. Текст нового модуля в редакторе кода

3.Сохраните модуль под именем MathLib, выбрав в меню команду File | Save (рисунок

2.8):

78

Рисунок 2.8. Окно сохранения модуля

4.Заметьте, что основная программа Console изменилась: в списке подключаемых модулей появилось имя модуля MathLib (рисунок 2.9). После слова in среда Delphi автоматически помещает имя файла, в котором находится модуль. Для стандартных модулей, таких как SysUtils, это не нужно, поскольку их местонахождение хорошо известно.

79

Рисунок 2.9. Текст программы Console в окне редактора

Теперь перейдем к содержимому модуля. Давайте объявим в нем константу Pi и две функции: Power — вычисление степени числа, и Average — вычисление среднего арифметического двух чисел:

unit MathLib;

interface

const

Pi = 3.14;

function Power(X, Y: Double): Double; function Average(X, Y: Double): Double;

implementation

function Power(X, Y: Double): Double; begin

Result := Exp(Y * Ln(X)); end;

function Average(X, Y: Double): Double; begin

Result := (X + Y) / 2; end;

end.

Вот как могла бы выглядеть программа, использующая модуль Math:

program Console;

{$APPTYPE CONSOLE}

uses SysUtils,

MathLib in 'MathLib.Pas';

begin Writeln(Pi);

Writeln(Power(2, 4));

Writeln(Average(2, 4)); Writeln('Press Enter to exit...'); Readln;

end.

После компиляции и запуска программы вы увидите на экране три числа (рисунок 2.10):

80