- •5. Препроцессор. Директивы препроцессора.
- •7.Работа с файлами. Текстовый и двоичный режим.
- •10.Перечислимый тип. Структуры. Объединения.
- •13.Спецификаторы класса памяти.
- •14.Пространства имён.
- •16.Понятие класса.
- •17.Функции-члены класса. Указатель this.
- •18.Конструкторы. Деструкторы.
- •19.Преобразования объектов класса.
- •20.Доступ к членам класса.
- •21.Статические члены класса.
- •23.Совместное использование.
- •27.Производные классы.
- •29.Указатели на члены класса.
- •30.Множественное наследование.
- •31.Структура dll-библиотеки.
- •32.Статическое и динамическое подключение dll-библиотек.
- •34.Регистры процессора.
- •36.Арифметические команды в языке ассемблера.
- •37.Команды сравнения и перехода в языке ассемблера.
- •38.Команды работы с битами в языке ассемблера.
- •39.Процедуры в языке ассемблера. Передача параметров в процедуру.
- •40.Процедуры в языке ассемблера. Возврат результата. Локальные данные.
19.Преобразования объектов класса.
Преобразования (изменения типа) объектов класса выполняются конструкторами и преобразующими функциями.
Такие преобразования, называемые пользовательскими, часто неявно применяются в дополнение к стандартным преобразованиям. Например, функция, ожидающая параметр типа Х, может вызываться не только с параметром типа Х, но и с параметром типа Т, если существует преобразование из Т в Х. Кроме того, пользовательские преобразования применяются для приведения инициализаторов, параметров функций, возвращаемых функциями значений, операндов в выражениях, управляющих выражений, операторах цикла ивыбора и для явного приведения типов.
Пользовательские преобразования применяются только там, где они однозначны.
3.1. Преобразование посредством конструктора
Конструктор с одним параметром задаёт преобразование типа своего параметра к типу своего класса.
Конструктор с одним параметром не обязательно вызывается явно.
class X { private: int x; public: X(int n); ... };
X::X(int n) { x = n; }
|
|
X a = 1; |
// Эквивалентно X a = X(1) |
Однако неявное преобразование может быть нежелательно в некоторых случаях.
class Str { private: char *str; public: Str(int n) { str = new char [n]; *str = 0; } Str(const char *p) { str = new char [strlen(p) + 1]; strcpy(str, p); } ~Str() { if (str) delete [] str; } };
|
|
|
Str s = 'a'; |
// Создание строки из int('a') элементов |
|
Неявное преобразование можно подавить, объявив конструктор с модификатором explicit. Такой конструктор будет вызваться только явно.
class Str { private: char *str; public: explicit Str(int n) { str = new char [n]; *str = 0; } Str(const char *p) { str = new char [strlen(p) + 1]; strcpy(str, p); } ~Str() { if (str) delete [] str; } }; |
|
Str s1 = 'a';
Str s2(10); |
// Ошибка – нет неявного преобразования char в Str // Правильно – создаётся строка из 10 символов |
3.2. Преобразующие функции
Функция-член класса Х, имя которой имеет вид operator <имя типа>, определяет преобразование из Х в тип, заданный именем типа. Такие функции называются преобразующими функциями или функциями приведения. Для такой функции не могут быть заданы ни параметры, ни возвращаемый тип.
class X { private: int x; public: X(int n); operator int(); ... }; X::X(int n) { x = n; }
X::operator int() { return x; }
int a; X b(0); |
|
a = (int)b; |
// Явный вызов преобразующей функции |
a = b; |
// Неявный вызов преобразующей функции |
3.3. Разрешение неоднозначности
Присваивание значения типа V объекту класса X допустимо в том случае, если имеется оператор присваивания X::operator= (Z) такой, что V является Z или существует единственное преобразование V в Z. Инициализация рассматривается аналогично.
В некоторых случаях значение требуемого типа может быть создано при помощи повторного использования конструкторов или операторов преобразования. Это должно осуществляться при помощи явных преобразований – допустим только один уровень неявных преобразований, определяемых пользователем. В некоторых случаях значение требуемого типа может быть создано более чем одним способом – такое недопустимо.
class X { ... X(int); X(char*); ... }; class Y { ... Y(int); ... }; class Z { ... Z(X); ... }; |
|
X f(X); Y f(Y); Z g(Z); |
|
void main() { f(1); f(X(1)); f(Y(1)); g("Mask"); g(X("Mask")); g(Z("Mask")); }
|
// Неоднозначность - f(X(1)) или f(Y(1))? // Правильно // Правильно // Ошибка – требуется применение двух преобр., определённых пользователем // Правильно – g(Z(X("Mask"))) // Правильно – g(Z(X("Mask"))) |
class XX { XX(int); }; void h(double); void h(XX);
void main() |
|
{ h(1); } |
// h(double(1)) или h(XX(1))? Вызов h(1) означает h(double(1)), // потому что в этом случае используются только стандартные преобразования. |
Правила преобразования не являются ни самыми простыми для реализации из всех возможных, ни самыми легкими для документирования, ни настолько общими, как можно себе представить. Однако они довольно безопасны, и их применение не приводит к неприятным сюрпризам. Гораздо легче вручную разрешить неоднозначности, чем найти ошибку, вызванную преобразованием, о котором и не подозревали.
