Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 16
Текст из файла (страница 16)
Пользователю нет необхолимости знать, что Наса реализован при помощи массива. Таким образом, реализация Наса может быть изменена, причем так, что это никак не повлияет на программу пользователя. Так как данные — это только часть того, что хочется «спрятать», понятие «сокрытие данных» тривиальным образом расширяется до понятия «сокрытие информации». Л именно, имена переменных, констант, функций и типов также могут быть сделаны локальными в модуле.
Соответственно, С++ позволяет поместить любое об ьявленне в пространство имен Я 8.2). Модуль Яаса демонстрирует олин из способов представления стека. В последуюгдих разделах используется множество стеков для иллюстрации различных стилей программирования. Глава 2.
Обзор Сч-ь 2.4.1. Раздельная компиляция Се+ поддерживает соглашения С о раздельной компиляции. Это можно использовать для организации программы в виде почти независимых частей. Как правило, мы помещаем объявления, которые описывают интерфейс модуля, в файл с характерным именем, отражающим его использование. Так, интерфейс патезрасез!асй( //интерфейс отй ризй (сйаг); сЬагрор (), будет помещен в файл з(асй.й, Пользователи смогут включить (тт(пс(ис/е) этот файл, называемьвй заголовочным файлолч, следующим образом: Ф(по(и де "з гасй. Ь //включить интерфейс оой(,/() ( 3(асйлризЬ ('с'); (/(Вгасйзрор ()!= 'с') еггог (" Такое невозлчожнор) ) Чтобы помочь компилятору, файл, содержащий реализацию модуля огней, также включит интерфейс (этот файл может называться, например, з(асй.с): и(по(ийе 'вгасй.й // включить интерфейс потеврасенгасй( //представление сопзг Гпг гпах в(ее = 200, сйаг о(так в(ее), Гпт(ар = 0; ооЫ Иасйгривй (сйаг с) ( /' проверить на переполнение и поиестить с в стек*/) сЬаг $тасйзрор () (/* проверить, не пуст ли стек, и извлечь снавол из стека*/) Код пользователя находится в третьем файле, скажем, изег.с.
Тексты изег.с и всасй.с совместно использует информацию об интерфейсе, содержащуюся в зсасй.й; во всем другом эти два файла независимы и могут быть раздельно откомпилированы. Графически, упомянутые фрагменты программы можно представить следующим образом: згасй.йь Раздельная компиляция имеет значение во всех реальных программах. Это непросто способ представления средств, таких как Иасй, в виде модулей. Строго говоря, использование раздельной компиляции не является элементом языка, а относится к 2.4. Модульное программирование вопросам конкретной его реализации, Однако, это вопрос большого практического значения. Наилучшим подходом является максимальное использование модульности, выражение этой модульности средствами языка и затем ее физическое представление в файлах для эффективной раздельной компиляции (главы 8, 9).
2.4.2. Обработка исключений Когда программа разработана в виде набора модулей, обработка ошибок должна рассматриваться в свете этих модулей. Какой модуль несет ответственность за обработку тех нли иныхошибок? Часто модуль, обнаружившии ошибку, не знает, какиедействия нужно предпринять. Действия по восстановлению скорее зависят от модуля, вызвавшего операцию, чем от модуля, который обнаружил ошибку, пьпаясь выполнить эту операцию.
По мере роста программ и, особенно, когда интенсивно !используются библиотеки, стандарты по обработке ошибок (или, в более широком смысле, «исключенийь) становятся очень важными. Рассмотрим снова пример 8!асн, Какие действия нужно предпринять, когда мы пытаемся поместить в стек (рцзЬ) слишком много символов (то есть в случае переполнения)? Автор модуля 8!асй не знает, что предпочел бы пользователь в этой ситуации, а пользователь не может сам обнаружить проблему (если бы мог, то не возникло бы никакого переполнения). Решением для автора 8(асй является обнаружение переполнения стека и сообщение о нем (неизвестному) пользователкь Тогда пользователь может предпринять необходимые действия.
Например; патеврасе 81асп( 0 интер!лес!с сои! риза (сйаг) спагрор (); с!азз Ооег/(от (); //тип, и!зедставлтощийшключение, связанное с переполнением При обнаружении переполнения 8!ас!с:рав!! () может вызвать код обработки исклю-; чений, то есть «генерирует (с)!точу) исключение Ооег(!ошм оо!с! Б!аг3гриз!ч (снагс) (/ (гор == тах з!ге) !!и огвооегЯогв(); // поместить с в стек !!!гош передает управление обработчику исключений типа 5(асйсОоег/?ош в некоторой функции, которая прямо или косвенно вызвала 8!асйсриз!г (). Для воплощения этого механизма реализация «раскругить стек вызовов функций для восстановления контекста вызывающей процедуры. Таким образом, !йгого работает как многоуровневая инструкция ! е(и!.и.
Например: оо!с! /() ( ! д ( // с возникаюи!ими здесь исключениями рпзбираетсч // обработчик, определенной ниже мд!!е (!гие) Яас!г:риз)с ('с'); Глава 2. Обзор С++ сагсп ~ВГасгггооегяозс~ ( // ошибка; переполнение стека; предпримем надлежащие дейсгпвия Цикл шЫе в 1гу-блоке пытается выполняться «вечном Соответственно, после очередного вызова, функция Яаснсризп () сгенерирует(гдгош) исключение Ооегу1ош, и управление будет передано сабсй-блоку, предоставляющему обработчик 51асйсОоег77ош. Использование механизма обработки исключений делает реакцию на ошибки более регулярной и понятной.
Подробное обсуждение см. в ~ 8,3 и главе 14. 2.5. Абстракция данных Модульность — фундаментальный аспект всех успешно работающих крупных систем. Она остается в центре внимания при обсуждении вопросов проектирования на протяжении всей книги. Однако, модули в той форме, как онн описаны выше, недостаточны для ясного представления сложных систем. Ниже я сначала излагаю способ использования модулей для представления некой формы типов данных, определяемых пользователем, а затем показываю, как обойти ряд проблем этого подхода, непосредственно определяя пользовательские типы.
2.5.1. Модули, определяющие типы Модульное программирование приводит к централизации управления данными оп- ределенного типа в отдельном модуле. Например, если мы хотим иметь много стеков. а не один, обеспечиваемый модулем Яасй в примерах выше, мы могли бы описать менеджер стеков с интерфейсом следующего вида: патезрасе Яасй ( зг исгйер; гуредег йерЬ зеас6; // определение ст ек и находи огся где- то в другом месте згасй сгеа1е ц, //создает повии стек ооЫ дезЬоу ~вгасй ф //удаляет стек з ооЫризп~згаслз,слагсГ //пол~еи1аетсвз слагрор ~згаслз~, //извлекает символ из з Объявление Игисг йер; говорит, что Мер (представ ление) является именем типа, но опрелеление типа остав- ляет на потом Я 5,7). Объявление ГуредеУРерй зщсй дает имя з1асй «ссылке на Мер> (подробности см, в й 5.5).
Идея заключается в том, что тип стека может задаваться с помощью 51аснсзтасй, а детали реализации скрыты от пользователей. Ыас/сеанс/сведет себя поч~и как переменная встроенного типа: 2.5. Абстракция данных // неудачное извлечение ди ни ь!х из стека я!гис! Вадрар ( ); со1дл () ( 8!асй: я1асй я1- 81асйссгеа1е (); О создает новый с~иск 8!асйся1асй я2 = 81асйссгеа1е (); //создает еще один новый стек 8!асйсрияй (я1, 'с'); 8!асйсрияй (я2, 'й'), (/ (8!асй:.рор (я1)!= 'с') Ягот Вид рор (); л/ (81асйгрор (я2)!= 'й') 1йгоилВад рор (); 8!асйсс!ея! оу (я1); 81асйсдея1гоу(я2) Мы могли реализовать этот Я!асй несколькими способами.
Важно, что пользователю нет необходимости знать, как мы это сделали. Пользователя не волнуют наши решения касательно изменения реализации 81асй, пока не затронут его интерфейс. Реализация стека может заранее создать несколько стеков и по 81асй ш! еа1е () возвращать ссылку на неиспользованный еше стек. 81асйсс(ея1гоу () может помечать конкретный стек как неиспользуемый, чтобы последующие 81асйпсгеа1е () снова могли воспользоваться им: 0 предслпавление //максимал ьний разл!е!л стека пат еярасе 81асй ( сопя! т1 тах я!ге = 200; я1гис1 Вер ( сйаг о[тах я)хе], !п11ор; сопя11п1тах = 1б; О вер!пина стека О максимальное количество стеков // представления стеков создаются заранее //ияед/1/ == йие, если я!асйя/!/ используепься Вер я1асйя[тах); Ьоо! плед[так); 1урес1е/ йерй я!а ей; ио(с(81асйсрияй (я!пей я, салаге) ( /' проверить стек я на переполнение и записать ашвол с '/ ) сйаг 81асй рор (я!асйя ( /* проверить, не пуст ли стекл, и верну~пи символ '/) 8!асйся1асй 81асй:.сгеа1е () ( // выбрить неиспользуемый йер, пол!етить его как используелилй, // иниииализировать и вернуть ссилкуна него ) оо!д 81асйсдеяггоу (я!а ей я) ( /* аометить стек я как неиспользуемь 0 "/ ) Что мы сделали? Мы заключили тип представления в оболочку из интерфейсных функций.
Как ведет себя получившийся естековый тинь зависит частично от того, как мы определили эти интерфейсные функции, частично от того, как мы предоставляем тип представления пользователям 81асй, и, наконец, от проектирования самого типа представления. Глава 2. Обзор С++ 68 2.5.2. Типы, определяемые пользователем С >+ стремится решить задачу, позволяя пользователю непосредственно определять типы, которые ведут себя (почти) также, как и встроенные.
Такой тип часто называют абстрактным типом данных. Я предпочитаю термин тип, определяемый пользователем (пользовательский тип). /1остат очно то гное определение абстрактного типа данных потребовало бы «абстрактной математической формулировки. При ее наличии то, что здесь называется типами, служило бы конкретными примерами таких истинно абстрактных сущностей. Парадигма программирования становится такой: Везде, где не нужно более одного объекта определенного типа, достаточно стиля про- граммировашгя с сокрытием данных при помощи модулей, Лрифметические типы, такие как дробные и комплексные числа, являются наибо- лее распространенными примерами типов, определяемых пользователем, Пример; с1авв сотр(ех ( йоиЫе ге, !т; риЫ!с // создать колтлексное 'шсло из двух веи(ественных сотр1ех (йоиЫе и йоиЫе 1) ( ге = г; (т = Ь ) 0 создать кояплексное число из одного вещественного сотр(ех (йоиЫе г) ( ге = д !т = О, ) // создать коиплексное чим о по умолчанию (О, О) сотр1ех () ( ге = (т = О; ) /(тепй сотр1ех прего(ог» (сотр1ех, сотр1ех); /г!епй совр(ех прего(ог — (сотр(ех, сотр(ех); //бинарная операция )г!епйсотр(елорега!ог — (сотр(ех); //упорная операция /г(епй сотр(ех орега1ог" (сотр(ех, сотр(ек), /г(епй сот р(ех орега1ог/ (сотр(ех, сотр(ех); // проверка на равенство //проверка на неравенство /г!епй Ьоо( орега!ог== (сотр! ех, сотр(ех); /г!епйЬоо1орега(ос|=(сотр(ех, сотр(ех); 0- Очень часто такое решение далеко от идеала.
Существенной проблемой является то, что предоставление таких «псевдотипов» пользователям может очень сильно зависеть от деталей типа представления, и пользователи вынуждены их знать. Например, если бы мы выбрали для определения стека более сложную структуру данных, значительно изменились бы правила присваивания и инициализации для Яас(с:в(ас(гв. Иногда это может быть и к лучшему. Однако, это демонстрирует то, что мы просто перенесли проблему создания хороших стеков из модуля о(ас(е в тип представления а(ас(к:в(ас(г.