то в классе "zzz" необходимо определить конструктор
class zzz
{
int par_zzz;
----------------- входной тип (класс)
zzz(xxx p); или
zzz(xxx& p);
L-------------------- выходной тип (класс)
};
void zzz::zzz(xxx &p)
{
par_zzz = ... p.par_xxx ...;
элемент объекта----- L-------элемент объекта
выходного класса входного класса
}
class xxx
{
friend class zzz;
int par_xxx;
};
со следующими свойствами:
- объект класса "zzz", который является выходным при преобразовании типов доступен как в любом конструкторе через ссылку на текущий объект this;
- элементам выходного объекта (например, par_zzz) должны быть присвоены значения с явным или неявным использованием ссылки this
this->par_zzz = ...
(*this).par_zzz = ...
par_zzz = ...
- объект или переменная того класса или базового типа, которые являются входными в преобразовании типов, доступны через соответствующий формальный параметр, который может быть как значением (копией объекта или переменной), так и неявной ссылкой.
Значение переменной или элементов входного объекта могут использоваться как аргументы при преобразовании типов;
- для доступа из функции класса "zzz" к приватной части объекта класса "xxx" класс "zzz" должен быть объявлен дружественным
в определении класса "xxx".
В качестве примера рассмотрим обратное преобразование базового типа long к типу dat - количество дней от начала летоисчисления преобразуется к дате. Здесь же рассмотрим другой класс
- man, в котором одним из элементов приватной части является дата.
static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
class dat
{
int day,month,year;
public:
dat(long); // Преобразование long в dat
dat(man&); // Преобразование man в dat
dat(); // Конструкторы
dat(int,int,int); //
dat(char *); //
~dat(); // Деструктор
};
class man
{
friend class dat; // Класс dat дружественен
... // классу man
int d,m,y; // Элемент "дата" в объекте
... // класса man
public:
man(dat&); // Преобразование dat в man
man(); // Конструктор
~man(); // Деструктор
};
//------ Преобразование man в dat ----------------------------//Используется ссылка на текущий объект this для выходного класса,
//формальный параметр - неявная ссылка - для входного класса
//-------------------------------------------------------void dat::dat(man& p)
{
day = p.d;
month = p.m;
year = p.y;
}
//------ Преобразование long в dat ---------------------------//Используется ссылка на текущий объект this для выходного класса
//формальный параметр типа long передается по значению
//-------------------------------------------------------
void dat::dat(long p)
{
year = p / 365.25; // Число лет с учетом високосных
p = p - (year-1)*365L - year/4; // Остаток дней в текущем году
year++; // Начальный год - 0001
for (month=1; p > 0; month++) // Вычитание дней по месяцам
{
p -= days[month];
if (month == 2 && year % 4 == 0) p--;
}
month--; // Восстановление последнего
p += days[month]; // месяца
if (month == 2 && year % 4 == 0) p++;
day = p + 1;
}
void main()
{
dat a("12-05-1990"); // Дата, заданная текстовой строкой
dat b; // Текущая дата
int c;
long d;
// Явное преобразование к long
printf("С 12-05-1990 прошло %4ld дней\n", (long)b-(long)a);
// Явное преобразование к int
printf("В этом году прошло %3d дней\n", (int)b);
c = b; // Неявное преобразование при присваивании
d = b - a; // Операция dat-dat
printf("С 12-05-1990 прошло %4ld дней\n",d);
printf("В этом году прошло %3d дней\n",c);
}
5.3 Переопределение операций new и delete
----------------------------------------
Операции создания и уничтожения объектов в динамической памяти могут быть переопределены следующим образом:
void *operator new(size_t size);
void operator delete (void *);
где void * - ссылка на область памяти, выделяемую под объект,
size - размер объекта в байтах.
Переопределение этих операций позволяет написать собственное
распределение памяти для объектов класса.
5.4 Переопределение операций [], (), ->
--------------------------------------
Переопределение () :
-------------------class one
{
public:
typeout operator()(type1,type2);
};
Вызов:
type1 a; // Вызов оператора совпадает с
type2 b; // синтаксисом вызова функции
one obj; // с именем данного объекта
... obj(a,b) ... эквивалентно obj.operator()(a,b)
Переопределение -> :
------------------class two
{
public: type Y;
};
class one
{
two operator->(); // Операция должна возвращать объект
или two* operator->(); // или ссылку на объект класса two,
}; // в котором определен элемент Y
Вызов:
one obj;
... obj->Y .. эквивалентно (obj.operator->()) ->Y
Переопределение [] : используется для моделирования виртуальных
-------------------- массивов элементов определенного типа.
class text_page
{
char **page; // Массив ссылок на строки
public:
int operator[](char*); // Ассоциативный поиск индекса
// по строке
char* operator[](int); // Выделение строки по индексу
};
5.5 Переопределение операции копирования объектов
------------------------------------------------
Kaк известно, определение объекта класса в виде
<имя класса> <объект 2> = <объект 1>
приводит к тому, что объект инициализируется путем побайтного копирования содержимого другого объекта без вызова конструктура. K таким же объектам относятся объекты - формальные параметры функций, которые инициализируются копиями фактических параметров.
Eсли функция возвращает объект, то оператор return также выполняет копирование объекта - операнда в объект назначения.
Taкое копирование не корректно в том случае, если объекты содержат ссылки на другие объекты или переменные в динамической памяти. В этом случае можно воспъльзоваться специальным конструктором копирования, параметром котрого является неявная ссылка на объект - источник, а this указывает на объект приемник. Будучи определенным, он вызывается во всех вышеперечисленных случаях копирования объектов один в другой.
Пример корректного конструктора копирования для класса строк имеет вид:
class string
{
char *s; // Ссылка на строку
int sz; // Длина строки
public: string(string&);// Конструктор копирования
}; // создает копию строки в динамической
// памяти для объекта - приемника
string::string(string& right)
{
s = new char[right->sz];
strcpy(s,right->s);
}
Лекция 6. Производные классы
---------------------------
6.1 Вложенные классы
-------------------
Понятие производного класса вводит в систему классов принцип
иерархии. Действительно, если определен класс объектов с достаточно общими свойствами то объект данного класса желательно включать в качестве одного из элементов в объекты других классов. Существует два способа такого включения, каждый из них имеет
собственные цели и особенности.
Первый случай представляет собой обычный способ построения
инрархической структуры данных, когда объект старого класса является одним из элементов данных "приватной" части нового класса.
Он имеет собственное имя (именован), по которому к нему можно обращаться как к объекту. В элементах-функциях нового класса можно
использовать элементы-функции и операции для объекта старого
класса. Рассмотрим в качестве примера класс man - информация о
человеке, включающая в себя даты рождения и поступления на работу.
class man
{
char name[20]; // Другие элементы класса
char *address;
dat dat1; // Дата рождения
dat dat2; // Дата поступления на работу
public:
void newadr(); // Элемент-функция
man(char*); // Конструктор
}
//----- Функция "Изменить адрес проживания" ----------------void man::newadr()
{
int n;
char s[80]; // Строка нового адреса
if (address != NULL)
delete address; // Освободить память
printf("Введите новый адрес:");
gets(s);
address = new char[strlen(s)+1];// Занять новую память
strcpy(address,s); // Заполнить поле адреса
}
Из данного примера видно, что именованные объекты старого класса можно использовать в элементах-функциях нового класса как обычные элементы, вызывать определенные для них элементы-функции старого класса и выполнять переопределенные для них операции. Заметим, что при этом элементы-функции нового класса не имеют доступа к приватной части объектов базового класса, то есть "содержимое" вложенных объектов для них закрыто.
Но здесь возникает вопрос, как инициализируются и уничтожаются объекты старого класса при создании или уничтожении объекта нового класса, то есть как взаимодействуют их конструкторы и деструкторы.
В случае, если конструктор объекта нового класса задан обычным образом, то перед вызовом этого конструктора будут вызваны конструкторы без параметров для входящих в него объектов старого класса. И наоборот, после вызова деструктора для объекта нового класса будут вызваны деструкторы вложенных объектов старого класса.
Однако при конструировании вложенных объектов им желательно передавать параметры. Поэтому при описании конструктора объекта нового класса можно в заголовке в явном виде указать тот вид конструктора объекта старого класса, который требуется. Кроме того, его параметры могут зависеть от параметров вызова конструктора нового класса:
class man
{
char name[20]; // Другие элементы класса
dat dat1; // Дата рождения
dat dat2; // Дата поступления на работу
public:
man(char *,char *,char *); // Конструкторы
man(char *);
}
//----- Конструктор класса man с неявным вызовом конструкторов
// для dat1 и dat2 без параметров
//----------------------------------------------------- man::man(char *p)
{
}
//----- Конструктор класса man с явным вызовом конструкторов
// для dat1 и dat2 с параметрами
//--------------------------------------------------------- man::man(char *p,char *p1, char *p2) : dat1(p1), dat2(p2)
{ ¦ ¦ ¦
// --- Тело конструктора --- ¦ ¦ ¦
} ¦ ¦ ¦
Вызов конструктора для ------------------ ¦ ¦
вложенного объекта dat1 ¦ ¦
В качестве параметра передается ------------- ¦
строка - второй параметр вызова ¦
конструктора для класса man Вызов конструктора для