- •Учебное пособие
- •Введение
- •Объектно-ориентированный подход
- •Объектно-ориентированное программирование Абстрактные типы данных
- •Базовые принципы объектно-ориентированного программирования
- •Простейший ввод и вывод
- •Объект cout
- •Манипуляторы hex и oct
- •Другие манипуляторы
- •Объект cin
- •Операторы для динамического выделения и освобождения памяти (new и delete)
- •Базовые конструкции объектно-ориентированных программ Объекты
- •Понятие класса
- •Конструктор копирования
- •Конструктор explicit
- •Указатели на компоненты класса
- •Встроенные функции (спецификатор inline)
- •Организация внешнего доступа к локальным компонентам класса (спецификатор friend)
- •Вложенные классы
- •Static-члены (данные) класса
- •Указатель this
- •Компоненты-функции static и const
- •Proxi-классы
- •Параметры ссылки
- •Независимые ссылки
- •Практические приемы ограничения числа объектов класса
- •Наследование (производные классы)
- •Конструкторы и деструкторы при наследовании
- •Виртуальные функции
- •Абстрактные классы
- •Виртуальные деструкторы
- •Множественное наследование
- •Виртуальное наследование
- •Перегрузка функций
- •Перегрузка операторов
- •Перегрузка бинарного оператора
- •Перегрузка унарного оператора
- •Дружественная функция operator
- •Перегрузка оператора []
- •Перегрузка оператора ()
- •Перегрузка операторов new и delete
- •Преобразование типа
- •Явные преобразования типов
- •Преобразования типов, определенных в программе
- •Шаблоны Параметризированные классы
- •Передача в шаблон класса дополнительных параметров
- •Шаблоны функций
- •Совместное использование шаблонов и наследования
- •Шаблоны класса и friend
- •Некоторые примеры использования шаблона класса Реализация smart-указателя
- •Классы поддерживающие транзакции
- •Задание значений параметров класса по умолчанию
- •Пространства имен
- •Ключевое слово using как директива
- •Ключевое слово using как объявление
- •Псевдоним пространства имен
- •Организация ввода-вывода
- •Состояние потока
- •Строковые потоки
- •Организация работы с файлами
- •Организация файла последовательного доступа
- •Создание файла произвольного доступа
- •Основные функции классов ios, istream, ostream
- •Основы обработки исключительных ситуаций
- •Перенаправление исключительных ситуаций
- •Исключительная ситуация, генерируемая оператором new
- •Генерация исключений в конструкторах
- •Задание собственной функции завершения
- •Спецификации исключительных ситуаций
- •Задание собственного неожиданного обработчика
- •Иерархия исключений стандартной библиотеки
- •Стандартная библиотека шаблонов (stl) Общее понятие о контейнере
- •Общее понятие об итераторе
- •Категории итераторов
- •Основные итераторы
- •Вспомогательные итераторы
- •Операции с итераторами
- •Контейнерные классы Контейнеры последовательностей
- •Контейнер последовательностей vector
- •Контейнер последовательностей list
- •Контейнер последовательностей deque
- •Ассоциативные контейнеры
- •Ассоциативный контейнер multiset
- •Ассоциативный контейнер set
- •Ассоциативный контейнер multimap
- •Ассоциативный контейнер map
- •Адаптеры контейнеров
- •Адаптеры stack
- •Адаптеры queue
- •Адаптеры priority_queue
- •Пассивные и активные итераторы
- •Алгоритмы
- •Алгоритмы сортировки sort, partial_sort, sort_heap
- •Алгоритмы поиска find, find_if, find_end, binary_search
- •Алгоритмы fill, fill_n, generate и generate_n
- •Алгоритмы equal, mismatch и lexicographical_compare
- •Математические алгоритмы
- •Алгоритмы copy, copy_backward, merge, unique и reverse
- •Алгоритмы работы с множествами
- •Алгоритмы swap, iter_swap и swap_ranges
- •Примеры реализации контейнерных классов Связанные списки
- •Реализация односвязного списка
- •Реализация двусвязного списка
- •Реализация двоичного дерева
- •Литература
- •Вопросы по курсу ооп
- •220013, Минск, п.Бровки, 6.
Преобразование типа
Выражения, содержащиеся в функциях и используемые для вычисления некоторого значения, записываются в большинстве случаев с учетом корректности по отношению к объектам этого выражения. В то же время, если используемая операция для типов величин, участвующих в выражении, явно не определена, то компилятор пытается выполнить такое преобразование типов в два этапа.
На первом этапе выполняется попытка использовать стандартные преобразования типов. Если это невозможно, то компилятор использует преобразования, определенные пользователем.
Явные преобразования типов
Явное приведение типа выполненное в стиле языка С имеет вид:
(тип) выражение.
То есть если перед выражением указать имя типа в круглых скобках, то значение выражения будет преобразовано к данному типу. Например:
int a = (int) b;
char *s=(char *) addr;
Недостатком их является полное отсутствие контроля, что приводит к ошибкам и путанице. Существует большая разница между приведением указателя на const объект к указателю на не const объект (изменяется только атрибут объекта), и приведением указателя на объект базового класса к указателю на объект производного класса (изменяется полностью тип указателя). Было бы неплохо более точно определять цель каждого приведения. Чтобы преодолеть недостатки приведения типов выполняемых в стиле языка С, в язык С++ введены четыре оператора приведения типа: static_cast, const_cast, dynamic_cast и reinterpret_cast. Спецификация преобразования имеет следующий вид:
оператор_приведения _типа<тип> (выражение).
Для преобразования с минимальным контролем можно использовать оператор staic_cast. Он позволяет выполнять преобразования, не проверяя типы выражений во время выполнения, а основываясь на сведениях, полученных при компиляции. Оператор static_cast позволяет выполнять преобразования не только указателя на базовый класс к указателю на производный, но и наоборот.
int i,j;
double d=static_cast<double>(i)*j;
В то же время, например, преобразование int к int* (и обратно) приведет к ошибке компиляции. Рассматриваемые далее операторы приведения используются для более узкого круга задач. Для преобразований не связанных между собой типов используется reinterpret_cast.
int i;
void *adr= reinterpret_cast<void *> (i);
Необходимо отметить, что reinterpret_cast не выполнит преобразование таких типов как float, double, объектов к типу указателя.
Если же нужно выполнить преобразование выражения имеющего тип с атрибутами char и volatile к типу без них, то можно использовать оператор const_cast:
const char *s;
char *ss=const_cast<char*> (s);
Следующий оператор dynamic_cast предназначен для приведения указателя или ссылки на объект базового класса к указателям или ссылкам на объекты производных классов. При этом можно определить, была ли попытка приведения типа успешной. В результате неудачной попытки преобразования возвращается либо нулевой указатель, либо возникает исключение (при преобразовании ссылок).
class A { . . . };
class B : public A { . . .};
class C : public B { . . .};
main()
{ A *pa;
B *pb;
C *pc;
. . .
pa= dynamic_cast<A*>(pc);
pb= dynamic_cast<B*>(pc);
}
dynamic_cast Operator
The expression dynamic_cast<type-id>( expression ) converts the operand expression to an object of type type-id. The type-id must be a pointer or a reference to a previously defined class type or a “pointer to void”. The type of expression must be a pointer if type-id is a pointer, or an l-value if type-id is a reference.
Syntax
dynamic_cast < type-id > ( expression )
If type-id is a
pointer to an unambiguous accessible direct or indirect base class of expression, a pointer to the unique subobject of type type-id is the result. For example:
class B { ... };
class C : public B { ... };
class D : public C { ... };
void f(D* pd)
{
C* pc = dynamic_cast<C*>(pd); // ok: C is a direct base class
// pc points to C subobject of pd
B* pb = dynamic_cast<B*>(pd); // ok: B is an indirect base class
// pb points to B subobject of pd
...
}
This type of conversion is called an “upcast” because it moves a pointer up a class hierarchy, from a derived class to a class it is derived from. An upcast is an implicit conversion.
If type-id is void*, a run-time check is made to determine the actual type of expression. The result is a pointer to the complete object pointed to by expression. For example:
class A { ... };
class B { ... };
void f()
{
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa);
// pv now points to an object of type A
...
pv = dynamic_cast<void*>(pb);
// pv now points to an object of type B
}
If type-id is not void*, a run-time check is made to see if the object pointed to by expression can be converted to the type pointed to by type-id.
If the type of expression is a base class of the type of type-id, a run-time check is made to see if expression actually points to a complete object of the type of type-id. If this is true, the result is a pointer to a complete object of the type of type-id. For example:
class B { ... };
class D : public B { ... };
void f()
{
B* pb = new D; // unclear but ok
B* pb2 = new B;
D* pd = dynamic_cast<D*>(pb); // ok: pb actually points to a D
...
D* pd2 = dynamic_cast<D*>(pb2); //error: pb2 points to a B, not a D
// pd2 == NULL
...
}
This type of conversion is called a “downcast” because it moves a pointer down a class hierarchy, from a given class to a class derived from it.
In cases of multiple inheritance, possibilities for ambiguity are introduced. Consider the class hierarchy shown in Figure 4.5:
Figure 4.5 Class Hierarchy Showing Multiple Inheritance
A pointer to an object of type D can be safely cast to B or C. However, if D is cast to point to an A object, which instance of A would result? This would result in an ambiguous casting error. To get around this problem, you can perform two unambiguous casts. For example:
void f()
{
D* pd = new D;
A* pa = dynamic_cast<A*>(pd); // error: ambiguous
B* pb = dynamic_cast<B*>(pd); // first cast to B
A* pa2 = dynamic_cast<A*>(pb); // ok: unambiguous
}
Further ambiguities can be introduced when you use virtual base classes. Consider the class hierarchy shown in Figure 4.6:
Figure 4.6 Class Hierarchy Showing Virtual Base Classes
In this hierarchy, A is a virtual base class. See Virtual Base Classes for the definition of a virtual base class. Given an instance of class E and a pointer to the A subobject, a dynamic_cast to a pointer to B will fail due to ambiguity. You must first cast back to the complete E object, then work your way back up the hierarchy, in an unambiguous manner, to reach the correct B object.
Consider the class hierarchy shown in Figure 4.7:
Figure 4.7 Class Hierarchy Showing Duplicate Base Classes
Given an object of type E and a pointer to the D subobject, to navigate from the D subobject to the left-most A subobject, three conversions can be made. You can perform a dynamic_cast conversion from the D pointer to an E pointer, then a conversion (either dynamic_cast or an implicit conversion) from E to B, and finally an implicit conversion from B to A. For example:
void f(D* pd)
{
E* pe = dynamic_cast<E*>(pd);
B* pb = pe; // upcast, implicit conversion
A* pa = pb; // upcast, implicit conversion
}
The dynamic_cast operator can also be used to perform a “cross cast.” Using the same class hierarchy, it is possible to cast a pointer, for example, from the B subobject to the D subobject, as long as the complete object is of type E.
Considering cross casts, it is actually possible to do the conversion from a pointer to D to a pointer to the left-most A subobject in just two steps. You can perform a cross cast from D to B, then an implicit conversion from B to A. For example:
void f(D* pd)
{
B* pb = dynamic_cast<B*>(pd); // cross cast
A* pa = pb; // upcast, implicit conversion
}
A null pointer value is converted to the null pointer value of the destination type by dynamic_cast.
When you use dynamic_cast < type-id > ( expression ), if expression cannot be safely converted to type type-id, the run-time check causes the cast to fail. For example:
class A { ... };
class B { ... };
void f()
{
A* pa = new A;
B* pb = dynamic_cast<B*>(pa); // fails, not safe;
// B not derived from A
...
}
The value of a failed cast to pointer type is the null pointer. A failed cast to reference type throws a bad_cast exception.
