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

3. Операции присваивания

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

Присваивание – это тоже операция, она является частью выражения. Значение правого операнда присваивается левому операнду.

x = 2; // переменной x присвоить значение 2

cond = x < 2; // переменной cond присвоить значение true, если x меньше 2,

// в противном случае присвоить значение false

3 = 5; // ошибка, число 3 неспособно изменять свое значение

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

int x = 0;

x = 3;

x = 4;

x = x + 1;

вначале объявляется переменная x с начальным значением 0. После этого значение x изменяется на 3, 4 и затем 5. Опять-таки, обратим внимание на последнюю строчку. При вычислении операции присваивания сначала вычисляется правый операнд, а затем левый. Когда вычисляется выражение x + 1, значение переменной x равно 4. Поэтому значение выражения x + 1 равно 5. После вычисления операции присваивания (или, проще говоря, после присваивания) значение переменной x становится равным 5.

У операции присваивания тоже есть результат. Он равен значению левого операнда. Таким образом, операция присваивания может участвовать в более сложном выражении:

z = (x = y + 3);

В приведенном примере переменным x и z присваивается значение y + 3.

Очень часто в программе приходится значение переменной увеличивать или уменьшать на единицу. Для того чтобы сделать эти действия наиболее эффективными и удобными для использования, применяются предусмотренные в Си++ специальные знаки операций: ++ (увеличить на единицу) и -- (уменьшить на единицу). Существует две формы этих операций: префиксная и постфиксная. Рассмотрим их на примерах.

int x = 0;

++x;

Значение x увеличивается на единицу и становится равным 1.

--x;

Значение x уменьшается на единицу и становится равным 0.

int y = ++x;

Значение x опять увеличивается на единицу. Результат операции ++ – новое значение x, т.е. переменной y присваивается значение 1.

int z = x++;

Здесь используется постфиксная запись операции увеличения на единицу. Значение переменной x до выполнения операции равно 1. Сама операция та же – значение x увеличивается на единицу и становится равным 2. Однако результат постфиксной операции – это значение аргумента до увеличения. Таким образом, переменной z присваивается значение 1. Аналогично, результатом постфиксной операции уменьшения на единицу является начальное значение операнда, а префиксной – его конечное значение.

Подобными мотивами оптимизации и сокращения записи руководствовались создатели языка Си (а затем и Си++), когда вводили новые знаки операций типа "выполнить операцию и присвоить". Довольно часто одна и та же переменная используется в левой и правой части операции присваивания, например:

x = x + 5;

y = y * 3;

z = z – (x + y);

В Си++ эти выражения можно записать короче:

x += 5;

y *= 3;

z -= x + y;

Т.е. запись oper= означает, что левый операнд вначале используется как левый операнд операции oper, а затем как левый операнд операции присваивания результата операции oper. Кроме краткости выражения, такая запись облегчает оптимизацию программы компилятором.

36. Множественное наследование. Двусмысленности при множественном наследовании. Виртуальные базовые классы. Инициализация виртуального базового класса. Порядок вызовов конструкторов и деструкторов в случае нескольких базовых классов

Множественное наследование (МН)

МН называется процесс создания произвольного класса из двух или более.

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

class X1 { ... };

class X2 { ... };

class X3 { ... };

class Y1: public X1, public X2, public X3 { ... };

Наличие нескольких прямых базовых классов называют множественным наследованием.

Определения базовых классов должны предшествовать их использованию в качестве базовых. При множественном наследовании никакой класс не может больше одного раза использоваться в качестве непосредственного базового. Однако класс может больше одного раза быть непрямым базовым классом:

class X { ...;

f () ;

...

};

class Y:

public X { ... };

class Z:

public X { ... };

class D:

public Y, public Z { ... };

В данном примере класс Х дважды опосредовано наследуется классом D.

Проиллюстрированное дублирование класса соответствует включению в производный объект нескольких объектов базового класса. В нашем примере существуют два объекта класса Х, и поэтому для устранения возможных неоднозначностей вне объектов класса D нужно обращаться к конкретному компоненту класса Х, используя полную квалификацию: D::Y::X::f() или D::Z::X::f(). Внутри объекта класса D обращения упрощаются Y::X::f() или Z::X::f(), но тоже содержат квалификацию.

Чтобы устранить дублирование объектов непрямого базового класса при множественном наследовании, этот базовый класс объявляют виртуальным. Для этого в списке базовых классов перед именем класса необходимо поместить ключевое слово virtual. Например, класс Х будет виртуальным базовым классом при таком описании:

class X { ...

f() ;

...

};

class Y:

virtual public X { ... };

class Z:

virtual public X { ... };

class D:

public Y, public Z { ... };

Теперь класс D будет включать только один экземпляр Х, доступ к которому равноправно имеют классы Y и Z.

Обратите внимание, что размеры производных классов при отсутствии виртуальных базовых равны сумме длин их компонентов и длин унаследованных базовых классов. <Накладные расходы> памяти здесь отсутствуют.

При множественном наследовании один и тот же базовый класс может быть включен в производный класс одновременно несколько раз, причем и как виртуальный, и как не виртуальный.

class X { ... };

class Y:

virtual public X { ... };

class Z:

virtual public X { ... };

class B:

virtual public X { ... };

class C:

virtual public X { ... };

class E:

public X { ... };

class D:

public X { ... };

class A:

public D, public B, public Y, public Z, public C, public E { ... };

В данном примере объект класса А включает три экземпляра объектов класса Х: один виртуальный, совместно используемый классами B, Y, C, Z, и два не виртуальных относящихся соответственно к классам D и E. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.

Возможны и другие комбинации виртуальных и невиртуальных базовых классов. Например:

class BB { ... };

class AA:

virtual public BB { ... };

class CC:

virtual public BB { ... };

class DD: public AA, public CC, public virtual BB { ... };

При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным компонентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифицированных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстрирует упомянутую неоднозначность и ее разрешение с помощью квалификационных имен компонентов:

class X { public: int d; ... };

class Y { public: int d; ... };

class Z: public X, public Y,

{

public:

int d;...d=X::d + Y::d;...

};

Виртуальные базовые классы всегда конструируются перед невиртуальными, вне зависимости от их расположения в иерархии наследования. Например, в приведенной иерархии у класса TeddyBear (плюшевый мишка) есть два виртуальных базовых: непосредственный – ToyAnimal (игрушечное животное) и экземпляр ZooAnimal, от которого унаследован класс Bear:

class Character { ... }; // персонаж

class BookCharacter : public Character { ... };

// литературный персонаж

class ToyAnimal { ... }; // игрушка

class TeddyBear : public BookCharacter,

public Bear, public virtual ToyAnimal

{ ... };

Эта иерархия изображена на рис. 18.5, где виртуальное наследование показано пунктирной стрелкой, а невиртуальное – сплошной.

Рис. 18.5. Иерархия виртуального наследования класса TeddyBear

Непосредственные базовые классы просматриваются в порядке их объявления при поиске среди них виртуальных. В нашем примере сначала анализируется поддерево наследования BookCharacter, затем Bear и наконец ToyAnimal. Каждое поддерево обходится в глубину, т.е. поиск начинается с корневого класса и продвигается вниз. Так, для поддерева BookCharacter сначала просматривается Character, а затем BookCharacter. Для поддерева Bear – ZooAnimal, а потом Bear.

При описанном алгоритме поиска порядок вызова конструкторов виртуальных базовых классов для TeddyBear таков: ZooAnimal, потом ToyAnimal.

После того как вызваны конструкторы виртуальных базовых классов , настает черед конструкторов невиртуальных, которые вызываются в порядке объявления: BookCharacter, затем Bear. Перед выполнением конструктора BookCharacter вызывается конструктор его базового класса Character.

Если имеется объявление:

TeddyBear Paddington;

то последовательность вызова конструкторов базовых классов будет такой:

ZooAnimal(); // виртуальный базовый класс Bear

ToyAnimal(); // непосредственный виртуальный базовый класс

Character(); // невиртуальный базовый класс BookCharacter

BookCharacter(); // непосредственный невиртуальный базовый класс

Bear(); // непосредственный невиртуальный базовый класс

TeddyBear(); // ближайший производный класс

причем за инициализацию ZooAnimal и ToyAnimal отвечает TeddyBear – ближайший производный класс объекта Paddington.

Порядок вызова копирующих конструкторов при почленной инициализации (и копирующих операторов присваивания при почленном присваивании) такой же. Гарантируется, что деструкторы вызываются в последовательности, обратной вызову конструкторов.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]