Процедурное программирование




Первоначальной (и, возможно, наиболее используемой) парадигмойпрограммирования было: Определите, какие процедуры вам нужны; используйте лучшие из известныхвам алгоритмов! Ударение делалось на обработку данных с помощью алгоритма,производящего нужные вычисления. Для поддержки этой парадигмы языкипредоставляли механизм передачи параметров и получения результатовфункций. Литература, отражающая такой подход, заполнена рассуждениями оспособах передачи параметров, о том, как различать параметры разных типов,о различных видах функций (процедуры, подпрограммы, макрокоманды,...) ит.д. Первым процедурным языком был Фортран, а Алгол60, Алгол68, Паскаль иС продолжили это направление. Типичным примером хорошего стиля в таком понимании может служитьфункция извлечения квадратного корня. Для заданного параметра она выдаетрезультат, который получается с помощью понятных математических операций: double sqrt (double arg) { // программа для вычисления квадратного корня } voide some_function () { double root = sqrt (2); //.. } Двойная наклонная черта // начинает комментарий, который продолжаетсядо конца строки. При такой организации программы функции вносят определенный порядок вхаос различных алгоритмов.

Модульное программирование

Со временем при в проектировании программ акцент сместился сорганизации процедур на организацию структур данных. Помимо всего прочегоэто вызвано и ростом размеров программ. Модулем обычно называютсовокупность связанных процедур и тех данных, которыми они управляют.Парадигма программирования приобрела вид: Определите, какие модули нужны; поделите программу так, чтобы данныебыли скрыты в этих модулях Эта парадигма известна также как "принцип сокрытия данных". Если вязыке нет возможности сгруппировать связанные процедуры вместе с данными,то он плохо поддерживает модульный стиль программирования. Теперь методнаписания "хороших" процедур применяется для отдельных процедур модуля.Типичный пример модуля - определение стека. Здесь необходимо решить такиезадачи: [1] Предоставить пользователю интерфейс для стека (например, функцииpush () и pop ()). [2] Гарантировать, что представление стека (например, в виде массиваэлементов) будет доступно лишь через интерфейс пользователя. [3] Обеспечивать инициализацию стека перед первым его использованием. Язык Модула-2 прямо поддерживает эту парадигму, тогда как С толькодопускает такой стиль. Ниже представлен на С возможный внешний интерфейсмодуля, реализующего стек: // описание интерфейса для модуля, // реализующего стек символов: void push (char); char pop (); const int stack_size = 100; Допустим, что описание интерфейса находится в файле stack.h, тогдареализацию стека можно определить следующим образом: #include "stack.h" // используем интерфейс стека static char v [ stack_size ]; // ``static'' означает локальный // в данном файле/модуле static char * p = v; // стек вначале пуст void push (char c) { //проверить на переполнение и поместить в стек } char pop () { //проверить, не пуст ли стек, и считать из него } Вполне возможно, что реализация стека может измениться, например, еслииспользовать для хранения связанный список. Пользователь в любом случае неимеет непосредственного доступа к реализации: v и p - статическиепеременные, т.е. переменные локальные в том модуле (файле), в котором ониописаны. Использовать стек можно так: #include "stack.h" // используем интерфейс стека void some_function () { push ('c'); char c = pop (); if (c!= 'c') error ("невозможно"); } Поскольку данные есть единственная вещь, которую хотят скрывать,понятие упрятывания данных тривиально расширяется до понятия упрятыванияинформации, т.е. имен переменных, констант, функций и типов, которые тожемогут быть локальными в модуле. Хотя С++ и не предназначался специальнодля поддержки модульного программирования, классы поддерживают концепциюмодульности ($$5.4.3 и $$5.4.4). Помимо этого С++, естественно, имеет ужепродемонстрированные возможности модульности, которые есть в С, т.е.представление модуля как отдельной единицы трансляции.

Абстракция данных

Модульное программирование предполагает группировку всех данных одноготипа вокруг одного модуля, управляющего этим типом. Если потребуются стекидвух разных видов, можно определить управляющий ими модуль с такиминтерфейсом: class stack_id { /*... */ }; // stack_id только тип // никакой информации о стеках // здесь не содержится stack_id create_stack (int size); // создать стек и возвратить // его идентификатор void push (stack_id, char); char pop (stack_id); destroy_stack (stack_id); // уничтожение стека Конечно такое решение намного лучше, чем хаос, свойственныйтрадиционным, неструктурированным решениям, но моделируемые таким способомтипы совершенно очевидно отличаются от "настоящих", встроенных. Каждыйуправляющий типом модуль должен определять свой собственный алгоритмсоздания "переменных" этого типа. Не существует универсальных правилприсваивания идентификаторов, обозначающих объекты такого типа. У"переменных" таких типов не существует имен, которые были бы известнытранслятору или другим системным программам, и эти "переменные" неподчиняются обычным правилам областей видимости и передачи параметров. Тип, реализуемый управляющим им модулем, по многим важным аспектамсущественно отличается от встроенных типов. Такие типы не получают тойподдержки со стороны транслятора (разного вида контроль), котораяобеспечивается для встроенных типов. Проблема здесь в том, что программаформулируется в терминах небольших (одно-два слова) дескрипторов объектов,а не в терминах самих объектов (stack_id может служить примером такогодескриптора). Это означает, что транслятор не сможет отловить глупые,очевидные ошибки, вроде тех, что допущены в приведенной ниже функции: void f () { stack_id s1; stack_id s2; s1 = create_stack (200); // ошибка: забыли создать s2 push (s1,'a'); char c1 = pop (s1); destroy_stack (s2); // неприятная ошибка // ошибка: забыли уничтожить s1 s1 = s2; // это присваивание является по сути // присваиванием указателей, // но здесь s2 используется после уничтожения } Иными словами, концепция модульности, поддерживающая парадигмуупрятывания данных, не запрещает такой стиль программирования, но и неспособствует ему. В языках Ада, Clu, С++ и подобных им эта трудность преодолеваетсяблагодаря тому, что пользователю разрешается определять свои типы, которыетрактуются в языке практически так же, как встроенные. Такие типы обычноназывают абстрактными типами данных, хотя лучше, пожалуй, их называтьпросто пользовательскими. Более строгим определением абстрактных типовданных было бы их математическое определение. Если бы удалось его дать,то, что мы называем в программировании типами, было бы конкретнымпредставлением действительно абстрактных сущностей. Как определить "болееабстрактные" типы, показано в $$4.6. Парадигму же программирования можновыразить теперь так: Определите, какие типы вам нужны; предоставьте полный набор операцийдля каждого типа. Если нет необходимости в разных объектах одного типа, то стильпрограммирования, суть которого сводится к упрятыванию данных, иследование которому обеспечивается с помощью концепции модульности, вполнеадекватен этой парадигме. Арифметические типы, подобные типам рациональных и комплексных чисел,являются типичными примерами пользовательских типов: class complex { double re, im; public: complex(double r, double i) { re=r; im=i; } complex(double r) // преобразование float->complex { re=r; im=0; } friend complex operator+(complex, complex); friend complex operator-(complex, complex); // вычитание friend complex operator-(complex) // унарный минус friend complex operator*(complex, complex); friend complex operator/(complex, complex); //... }; Описание класса (т.е. определяемого пользователем типа) complex задаетпредставление комплексного числа и набор операций с комплексными числами.Представление является частным (private): re и im доступны только дляфункций, указанных в описании класса complex. Подобные функции могут бытьопределены так: complex operator + (complex a1, complex a2) { return complex (a1.re + a2.re, a1.im + a2.im); } и использоваться следующим образом: void f () { complex a = 2.3; complex b = 1 / a; complex c = a + b * complex (1, 2.3); //... c = - (a / b) + 2; } Большинство модулей (хотя и не все) лучше определять какпользовательские типы.


Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: