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

Демидов Основы программирования в примерах на языке ПАСЦАЛ 2010

.pdf
Скачиваний:
128
Добавлен:
16.08.2013
Размер:
1.28 Mб
Скачать

Глава 8. Структурирование программ

Программы сложной структуры

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

вразбиении задачи на более мелкие подзадачи, решении задач по отдельности и построении решения основной задачи на базе решений подзадач. Использование данного приёма в программировании приводит к построению программ сложной структуры, где каждый блок соответствует решению одной из подзадач и логически может быть отделён от других. Усложнение структуры программ сопровождается возникновением целого класса ошибок, обусловленных взаимосвязью блоков программы. Эту взаимосвязь часто называют «сцеплением программного кода». Порочность сцепления проявляется тогда, когда изменения в одном блоке программы приводят к неожиданным изменениям поведения других блоков программы. Поэтому, в целях снижения вероятности возникновения ошибок, стремятся снизить сцепление.

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

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

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

var

a: array[1..10] of integer;

i:integer;

//определение процедуры с именем Print procedure Print;

81

begin

for i:=1 to 10 do write(a[i], ' '); writeln;

end;

begin

… // заполнение массива a i := 4;

Print; // вызов процедуры по имени // здесь i уже не равно 4

… // например, сортировка массива a Print;

end.

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

procedure Print; var i: integer; begin

for i:=1 to 10 do write(a[i], ' '); writeln;

end;

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

Глобальные и локальные переменные. Область видимости переменных

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

Локальные переменные – переменные, объявленные внутри процедуры или функции. Память под значения локальных пере-

82

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

Формальные и фактические параметры

Построенная процедура Print до сих пор не слишком полезна, так как умеет выводить на печать лишь массив а, да еще и только длины 10. Чтобы обобщить процедуру на все подобные массивы, необходимо объявить новый тип данных и параметризовать процедуру:

const

n = 10;

type

tarray = array[1..n] of integer;

var

a,b: tarray;

procedure Print(mas: tarray); var i: integer;

begin

for i:=1 to n do write(mas[i], ' '); writeln;

end;

begin

// заполнение массива a Print(a);

// заполнение массива b Print(b);

end.

Здесь в заголовке процедуры после имени определён список передаваемых параметров – параметр mas типа tarray. Следует отметить, что в заголовке процедуры нельзя определять новый тип, но можно использовать любые встроенные типы или типы, определенные ранее в программе.

83

Теперь процедура Print умеет выводить на экран любой массив типа tarray и использует глобальную константу n, что вполне допустимо.

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

ными.

Способы передачи параметров

Аналогично процедуре Print для вывода массива можно написать процедуру Init для ввода массива:

const

n = 10;

type

tarray = array[1..n] of integer;

var

a,b: tarray;

procedure Init(mas: tarray); var i: integer;

begin randomize;

for i:=1 to n do mas[i] := random(100)-50; end;

begin

Init(a);

Init(b); end.

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

ся. В данном случае говорят о передаче параметров по значению.

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

84

procedure Init(var mas: tarray); var i: integer;

begin randomize;

for i:=1 to n do mas[i] := random(100)-50; end;

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

Существует второй способ передачи параметра по ссылке, при котором значение передаваемой переменной менять нельзя. Для этого перед объявлением параметра следует указать модификатор const:

procedure Print(const mas: tarray); var i: integer;

begin

for i:=1 to n do write(mas[i], ' '); writeln;

end;

Здесь параметр mas также становится синонимом передаваемой по ссылке переменной, но менять её значение уже нельзя. Данный способ позволяет снизить накладные расходы на вызов процедуры: новая переменная не создаётся, и копирование не происходит.

Вообще, по значению в качестве параметров можно передавать любые выражения, а по ссылке – только переменные. Попытки передать по ссылке выражение, не являющееся переменной, приведёт

квозникновению ошибки компиляции.

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

var res: integer;

procedure Sum(a: integer; b: integer; var c: integer); begin

c:=a+b;

85

end;

begin

Sum(3,5,res); res := res * 8;

end.

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

procedure Sum(a,b: integer; var c: integer);

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

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

function Sum(a, b: integer): integer; begin

Sum := a+b; end;

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

begin

res := Sum(3,5) * 8; end.

Следует предостеречь от использования имени функции в качестве локальной переменной. Хорошим тоном считается однократное использование имени функции при возврате результата.

Если функция не имеет параметров, то вхождение имени функции в какое-либо выражение внутри тела функции приведёт к её вызову, а вовсе не к подстановке значения, присвоенного функции ранее. Например:

86

function Calculate: integer; var i: integer;

begin

Calculate := 67;

// далее вместо подстановки 67 функция вызовет себя i := Calculate * 7;

Calculate := i; end;

Гораздо лучше ввести дополнительную локальную переменную и в конце вычислений присвоить функции её значение:

function Calculate: integer; var

i,j: integer; begin

j := 67;

i := j * 7; Calculate := i;

end;

Вложенные подпрограммы

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

procedure proc1(a: integer); procedure proc2(a,b: integer); var

begin

//здесь видны глобальные переменные,

//параметры родительской функции,

//собственные параметры и локальные переменные

end; var

begin

//здесь видны глобальные переменные,

//собственные параметры и локальные переменные

end;

87

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

Модульная структура программ

Модули – более крупные структурные единицы программы. Они имеют имя и могут содержать совокупность процедур и функций, а также ряд дополнительных типов данных, переменных, констант. В языке Паскаль модули определяются в отдельных файлах и начинаются с объявления module <имя>. К основной программе модули подключаются с помощью ключевого слова uses <список имён модулей>. В модули имеет смысл объединять логически связанные группы процедур и функций. Программист может создавать свои модули (например, для работы с мышкой, работы со специальными структурами данных и т.п.), а также использовать модули стандартной библиотеки языка. Модули могут использовать другие модули.

Некоторые стандартные модули

Любая программа на языке Паскаль, созданная в среде Turbo Pascal, может использовать по умолчанию средства модуля System. Его не требуется указывать в объявлении uses. Модуль содержит базовые средства ввода-вывода, функции работы со строками, динамической памятью, числами с плавающей запятой, а также встроенные константы, системные переменные и др.

Модуль Сrt используют для организации работы с экраном в текстовом режиме, с клавиатурой, курсором и встроенным динамиком.

Модуль Graph предназначен для работы с экраном в графическом режиме.

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

1)объявление модуля (unit);

2)объявление используемых модулей (uses);

3)интерфейсная секция модуля (interface);

4)секция реализации модуля (implementation);

5)секция инициализации (begin … end).

Пример модуля, предоставляющего математические функции:

unit MathUtils; interface

88

function Sum(a,b: real):real; function Mult(a,b: real):real;

implementation

function Sum(a,b: real):real; begin

Sum := a+b; end;

function Mult(a,b: real):real; begin

Mult:= a*b; end;

Использование модулей

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

Пример использования модуля:

program math;

// подключение модуля MathUtils

uses MathUtils;

begin

writeln(Mult(5,Sum(3,8)); // вызов функций модуля end.

При запуске основной программы система осуществляет поиск и подключение скомпилированных модулей к программе. Поиск модулей и динамических библиотек выполняется в заранее определенных местах дискового пространства: системный каталог среды, системный каталог операционной системы, текущий каталог, каталоги для поиска, указанные в среде (для Turbo Pascal – с помощью меню Options/Directories), каталоги, указанные в переменной $PATH операционной системы.

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

89

Задания по процедурам и функциям

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

const n = 10;

type tarray = array[1..n] of real;

function diff(const a, b: tarray); var

i: integer; sum, rez: real;

begin

sum := 0;

for i:=1 to n do sum := sum + a[i];

rez := sum/n;

sum := 0;

for i:=1 to n do sum := sum + b[i];

rez := rez – sum/n;

diff := rez; end;

После оптимизации получаем решение, в два раза более эффективное:

function diff(const a, b: tarray); var

i: integer; sum: real;

begin

sum := 0;

for i:=1 to n do

sum := sum + a[i] – b[i]; diff := sum/n;

end;

2. Что напечатает следующая программа?

var a, b, c, d, e: integer;

procedure X(a, b, c: integer; var d: integer);

90

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]