Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 16
Текст из файла (страница 16)
2.5. Абстракция данных 2.5.1. Модули, определяющие типы Модульное программирование естественным образом приводит к идее о централизованном управлении всеми данными одного и того же типа с помощью специального управляющего модуля. Например, чтобы работать одновременно со множеством стеков, а не с единственным, представленным выше модулем 5(ас)с, можно определить одноименный менеджер стеков с интерфейсом следующего вида: патезрасе агава ( зссисз Яер; зурвдву авра агась; УУ определение раскладки стека находится в другом месте У создать новый стек УУудалить стек з ззасй ссеазе (); иоЫ йвзгвоу (маса з); Модульность является фундаментальным аспектом успешных программных проектов большого размера.
В настоящей книге она всегда в центре внимания при обсуждении вопросов проектирования программ. Однако способ объявления и использования модулей, рассмотренный нами до сих пор, не является адекватным в случае сложных программных систем. Сейчас я рассмотрю применение модулей для логического формирования того, что можно назвать пользовательскими типами данных, а затем преодолею связанные с таким подходом недостатки с помощью специальных синтаксических конструкций языка С++, предназначенных для явного создания пользовательских типов.
Глава 2. Обзор языка С++ 70 гоЫ рияп (яяас(я я, сваг с); сйаг рор (Вас(я я); ) УУ поместить с в я УУ извлечь я из стека (рор я) Объявление заися Вер; говорит о том, что Мер это имя типа, определение которого будет дано позже (55.7). Объявление яуреде)" Веря яяаеа; даЕт ИМя я(аСК лССЬПКаМ На Вср> (ПОдрОбНОСтн СМ. В В5.5). ЦЕНтраЛЬНая ИдЕя СОСтО- ит в том, что конкретный стек создается как переменная типа Ягаса::ягасй, а остальные детали от пользователя скрыты. Переменные типа Ягаса:: ягас)с ведут себя почти как переменные встроенных типов: мгисяВад рор ( ); гоЫг () ( Я(пса:: Вас)я я1 = Яяас(с:: сгеаяе ( ); Яяаск::яяаск я2 = Ягасй::сгеаяе(); УУ создаем новый стек У создаем еще один новый стек Яяасб::рияй (я1, 'с ' ); Яяас(я::риза (я2, 'й' ); »7'(Яяасй::рор(я1) ! = 'с') яйгою Вой рор(); 0" (Яяасй::рор(я2) ! = 'й' ) яйго»г Вад рор(); Яяаск:: деяягоу (я1); Яяаса:: Неяягоу (з2); ) патеярасе Яяасй У представление сопля (пя тах я(ее= 200; яп ися Вер ( сйаг г [азах я(яе) 1пг яор; )' сопи шя тах = 10) УУ максимальное количество стеков Реализацию модуля Ягасй можно выполнить разными способами.
Важно, что пользователю нет необходимости знать, как именно мы это делаем: при неизменном интерфейсе изменение реализации не влияет на код пользователя. Реализация модуля Ягас1с может заранее разместить в памяти несколько стеков, и тогда функция Ягаса:: сгеаге () будет просто возвращать ссылку на неиспользуемый стек.
Функция Ягасй::НеяятоуО, в свою очередь, будет помечать стек как «свободный», так что функция Ягасй::сгеаге() может использовать его повторно: 2.5. Абстракция данных )сер масяя (тах) с УУ априорная раскладка стеков Ьоо! илес!(тах) 1 УУ иява(с! == (гив если ясас(сяЯ используется !урсс(ву !гсрь я!ос!с! ) со!с! о!асяс срияЬ (я1асЬ я, сяаг с) ( ! * проверить я на переполнение и рияЬ с *У сяаг о!вся::рор (я1асЬ я) ( У* проверить, нс пуст ли я и рор *У ) Ьсасй:: ясасЬ осасЬ:: сгеа1е ( ) ( У выбрать неиспользуемый )(ер, пометить как используемый, УУ инсн(иолияировать его, и вернуть ссылку но него ) уо!с! а!асяс !с!ел!яву (Мося я) ( У* «олсетить я как нсиспольяувмьсй *У ) Что мы здесь сделалн по существу? Мы полностью определили интерфейсные функции, используя тип яас)с из реализации модуля Фаей.
Результирующее поведение созданного таким образом «стекового псевдотипа» зависит от нескольких факторов: от того, как именно мы определили интерфейсные функции; от того, как мы предоставляем тип огасЬ:: ягас(с пользователям, а также от деталей определения типов б)асЬ:: ягасЬ и Яасйс !мер (так называемых типов представления — гергеяепгайоп (урез). Рассмотренное решение далеко не идеальное. Существенной проблемой является зависимость способа представления таких псевдотипов пользователю от деталей устройства внутренних типов представления, а ведь пользователи от таких деталей должны быть изолированы.
Действительно, стоит нам применить более сложные структуры для внутреннего представления стеков, как сразу же изменятся правила присваивания и инициализации для огасЬс сягасЬ. Может быть, иногда это и неплохо. Однако ж ясно, что проблема создания удобного типа для стеков просто переместилась в тип представления о!асйс: я!ас(с. Более фундаментально, пользовательские типы, реализованные посредством модуля с доступом ко внутренней реализации, ведут себя не как встроенные типы и имеют меньшую поддержку. Например, время доступа к типу представления огасЬ: слсер контролируется функциями Кгасй: ссгеаге() и огасЬ::с(еяггоу(), а не обычными правилами языка.
2.5.2. Типы, определяемые пользователем Для преодоления рассмотренной выше проблемы язык С++ позволяет пользователю созлавать новые типы, которые ведут себя (почти) так же, как типы встроенные. Их часто называют абстрактными типами данных. Однако я предпочитаю называть их типами, определяемыми пользователем (ияег-с(еГ~псс! !урез). Строгое определение абстрактных типов данных потребовало бы математически точной «абстрактной» формулировки. При ее наличие то, что мы называем типами, было бы конкретными примерами таких истинно абстрактных сущностей.
Программная парадигма становится такой: Решите, какие типы нужны; обеспечьте полный набор операций для каждого типа. Глава 2. Обзор языка С+-ь 72 Если не требуется создавать более одного объекта определенного типа, то достаточно рассмотренного стиля программирования с сокрытием данных на базе модулей. Математические типы, такие как рациональные или комплексные числа, являются естественными примерами типов, определяемых пользователем.
Вот иллюстрация на эту тему: с1авв сотр!ех ( йоиЫе ге, !т! риЬНс: сотргех(йоиЫе г, йоиЫе !) ( ге=г; (т=1; ) сотр1ех(йоиЫе г) ( ге=г; (т=О; ) сотр1ех ( ) ( ге = !т = О; ) ~пелй сотргех орегагогв (сотргех, сотргех); 3пелй сотргех орегагог- (сотр(ех, сотргех); упелй сотргех орегагог- (сотр(ех); /пелй сотр(ех орегагог* (сотр(ех, сотр!ек) ! Яелй совр!ех орегагог/ (сотргех, сотргех); упелй Ьоо! орегагог== (сотр(ех, сотр!ех); ~пелй Ьоог орегагог! = (совр!ех, сотр(ех); // ... // создать сотр!ех из двух скаяяров // создать сотр1ех из скаляра У ло умолчанию: (О,О) 7 бинарная операция //унарная олераиия // проверка на равенство // проверка на неравенство Объявление «ласса сотр1ех (то есть типа, определяемого пользователем) задает как представление комплексных чисел, так и набор операций над комплексными числами. При этом представление является закрытым (рг(гаге); доступ к полям ге и !и имеют лишь функции, перечисленные в объявлении класса сотр1ех.
Их можно определить примерно так: сотр1ех орегагог+ (сотр!ех а1, сотргех а2) ( ге!игл совр(ех (а1. гева2. ге, а1. !т+а2.!т) го!й г" (сотр1ех с) сотргех а = 2. 3; сотр!ех Ь = 1/а; сотр1ех с = аьЬ*сотр(ех (1, 2. 3) гГ(с! = Ы с = - (Ь/а) +2*Ь! Функции с именем, совпадающим с именем класса, называют конструкторами Конструктор задает способ инициализации объекта класса.
Класс сотр!ех объявляет три конструктора. Один из них преобразует вещественное число типа йоиЫе в комплексное число, другой создает комплексное число из пары вещественных, а третий строит комплексное число со значением по умолчанию. Использовать класс сощр)ех можно следующим образом: 2.5. Абстракция данных Компилятор преобразует выполняемые над комплексными числами операции в вызовы соответствующих функций из определения класса сотр!ех. К примеру, для выражения с . = Ь вызывается функция орегаьэг) = (с,Ь), а вместо выражения 1/а вызывается функция орегагог/(сотр!ех ( Т,а). Большинства модулей, хотя и не все из них, лучше формулируются в виде типов, определяемых пользователем. 2.5.3.
Конкретные типы Типы, определяемые пользователем, разрабатываются с разными целями. Рассмотрим определяемый пользователем тип В!вой по аналогии с рассмотренным выше типом сотр1ех. Пример станет более реалистичным, если мы будем передавать стеку в момент его создания аргумент, фиксирующий предельную емкость (размер) стека: риЫ!с: с!аьв ИЫег/)он ( ); с!аьв Огеьу)от ( ); с!авв Вай з!ге ( ); д используется как исключение д используется как исключение д используется как исключение Б~ася(!пг з); /конструктор -В!вой ( ); д деструктор го!0 ризд (сваг с); сваг рор ( ); )' Конструктор Ягасй(/а!) вызывается автоматически, когда создается объект класса.
Он берет на себя заботу об инициализации. Если требуется очистка памяти (с)еапцр) в момент выхода объекта из области видимости, можно объявить функцию, называемую деструктором (ь!ез!гисгог, по цели своих действий противоположна конструктору): д конструктор атак:: -бгасй ( ) ( ве!его(] г; д деструктор д уничтожить элементы для дальнейшего д использования паьити ф6.2.6) с1аьв бзасй ( сваг* г; !и! гор; 1п! тах з!се! о!ага:: о!ага (!и! з) ( гор = 0; г7( з<0 () 10000<э ьпах з!ге = 3; г = пеп сваг[в]; ) ) гдгои Вай з!зе (); д ]] означает ИЛИ д расположить элементы в свободной памяти (куча, дином. память) Глава 2.