ТОП 10:

Перегрузка операции присваивания.



Операция отличается тремя особенностями:

- операция не наследуется;

- операция определена по умолчанию для каждого класса в качестве операции поразрядного копирования объекта, стоящего справа от знака операции, в объект, стоящий слева.

- операция может перегружаться только в области определения класса. Это гарантирует, что первым операндом всегда будет леводопустимое выражение.

Формат перегруженной операции присваивания:

имя_класса& operator=(имя_класса&);

Отметим две важные особенности функции operanor=.

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

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

Вопросы к защите лабораторной работы

 

1. Какие функции называются дружественными? Как в классе определяются дружественные функции?

2. Отличие в использовании дружественных функций при перегрузке операций от функций-членов.

  1. Что значит дружественные классы?
  2. Как определяются дружественные классы?
  3. Как определяются взаимодружественные классы?
  4. Что такое абстрактный тип данных?
  5. Каковы синтаксис/семантика “операции-функции”?
  6. Как можно вызвать операцию-функцию?
  7. Нужно ли перегружать операцию присваивания относительно определенного пользователем типа данных, например класса? Почему?
  8. Можно ли изменить приоритет перегруженной операции?
  9. Можно ли изменить количество операндов перегруженной операции?
  10. Можно ли изменить ассоциативность перегруженной операции?
  11. Можно ли, используя дружественную функцию, перегрузить оператор присваивания?
  12. Все ли операторы языка С++ могут быть перегружены?
  13. Какими двумя разными способами определяются перегруженные операции?
  14. Все ли операции можно перегрузить с помощью глобальной дружественной функции?

17. В каких случаях операцию можно перегрузить только глобальной функцией?

  1. В каких случаях глобальная операция-функция должна быть дружественной?
  2. Наследуются ли перегруженные операции?
  3. В чем отличие синтаксиса операции-функции унарной и бинарной операции?

Лабораторная работа 3

Тема: Наследование. Создание динамического списка объектов,

Связанных наследованием.

Цель работы: Изучить механизм наследования и возможности порождения новых типов данных на основе уже существующих классов. Изучить определение виртуальных функций и их использование для позднего связывания.

Задания по лабораторной работе

 

Для классов предыдущей лабораторной работы реализовать иерархию, изменяя отдельные методы и добавляя члены-данные (по усмотрению студента и преподавателя). В иерархию должно входить 2 производных класса. Несколько методов (по смыслу) должны быть виртуальными.

Реализовать двунаправленный список из объектов базового и производного классов. Для реализации списка создать. класс списка со следующими членами-данными: указателями на первый и последний элементы списка и методами добавления элемента к списку, удаления элемента из списка по номеру, вывода списка. Конструктор должен создавать пустой список, деструктор – освобождать память, выделенную под элементы списка.

Изменить демонстрационную программу.

 

Краткие теоретические сведения

Простое наследование

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

Базовый класс часто называют предком, а производный класс - потомком.

Производный класс наследует из базового данные-члены и функции-члены, но не конструкторы и деструкторы.

 

class Tbase { //Базовый класс

private:

int count;

public:

TBase() {count = 0;}

void SetCount(int n) {count = n;}

int GetCount(void) {return count;}

};

class TDerived: public TBase { //Производный класс

public:

TDerived(): Tbase() {}

void ChangeCount(int n) {SetCount(GetCount() + n);}

};

 

Производный класс назван TDerived. За именем следует двоеточие и одно из ключевых слов - public, protected, private. После этих элементов имя базового класса TBase.

В иерархии классов соглашение относительно доступности компонентов класса следующее:

private член класса может использоваться только функциями – членами данного класса и функциями – “друзьями” своего класса. В производном классе он недоступен.

protected – то же, что и private, но дополнительно член класса с данным атрибутом доступа может использоваться функциями-членами и функциями – “друзьями” классов, производных от данного.

public – член класса может использоваться любой функцией, которая является членом данного или производного класса, а также к public - членам возможен доступ извне через имя объекта.

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

 

TDerived(): TBase() {}

Кроме этого в нем могут выполняться и другие действия, например:

TDerived():TBase() {cout <<" I am being initialized" <<endl;}

 

Можно реализовать объявленный конструктор отдельно:

TDerived::TDerived() : TBase()

{ // операторы

}

 

Объекты класса конструируются снизу вверх: сначала базовый, потом компоненты-объекты (если они имеются), а потом сам производный класс. Таким образом, объект производного класса содержит в качестве подобъекта объект базового класса.

Уничтожаются объекты в обратном порядке: сначала производный, потом его компоненты-объекты, а потом базовый объект.

Таким образом, порядок уничтожения объекта противоположен по отношению к порядку его конструирования.

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

 

void ChangeCount(int n) {SetCount(GetCount() +n);}

 

В производном классе деструктор нужен только в том случае, если необходимо что-то удалять.

 

#include <iostream.h>

#include <string.h>

 

class TBase { //Базовый класс

private:

char *basep;

public:

TBase(const char *s) {basep = strdup(s); }

~TBase() {delete basep;}

const char *GetStr(void) { return basep; }

};

 

class TDerived: public TBase { //Производный класс

private:

char *uppercasep;

public:

TDerived(const char* s): TBase(s) { uppercasep = strupr(strdup(s));}

~TDerived() {delete uppercasep;}

const char *GetUStr(void) { return uppercasep;}

 

};

void main() {

TBase president("George Washington");

cout << "Original string: " << president.GetStr() << endl;

TDerived pres("George Washington");

cout << "Uppercase string:" << pres.GetUStr() << endl;

}

 

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

 

#include <iostream.h>

#include <string.h>

class TBase { //Базовый класс

private:

char *basep;

public:

TBase(const char *s) {basep = strdup(s); }

~TBase() {delete basep;}

void Display(void) { cout << basep << endl;}

};

class TState: public TBase { //Производный класс

public:

TState(const char *s):TBase (s) {}

void Display(void); // замещающая функция

};

 

void TState::Display(void) {

cout << "State: "; //Новый оператор

TBase::Display(); //Вызов замещенной функции

}

void main() {

TState object("Welcome to Borland C++5 programming!");

object.Display();

}

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

Построение производного класса на основе нескольких базовых выглядит очень просто: вместо имени одного базового класса (вместе с его атрибутом) используется список имен, разделенный запятыми, например:

 

class A { /*…*/ };

class B { /*…*/ };

 

class C: public A, private B { /*…*/ };

 

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

 

С::С( int a, char* str) : A(a), B(str) { /*…*/ };

 

Абстрактные классы

Абстрактным называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция.

Чистой виртуальной функцией называется компонентная функция, которая имеет следующее определение:

virtual тип имя_функции (список_формальных_параметров) = 0;

 

Чистая виртуальная функция ничего не делает и недоступна для вызовов. Ее назначение – служить основой для подменяющих ее функций в производных классах. Абстрактный класс может использоваться только в качестве базового для производных классов.

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

 

class Abstract {

public:

virtual void f1(void) ; //обычная виртуальная

virtual void f2(void) = 0 ; //чистая виртуальная

};

 

Компилятору не потребуется реализация функции-члена f2 в отличии от остальных функций-членов в классе.

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

 

class MyClass: public Abstract {

public:

virtual void f2(void); //бывшая чисто виртуальная функция

};

 

Виртуальные функции

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

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

Пример: имеется 3 класса: класс В производный от класса А, а класс С производный от класса В.

A aObject; //Объявление объектов классов

B bОbject;

C cОbject;

По определению указатель на класс А может ссылаться на любой из этих объектов, так как они связаны наследованием. Эта взаимосвязь работает только в одном направлении.

 

A *p;

p = &cОbject;

 

Этот принцип становится особенно важным, если в связанных отношениями родства классах определяются виртуальные функции. Эти Функции имеют точно такой же вид и программируются точно так же, как обыкновенные, но добавляется ключевое слово virtual.

 

class A {

public:

virtual void vf();

};

 

class B: public A {

public:

virtual void vf();

};

 

Когда в классе определяется виртуальная функция, имеющая одинаковое имя с виртуальной функцией класса-предка, такая функция называется замещающей. Указатель *р ссылается на объект типа С, следовательно

р->vf(); //указывает на виртуальную функцию класса С.

 

#include <iostream.h>

 

class TValue {

public:

virtual double func(double x) { return x*x; }

double culc(double x) { return func(x)/2; }

};

class DValue: public TValue {

public:

virtual double func(double x) { return x*x*x; }

};

void main() {

TValue obj1;

cout << obj1.culc(3) << endl;

DValue obj2;

cout << obj2.culc(3) << endl;

}

 

Виртуальными могут быть только нестатические функции-члены.

Виртуальность наследуется. После того как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.

Конструкторы не могут быть виртуальными, в отличие от деструкторов. Практически каждый класс, имеющий виртуальную функцию, должен иметь виртуальный деструктор.

 

Вопросы к защите лабораторной работы

 

1. Что такое наследование? Объясните механизм наследования в С++.

2. Какое бывает наследование?

3. Как осуществляется простое или множественное наследование?

4. Какой класс называется базовым, а какой производным?

5. Как определяется доступ к членам базового класса членов производного класса?

6. Что такое защищенные члены класса?

7. Как влияют спецификаторы public, protect, private на статус наследования?

8. Какие члены класса наследуются.

9. Вызов конструкторов при наследовании.

10. Написание конструкторов в производном классе.

11. Какой класс называется абстрактным классом? Могут ли существовать экземпляры абстрактного класса?

12. Что такое виртуальные функции и как они определяются в базовом и производном классах?

13. Что такое чисто виртуальная функция? Как называется класс, содержащий чисто виртуальную функцию?

14. Что такое полиморфизм?

15. Каков механизм реализации полиморфизма?

 


 

Лабораторная работа 4

 







Последнее изменение этой страницы: 2016-04-08; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.234.245.125 (0.027 с.)