ТОП 10:

Перегрузка бинарных операций



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

 

Тема:Разработка классов, создание конструкторов и деструкторов. Использование статических членов класса.

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

Задания на лабораторную работу

Реализовать класс в соответствии с вариантом. Класс должен обеспечивать набор методов для работы с данными. Создать констpуктоpы: констpуктоp по умолчанию, конструктор с параметрами, констpуктоp копирования.

Реализовать указанные классы с динамическим выделением памяти для хранения некоторых полей.

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

Составить демонстpационную пpогpамму. Для реализации демонстpационной пpогpаммы использовать отдельный модуль.

Пpогpамму постpоить с использованием пpоекта.

Посмотpеть pаботу пpогpаммы в отладчике, обpатить внимание на пpедставление данных.

Постpоить пpогpамму без отладочной инфоpмации. Обpатить внимание на pазмеp пpогpаммы.

Вариант 1.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: изменение значений, вывод значений.

Вариант 2.

Постpоить класс для pаботы со cтpоками. Класс должен включать следующие поля: стpока, длина строки.

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: изменение стpоки, вывод стpоки.

Вариант 3.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: вычисление значения многочлена для данного параметра, вывод многочлена в удобной форме.

Вариант 4.

Постpоить класс для pаботы с квадратными матрицами. Класс должен включать следующие поля: порядок матрицы, набор коэффициентов.

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: транспонирование матрицы, вывод матрицы в удобной форме.

Вариант 5.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: сложение, сокращение, вывод дроби в удобной форме.

Вариант 6.

Постpоить класс для pаботы с целыми произвольной точности. Класс должен включать следующие поля: длину и массив – само число.

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: сложение, вывод числа в удобной форме.

Вариант 7.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: изменение значений, вывод значений.

Вариант 8.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: вычисление периметра, вычисление площади, вывод значений.

Вариант 9.

Постpоить класс для работы с окружностями. Класс должен включать следующие поля: координаты центра, радиус, цвет заливки.

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: вычисление длины окружности, вычисление площади, вывод значений.

Вариант 10.

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

Класс должен обеспечивать пpостейшие методы для pаботы с полями класса: изменение значений, вывод значений.

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

Компоненты класса

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

тип_класса имя_класса {список_членов_класса};

где

тип_класса – одно из служебных слов class, struct, union;

имя_класса – идентификатор;

список_членов_класса – определения и описания типизированных данных и принадлежащих классу функций.

Функции – это методы класса, определяющие операции над объектом.

Данные – это поля объекта, образующие его структуру. Значения полей определяет состояние объекта.

 

Рассмотрим реализацию понятия даты с использованием struct для того, чтобы определить представление даты и множества функций для работы с переменными этого типа:

struct date {

int month, day, year; // дата: месяц, день, год

void set(int, int, int);

void get(int*, int*, int*};

void next();

// ...

};

 

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

 

date today; // сегодня

void f() {

today.set(18,1,1985);

today.next();

}

 

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

 

void date::next() {

if ( ++day > 28 ) {

// делает сложную часть работы

}

}

 

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

Описание date в предыдущем подразделе дает множество функций для работы с date, но не указывает, что эти функции должны быть единственными для доступа к объектам типа date. Это ограничение можно наложить, используя вместо struct class:

 

class date {

int month, day, year;

public:

void set(int, int, int);

void next();

};

 

Для описания объекта класса (экземпляра класса) используется конструкция

имя_класса имя_объекта;

Для изменения видимости компонент в определении класса можно использовать спецификаторы доступа: public, private, protected.

Общедоступные (public) компоненты класса доступны в любой части программы. Они могут использоваться любой функцией как внутри данного класса, так и вне его. Доступ извне осуществляется через имя объекта:

имя_объекта.имя_члена_класса

ссылка_на_объект.имя_члена_класса

указатель_на_объект->имя_члена_класса

Собственные (private) компоненты класса локализованы в классе и не доступны извне. Они могут использоваться функциями – членами данного класса и функциями – “друзьями” того класса, в котором они описаны.

Защищенные (protected) компоненты доступны внутри класса и в производных классах.

В том, что доступ к структуре данных ограничен явно описанным списком функций, есть несколько преимуществ. Любая ошибка, которая приводит к тому, что дата принимает недопустимое значение (например, Декабрь 36, 1985), должна быть вызвана кодом функции-члена, поэтому первая стадия отладки, локализация, выполняется еще до того, как программа будет запущена.

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

 

class x {

int m;

public:

int readm() { return m; }

};

x aa;

x bb;

 

void f() {

int a = aa.readm();

int b = bb.readm();

// ...

}

 

В первом вызове члена member() m относится к aa.m, а во втором - к bb.m.

Указатель на объект, для которого вызвана функция-член, является скрытым параметром функции. В каждой функции класса x указатель this неявно описан как

 

x* this;

 

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

 

Конструкторы

Использование для обеспечения инициализации объекта класса функций вроде set_date() (установить дату) чревато ошибками. Поскольку нигде не утверждается, что объект должен быть инициализирован, то программист может забыть это сделать, или сделать это дважды. Есть более хороший подход: дать возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она называется конструктором. Конструктор распознается по тому, что имеет то же имя, что и сам класс. Например:

 

class date {

// ...

date(int, int, int);

};

 

Когда класс имеет конструктор, все объекты этого класса будут инициализироваться. Если для конструктора нужны параметры, они должны задаваться:

 

date today = date(23,6,1983);

date xmas(25,12,0); // сокращенная форма

date my_burthday; // недопустимо, опущена инициализация

 

Часто бывает хорошо обеспечить несколько способов инициализации объекта класса. Это можно сделать, задав несколько конструкторов. Например:

 

class date {

int month, day, year;

public:

// ...

date(int, int, int); // день месяц год

date(char); // дата в строковом представлении

date(int); // день, месяц и год сегодняшние

date(); // дата по умолчанию: сегодня

};

 

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

 

date today(4);

date july4("Июль 4, 1983");

date guy("5 Ноя");

date now; // инициализируется по умолчанию

 

Один из способов сократить число родственных функций – использовать параметры со значением по умолчанию.

 

class date {

int month, day, year;

public:

// ...

date(int d =0, int m =0, int y =0); // параметры со значениями по умолчанию

date(date & d); // конструктор копирования

};

date::date(int d, int m, int y) {

day = d ? d : today.day;

month = m ? m : today.month;

year = y ? y : today.year;

// проверка, что дата допустимая

// ...

}

date::date(date & d) {

day = d.day ;

month = d.month;

year = d.year;

}

 

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

- Для конструктора не определяется тип возвращаемого значения. Даже тип void не допустим.

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

- Конструкторы не наследуются.

- Конструкторы не могут быть описаны с ключевыми словами virtual, static, const, mutuable, valatile.

Конструктор всегда существует для любого класса, если он не определен явно, то он создается автоматически. По умолчанию создается конструктор без параметров и конструктор копирования. Если конструктор описан явно, то конструктор по умолчанию не создается. По умолчанию конструкторы создаются общедоступными (public).

Параметром конструктора не может быть его собственный класс, но может быть ссылка на него (T&). Без явного указания программиста конструктор всегда автоматически вызывается при определении (создании) объекта. В этом случае вызывается конструктор без параметров. Для явного вызова конструктора используются две формы:

имя_класса имя_объекта (фактические_параметры);

имя_класса (фактические_параметры);

 

Деструкторы

Для многих типов также требуется обратное действие, деструктор, чтобы обеспечить соответствующую очистку объектов этого типа. Имя деструктора для класса X есть ~X() («дополнение конструктора»). В частности, многие типы используют некоторый объем памяти из свободной памяти, которая выделяется конструктором и освобождается деструктором.

 

Статические члены класса

Каждый объект одного и того же класса имеет собственную копию данных класса. Но существуют задачи, когда данные должны быть компонентами класса, и иметь их нужно только в единственном числе. Такие компоненты должны быть определены в классе как статические (static). Статические данные классов не дублируются при создании объектов, т.е. каждый статический компонент существует в единственном экземпляре.

class Point{

int x, y;

static int counter;

public:

Point(void){ x = y = 0; count++;}

Point(int _x, int _y){ x = _x; y = _y; counter++;}

~Point(){ counter--;}
static int getNumber(){return counter;}

};

 

int Point::counter = 0;
void main() {
Point pp = new Point[20];
Point p1(3,33);
cout << Point::getNumber()<<‘\n’;
delete [] pp;
cout << p1::getNumber()<<‘\n’;
}

 

Доступ к статическому компоненту возможен только после его инициализации. Для инициализации используется конструкция

тип имя_класса : : имя_данного инициализатор;

Например, int Point::counter = 0;

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

имя_объекта.имя_компонента

или через имя класса

имя_класса : : имя_компонента

Однако так можно обращаться только к public компонентам.

К private статической компоненте извне можно обратиться с помощью компонента-функ-ции этого класса. Эту проблему решают статические компоненты-функции. Эти функции можно вызвать через имя класса.

имя_класса : : имя_статической_функции

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

 

1. Что такое класс? Для чего описываются классы?

2. Чем отличается класс от структуры?

3. Какие модификаторы используются для ограничения доступа к членам класса?

4. Для чего в классе определяется конструктор? Сколько может быть конструкторов в классе? Когда вызывается конструктор?

5. Может ли быть конструктор описан в области доступа private?

6. Что будет, если в классе не определен конструктор?

7. Для чего в классе определяется деструктор? Сколько может быть деструкторов в классе? Когда вызывается деструктор?

8. Может ли быть деструктор описан в области доступа private?

9. Что будет, если в классе не определен деструктор?

10. Что такое конструктор, деструктор по умолчанию? Когда они вызываются?

11. Что такое экземпляр класса?

12. Как можно обратиться к членам-данным и членам-функциям класса?

13. Для чего в классе задаются статические члены? В чем отличие их использования от обычных членов класса?

 


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

Тема: Переопределение операций. Дружественные функции.

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

Задания на лабораторную работу

 

Для класса из лаб. работы №1 реализовать набор операций для работы с объектами класса: сложение, вычитание, присваивание, инкремент постфиксный и префиксный, преобразование к разным типам (по усмотрению студентов и преподавателя). Операции сложения, присваивания, инкремента, преобразования реализовать как методы класса, операцию вычитания – как дружественную функцию.

Изменить демонстрационную пpогpамму, продемострировав все перегруженные операции.

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

 

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

Примером абстрактного типа данных является класс в языке С++. В С есть возможность использовать знаки стандартных операций для записи выражений как для встроенных, так и для АТД.

 

Перегрузка оперций

В языке С++ для перегрузки операций используется ключевое слово operator, с помощью которого определяется специальная операция-функция (operator function).

Формат операции-функции:

тип_возвр_значения operator знак_операции (специф_параметров)

{операторы_тела_функции}

Перегрузка унарных операций

- Любая унарная операция Å может быть определена двумя способами: либо как компонентная функция без параметров, либо как глобальная (возможно дружественная) функция с одним параметром. В первом случае выражение Å Z означает вызов Z.operator Å (), во втором - вызов operator Å(Z).

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

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

 

class ZZ {

public:

friend ZZ operator-(ZZa);

ZZ operator-();

};

 

Примеры перегрузок

Оператор индексирования массива. Можно перегрузить оператор индексирования массива [] для реализации доступа к данным-членам класса, подобного доступу к элементу массива, даже если эти данные имеют вид отдельных членов класса или связного списка.

 

class Psevdoarray {

private:

int val0, val1, val2, val3;

public:

Psevdoarray( int v0, int v1, int v2, int v3)

{ val0 = v0; val1 = v1; val2 = v2; val3 = v3;}

int GetInt(unsigned i);

int operator[] (unsigned i);

};

void main() {

Psevdoarray pa(10, 20, 30,40);

for( int i=0; i<=3; i++)

cout << "pa[" << i << "] ==" << pa[i] << endl;

}

int Psevdoarray::GetInt(unsigned i) {

switch (i) {

case 0: return val0;

case 1: return val1;

case 2: return val2;

case 3: return val3;

default: return val0;

}

}

int Psevdoarray::operator[](unsigned i) {

return GetInt(i);

}

 

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

class X {

int x;

public:

int operator()(void);

X(int n) { x=n; }

};

int X::operator()(void) {

return x;

}

main() {

X object = 100;

int g == object(); //выглядит как вызов функции

cout << q; //на самом деле object.operator()();

return 0;

}

Перегрузка операторов new и delete. Для перегрузки new следует использовать прототип функции вида: void * operator new(size_t size); В дальнейшем обращение к оператору new для выделения памяти объектам класса будут перенаправлены замещающей функции. Функция должна возвращать адрес памяти, выделенной объекту. Вместо кучи можно выделять память из статического буфера, на диске или другом запоминающем устройстве.

 

class DemoNew {

private:

int x;

public:

DemoNew();

void * operator new(size_t size);

void operator delete(void *p);

};

char buf[512];

int index;

void main() {

cout << endl << "Creating local instance";

DemoNew b1;

cout << endl << "Allocating space via new";

DemoNew *b2 = new DemoNew;

DemoNew *b3 = new DemoNew;

DemoNew *b4 = new DemoNew;

DemoNew *b5 = new DemoNew;

}

DemoNew::DemoNew() {

cout << endl << "Inside constructor";

x = index;

}

void *DemoNew::operator new(size_t size) {

cout << endl << "Inside overloaded new. Size == "<< size;

if ( index >=512 - sizeof(DemoNew)) //Проверка наличия

return 0; //пространства в buf

else {

int k = index;

index+= sizeof(DemoNew);

return &buf[k];

}

}

 

Оператор delete служит для удаления объектов, адресованных указателями. Прототип функции перегрузки оператора delete должен иметь вид: void operator delete(void *p); , где р ссылается на удаляемый объект.

 

void DemoNew::operator delete(void *p) {

cout << endl << "Deleting object at "<< p;

}

 

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

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

 

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

 

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

 

Задание представляет собой типовую задачу по разработке шаблонов стандартных структур данных. Протестировать структуру данных. В качестве хранимых объектов использовать встроенные типы С++ (int, float и т.д.).

Вариант 1

Структура данных: стек, реализованный динамическим массивом.

Способ хранения объектов: об"екты.

Операция: загрузка об"екта в стек.

Операция: извлечение из стека.

Вариант 2

Структура данных: стек, реализованный динамическим массивом.

Способ хранения объектов: ссылки на об"екты.

Операция: загрузка об"екта в стек.

Операция: извлечение из стека.

Вариант 3

Структура данных: стек, реализованный односвязным списком.

Способ хранения объектов: об"екты.

Операция: загрузка об"екта в стек.

Операция: извлечение из стека.

Вариант 4

Структура данных: стек, реализованный односвязным списком.

Способ хранения объектов: ссылки на об"екты.

Операция: загрузка об"екта в стек.

Операция: извлечение из стека.

Вариант 5

Структура данных: дек с ограниченным выходом, реализованный динамическим массивом

Способ хранения объектов: об"екты.

Операция: добавление с двух концов

Операция: удаление с одного конца

Вариант 6

Структура данных: дек с ограниченным входом, реализованный динамическим массивом

Способ хранения объектов: ссылки на об"екты.

Операция: добавление с одного конца

Операция: удаление с двух концов

Вариант 7

Структура данных: дек с ограниченным выходом, реализованный двунаправленным списком

Способ хранения объектов: об"екты.

Операция: добавление с двух концов

Операция: удаление с одного конца

Вариант 8

Структура данных: дек с ограниченным входом, реализованный двунаправленным списком

Способ хранения объектов: ссылки на об"екты.

Операция: добавление с одного конца

Операция: удаление с двух концов

Вариант 9

Структура данных: односвязный список

Способ хранения объектов: ссылки на об"екты.

Операция: добавление в конец списка

Операция: удаление по логическому номеру

Вариант 10

Структура данных: односвязный список

Способ хранения объектов: об"екты.

Операция: добавление в конец списка

Операция: удаление по логическому номеру

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

Шаблон функции

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

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

В С++ параметризированная функция создается с помощью ключевого слова template. Фо







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

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