Б. Страуструп - Язык программирования С++ (1119446), страница 15
Текст из файла (страница 15)
Программист может задавать эти преобразования явно, а может полагаться натранслятор, который выполняет их неявно в том случае, когда они необходимы и однозначны:complexcomplexa = b +a = b +a = complex ( 1 );b = 1;complex ( 2 );2;// неявно: 1 -> complex ( 1 )// неявно: 2 -> complex ( 2)38Бьерн Страуструп.Язык программирования С++Преобразования типов нужны в С++ потому, что арифметические операции со смешанными типамиявляются нормой для языков, используемых в числовых задачах. Кроме того, большая частьпользовательских типов, используемых для "вычислений" (например, матрицы, строки, машинныеадреса) допускает естественное преобразование в другие типы (или из других типов).Преобразования типов способствуют более естественной записи программы:complex a = 2;complex b = a + 2;b = 2 + a;// это означает: operator + ( a, complex ( 2 ))// это означает: operator + ( complex ( 2 ), a )В обоих случаях для выполнения операции "+" нужна только одна функция, а ее параметрыединообразно трактуются системой типов языка.
Более того, класс complex описывается так, что дляестественного и беспрепятственного обобщения понятия числа нет необходимости что-то изменять дляцелых чисел.1.4.6 Множественные реализацииОсновные средства, поддерживающие объектно-ориентированное программирование, а именно:производные классы и виртуальные функции,- можно использовать и для поддержки абстракцииданных, если допустить несколько реализаций одного типа. Вернемся к примеру со стеком:template < class T >class stack{public:virtual void push ( T ) = 0;virtual T pop () = 0;};// чистая виртуальная функция// чистая виртуальная функцияОбозначение =0 показывает, что для виртуальной функции не требуется никакого определения, а классstack является абстрактным, т.е. он может использоваться только как базовый класс.
Поэтому стекиможно использовать, но не создавать:class cat { /* ... */ };stack < cat > s;// ошибка: стек - абстрактный классvoid some_function ( stack <cat> & s, cat kitty ) // нормально{s.push ( kitty );cat c2 = s.pop ();// ...}Поскольку интерфейс стека ничего не сообщает о его представлении, от пользователей стекаполностью скрыты детали его реализации.Можно предложить несколько различных реализаций стека.
Например, стек может быть массивом:template < class T >class astack : public stack < T >{// истинное представление объекта типа стек// в данном случае - это массив// ...public:astack ( int size );~astack ();void push ( T );T pop ();};Можно реализовать стек как связанный список:39Бьерн Страуструп.Язык программирования С++template < class T >class lstack : public stack < T >{// ...};Теперь можно создавать и использовать стеки:void g (){lstack < cat > s1 (astack < cat > s2 (cat Ginger;cat Snowball;some_function ( s1,some_function ( s2,}100 );100 );Ginger );Snowball );О том, как представлять стеки разных видов, должен беспокоиться только тот, кто их создает (т.е.функция g()), а пользователь стека (т.е.
автор функции some_function()) полностью огражден от деталейих реализации. Платой за подобную гибкость является то, что все операции над стеками должны бытьвиртуальными функциями.1.5 Поддержка объектно-ориентированного программированияПоддержку объектно-ориентированного программирования обеспечивают классы вместе с механизмомнаследования, а также механизм вызова функций-членов в зависимости от истинного типа объекта(дело в том, что возможны случаи, когда этот тип неизвестен на стадии трансляции).
Особенно важнуюроль играет механизм вызова функций-членов.Не менее важны средства, поддерживающиеабстракцию данных (о них мы говорили ранее). Все доводы в пользу абстракции данных ибазирующихся на ней методов, которые позволяют естественно и красиво работать с типами,действуют и для языка, поддерживающего объектно-ориентированное программирование. Успех обоихметодов зависит от способа построения типов, от того, насколько они просты, гибки и эффективны.Метод объектно-ориентированного программирования позволяет определять более общие и гибкиепользовательские типы по сравнению с теми, которые получаются, если использовать толькоабстракцию данных.1.5.1 Механизм вызоваОсновное средство поддержки объектно-ориентированного программирования - это механизм вызовафункции-члена для данного объекта, когда истинный тип его на стадии трансляции неизвестен.
Пусть,например, есть указатель p. Как происходит вызов p->rotate(45)? Поскольку С++ базируется настатическом контроле типов, задающее вызов выражение имеет смысл только при условии, чтофункция rotate() уже была описана. Далее, из обозначения p->rotate() мы видим, что p являетсяуказателем на объект некоторого класса, а rotate должна быть членом этого класса. Как и при всякомстатическом контроле типов проверка корректности вызова нужна для того, чтобы убедиться (насколькоэто возможно на стадии трансляции), что типы в программе используются непротиворечивым образом.Тем самым гарантируется, что программа свободна от многих видов ошибок.Итак, транслятору должно быть известно описание класса, аналогичное тем, что приводились в $$1.2.5:class shape{// ...public:// ...virtual void rotate ( int );// ...};а указатель p должен быть описан, например, так:40Бьерн Страуструп.Язык программирования С++T * p;где T - класс shape или производный от него класс.
Тогда транслятор видит, что класс объекта, накоторый настроен указатель p, действительно имеет функцию rotate(), а функция имеет параметр типаint. Значит, p->rotate(45) корректное выражение.Поскольку shape::rotate() была описана как виртуальная функция, нужно использовать механизм вызовавиртуальной функции. Чтобы узнать, какую именно из функций rotate следует вызвать, нужно до вызоваполучить из объекта некоторую служебную информацию, которая была помещена туда при егосоздании.
Как только установлено, какую функцию надо вызвать, допустим circle::rotate, происходит еевызов с уже упоминавшимся контролем типа. Обычно в качестве служебной информации используетсятаблица адресов функций, а транслятор преобразует имя rotate в индекс этой таблицы. С учетом этойтаблицы объект типа shape можно представить так:centervtbl:color&X::draw&Y::rotate......Функции из таблицы виртуальных функций vtbl позволяют правильно работать с объектом даже в техслучаях, когда в вызывающей функции неизвестны ни таблица vtbl, ни расположение данных в частиобъекта, обозначенной ... .
Здесь как X и Y обозначены имена классов, в которые входят вызываемыефункции. Для объекта circle оба имени X и Y есть circle. Вызов виртуальной функции может быть по сутистоль же эффективен, как вызов обычной функции.1.5.2 Проверка типаНеобходимость контроля типа при обращениях к виртуальным функциям может оказатьсяопределенным ограничением для разработчиков библиотек. Например, хорошо бы предоставитьпользователю класс "стек чего-угодно". Непосредственно в С++ это сделать нельзя. Однако, используяшаблоны типа и наследование, можно приблизиться к той эффективности и простоте проектирования ииспользования библиотек, которые свойственны языкам с динамическим контролем типов.
К такимязыкам относится, например, язык Smalltalk, на котором можно описать "стек чего-угодно". Рассмотримопределение стека с помощью шаблона типа:template < class T > class stack{T * p;int sz;public:stack ( int );~stack ();void push ( T );T & pop ();};Не ослабляя статического контроля типов, можно использовать такой стек для хранения указателей наобъекты типа plane (самолет):stack < plane * > cs ( 200 );void f (){cs.push ( new Saab900 );// Ошибка при трансляции :// требуется plane*, а передан car*cs.push ( new Saab37B );// прекрасно: Saab 37B - на самом// деле самолет, т.е.
типа planecs.pop () -> takeoff ();cs.pop () -> takeoff ();}41Бьерн Страуструп.Язык программирования С++Если статического контроля типов нет, приведенная выше ошибка обнаружится только при выполнениипрограммы:// пример динамическое контроля типа// вместо статического; это не С++Stack s;// стек может хранить указатели на объекты// произвольного типаvoid f (){s.push ( new Saab900 );s.push ( new Saab37B );s.pop () -> takeoff ();// прекрасно: Saab 37B - самолетcs.pop () -> takeoff ();// динамическая ошибка:// машина не может взлететь}Для способа определения, допустима ли операция над объектом, обычно требуется большедополнительных расходов, чем для механизма вызова виртуальных функций в С++.Рассчитывая на статический контроль типов и вызов виртуальных функций, мы приходим к иному стилюпрограммирования, чем надеясь только на динамический контроль типов.
Класс в С++ задает строгоопределенный интерфейс для множества объектов этого и любого производного класса, тогда как вSmalltalk класс задает только минимально необходимое число операций, и пользователь вправеприменять незаданные в классе операции. Иными словами, класс в С++ содержит точное описаниеопераций, и пользователю гарантируется, что только эти операции транслятор сочтет допустимыми.1.5.3 Множественное наследованиеЕсли класс A является базовым классом для B, то B наследует атрибуты A. т.е.