- •Глава 12
- •Конструкторы и деструкторы. Список инициализации элементов
- •Конструкторы по умолчанию и конструкторы копирования
- •Указатель this
- •Встраиваемые (inline-) функции
- •Статические члены класса
- •Использование указателей на функции-члены класса
- •Массивы объектов класса
- •Дружественные функции и друзья класса
- •Глава 13
- •Множественное наследование
- •Виртуальные базовые классы
- •Глава 14
- •Перегрузка функций
- •Перегрузка конструкторов
- •Создание и использование конструкторов копирования
- •Определение адреса перегруженной функции
Перегрузка конструкторов
Чаще всего перегрузка применяется при создании перегруженных конструкторов (перегружать деструктор нельзя!). Целью этой перегрузки является желание предоставить пользователю как можно больше вариантов создания представителей класса. На самом деле мы уже неоднократно встречались с перегрузкой конструкторов, хотя и не говорили об этом. Если класс предоставляет конструктор с параметрами и конструктор по умолчанию, мы уже имеем дело с перегрузкой конструкторов. Как мы уже знаем, конструктор по умолчанию необходим при выделении динамической памяти массиву объектов (ибо динамический массив объектов не может быть инициализирован).
Другой случай, когда возникает необходимость в перегрузке конструкторов, - желание обеспечить пользователя конструкторами преобразования типа. Рассмотрим функцию, которой передается аргумент, являющийся ссылкой на объект класса AnyClass:
void func(AnyClass& rOb);
Любая попытка вызвать эту функцию с аргументом другого типа приведет к ошибке компиляции, потому что компилятор не знает, как осуществить нужное преобразование типа. Чтобы указать компилятору, как осуществить преобразование какого-то типа Т в тип данных AnyClass, нужно просто задать в соответствующем классе конструктор, принимающий единственный параметр типа т. При этом будет использовано уже отмечавшееся нами ранее свойство компилятора трактовать конструкцию
AnyClass ob(t);
как
AnyClass ob = AnyClass(t);
если в классе определен конструктор
AnyClass(T t) ;
В рассматриваемом случае это означает, что вызов функции с аргументом t типа Т, то есть
func(t);
трактуется как
func(AnyClass(t));
При этом осуществляется неявное преобразование типа данных. Неявное потому, что исходный код не вызывает явно конструктор для преобразования. С этой целью компилятор каждый раз будет создавать временный объект класса AnyClass, которому и будет передавать параметр, переданный в функцию.
Следующий пример это демонстрирует.
class AnyClass
{
int x;
public:
//Конструктор по умолчанию
AnyClass () ;
//Конструктор преобразования типа int
AnyClass(int);
//Конструктор преобразования типа long
AnyClass(long);
//Конструктор преобразования типа double
AnyClass(double);
};
AnyClass::AnyClass() {x = 0;}
AnyClass::AnyClass(int _x) {x = _x;}
AnyClass::AnyClass(long _x) {x = int(_x);}
AnyClass::AnyClass(double _x) {x = _x;}
void func(AnyClass& rOb)
{
//Тело функции
}
main()
{
func(10); //Преобразование int в AnyClass
func(10L); //Преобразование long в AnyClass
//Преобразование double в AnyClass
func(10.0);
AnyClass ob;
func(ob); //Преобразование не нужно
return 0;
}
Этот же способ преобразования типа данных может быть использован и для преобразования из одного класса в другой. Приведенный ниже пример демонстрирует конструктор преобразования из одного класса в другой.
class AnyClass
{
int x;
public:
AnyClass(){x =0;}
int GetX(){return x;}
};
class OtherClass
{
int z ;
public:
//Конструктор по умолчанию
OtherClass(){z =0;}
//Конструктор преобразования
OtherClass(AnyClass& cl) ;
};
//Определение конструктора преобразования
OtherClass::OtherClass(AnyClass& cl)
{
z = cl.GetX() ;
}
void func(OtherClass& Ob)
{
//Тело функции
}
main ()
{
AnyClass ob;
//Преобразование AnyClass в Othlass
func(ob);
return 0;
}
Несмотря на то, что данный пример работает, преобразование типа, которое он осуществляет, не имеет большого смысла. Ответственность за осуществляемое преобразование типа, как всегда, лежит на программисте.
Определенные пользователем преобразования типа применяются компилятором в следующих ситуациях:
• при инициализации объектов;
• при вызове функций;
• при возврате функцией значений.
Разумеется, их можно применять и для явного преобразования типа. Например:
main ()
{
AnyClass ob;
//Явное преобразование типа
OtherClass newOb = (OtherClass) ob;
}
Рассмотрим теперь пример, который демонстрирует пользу перегрузки другого конструктора - конструктора копирования. Пусть дан класс, который предназначен для использования в программе прямоугольных областей:
class Rect
{
public:
Rect() ;
Rect(int, int);
Rect(int, int, int, int);
Rect(const Rect&);
Rect(const Rect&, int =0, int =0);
private:
int x, y, w, h;
};
Rect:: Rect()
{
x=y = w = h = 0;
}
Rect:: Rect(int _x, int _y)
{
x = _x;
y = _y;
w = h = 100;
}
Rect:: Rect(int _x, int _y, int _w, int _h)
{
x = _x;
у = _y;
w = _w;
h = _h;
}
Rect:: Rect(const Rect& rc)
{
x = rc.x;
у = rc.у;
w = rc.w;
h = rc.h;
}
Rect:: Rect(const Rect& rc, int _x, int _y)
{
x = _x;
у = _у;
w = rc.w;
h = rc.h;
}
main()
{
Rect rc(5,10,10,200);
Rect newrc(rc, 15, 40);
return 0;
}
Данный класс содержит несколько конструкторов, которые должны предоставить пользователю класса достаточную гибкость в создании нужных ему прямоугольных областей. Среди прочих данный класс содержит перегруженный конструктор копирования
Rect(const Rect&, int =0, int =0);
Приведенный код перегруженного конструктора копирования копирует некоторые из данных-членов объекта, указанного ссылкой, но какие-то данные-члены нового объекта инициализирует по-другому. Данный конструктор можно квалифицировать как конструктор копирования благодаря тому, что первый его параметр представляет собой (константную) ссылку на объект класса, а остальные - это аргументы по умолчанию. Таким образом, вполне возможно создавать конструкторы копирования, содержащие дополнительные параметры. Это предоставляет чрезвычайную гибкость при создании разнообразных конструкторов копирования.
