Light-electric.com

IT Журнал
27 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

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

C Урок 19. Модульное программирование. Раздельная компиляция

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

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

Заголовочный файл, или как его ещё называют header-файл — это файл, в котором обычно находятся подключения всяческих других заголовочных файлов, библиотек, прототипы функций, некоторые глобальные переменные, структуры, массивы, указатели, макросы и прочие объявления, которые вполне могли бы находиться и в файле с исходным кодом, но, во-первых они его загромождают чрезмерной информационной нагрузкой, а также, благодаря заголовочному файлу, при его подключении в другие файлы становятся доступными многие ресурсы из модуля, частью которого данный файл является. Заголовочные файлы как правило имеют расширение h.

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

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

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

Пока мы создаём проект, как и прежде, из проекта прошлого занятия с именем MYPROG18 и присвоим ему имя MYPROG19.

Откроем файл main.c и в функции main(), как обычно, удалим весь код тела кроме возврата нуля, останется от него вот это

int main()

return 0 ; //Return an integer from a function

Функцию menu() тоже удалим.

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

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

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

Следующий вопрос, который возникает, раз модуль это независимая структура, можно сказать «вещь в себе», то она не имеет связи с внешним миром, а нас это не устраивает. Для связи модулей с внешним миром используется заголовочные файлы, их также называют хэдерами/хидерами и они имеют расширение .h. Назвать хэдер можно как угодно, но удобнее, чтобы его название совпадало с названием модуля, lcd.h и ds18b20.h, также надо сказать, что все подключаемые файлы(#include) удобно вынести в хэдер и подключать только его вначале модуля.
Когда хэдера не было, начало lcd.с выглядело так

а после создание хэдера стало выглядеть так

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

В файле ds18b20.h будут объявлены следующие прототипы:

Что касается макросов, можно вынести макросы, отвечающие за выполнение условной компиляции

А где-то в коде есть конструкция, которая выполняется если предыдущая строчка раскомментирована

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

Но в то же время в хэдере не надо размещать то, что не понадобится в других модулях:

    макросы типа

  • переменные, которые будут использоваться только внутри модуля с ключевым словом static
  • переменные с квалификатором extern
  • прототипы функций, которые нужны для каких-то промежуточных действий, например, функцию, которая переводит число в BCD формат
  • Теперь пару слов про подключение хэдеров, при программировании микроконтроллеров AVR почти во всех модулях подключается хэдер для работы с портами ввода-вывода.

    То есть он подключается в lcd.h и в ds18b20.h, теперь если мы подключим эти два заголовка в основном файле программы, то avr/io.h подключится дважды, хотя достаточно было и одного. Для того чтобы избежать возникновения такой ситуации и хэдер не подключился дважды используют #include guards, который выглядит следующим образом.

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

    Преимущество использование #pragma once вместо #include guards можно почитать тут.
    Кстати подключать можно не только хэдеры, а также файлы с расширением , если это надо

    Читать еще:  Среда программирования visual basic

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

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

    Дата добавления: 2013-12-23 ; просмотров: 11133 ; Нарушение авторских прав

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

    Концепции модульного программирования.В основе модульного программирования лежат три основных концепции:

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

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

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

    · синтаксическая обособленность, т. е. выделение модуля в тексте синтаксическими элементами;

    · семантическая независимость, т. е. независимость от места, где программная единица вызвана;

    · общность данных, т. е. наличие собственных данных, сохраняющихся при каждом обращении;

    · полнота определения, т. е. самостоятельность программной единицы.

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

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

    · потребность организационного расчленения крупных разработок;

    · возможность параллельного исполнения модулей (в контексте параллельного программирования).

    Определения модуля и его примеры.Приведем несколько дополнительных определений модуля.

    · Модуль — это совокупность команд, к которым можно обратиться по имени.

    · Модуль — это совокупность операторов программы, имеющая граничные элементы и идентификатор (возможно агрегатный).

    Функциональная спецификация модуля должна включать:

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

    · описание семантики функций, выполняемых модулем по каждому из его входов.

    Разновидности модулей.Существуют три основные разновидности модулей:

    1) «Маленькие» (функциональные) модули, реализующие, как правило, одну какую-либо определенную функцию. Основным и простейшим модулем практически во всех языках программирования является процедура или функция.

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

    a)задачи в языке программирования Ada;

    b)кластер в языке программирования CLU;

    c)классы в языках программирования C++ и Java.

    3) «Большие” (логические) модули, объединяющие набор «средних» или «маленьких» модулей. Примеры «больших» модулей в языках программирования:

    a)модуль в языке программирования Modula-2;

    b)пакеты в языках программирования Ada и Java.

    Набор характеристик модуля предложен Майерсом [Майерс 1980]. Он состоит из следующих конструктивных характеристик:

    1) размера модуля;

    В модуле должно быть 7 (+/-2) конструкций (например, операторов для функций или функций для пакета). Это число берется на основе представлений психологов о среднем оперативном буфере памяти человека. Символьные образы в человеческом мозгу объединяются в «чанки» — наборы фактов и связей между ними, запоминаемые и извлекаемые как единое целое. В каждый момент времени человек может обрабатывать не более 7 чанков.

    Модуль (функция) не должен превышать 60 строк. В результате его можно поместить на одну страницу распечатки или легко просмотреть на экране монитора.

    2) прочности (связности) модуля;

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

    Связность (прочность) модуля (cohesion) — мера независимости его частей. Чем выше связность модуля — тем лучше, тем больше связей по отношению к оставшейся части программы он упрятывает в себе. Можно выделить типы связности, приведенные ниже.

    Функциональная связность. Модуль с функциональной связностью реализует одну какую-либо определенную функцию и не может быть разбит на 2 модуля с теми же типами связностей.

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

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

    Обратим внимание на то, что средства для задания информационно прочных модулей отсутствовали в ранних языках программирования (например, FORTRAN и даже в оригинальной версии языка Pascal). И только позже, в языке программирования Ada, появился пакет — средство задания информационно прочного модуля.

    3) сцепления модуля с другими модулями;

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

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

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

    · Сцепление по простым элементам данных.

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

    4) рутинности (идемпотентность, независимость от предыдущих обращений) модуля.

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

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

    Читать еще:  Язык программирования си

    · В большинстве случаев делаем модуль рутинным, т. е. независимым от предыдущих обращений.

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

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

    Что такое модульное программирование и кому оно нужно

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

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

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

    Классическая проблема программирования

    В западной литературе существует термин «big ball of mud» для описания архитектуры программы. Давайте переведём его дословно. Графически «большой шар грязи» можно представить в виде точек на окружности, символизирующих функциональные элементы, и прямых – связей между ними:

    Похоже на ваши глаза перед сдачей проекта, не так ли?

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

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

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

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

    • Ускорение разработки.
    • Повышение надёжности.
    • Упрощение тестирования.
    • Взаимозаменяемость.

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

    Но не всё так просто.

    Проблемы модульного программирования

    Сама по себе идея использования модулей не сильно упрощает код, важно минимизировать количество прямых связей между ними. Здесь мы подходим к понятию «инверсия управления» (IoC). Упрощённо – это принцип программирования, при котором отдельные компоненты кода максимально изолированы друг от друга. То есть детали одного модуля не должны влиять на реализацию другого. Достигается это при помощи интерфейсов или других видов представления, не обеспечивающих прямого доступа к модульному коду.

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

    В модульном программировании существует три основные реализации:

    • Внедрение зависимостей. Способ, при котором каждый элемент имеет свой интерфейс, взаимодействие модулей происходит через интерфейсы.
    • Фабричный метод. Основывается на существовании некого объекта, предназначенного для создания других объектов. Иначе говоря, введение в программу прототипа, объединяющего общие черты для большинства объектов. Прямого взаимодействия между модулями нет, все параметры наследуются от «завода».
    • Сервисный метод. Создаётся один общий интерфейс, являющийся буфером для взаимодействия объектов. Похожую функцию в реальной жизни выполняют колл-центры, магазины, площадки для объявлений и т.д.

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

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

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

    1. Обеспечить чёткое функциональное разделение кода. При возникновении ошибок можно быстро определить источник, а исправления не приведут к появлению новых сбоев.
    2. Минимизировать количество связей. Это позволит упростить разработку, отдав на откуп нескольким разработчикам разные модули. Или вы сможете самостоятельно разрабатывать каждый блок без оглядки на другие, что тоже экономит время и силы.
    3. Создать иерархию с чёткой вертикалью наследования. Это повышает надёжность кода, так как тестирование провести проще, а результаты информативнее.

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

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

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

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

    Классическая проблема программирования

    В западной литературе существует термин «big ball of mud» для описания архитектуры программы. Давайте переведём его дословно. Графически «большой шар грязи» можно представить в виде точек на окружности, символизирующих функциональные элементы, и прямых – связей между ними:

    Читать еще:  Объектно ориентированное программирование примеры

    Похоже на ваши глаза перед сдачей проекта, не так ли?

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

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

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

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

    • Ускорение разработки.
    • Повышение надёжности.
    • Упрощение тестирования.
    • Взаимозаменяемость.

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

    Но не всё так просто.

    Проблемы модульного программирования

    Сама по себе идея использования модулей не сильно упрощает код, важно минимизировать количество прямых связей между ними. Здесь мы подходим к понятию «инверсия управления» (IoC). Упрощённо – это принцип программирования, при котором отдельные компоненты кода максимально изолированы друг от друга. То есть детали одного модуля не должны влиять на реализацию другого. Достигается это при помощи интерфейсов или других видов представления, не обеспечивающих прямого доступа к модульному коду.

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

    В модульном программировании существует три основные реализации:

    • Внедрение зависимостей. Способ, при котором каждый элемент имеет свой интерфейс, взаимодействие модулей происходит через интерфейсы.
    • Фабричный метод. Основывается на существовании некого объекта, предназначенного для создания других объектов. Иначе говоря, введение в программу прототипа, объединяющего общие черты для большинства объектов. Прямого взаимодействия между модулями нет, все параметры наследуются от «завода».
    • Сервисный метод. Создаётся один общий интерфейс, являющийся буфером для взаимодействия объектов. Похожую функцию в реальной жизни выполняют колл-центры, магазины, площадки для объявлений и т.д.

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

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

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

    1. Обеспечить чёткое функциональное разделение кода. При возникновении ошибок можно быстро определить источник, а исправления не приведут к появлению новых сбоев.
    2. Минимизировать количество связей. Это позволит упростить разработку, отдав на откуп нескольким разработчикам разные модули. Или вы сможете самостоятельно разрабатывать каждый блок без оглядки на другие, что тоже экономит время и силы.
    3. Создать иерархию с чёткой вертикалью наследования. Это повышает надёжность кода, так как тестирование провести проще, а результаты информативнее.

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

    Бьерн Страуструп — Язык программирования С++. Вступление, глава 1
    Страница 18. Модульное программирование

    1.2.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). Помимо этого С++, естественно, имеет уже
    продемонстрированные возможности модульности, которые есть в С, т.е.
    представление модуля как отдельной единицы трансляции.

    0 0 голоса
    Рейтинг статьи
    Ссылка на основную публикацию
    ВсеИнструменты