Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
68.18 Кб
Скачать

2.9 Присваивание объектов

Один объект можно присвоить другому, если оба объекта имеют одинаковый тип. По умолчанию, когда объект A присваивается объекту B, осуществляется побитовое копирование всех элементов-данных A в соответствующие элементы-данные B. Если объекты имеют разные типы, то компилятор выдаст сообщение об ошибке. Одинаковыми должны быть имена типов, а не их физическое содержание.

Особенно внимательным нужно быть при присваивании объектов, в описании типа которых содержатся указатели. Чтобы избежать недоразумений, используют перегруженный оператор присваивания, в котором явным образом описывается (т.е. контролируется) процесс присваивания элементов-данных одного объекта соответствующим элементам-данным другого объекта.

class Pair

{

int a, *b;

public:

Pair operator = (Pair p)

{

a = p.a;

*b = *(p.b);

return *this;

}

...

};

пример с трехмерным вектором;

class _3d

{

double x, y, z;

public:

_3d ();

_3d (double initX, double initY, double initZ);

double mod()

{return sqrt (x*x + y*y +z*z);}

double projection(_3d r)

{return (x*r.x + y*r.y + z*r.z) / mod();}

_3d operator + (_3d b);

_3d operator = (_3d b);

};

_3d _3d::operator = (_3d b)

{

x = b.x;

y = b.y;

z = b.z;

return *this;

}

Каждая функция представлена в единственном экземпляре и в момент вызова получает один скрытый параметр - указатель на экземпляр переменной, для которого она вызвана. Этот указатель имеет имя this. Если используемая переменная не описана внутри функции, не является глобальной, то считается, что она является членом структуры и принадлежит рабочей переменной this. Поэтому при реализации функций операторов мы опускали путь доступа к полям структуры, для которой этот оператор будет вызываться.

В качестве аргументов функций-операторов выступают операнды, а возвращаемое значение - результат применения оператора. В частности, для оператора « =» это необходимо, чтобы обеспечить возможность последовательного присваивания (a=b=c). Бинарные операторы имеют один аргумент - второй передается через указатель this. Унарные, соответственно, один - this.

2.10 Передача в функции и возвращение объекта

Объекты можно передавать в функции в качестве аргументов точно так же, как передаются данные других типов. В C++ методом передачи параметров по умолчанию является передача объектов по значению. Это означает, что внутри функции создается копия объекта-аргумента, и эта копия, а не сам объект, используется функцией. Следовательно, изменения копии объекта внутри функции не влияют на сам объект.

При передаче объекта в функцию появляется новый объект. Когда работа функции, которой был передан объект, завершается, то удаляется копия аргумента. И вот на что здесь следует обратить внимание.

Объект внутри функции - это побитовая копия передаваемого объекта, а это значит, что если объект содержит в себе, например, некоторый указатель на динамически выделенную область памяти, то при копировании создается объект, указывающий на ту же область памяти. И как только вызывается деструктор копии, где, как правило, принято высвобождать память, то высвобождается область памяти, на которую указывал объект-« оригинал», что приводит к разрушению исходного объекта.

class ClassName

{

public:

ClassName()

{

cout << 'Работа конструктора \n';

}

~ClassName()

{

cout << 'Работа деструктора \n';

}

};

void f(ClassName obj)

{

cout << 'Работа функции f \n';

}

main()

{

ClassName c1;

f(c1);

}

Эта программа выполнит следующее.

Работа конструктора

Работа функции f

Работа деструктора

Работа деструктора

Конструктор вызывается только один раз. Э то происходит при создании с1. Однако деструктор срабатывает дважды: один раз для копии obj, второй раз для самого объекта c1. Тот факт, ч то деструктор вызывается дважды, может стать потенциальным источником проблем , например, для объектов, деструктор которых высвобождает динамически выделенную область памяти.

Похожая проблема возникает и при использовании объекта в качестве возвращаемого значения.

Для того чтобы функция могла возвращать объект, нужно: во-первых, объявить функцию так, чтобы ее возвращаемое значение имело тип класса, во-вторых, возвращать объект с помощью обычного оператора return. Однако если возвращаемый объект содержит деструктор, то в этом случае возникают похожие проблемы, связанные с « неожиданным » разрушением объекта.

class ClassName

{

public:

ClassName()

{

cout << 'Работа конструктора \n';

}

~ClassName()

{

cout << 'Работа деструктора \n';

}

};

ClassName f()

{

ClassName obj;

cout << 'Работа функции f \n';

return obj;

}

main()

{

ClassName c1;

49

c1 = f();

}

Эта программа выполнит следующее.

Работа конструктора

Работа конструктора

Работа функции f

Работа деструктора

Работа деструктора

Работа деструктора

Конструктор вызывается два раза: для с1 и obj. Однако деструкторов здесь три. Понятно, что один деструктор разрушает с1, еще один - obj. «Лишний» вызов деструктора (второй по счету) вызывается для так называемого временного объекта, который является копией возвращаемого объекта.

Формируется эта копия, когда функция возвращает объект. После того, как функция возвратила свое значение, выполняется деструктор временного объекта. Понятно, что если деструктор, например, высвобождает динамически выделенную память, то разрушение временного объекта приведет к разрушению возвращаемого объекта.

Одним из способов обойти такого рода проблемы является создание особого типа конструкторов: конструкторов копирования.