- •№1 Объекты и классы
- •Void main ( ) {/* ... */}
- •№5 Конструкторы класса
- •Конструкторы с аргументами
- •№7 Преобразование типов в производных классах
- •№8 Расширение характеристик при наследовании классов
- •Void Display ( ) ;
- •Void Display ( ) { }
- •Void Display ( ) ;
- •№9 Перегрузка и переопределение членов класса
- •№10 Использование виртуальных базовых классов
- •Int value;
№7 Преобразование типов в производных классах
В C++ классы, как правило, не разрознены, а иерархически связаны друг с другом. Вследствие этого, иногда приходится выполнять какие-то преобразования между классами, находящимися на разных уровнях дерева. Рассмотрим следующих два класса:
class А { };
class В: public A {};
Поскольку В порожден от А, наследуемые компоненты А доступны в В. Следовательно, В - это что-то вроде надмножества А. Таким образом, возможно преобразование объекта В в А, но не наоборот.
Неявное преобразование, когда необходимо, осуществляется и при вызове функций, как показано в листинге 1.
Листинг 1. Неявное преобразование типов при вызове функций
void foo(A* object) {}
void main ()
{A* ap = new A;
B* bp = new B;
Foo (ap);
foo (bp) ;}
Поддержка преобразования классов является замечательной возможностью C++. Она позволяет компилятору во время компиляции эффективно разделять и отслеживать объекты. Расширение подобного качества превращается в полиморфизм, предоставляющий возможность объектам вести себя полностью независимо, т.е. способом, определяемым во время выполнения.
Порождая один класс от другого, можно легко прийти к такой ситуации, когда в нескольких классах используются переменные и функции с одинаковыми именами. Как сообщить компилятору, с какой именно копией переменной или функции работать? При поиске нужной функции компилятор просматривает дерево наследования начиная с класса В и использует "ближайшую" к классу В функцию. Если имя в базовом классе переобъявляется в производном классе, то имя в производном классе подавляет соответствующее имя в базовом классе.
В C++ можно принудить компилятор "видеть" за пределами текущей области видимости и обеспечить доступ к именам, которые в противном случае были бы невидимыми. Это делается с помощью оператора разрешения видимости, а процесс, естественно, называется разрешением видимости. Общая форма оператора разрешения видимости такова:
<имя класса> : : <идентификатор из класса>
где <имя класса> — это имя базового или производного класса, а <идентификатор из класса> — это имя любой переменной или функции, объявленной в классе.
Для того чтобы использовать оператор разрешения видимости, модифицируем класс В следующим образом:
class В: public A {
public:
int foo ( ) { return 2; }
int f ( ) { return A::foo( ); }
};
Теперь вызов функции В : : f ( ) приведет к вызову функции foo ( ) класса А. Оператор разрешения видимости используется не только внутри функций класса, но и при вызове функции.
Если есть несколько уровней наследования, то оператор разрешения видимости предоставляет доступ к элементам любого базового класса с учетом, разумеется, привилегий доступа. Заметьте, что разрешение видимости нужно только тогда, когда производный класс использует такой же идентификатор, как и в базовом классе. Рассмотрим листинг 2.
Листинг 2. Использование разрешения видимости после оператора . (точка)
class A { public:
int value;
};
class В: public A {
public:
int count;
};
void main( )
{
В b;
int i = b.count;
int j = b.B : : count; // излишне, но правильно
int k = b.value;
int l = b.A : : value; // тоже излишне, и тоже правильно
}
Представленный фрагмент кода показывает необычный синтаксис, используемый с оператором разрешения видимости. Подобная запись применяется с указателями на объект и ссылками. Оператор разрешения видимости не нужен, если идентификатор встречается в дереве наследования только один раз или находится в данной области видимости.