Директивное программирование - один из наиболее естественных для человека подходов к написанию программ. Ведь программа в этом случае состоит из операторов присваивания и предложений, управляющих последовательностью их выполнения. При написании подобной программы необходимо найти такую цепочку команд, которая приведет в конце концов к вычислению (и, возможно, печати) одной или нескольких искомых величин.
Директивное программирование стали называть процедурным, когда в процессе увеличения сложности моделируемых систем и размера получаемых программ возникла концепция подпрограмм, называемых также процедурами (procedure), функциями (function) или методами (method). Подпрограмма позволяет локализовать в ней процесс выполнения определенного действия, которое может быть повторено многократно с помощью механизма вызова.
При этом исходная программа превращается из одной большой цепочки команд в значительно более короткую и понятную последовательность вызовов подпрограмм, решающих более простые подзадачи. При вызове подпрограммы ей часто передают так называемые параметры, а она после завершения своей работы обычно возвращает некоторый результат. Механизмы передачи параметров в различных языках программирования сильно отличаются друг от друга.
Ниже приводится пример программы на языке C, в которой кроме главной функции main используются еще две подпрограммы - print_array, печатающая элементы переданного ей массива целых чисел, и selection, сортирующая массив, переданный ей в качестве аргумента.
#includeРазместите текст этой программы в файле с именем sort.c и выполните следующие команды, компилирующие и запускающие ее:void print_array(int c[], int n, char* t) { int i; printf("%s",t); for (i = 0; i n; i += 1) printf("\ta[%d]=%d", i, c[i]); printf("\n"); } void selection(int c[], int n) { int i, j, k, x; for (i = 0; i n; i += 1) { for (x = c[k=i], j = i + 1; j n; j++) if (c[j] x) x = c[k=j]; c[k] = c[i]; c[i] = x; } } int main(void) { int a[] = {8, 3, 2, 7, 9, 5}; int n = sizeof(a)/sizeof(int); print_array(a, n, "Исходный массив\n"); selection(a, n); print_array(a, n, "Отсортированный массив\n"); return 0; }
cc sort.c ./a.outФункция main дважды вызывает процедуру print_array: сначала для печати исходного массива, а затем, после вызова функции selection, для печати его же в уже отсортированном виде. Однажды реализованные функции print_array и selection могут быть использованы при написании относительно большой программы многократно.
Для того чтобы программа была понятной, подпрограммы не должны быть слишком большими. Увеличение числа подпрограмм тоже ведет к значительному росту сложности программы в целом и, как следствие, к снижению ее надежности. Отсюда следует вывод: написать большую правильно работающую программу, используя классическое процедурное программирование практически невозможно.
Со временем при проектировании программ акцент сместился с организации процедур на организацию структур данных. Современные директивные языки программирования предлагают еще один метод структурирования программ: инкапсуляция (от слова capsule - капсула, контейнер) данных и подпрограмм в более крупные объекты, называемые модулями. Большую часть данных модуля и выполняемые операторы можно скрыть таким образом, что их нельзя будет изменить или использовать способами, отличными от заранее предопределенных. Эта парадигма известна, как принцип сокрытия данных. Если в языке нет возможности сгруппировать процедуры вместе с данными, то он плохо поддерживает модульный стиль программирования.
Типичный пример модуля - реализация структуры данных, называемой стеком. Стек можно уподобить коробке с листами бумаги. Новый лист кладется в стопку поверх остальных. Только верхний лист может быть прочитан или извлечен из коробки. Для того чтобы извлечь некоторый лист из коробки, необходимо сначала вынуть все те, что лежат над ним.
Стек функционирует точно также, только в нем хранится совокупность произвольных элементов. Новый элемент помещается на вершину стека с помощью операции втолкнуть (push). Виден в стеке только самый верхний элемент, который может быть извлечен из него командой вытолкнуть (pop). Иногда говорят, что стек задает дисциплину обслуживания LIFO (Last In First Out - последним пришел, первым выйдешь). Организация данных в виде стека широко распространена в программировании. Например, управление автоматически распределяемой памятью в процессе выполнения программы производится по принципу стека.
При модульном подходе задача сначала разбивается на подзадачи и осуществляется реализация этих подзадач, а затем эти подзадачи комбинируются друг с другом для решения основной задачи. Программа, реализующая работу со стеком, написанная в модульном стиле, не позволит пользователю добраться до внутреннего представления данных стека. Доступ к его элементам будет возможен только с помощью методов push и pop.
Способность языка поддерживать модули не помогает разработчику оптимально разбить программу на модули. В то же время именно от качества декомпозиции и способности разработчика программы выбрать наиболее подходящую структуру для ее реализации зависит качество всей системы. Объектно-ориентированное программирование, с которым мы познакомимся чуть позже, предлагает принципиально иной подход к решению данной проблемы.