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

Технология разработки программного обеспечения

..pdf
Скачиваний:
19
Добавлен:
05.02.2023
Размер:
3.09 Mб
Скачать

относительно несложно. Конструкция if then else состоит из трех узлов (один предикат и две функции); для нее можно определить функцию, включающую в себя функции, соответствующие частям then и else оператора. Изучение сложных программ основано на объединении сведений о более мелких составных частях программы.

Рис. 4.6 – Примеры непростых программ

Рассмотрим программу, которая имеет несколько операторов

goto.

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

Для структурного проектирования обычно используются следующие операторы: if then else, while do, последовательность. Указанные управляющие структуры помогают программистам создать простые программы (функцию, которая преобразует входные данные в выходные). Пусть f(x) – сегмент программы, состоящей из оператора if

thenelse (рис. 4.7).

if p(x) then g(x) else h(x)

51

IF – THEN – ELSE

Рис. 4.7 – Разбиение на элементарные программы

Функции g(x) и h(x) проще, чем функция f(x); таким образом, их спецификации должны быть проще. Если их спецификации известны, то функция f(x) определяется следующим образом:

f x p x g x p x h x .

Программист может формально определить f(x), зная более простые функции g(x) и h(x) (рис. 4.8).

Рис. 4.8 – Сложная программа

В языке PDL используются элементарные подпрограммы с наименьшим числом узлов. Операторы if и do while являются минимальным набором, поскольку было доказано, что любая программная функция может быть представлена программой с указанными двумя управляющими структурами. Однако вместо них можно использовать другие конструкции, например, repeat until.

52

repeat

набор операторов; until (выражение);

Что эквивалентно следующему:

набор операторов; do while (выражение); набор операторов; end;

Кроме того, такие же функции может реализовывать оператор do case с произвольным числом узлов. Для передачи управления предназначен оператор leave. При его использовании передача управления принимает вид «остановить выполнение данной функции».

Заметим, что «интеллектуальная управляемость», а не «отсутствие операторов goto» является движущей силой процесса проектирования.

4.3. Данные

4.3.1. Обзор структур данных

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

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

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

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

53

альные операторы. Цель наших исследований – понять, по какой концепции строятся абстрактные (определенные пользователем) типы данных из агрегативных структур.

4.3.1.1. Массивы

Массивы – это простейшие агрегативные данные в языках программирования. Массивом называется упорядоченный набор данных одного типа

declare A(10) FIXED BINARY;

Объявлен массив A из десяти двоичных переменных с фиксированной точкой с именами A(1), A(2), A(3),. . . , A(9) A(10). Аналогично

declare B(5:10) FIXED BINARY;

объявлен массив B из шести элементов с именами B(5), B(6), . . , B(10). Массивы могут быть как одномерными, так и многомерными.

4.3.1.2. Структуры

Самой сложной разновидностью данных в языках программирования являются структуры. Структурой называют поименованную совокупность различных типов данных.

declare 1 X,

2 Y FIXED BINARY,

2 Z BIT(12);

Объявляется структура с именем Х, состоящая из двоичной переменной с фиксированной точкой с именем X.Y и строки длиной 12 бит с именем X.Z.

Структуры могут использоваться для создания переменных нового типа.

4.3.1.3. Списки

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

Список может быть объявлен как:

declare 1 LIST(N),

2 DATA_ENTRIES TYPE (некоторый тип данных), SIZE; /* текущий размер списка */

54

Элементами списка являются DATA_ENTRIES(1),

DATA_ENTRIES(2),... , DATA_ENTRIES(SIZE).

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

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

declare 1 LIST BASED,

2 DATA_ENTRIES TYPE (некоторый тип данных); 2 FPRT POINTER /* указатель следующей записи

в списке */

LIST_HEAD POINTER; /* указатель первого элемента в списке */

В первом случае элементами списка являются

LIST.DATA_ENTRIES(1), LIST.DATA_ENTRIES(2), LIST.DATA_ENTRIES(3),

...

LIST.DATA_ENTRIES(SIZE),

а во втором

LIST_HEAD DATA_ENTRIES (LIST_HEAD FPTR) DATA_ENTRIES

((LIST_HEAD FPTR) FPTR) DATA_ENTRIES

...

Для работы со списками обычно используются следующие операторы (рис. 4.9а):

1)ADD – поместить новый элемент в список;

2)DELETE – удалить элемент из списка;

3)SEARCH – проверить наличие элемента в списке.

55

 

pop

pus

 

add

 

search

empty

delete

 

 

а) Список

 

в) Стек

delete

insert

 

б) Очередь

 

delete

 

search

 

insert

 

 

member delete add

г) Множество д) Дерево

Рис. 4.9 – Агрегативные структуры и соответствующие им операторы

4.3.1.4. Очереди

Очередь – это упорядоченный список, в один конец которого элементы добавляются, а из другого изымаются (рис. 4.9б). Очередь называют списком FIFO – Fist In, Fist Out (поступивший первым обслуживается первым). Очередь может быть организована любым из рассмотренных выше способов, однако второй способ (использование указателей) более эффективен. Для обслуживания очереди необходимо две операции:

1)INSERT – добавить элемент в очередь;

2)DELETE – удалить элемент из очереди.

4.3.1.5.Стеки

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

56

списком LIFO – Last In, Fist Out (поступивший последним обслуживается первым). Аналогично очереди, стек может быть организован любым из рассмотренных выше способов, однако использование массивов более эффективно. Для работы со стеком обычно используются три операции (рис. 4.9в).

1)PUSH – поместить элемент в стек;

2)POP – извлечь элемент из стека;

3)EMPTY – функция, принимающая значение ИСТИННО, если стек не заполнен.

4.3.1.6.Множества

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

Множества обрабатываются с использованием следующих операторов (рис. 4.9г):

1)INSERT – добавить новый элемент во множество;

2)DELETE – удалить элемент из множества;

3) MEMBER – функция, которая принимает значение ИСТИННО, если данная переменная находится во множестве.

4.3.1.7. Графы

Направленный граф – это структура, состоящая из узлов и дуг,

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

declare 1 GRAPH BASED,

2 DATA_ENTPIES TYPE (некоторый тип данных), 2 EDGES(N) POINTER;

Для ненаправленных графов дугам соответствуют два направления – вперед и назад:

declare 1 GRAPH BASED,

2 DATA_ENTPIES TYPE (некоторый тип данных), 2 FORWARD_EDGES(N) POINTER,

2 BACKWARD_EDGES(N) POINTER;

Операторы для работы с графами представлены на рис. 4.9д.

57

4.3.1.8. Деревья

Дерево – это направленный граф, обладающий следующими свойствами:

1)только один узел не имеет дуг, входящих в него (корневой узел);

2)в каждый узел входит не более одной дуги;

3)в каждый узел можно попасть из корневого узла за несколько шагов.

4.3.2.Абстрактные конструкции

В современных языках программирования основное внимание уделяется структурам данных. Управляющие операторы остались почти такими же, какими они были в первых версиях языка ALGOL. Кроме использования оператора case и замены оператора goto, обработка операторов if, for и процедур вызова претерпела незначительные изменения.

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

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

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

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

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

58

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

declare 1 STACK, 2 TOP FIXED,

2 ENTRIES(100) FIXED;

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

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

Имя_модуля: module Описание структуры данных fun1: function

Операторы функции end

fun2: function

Операторы функции end

end имя_модуля

Другие модули системы обращаются к этому модулю с помощью функций (fun1, fun2), и только непосредственно в модуле известна подробная структура данных.

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

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

59

возможность формирования структур данных абстрактного вида;

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

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

4.3.2.1. Фиксированные типы данных абстрактного типа

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

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

declare 1 STACK,

2 ENTRIES(100) TYPE(INTEGER),

2 TOPOFSTACK TYPE(INTEGER);

Модуль, использующий данные Модуль, определяющий данные

 

1

 

1

TOPOFSTACK

2

TOPOFSTACK

2

3

3

 

 

 

4

 

4

 

5

 

5

 

6

 

6

 

 

 

100

 

100

 

Элементы

 

Элементы

Рис. 4.12 – Фиксированные данные абстрактного типа

После этого программист должен написать процедуры для PUSH, POP и любых других функций над стеком. Если проект по-

60