- •5. Перегрузка, копирование и преобразование
- •5.1. Перегрузка операторов
- •5.1.1. Определение дополнительных функций-операторов
- •5.1.2. Общие принципы перегрузки операторов
- •5.1.3. Перегрузка оператора присваивания
- •5.2. Применение конструкторов копирования и преобразования
- •5.2.1. Конструкторы копирования
- •5.2.2. Конструкторы преобразования
- •5.2.3. Инициализация массивов
5.2.2. Конструкторы преобразования
Конструктор преобразования класса – это конструктор с единственным параметром, тип которого отличается от типа класса. Такой конструктор обычно инициализирует новый объект, используя данные существующих переменных или объектов другого типа. Например, в класс CCurrency можно добавить конструктор преобразования, который позволит инициализировать объект, используя сумму долларов и центов, выраженную одним действительным числом в формате с плавающей запятой.
class CCurrency
{
// ...
public:
// ...
CCurrency (double DolAndCen)
{
Dollars = long (DolAndCen);
Cents = int ({DolAndCen - Dollars) * 100.0 + 0.5);
}
// ...
};
Заметьте: конструктор округляет число центов, сохраняемое в параметре DolAndCen, до ближайшего целого числа. Кроме того, обратите внимание: конструктор выполняет явное преобразование типов данных, используя разрешенный в языке C++ альтернативный синтаксис, отличающийся от традиционной записи операции приведения типов. Например, в конструкторе используется выражение long (DolAndCen), а не традиционная запись (long) DolAndCen. Безусловно, в C++ допустимо использовать приведение типов, хотя новый синтаксис может облегчить чтение некоторых выражений.
Такой конструктор преобразования позволяет инициализировать объекты класса CCurrency следующим образом.
CCcurrency Bucksl(29.63);
CCurrency Bucks2 = 43.247; // будет округлено до 43.25
CCurrency Bucks3 (2.0e9); // почти максимально допустимое
// число долларов CCurrency *Bucks = new CCurrency (534.85);
Приведенное ранее определение класса CCurrency содержит следующий конструктор:
CCurrency (long Dol, int Cen)
{
SetAmount (Dol, Cen);
}
Этот конструктор можно переделать в конструктор преобразования, добавив для второго параметра стандартное значение.
CCurrency (long Dol, int Cen = 0)
{
SetAmount (Dol, Cen);
}
Так как полученный конструктор может теперь принимать один параметр, его можно использовать для инициализации объектов класса ccurrency, задавая только количество долларов.
// установка значений: Dollars = 25 и Cents = О
CCurrency Dough = 25L;
CCurrency *PDough = new CCurrency (25L);
Символ L добавляется в конец каждой целочисленной константы, задавая ей тип long. Если данный символ не указан, константа будет рассматриваться как константа типа int. В этой ситуации компилятор не определит, преобразовывать ли тип int в double, чтобы вызвать конструктор для double, либо преобразовывать в long с тем, чтобы вызвать конструктор для long. Такая ситуация называется неоднозначным вызовом перегруженной функции и порождает ошибку компиляции, Преобразования типа int в double и long являются стандартными преобразованиями. Переменные типов int и long рассматриваются как различные типы, требующие преобразования.
Рассмотрим пример с добавлением конструктора преобразования в CMessage:
class CMessage
{
// ...
public: // .. .
CMessage (const char *String)
{
Buffer = new char [strlen (String)+1];
scrcpy (Buffer, String);
}
// ...
};
Теперь объект можно инициализировать одной строкой.
CMessage Note = "do it now";
CMessage *PNote = new CMessage ("remember!");
Неявное использование конструкторов преобразования
Помимо использования конструктора преобразования при явном создании объекта класса и инициализации его единственным значением другого типа, компилятор также вызывает соответствующий конструктор преобразования для преобразования переменной-члена какого-либо другого типа в объект класса. Другими словами, конструктор преобразования указывает компилятору, как преобразовывать объекты или переменные различных типов в объект данного класса. Например, два конструктора преобразования класса CCurrency позволят присвоить значение типа double или long существующему объекту этого класса.
CCurrency Bucks;
Bucks = 29.95;
Bucks = 35L;
И в первом, и во втором присваивании компилятор в первую очередь преобразовывает константу в объект класса CCurrency, используя соответствующий конструктор преобразования, а затем присваивает его объекту Bucks класса CCurrency. Символ L во втором операторе присваивания необходим для исключения неоднозначного вызова конструктора преобразования.
В следующем примере предположим, что функция имеет параметр типа CCurrency.
void Insert (CCurrency Dinero);
Так как для класса CCurrency определены оба конструктора преобразования, данной функции можно передать значение типа double или long, как объект класса CCurrency. Компилятор вызовет соответствующий конструктор для преобразования аргумента в объект класса CCurrency.
Важное преимущество, которое дает создание конструкторов преобразования, состоит в применении перегруженных операторов, определенных для класса, и в том, что пропадает необходимость в написании отдельной функции-оператора для каждой ожидаемой комбинации операндов. Например, CCurrency в настоящий момент имеет три функции operator+:
class CCurrency
{
// ...
public:
// ...
CCurrency operator+ (const CCurrency sCurr)
{
return CCurrency (Dollars + Curr.Dollars, Cents + Curr.Cents);
}
CCurrency operator+ (long Dol)
{
return CCurrency (Dollars + Dol, Cents);
}
friend CCurrency operator+ (long Dol, const CCurrency &Curr);
// . . .
};
Теперь, благодаря конструктору, преобразующему значения типа long в объекты класса CCurrency, можно исключить две функции operator+ и переписать функцию, которая не является членом класса.
class CCurrency
{
// ...
public:
// ...
friend CCurrency operator+ (const CCurrency &Curr1,
const CCurrency &Curr2);
// ...
};
CCurrency operator+ (const CCurrency &Curr1,
const CCurrency &Curr2)
{
return CCurrency (Currl.Dollars + Curr2.Dollars,
Currl.Cents + Curr2.Cents);
}
Эта единственная функция-оператор может выполнять следующие операции сложения.
CCurrency Bucks1 (39, 95);
CCurrency Bucks2 (149, 85);
CCurrency Bucks3;
Bucks3 = Bucks1 + Bucks2;
Bucks3 = Bucks1 + 10L;
Bucks3 = 15L + Bucks1;
Во втором и третьем выражениях компилятор сначала преобразовывает константу типа long в объект CCurrency, используя соответствующий конструктор преобразования, а затем вызывает дружественную функцию-оператор для сложения двух объектов.
Кроме того, так как теперь CCurrency имеет конструктор преобразования с параметром типа double, единственная функция-оператор может обрабатывать значения с плавающей запятой.
CCurrency Bucks1 (39, 95);
CCurrency Bucks2 (149, 85);
CCurrency Bucks3;
Bucks3 = Bucksl + 29.51;
Bucks3 = 87.64 + Bucks1;
В листингах 5.1 и 5.2 приведены окончательные версии классов CCurrency и CMessage, в том числе новые конструкторы копирования и преобразования.
Листинг 5.1
// CCurr.h: файл заголовков класса CCurrency
#include <string.h>
#include <iostream.h>
class CCurrency
{
private:
long Dollars;
int Cents;
public:
CCurrency () // конструктор по умолчанию
{
Dollars = Cents = 0;
}
CCurrency (long Dol, int Cen =0) // конструктор преобразования
{
SetAmount (Dol, Cen) ;
}
CCurrency (double DolAndCen) // конструктор преобразования
{
Dollars = long (DolAndCen);
Cents = int ((DolAndCen - Dollars) * 100.0 + 0.5);
}
void GetAmount (long *PDol, int *PCen)
{
*PDol = Dollars;
*PCen = Cents;
}
void PrintAmount ()
{
cout.fill ('0');
cout.width (1);
cout << '$' << Dollars << '.';
cout.width (2);
cout << Cents << '\n';
}
void SetAmount (long Dol, int Cen)
{
Dollars - Dol + Cen / 100;
Cents = Cen % 100;
}
friend CCurrency operator+ (const CCurrency &Curr1,
const CCurrency &Curr2);
};
CCurrency operator+ (const CCurrency &Curr1, const CCurrency &Curr2)
{
return CCurrency (Corrl.Dollars + Curr2.Dollars,
Currl.Cents + Curr2.Cents);
}
Листинг 5.2
// CMess.h: файл заголовков класса CMessage
#include <string.h>
#include <iostream.h>
class CMessage
{
private:
char *Buffer;
public:
CMessage () // конструктор по умолчанию
{
Buffer = new char ('\0');
}
CMessage (const CMessage &Message) // конструктор копирования
{
Buffer = new char [strlen (Message.Buffer) + 1];
strcpy (Buffer, Message.Buffer);
}
CMessage (const char *String) // конструктор преобразования
{
Buffer = new char [strlen (String) + 1];
strcpy (Buffer, String);
}
~CMessage ()
{
delete [] Buffer;
}
void Display ()
{
cout << Buffer << '\n';
}
void Set (char *String)
{
delete [] Buffer;
Buffer = new char (strlen (String) + 1];
strcpy (Buffer, String);
}
CMessage & operator= (const CMessage &Message)
{
if (&Message = this)
return *this;
delete [] Buffer;
Buffer = new char [strlen (Message.Buffer) + 1];
strcpy (Buffer, Message.Buffer);
return *this;
}
};
Примечание
Конструкторы преобразования класса указывают компилятору, как преобразовать какой-либо тип данных в объект класса. Кроме того, можно написать функцию преобразования, предоставляющую компилятору способ преобразования объекта класса в данные какого-либо другого типа. Функция преобразования определяется как функция-член и подобна функции-оператору, используемой для перегрузки стандартных операторов. Однако функция преобразования менее универсальна, чем аналогичный конструктор, и должна быть тщательно продумана во избежание неопределенности.