Главная страница
Навигация по странице:

  • СТРУКТУРЫ УПРАВЛЕНИЯ И ПОДПРОГРАММЫ 2.1. ТЕОРИЯ ПЕРВИЧНЫХ ПРОГРАММ

  • 2.5. ПОДПРОГРАММЫ. ПРОЦЕДУРЫ И ФУНКЦИИ

  • 2.6. ПЕРЕДАЧА ПАРАМЕТРОВ

  • Например , procedure

  • ТЕХНОЛОГИЯ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ 3.1. ПОНЯТИЕ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

  • 3.2. ПРИНЦИП УТАИВАНИЯ ИНФОРМАЦИИ

  • 3.3. МЕТОДЫ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

  • 3.4. СТРУКТУРНАЯ СХЕМА ПРОГРАММЫ И СРЕДСТВА ДЛЯ ЕЕ ИЗМЕНЕНИЯ

  • 3.5. КРИТЕРИИ ОЦЕНКИ КАЧЕСТВА СТРУКТУРНОЙ СХЕМЫ ПРОГРАММЫ

  • 3.6. МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ

  • СТРУКТУРА МОДУЛЯ В OBJECT

  • Лекции по программированию. Основные понятия классификация программного обеспечения


    Скачать 1.57 Mb.
    НазваниеОсновные понятия классификация программного обеспечения
    АнкорЛекции по программированию.doc
    Дата15.01.2018
    Размер1.57 Mb.
    Формат файлаdoc
    Имя файлаЛекции по программированию.doc
    ТипДокументы
    #14117
    КатегорияИнформатика. Вычислительная техника
    страница2 из 6
    1   2   3   4   5   6

    1.8. СРЕДЫ И РЕАЛИЗАЦИИ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ

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

    Старейшей средой программирования считается UNIX - ма­шинно-независимая операционная система с разделением времени. Она предоставляет многочисленные мощные инструментальные средства для производства ПО и эксплуатации разнообразных язы­ков. Работа с этой средой осуществляется с помощью графического интерфейса, устанавливаемого поверх нее. Во многих случаях этим интерфейсом является CommonDesktopEnvironment (CDE).

    Последнюю стадию развития сред разработки ПО представляют MicrosoftVisual C++, VisualBASIC, Delphiи JavaDevelopmentKit, которые предлагают легкий способ создания графических интер­фейсов для программ пользователя.

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

    Компилируемые языки. Компилируемыми принято счи­тать такие языки как С, C++, FORTRAN, Pascalи Ada. Это означает, что программы, написанные на этих языках, транслируются в ма­шинный код данного компьютера перед началом выполнения. Про­граммная интерпретация при этом ограничивается только интер­претацией набора программ поддержки выполнения, которые мо­делируют элементарные операции исходного языка, не имеющие близкого аналога в машинном языке.

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

    Интерпретируемые языки. Реализуются с использова­нием программного интерпретатора. К таковым относятся языки LISP, ML, Perl, Postscript, Prologи Smalltalk. При такой реализации транслятор выдает не машинный код используемого компьютера, а некую промежуточную форму программы. Эта форма легче для выполнения, чем исходная программа, но все же она отличается от машинного кода.

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

    Развитие Всемирной паутины WWWи появление языка Javaвнесли изменения в описанную схему. Язык Javaпохож скорее на Pascalи C++, чем на LISP, но в большинстве случаев реализуется как интерпретируемый язык. Компилятор Javaвырабатывает про­межуточный набор байт-кодов для виртуальной машины Java. Пе­редача байт-кодов на локальный компьютер (даже если он медлен­нее, чем web-сервер) выгоднее в отношении временных затрат, чем передача результатов выполнения программы на web-сервере. Од­нако web-сервер не в состоянии предугадать машинную архитекту­ру хост-компьютера. Поэтому браузер создает виртуальную маши­ну Java, которая и выполняет стандартный набор байт-кодов Java.
    СТРУКТУРЫ УПРАВЛЕНИЯ И ПОДПРОГРАММЫ

    2.1. ТЕОРИЯ ПЕРВИЧНЫХ ПРОГРАММ
    Теория первичных программ была предложена Маддуксом в качестве обобщения методологии структурного программи­рования для определения однозначной иерархической декомпози­ции блок-схем. В этой теории предполагается, что графы программ могут содержать три класса узлов (рис. 2):

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

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

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


    Любая блок-схема состоит только из этих трех компонентов.

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

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


    На рис. 3 изображены все первичные программы, которые включают в себя не более четырех узлов. Первичные программы а, б, д, и представляют собой последовательности функциональных узлов. Первичная программа еконструкция if-then, .ж— do-while, з - repeat-until, к- if-then-else, л - do-while-do.

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

    Многие из описанных выше наборов управляющих структур были включены в существующие языки программирования. Эти структуры представляют собой первичные программы с неболь­шим количеством узлов и просты для понимания.

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

    2.2. АЛЬТЕРНАТИВЫ

    Операторы выбора используются для выбора одного из нескольких возможных путей, по которому должно выполняться вычисление. Обобщенный оператор выбора называется case-оператором (switch-оператор в языке С).

    Условный оператор является частным случаем case- или switch-оператора, в котором выражение имеет булев тип. Так как булевы типы имеют только два допустимых значения, условный оператор делает выбор между двумя возможными путями. Конструкция для двух альтернатив на Паскале имеет следующий вид:

    if L

    then begin

    {Действиепри L-True} end; else begin

    {Действие при L=False} end; здесь L-логическое выражение.

    Вариант конструкции для нескольких альтернатив имеет вид:

    Switch : = 0;

    L1 : = . . .

    L2 : = . . .

    L3 : = . . .

    if L1 then Swich : = 1;

    if L2 then Swich : = 2;

    if L3 then Swich : = 3;

    case Swich of 1: begin

    {Действиепри L1=True} end; 2 : begin

    {Действиепри L2=True} end; 3:begin

    {Действиепри L3=Тrие} end; else begin

    {Вывод сообщения об ошибочном кодировании модуля} end; end; {EndofCase}

    2.3. ЦИКЛЫ

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

    Цикл while-do Цикл repeat-until

    {Подготовка} {Подготовка}

    While L do repeat
    begin {телоцикла}

    {телоцикла}until L;
    end;

    здесь L -логическое выражение, которое при значении Trueявля­ется условием продолжения выполнения while-doили условием окончания выполнения repeat-until. Подготовка и тело цикла явля­ются цепочками функциональных узлов.

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

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

    for <параметр_цикла> := <нач_знач>

    to <кон знач> do <оператор>;

    здесь for, to, do — зарезервированные слова; <параметр _цикла> — переменная любого перечисляемого типа.

    Цикл выполняется для каждого из значений от <нач_знач>

    и до <кон_знач>.

    2.4. ОПЕРАТОРЫ ПЕРЕХОДА

    Оператор безусловного перехода имеет следующий вид: goto, здесь goto — зарезервированное слово: <метка> — метка.

    Метка - это произвольный идентификатор, позволяющий име­новать некоторый оператор программы и таким образом ссылаться на него.

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

    Первая состоит в том, что многие циклы не могут завершаться в точке входа, как этого требует цикл while.

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

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

    В современных языках ObjectPascal, Ada, C++ и Eiffelесть спе­циальные языковые конструкции, так называемые исключения, ко­торые непосредственно решают и эту проблему.
    2.5. ПОДПРОГРАММЫ. ПРОЦЕДУРЫ И ФУНКЦИИ

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

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

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

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

    Описание процедуры состоит из заголовка и тела. Заголовок процедуры имеет вид:

    procedure <имя> [ (<сп.ф.п.>) ] ;

    здесь <имя> имя процедуры (правильный идентификатор); <сп. ф. п. > - список формальных параметров.

    Список формальных параметров необязателен и может отсутст­вовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их типы, например procedure MyProc (a: Real; b: Integer; с: Char);

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

    function <имя> [(<сп.ф.п.>)]: <тип>;

    здесь <тип> — тип возвращаемого функцией результата. Заголовок функции может иметь следующий вид:

    function MyFunc (a, b: Real): Real;

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

    1. в заголовке функции помимо описания формальных пара­метров обязательно указывается тип возвращаемого ею результата;

    2. для возврата функцией значения в точку вызова среди ее операторов должен быть хотя бы один, в котором имени функции или переменной Resultприсваивается значение результата;

    3. вызов процедуры выполняется отдельным оператором;

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

    2.6. ПЕРЕДАЧА ПАРАМЕТРОВ

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

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

    Смысл используемых фактических параметров зависит от то­го, в каком порядке они перечислены при вызове подпрограммы. Поэтому программист должен сам следить за правильным поряд­ком перечисления фактических параметров при обращении к под­программе. Формальные параметры подпрограммы могут быть трех видов:

    параметры-значения;

    параметры-переменные;

    параметры-константы.

    Например, procedure MyProc (A: Real; var В: Real; const С: String);

    здесь А - параметр-значение, в - параметр-переменная, С - пара­метр-константа.

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

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

    Если параметр определен как параметр-значение, то перед вы­зовом подпрограммы это значение вычисляется, полученный ре­зультат копируется во временную память и передается подпро­грамме. Любые возможные изменения в подпрограмме параметра-значения никак не воспринимаются вызывающей программой, так как в этом случае изменяется копия фактического параметра.

    Если параметр определен как параметр-переменная, то при вы­зове подпрограммы передается сама переменная, а не ее копия (фактически в этом случае подпрограмме передается адрес пере­менной). Изменение параметра-переменной приводит к изменению самого фактического параметра в вызывающей программе.

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

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

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

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

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

    Нетипизированные параметры. Одним из свойств языка ObjectPascalявляется возможность использования нетипи­зированных параметров.

    Параметр считается нетипизированным, если тип формального параметра-переменной в заголовке подпрограммы не указан, при этом соответствующий ему фактический параметр может быть пе­ременной любого типа. Нетипизированными могут быть только параметры-переменные: procedure MyProc(var aParametr);

    Нетипизированные параметры обычно используются в случае, когда тип данных несуществен. Такие ситуации чаще всего возни­кают при разного рода копированиях одной области памяти в дру­гую, например, с помощью процедур BlockRead, BlockWrite, Move-Memoryи т. п.

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

    type

    Prod = procedure (a, b, с: Real; var d: Real);

    РгосЗ == procedure;

    Fund = function: String;

    Func2 = function (var s: String): Real;

    В программе могут быть объявлены переменные процедурных типов, например:

    var

    p1 : Proc1;

    fl, f2: Func2;

    ар: array [1. . N] of Prod;

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

    2.7. РЕКУРСИЯ

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

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

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

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

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

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

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

    Безусловные рекурсивные процедуры приводят к бесконечным процессам, и на эту проблему нужно обратить особое внимание, так как практическое использование процедур с бесконечным са­мовызовом невозможно.

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

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

    1) форма с выполнением действий до рекурсивного вызова (на
    рекурсивном спуске);

    procedure Rec; begin

    S;

    if условие then Rec; end;

    2) форма с выполнением действий после рекурсивного вызова
    (на рекурсивном возврате);

    procedure Rec; begin

    if условие then Rec;

    S; end;

    3) форма с выполнением действий как до, так и после рекур­сивного вызова (с выполнением действий как на рекурсивном спуске, так и на рекурсивном возврате).

    procedure Rec; begin

    S1;

    if условие then Rec; S2 ; end;

    Все формы рекурсивных процедур находят применение на практике. Многие задачи, в том числе вычисление факториала, без­различны к тому, какая используется форма рекурсивной процеду­ры. Однако есть классы задач, при решении которых программисту требуется сознательно управлять ходом работы рекурсивных про­цедур и функций. Такими, в частности, являются задачи, исполь­зующие списковые и древовидные структуры данных.
    ТЕХНОЛОГИЯ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

    3.1. ПОНЯТИЕ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

    Исторически сложилось так, что императивные языки в настоящее время доминируют в программировании. Однако ис­следования, проведенные в 70-80-х годах XX века, показали, что аппликативная методика обеспечивает более эффективные способы верификации программ и доказательство их корректности. Это видно из блок-схем, представленных на рис. 4. На рис. 4, а изобра­жена блок-схема, типичная для программ 60-х годов XX века. В ней нет никакой явной структуры. Такие программы называют про-граммами-спагетти. Из-за большого числа нерациональных пере­дач управления назад и вперед трудно понять, каково состояние программы в каждый момент времени в процессе ее выполнения.

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

    Структурное программирование - подход, при котором для передачи управления в программе используются только три конст­рукции, допускающих последовательную, условную и итеративную передачи управления. При этом безусловная передача управления например, оператором goto запрещается.




    Рис. 4. Аппликативные методы в императивных языках

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

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

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

    повышение производительности труда программистов при на­писании и контроле программ;

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

    создание программ коллективом разработчиков;

    окончание создания программ в заданный срок.

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

    3.2. ПРИНЦИП УТАИВАНИЯ ИНФОРМАЦИИ

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

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

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

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

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

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

    1) действуют все идентификаторы, определенные внутри про­цедуры/функции;

    1. действуют все идентификаторы окружающего контекста, ес­ли их имена отличаются от имен, объявленных внутри процеду­ры/функции;

    2. локальные идентификаторы процедуры/функции во внешнем окружении не действовуют никогда;

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

    3.3. МЕТОДЫ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

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

    решения частных задач приводят к решению общей задачи;

    данная последовательность отдельных действий наиболее ра­циональна;

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

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

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

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

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

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

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

    3.4. СТРУКТУРНАЯ СХЕМА ПРОГРАММЫ И СРЕДСТВА ДЛЯ ЕЕ ИЗМЕНЕНИЯ



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

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

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

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

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

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

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

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

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

    Флаг — переменная, значение которой свидетельствует о том, что некоторый аппаратный или программный компонент находится в определенном состоянии или что для него выполняется опреде­ленное условие. Флаг используется для реализации условного ветвления и прочих процессов принятия решений.

    Семафор - тип данных специального назначения, который яв­ляется средством управления доступом к критическому ресурсу со стороны совместно идущих последовательных процессов.

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

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

    3.5. КРИТЕРИИ ОЦЕНКИ КАЧЕСТВА

    СТРУКТУРНОЙ СХЕМЫ ПРОГРАММЫ

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

    1. полнота выполнения специфицированных функций;

    2. возможность быстрого и дешевого пополнения новыми, ра­нее не специфицированными функциями;

    3. обозримость (понятность) для проектировщика составных частей программы;

    4. максимальная независимость отдельных частей программы;

    5. возможность связывания подпрограмм редактором связей;

    6. достаточность оперативной памяти;

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

    8. отсутствие разных модулей со сходными функциями. Один и тот же модуль должен вызываться на разных уровнях схемы иерархии;

    9. достижение такого графика работы коллектива программи­стов при реализации программы, который обеспечивает равномер­ную загрузку коллектива;




    1. всемерное сокращение затрат на тестирование программы.
      Хорошая схема иерархии в 2-5 раз сокращает затраты на тестиро­вание по сравнению с первоначальным вариантом;

    2. использование в данном проекте как можно большего числа проработанных в предшествующих проектах модулей и библиотек при минимальном объеме изготавливаемых заново частей.

    Генерация вариантов прекращается при невозможности даль­нейших улучшений. Рациональная структура программы обеспечи­вает сокращение общего объема текстов в 2-3 раза, что соответст­венно удешевляет создание программы и ее тестирование, на кото­рое обычно приходится не менее 60% от общих затрат. При этом облегчается и снижается стоимость сопровождения программы.

    3.6. МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ

    Реализация принципа структурного программирования осуществляется с использованием макрокоманд и механизмов вы­зова подпрограмм. Эти же механизмы подходят и для реализации модульного программирования, которое можно рассматривать как часть структурного подхода.

    Необходимо различать использование слова модуль, когда име­ется в виду единица дробления большой программы на отдельные блоки (которые могут быть реализованы в виде процедур и функций) и когда имеется ввиду синтаксическая конструкция языков программирования (unitв ObjectPascal).

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

    Концепцию модульного программирования можно сформули­ровать в виде нескольких понятий и положений:

    1. большие задачи разбиваются на ряд более мелких, функционально самостоятельных подзадач — модулей, которые связаны ме­жду собой только по входным и выходным данным;

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

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

    4. исходный текст модуля должен иметь заголовок и интер­фейсную часть, где отражаются назначение модуля и все его внеш­ние связи;

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

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

    При работе с модулями нужно помнить их основное отличие от процедур и функций. Традиционные правила сферы действия гло­бальных и локальных переменных для модулей не работают. Эта язы­ковая конструкция разработана так, чтобы исключить влияние гло­бальных переменных, объявленных в главной программе, на внутрен­ние описания модуля. Поэтому, если возникает необходимость ввести доступные для всех блоков программы глобальные описания то сле­дует создать модуль глобальных объявлений и включить его в список импорта всех модулей, где нужны его описания.
    3.7. СТРУКТУРА МОДУЛЯ В OBJECTPASCAL

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

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

    unit <идентификатор_модуля>;

    Для правильной работы среды программирования это имя должно совпадать с именем дискового файла, в который помещает­ся исходный текст модуля. Далее следует

    {Интерфейсный раздел} interface

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

    Здесь содержатся объявления всех глобальных объектов модуля (типов, констант, переменных и подпрограмм), которые должны стать доступными основной программе и/или другим модулям. При объявлении глобальных подпрограмм в интерфейсной части ука­зывается только их заголовок.

    Связь модуля с другими модулями устанавливается специаль­ным предложением:

    {Список импорта интерфейсного раздела} uses <список_модулей>

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

    {Список экспорта интерфейсного раздела} const type var

    procedure function

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

    {Раздел реализации) implementation

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

    {Список импорта раздела реализации) uses

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

    {Подразделы внутренних для модуля описаний} label const type var

    procedure function

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

    Исполняемая часть содержит описания подпрограмм, объяв­ленных в интерфейсной части. Описанию подпрограммы должен предшествовать заголовок, в котором можно опускать список фор­мальных параметров и тип результата для функции. Если заголовки указаны с параметрами, то их список должен быть идентичен тако­му же списку для соответствующей процедуры или функции в разделе interface.
    {Раздел инициализации} initialization

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

    {Раздел завершения) finalization

    1   2   3   4   5   6


    написать администратору сайта