Вы находитесь на странице: 1из 86

Технический Университет Молдовы

Кожухарь С.А. Чорбэ Р. В.

ПРОГРАММИРОВАНИЕ НА
ЯЗЫКЕ С++
Методические указания к лабораторным работам

Кишинэу-2005
Технический Университет Молдовы

Кафедра Информационных Технологий

ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ С++


Методические указания к лабораторным работам

Кишинэу
ТУМ
2005
Данное пособие предназначено изучению основ объектно-
ориентированного программирования на языке С++. Пособие
организованно в виде лабораторных работ, и содержит семь тем.
Каждая тема состоит как из теоретической, так и практической
частей. Пособие рекомендуется студентам специальности
Информационные Технологии, а также всем желающим изучить
язык С++ и основы ООП.

Авторы: Магистр ИТ Кожухарь С.А.


Магистр ИТ Чорбэ Р. В.

Ответственный редактор: Конф. Др. Бешлиу В.А. !!!

Рецензент:

 U.T.M., 2005 !!!!


ЛАБОРАТОРНАЯ РАБОТА №1

Тема: Структура – механизм абстракции


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

Основные понятия

Структура – это объединенное в единое целое множество


элементов, обычно разных типов. Структуру можно сравнить с
массивом, однако, массив состоит из однородных элементов;
кроме того, обращение к элементам массива происходит по
индексу, тогда как обращение к полям структуры имеет
специфическую форму, о чем будет сказано далее.
Структура представляет собой абстрактный тип данных,
что обозначает производный тип, то есть созданный
программистом, с использованием уже существующих типов.
Введение абстрактных типов данных способствует смещению
акцента в сторону предметной области, то есть существует
возможность создания и использования типов данных, с
наибольшей полнотой отображающей особенности
программируемой задаче.
Приведем пример определения структуры
struct Book{
// определение полей структуры
char *author;
char *title;
int year;
int pages;
};
3
Перед словом struct иногда добавляется зарезервированное
слово typedef. Однако, такой синтаксис свойственен С, а не С++.
Элементы структуры могут быть как встроенных типов, так и
производных. Единственное исключение состоит в том, что
структура не может содержать поле своего типа. В нашем случае
определена структура, содержащая поля целого и символьного
типа. Теперь для определения переменных нам достаточно
использовать имя типа точно так же как и встроенные типы:

Book b1, b2, bs[10], *bptr;

В данном случае мы определили две переменные книги,


один массив из десяти книг и указатель на книгу. Для обращения
к полям структуры используются уточненные имена, состоящие
из имени переменной-структуры, оператора выбора “.” и имени
переменной-поля.

b1.pages = 153;
bs[i].pages = 24;

Если используется указатель, то тогда вместо оператора


выбора “.” используется оператор косвенного выбора “->”.

bptr = new Book; // Выделение памяти под


новую переменную
bptr->pages = 176;

В случае если структура содержит в себе поле сложного


типа, может получиться цепочка обращений:

Circle1.Center.x = 20;
Circle1.Center.y = 10;

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

struct Date{
int day, month, year;
4
};
struct Student{
char *name;
Date birthDay;
float media;
};

При работе со структурами хотелось бы иметь возможность


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

void setValues(Student* sptr, char* newN,float newM,Date d)


{
sptr->name = new char[strlen(newN)+1];
strcpy(sptr->name,newN);
sptr->media = newM;
sptr->birthDay = d;
}

Данный вариант функции имеет один недостаток,


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

void setValues(Student* sptr, char* newN,float newM,Date d)


{
delete[] sptr->name;
sptr->name = new char[strlen(newN)+1];
...
}

Но такое решение тоже имеет свои недостатки, возможно


даже более опасные, чем в первом случае. Опасность состоит в
попытке удаления невыделенной памяти при первом вызове
5
данной функции. Можно придумать несколько решений. Одно
состоит в использовании дополнительного параметра, можно со
значением по умолчанию, которое укажет первый ли раз
вызывается функция (для данного объекта) или нет. Такое
дополнение не очень удачно, во первых, требуется отслеживать
очередность вызовов, что ложиться на плечи программиста-
пользователя класса, во вторых, функция должна в себе
содержать условие, что замедляет выполнение функции.
Другое решение состоит в задании начального значения
указателя. Для этого после создания объекта полям-указателям
присваивается значение NULL;

Book b;
b.author = NULL;
setAuthor(&b, ”Arthur Conan Doyle”);

Для того чтобы пример был законченным, нужно не забыть


удалить память, выделенную ранее. Для этого создадим еще одну
функцию.

void freeMem(Book* bp){


delete[] bp->author;
bp->author = NULL;
... // тоже для остальных полей указателей
}

Указателю присваивается NULL для обеспечения


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

6
Контрольные вопросы:

1. Что обозначает понятие – абстрактный тип данных?


2. Как определяется структура?
3. Можно ли определить структуру без указания
названия типа?
4. Чем отличается структура от других типов данных?
5. Как определяется переменная типа структуры?
6. Когда используется точка, а когда стрелочка?
7. Чем отличается структура в языках С и С++?
8. Может ли структура содержать в себе другую
структуру?
9. Может ли структура содержать в себе указатель на
саму себя?
10. Может ли быть создана переменная типа структуры
динамически?
11. Что значит передача по ссылке?

7
Задания
Вариант 1
а) Создать абстрактный тип данных, для комплексных чисел
используя структуру. Определить функции установки/чтения
значений реальной и мнимой части, сложения, вычитания,
умножения, деления, сравнения (меньше, больше, и т.д.).
Определить функцию-норму комплексных чисел. Все функции
должны быть определены, как глобальные и быть
переносимыми.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на int и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
нормы вектора. Для примера, в функции main, организовать
сложение двух векторов.
Вариант 2
а) Создать абстрактный тип данных - собака, у которой есть
порода, кличка и возраст (структура). Определить функции
установки и изменения данных и удаления выделенной памяти.
Для задания текстовых полей использовать оператор new.
Определить функцию сортировки массива собак по возрасту +
кличке. То есть собаки одного возраста сортируются в
алфавитном порядке.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на float и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы элементов вектора. Для примера, в функции main,
организовать поэлементное умножение двух векторов.

8
Вариант 3
а) Создать абстрактный тип данных - компьютер, содержащий
информацию о фирме-изготовителе, процессоре, тактовой
частоте и др. Определить функции установки данных, изменения
информации, сравнения. Для задания текстовых полей
использовать оператор new. Освободить выделенную память. В
функции main представить пример сортировки массива
компьютеров по полям тактовая частота + процессор.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на double и число элементов.
Определить функции: инициализации, удаления вектора,
установки/изменения размера, доступа к элементам вектора,
вычисления суммы положительных элементов вектора. Для
примера, в функции main, организовать умножение вектора на
число.
Вариант 4
а) Создать абстрактный тип данных (структура) - страна, у
которой есть название, материк на котором находится страна и
количество жителей. Определить функции установки названия
страны и количества населения, изменения данных, сравнения
стран и освобождения памяти. Для задания названия города
использовать оператор new. В main-е, привести пример поиска
городов по названию и населению.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на int и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
среднего положительных элементов вектора. Для примера, в
функции main, организовать сравнение двух векторов.

9
Вариант 5
а) Создать абстрактный тип данных (структура) - сотрудник, у
которой есть имя, специальность, разряд и заработная плата.
Определить функции установки, изменения данных и сравнения
сотрудников. Для задания текстовых полей использовать
оператор new. Освободить память. В main-е, привести пример
сортировки сотрудников по разным критериям.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на long и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы отрицательных элементов вектора. Для примера, в
функции main, организовать сложение двух векторов.
Вариант 6
а) Создать абстрактный тип данных (структура) - дом, у которой
есть название фирмы строителя, адрес, количество этажей и
квартир. Определить функции установки, изменения данных,
сравнения домов. Для задания текстовых полей использовать
оператор new. Освободить память. В main-е, привести пример
сортировки домов по количеству этажей + адресу в алфавитном
порядке.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на float и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
нормы вектора. Для примера, в функции main, организовать
сложение элементов вектора и числа.

10
Вариант 7
а) Создать абстрактный тип данных (структура) - монитор, у
которой есть название фирмы производителя, размер в дюймах,
количество цветов и разрешающая способность. Определить
функции установки, изменения данных, сравнения мониторов.
Для задания текстовых полей использовать оператор new.
Освободить память. В main-е, привести пример поиска
подходящего монитора по размеру и другим данным.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на byte и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
произведения отрицательных элементов вектора. Для примера, в
функции main, организовать поэлементное умножение векторов.
Вариант 8
а) Создать абстрактный тип данных (структура) - книга, у
которой есть название, автор, издательство, объем в страницах и
год издания. Определить функции установки, изменения данных,
сравнения. Для задания текстовых полей использовать оператор
new. Освободить память. В main-е, привести пример поиска
нужной книги по названию и по автору.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на short и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы четных элементов вектора. Для примера, в функции main,
организовать сравнение векторов.

11
Вариант 9
а) Создать абстрактный тип данных (структура) - студент, у
которого есть имя, специальность, год обучения и средний балл.
Определить функции установки, изменения данных, сравнения.
Для задания текстовых полей использовать оператор new.
Освободить память. В main-е, привести пример сортировки
студентов по специальностям + успеваемости.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на long и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы четных элементов вектора. Для примера, в функции main,
организовать поиск позиции максимального элемента вектора.

Вариант 10
а) Создать абстрактный тип данных (структура)–программный
ресурс (software), у которого есть название, фирма
производитель, год издания и версия. Определить функции
установки, изменения данных, сравнения. Для задания текстовых
полей использовать оператор new. Освободить память. В main-е,
привести пример поиска нужного продукта по нескольким
критериям.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на double и число элементов.
Определить функции: инициализации, удаления вектора,
установки/изменения размера, доступа к элементам вектора,
вычисления суммы четных элементов вектора. Для примера, в
функции main, организовать сложение вектора и числа.

12
Вариант 11
а) Создать абстрактный тип данных (структура) - фирма, у
которой есть название, организационное форма, адрес и год
учреждения. Определить функции установки, изменения данных,
сравнения. Для задания текстовых полей использовать оператор
new. Освободить память. В main-е, привести пример поиска
нужной фирмы по нескольким критериям.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на int и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы четных элементов вектора. Для примера, в функции main,
организовать сложение двух векторов.

Вариант 12
а) Создать абстрактный тип данных (структура) - файл, у
которого есть название, дата и время создания и размер.
Определить функции установки, изменения данных, сравнения.
Для задания текстовых полей использовать оператор new.
Освободить память. В main-е, привести пример сортировки
файлов по некоторым критериям.
b) Создать абстрактный тип данных (структура) - вектор,
который имеет указатель на float и число элементов. Определить
функции: инициализации, удаления вектора, установки/
изменения размера, доступа к элементам вектора, вычисления
суммы четных элементов вектора. Для примера, в функции main,
организовать поиск позиции минимального элемента вектора.

13
ЛАБОРАТОРНАЯ РАБОТА №2

Тема: Конструктор – функция инициализации объектов класса


Цели работы:
 изучение основ определения и использования
конструкторов;
 изучение основ определения и использования деструкторов;
 изучение типов конструкторов;

Основные понятия

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

class Book{
char *author;
int year;
int pages;
public:
void Init(char*, int, int);
void Destroy();
};
void Book::Init(char* a, int y, int p){
author = new char[strlen(a)+1];
strcpy(author,a);
year=y;
pages=p;
}
void Book::Destroy(){
delete[] author;
}

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

Определение и использование
Чтобы помочь избежать этой ошибки, С++ обеспечивает
механизм автоматической инициализации для определяемых
пользователем классов - конструктор класса. А для
завершающих операций – деструктор.
Конструктор – специальная функция класса, которая
вызывается автоматически при создании объекта типа класса.
Имя конструктора совпадает с именем класса, не возвращает
никакого результата, даже void. Компилятор гарантирует
единственный вызов конструктора для одного объекта.
Деструктор – специальная функция класса, которая
вызывается автоматически при уничтожении объекта. Имя
деструктора совпадает с именем класса, перед которым ставится
символ “~”. Компилятор гарантирует единственный вызов
деструктора для одного объекта. Деструктор не может иметь
параметров и по этому не может быть перегружен.
При создании автоматической переменной деструктор
вызывается автоматически при выходе из области видимости, то
есть за рамки блока, в котором определена переменная. Для
динамических переменных дела обстоят совсем по-другому, для
освобождения занятой памяти используется оператор delete,
который и вызывает деструктор.
Приведем аналогичный первому пример с использованием
конструктора и деструктора:

15
class Book{
char *author;
int year;
int pages;
public:
Book(char*, int, int);
~Book();
};
Book::Book(char* a, int y, int p){
author = new char[strlen(a)+1];
strcpy(author,a);
year=y;
pages=p;
}
Book::~Book(){
delete[] author;
}
void main(){
Book b(“Stroustrup”,2000,890);
// Book b1; // попытка создание объекта
без // вызова конструктора
// приведет к ошибке

Book* ptr = new Book(“Lippman”,1996, 1200);

// Динамическое создание объекта


delete ptr;
// И в этом случае нужно
самому // освобождать память
}
// здесь автоматически вызывается деструктор для b.

Типы конструкторов
Существует четыре вида конструкторов: по умолчанию,
копий, приведения типа и основной. Эта классификация
основана на правилах определения и использования
конструкторов.

16
 Конструктор по умолчанию – конструктор, не имеющий
параметров, либо параметра которого имеют значения по
умолчанию.
 Конструктор копий (копирования) – конструктор
получающий ссылку на объект того же типа.
 Конструктор приведения типа – конструктор, приводящий
объект одного типа к другому.
 Основной конструктор, – не подпадающий ни под одну из
вышеперечисленных категорий.
Приведем пример:

class Book{
char *author;
int year;
int pages;
public:
Book(); // конструктор по
// умолчанию
Book(const Book&); // конструктор копий
Book(const char*); // конструктор
// приведения типа
Book(char*, int, int); // основной
...
};
...
void main(){
Book b(“Stroustrup”,1997,890); // основной
Book b1 = b, b11(b); // копий
Book b2 = “Stroustrup”, b21(“Bjarne”);
// приведения типа
Book b3; //по умолчанию
}

Правила создания генерируемых конструкторов и


отношения между ними и пользовательскими конструкторами.
Конструктор создается даже в том случае, когда пользователь не
описал ни одного. Генерируются конструкторы по умолчанию и
копий. Однако генерируемый конструктор по умолчанию
17
абсолютно ничего не делает, а генерируемый конструктор копий
использует побитовое копирование, что не всегда может
устроить нас. Если же, был создан, какой либо пользовательский
конструктор, остальные не генерируются, за исключением
конструктора копий.

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

Book::Book(const Book& b){


author = new char[strlen(b.author)+1];
strcpy(author, b.author);
year = b.year;
pages = b.pages;
}

Конструкторы должны использовать механизмы


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

18
Book::Book(const Book& b): year(b.year),pages(b.pages),
author(new char[strlen(b.author)+1])
{
strcpy(author, b.author);
}
// вызов функции strcpy не удалось "запихнуть" в
// инициализатор.

Если предполагается, что будут создаваться массивы


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

Контрольные вопросы:

1. Что обозначает понятие – инициализация?


2. Какие зарезервированные слова используются для
определения конструктора и деструктора?
3. Может ли конструктор вернуть результат?
4. Могут ли быть классы без конструкторов?
5. Сколько конструкторов могут быть в одном классе?
А деструкторов? Почему?
6. Перечислите типы конструкторов?
7. Как вызывается конструктор?
8. Зачем определять конструктор копий?
9. Зачем определять конструктор приведения типа?
10. В каких случаях вызывается конструктор копий?
Каков синтаксис вызова?
11. Что такое инициализаторы?
12. В каком случае обязательно определять конструктор
по умолчанию?

19
Задания

Вариант 1
а) Создать класс Date - дата с полями: день (1-28..31), месяц (1-
12), год (целые числа). Определить конструкторы, функции-
члены установки дня, месяца и года, функции-члены получения
дня, месяца и года. Также две функции печати: печать по
шаблону: "19 апреля 2003 года" и "19.04.2003". Функции
установки полей класса должны проверять корректность
задаваемых параметров.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на int, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 2
а) Создать класс Time - время с полями: часы (0-23), минуты (0-
59), секунды (0-59). Определить конструкторы, функции-члены
установки времени, функции получения часа, минуты и секунды,
а также две функции печати: печать по шаблону: "16 часов 18
минут 3 секунды" и "4 p.m. 18 минут 3 секунды". Функции
установки полей класса должны проверять корректность
задаваемых параметров.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на double, количество строк и столбцов и переменную -
20
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 3
а) Создать класс Stack - стек. Поля – общее и используемое
количество элементов и указатель для динамического выделения
памяти. Определить конструкторы: по умолчанию, копий и с
параметром, обозначающим необходимое количество элементов.
Функции Push и Pop для занесения и извлечения соответственно.
Функции IsEmpty и IsFull для определения состояния стека.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на float, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.

21
Вариант 4
а) Создать класс File – файл, содержащий информацию о полном
имени файла и ассоциативном приложении (doc – Word, psd –
Photoshop, etc), используя динамическую память, размер, дату и
время создания. Определить все конструкторы, конструктор
приведения типа - параметр обозначающий имя файла.
Определить функции переименования файла, перемещения в
другую папку и изменения ассоциативного приложения.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на long, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 5
а) Создать класс Document – документ, содержащий информацию
о названии, теме, авторе документа используя динамическую
память, количество страниц, дату и время последней редакции.
Определить все конструкторы: конструктор приведения типа -
параметр обозначающий название документа. Определить
функции переназначение темы, изменение даты последней
редакции и др.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на byte, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
22
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 6
а) Создать класс Image – картинка, содержащий следующую
информацию: имя файла, формат сжатия, размеры изображения,
размер изображения в байтах, компрессия (%). Определить все
конструкторы, конструктор приведения типа - параметр
обозначающий имя файла. Определить функции переназначение
имени файла, формата, размеров и др.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на Complex, количество строк и столбцов и
переменную - код ошибки. Определить конструктор без
параметров, конструктор с одним параметром – квадратная
матрица и конструктор с двумя параметрами – прямоугольная
матрица и др. Определить методы доступа: возвращение и
определение значения элемента (i,j). Определить функции
сложения и вычитания (матрицы с матрицей), умножение
матрицы на матрицу. Определить умножение матрицы на число.
Проверить работу этого класса. В случае нехватки памяти,
несоответствия размерностей, выхода за пределы используемой
памяти устанавливать код ошибки.
Вариант 7
а) Создать класс Queue - очередь. Поля – количество элементов и
указатель для динамического выделения памяти. Определить
конструкторы: по умолчанию, копий и с параметром,
обозначающим необходимое количество элементов. Функции
23
add и get для занесения и извлечения соответственно. Функции
isEmpty и isFull для определения состояния очереди.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на int, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 8
а) Создать класс String – строка, используя динамическую
память. Определить конструкторы: по умолчанию, копий и с
параметром – указателем на строку типа char. Определить
функции присваивания одной строки другой, сравнения, поиска
подстроки, количества символов и др.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на double, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.

24
Вариант 9
а) Создать класс Дисциплина, содержащий информацию о
предмете обучения, преподавателе, количестве часов и о форме
сдачи (экзамен или зачет). Использовать динамическую память
для задания текстовых полей. Определить конструкторы: по
умолчанию, копий и с параметром, обозначающим название
предмета. Определить функции переназначения преподавателя,
количества часов и формы сдачи.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на float, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 10
а) Создать класс Soft – файл, содержащий информацию о полном
имени файла и ассоциативном приложении (doc – Word, psd –
Photoshop, etc), используя динамическую память, размер, дату и
время создания. Определить конструкторы: по умолчанию,
копий и с параметром, обозначающим имя файла. Определить
функции переименования файла, перемещения в другую папку и
изменения ассоциативного приложения.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на long, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
25
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.
Вариант 11
а) Создать класс Group – группа, содержащий информацию о
коде группы, специальность, количестве студентов,
соответствующий руководитель группы и год обучения.
Использовать динамическую память для задания текстовых
полей. Определить все виды конструкторов. Определить
функции переназначения руководителя и количества студентов.
Определить функцию назначения года обучения таким образом,
что бы можно было только увеличивать значения этого поля и
оно не могло содержать значение более 5.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на byte, количество строк и столбцов и переменную -
код ошибки. Определить конструктор без параметров,
конструктор с одним параметром – квадратная матрица и
конструктор с двумя параметрами – прямоугольная матрица и др.
Определить методы доступа: возвращение и определение
значения элемента (i,j). Определить функции сложения и
вычитания (матрицы с матрицей), умножение матрицы на
матрицу. Определить умножение матрицы на число. Проверить
работу этого класса. В случае нехватки памяти, несоответствия
размерностей, выхода за пределы используемой памяти
устанавливать код ошибки.

26
Вариант 12
а) Создать класс Set – множество целых чисел, используя
динамическую память. Определить конструкторы: по умолчанию
и копий. Определить функции включения нового элемента во
множество, определения принадлежности элемента множеству,
сложения, вычитания и пересечения множеств.
b) Создать класс Matrix-матрица. Данный класс содержит
указатель на Complex, количество строк и столбцов и
переменную - код ошибки. Определить конструктор без
параметров, конструктор с одним параметром – квадратная
матрица и конструктор с двумя параметрами – прямоугольная
матрица и др. Определить методы доступа: возвращение и
определение значения элемента (i,j). Определить функции
сложения и вычитания (матрицы с матрицей), умножение
матрицы на матрицу. Определить умножение матрицы на число.
Проверить работу этого класса. В случае нехватки памяти,
несоответствия размерностей, выхода за пределы используемой
памяти устанавливать код ошибки.

27
ЛАБОРАТОРНАЯ РАБОТА №3

Тема: Перегрузка операторов


Цели работы:
 изучение необходимости перегрузки операторов;
 изучение синтаксиса определения операторов;
 изучение типов операторов;
 изучение форм перегрузки;

Основные понятия

Мотивация
Когда заходит разговор об операторах, то чаще всего
программисты говорят что, либо не умеют ими пользоваться,
либо никогда их не используют. Причина такого отношения к
операторам состоит в том, что их использование, принося пользу
(запись становится короче), в тоже время усложняют понимание
кода, так как не всегда возможно, либо тяжело отследить какой
оператор используется встроенный или перегруженный.
Особенно эта проблема остро стоит перед начинающими
программистами, однако к этому синтаксису легко привыкнуть,
после нескольких упражнений. Самый же главный вопрос
состоит в том, так уж необходима перегрузка операторов? Ответ
абсолютно однозначен – необходима. Есть случаи, в которых
просто невозможно без них обойтись. Таковым является
оператор присваивания. Конечно, компилятор сам может создать
необходимый код, либо мы можем определить метод, на пример
Assign (Как сделано на Java). Однако, оба эти решения не
идеальны.
Недостаток первого состоит в том, что в этом случае будет
использовано побитовое копирование, что устраивает нас только
до тех пор, пока в классе не используются указатели:

28
class Book{
char* name;
public:
Book(char n){
name = new char[strlen(n)+1];
strcpy(name,n);
}
~Book(){
delete[] name;
}
void print(){
cout<<name;
}
};
void main(){
Book b(“Stroustrup”), b2(“Lippman”);
b2 = b;
b.print();
b2.print();
}

Приведенный пример иллюстрирует проблему, связанную с


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

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

Определение и использование
Но что же такое оператор? Оператор это функция с
предопределенным именем и специальным синтаксисом вызова.
Определяется операторы при помощи зарезервированного слова
operator, после которого следует символ операции. В остальном
это обычная функция, имеющая результат и параметры.
Complex operator+(const Complex& r){
return Complex(re+r.re,im+r.im);
}

Теперь данный оператор можно использовать, так же как и


встроенный, но можно использовать и функциональную форму:
Complex c1,c2,c3;
c3=c1+c2; // операторная запись
c3=c1.operator+(c2); // функциональная запись

Типы операторов
Все операторы делятся на две группы: унарные и
бинарные1. Унарными называются операторы имеющие один
единственный оператор. На пример оператор & - взятие адреса, +
+ - инкрементация, ! – отрицание и др. Бинарные имеют два
операнда: + - сложение, * - умножение и др.
О бинарных операторах многого не скажешь, а вот унарные
имеют две формы (правда не все). Например, оператор ++ может
быть записан как i++, а можно и ++i. Как же компилятору
различить какую реализацию вызвать? Для этого было введено

1
На самом деле бывают еще и тернарные операторы, точнее оператор,
так как он один единственный и не перегружаемый, по этому не
вызывающий у нас интерес. Это оператор ?:.

30
правило: постфиксный указатель имеет один дополнительный
параметр целого типа:

Complex& operator++(); // префиксная форма


Complex operator++(int); // постфиксная форма

Формы перегрузки
Существует две формы перегрузки: как метод класса (с
этим способом мы уже знакомы) и как дружественная функция.
Дружественный оператор определяется по правилам обычным,
для дружественных функций. Встает вполне закономерный
вопрос: а зачем собственно используются обе формы
перегрузки? Неужели недостаточно перегрузки как метод
класса? Зачем оператору быть другом класса? Чтобы ответить на
этот вопрос нужно вспомнить что, например, оператор << всегда
определяется как дружественный. Причина этого состоит в том,
как вызываются методы класса. Практически всегда передается
объект, используя скрытый указатель this. Если нужно еще что-
нибудь передать, то используются обычные параметры. То есть
первый операнд обязательно должен быть представителем
данного класса. В случае оператора << первый операнд типа
ostream а не определяемого нами. Другими словами если нужно
сложить комплексное с целым то это может сделать как
оператор-метод класса, так и друг, а сложить целое и
комплексное – только друг. То есть в данном случае от перемены
мест слагаемых все меняется.
Перечень операторов
Не все операторы могут быть переопределены. Это
связано с синтаксисом языка, перегрузка абсолютно всех
операторов привела бы к слишком сложным правилам их
использования. Нельзя перегружать следующие операторы:

“.”, “.*”, “?:”, “::”, “sizeof”, “#”, “##”.

31
Однако можно перегружать некоторые операторы, на
которых и не подумаешь:

“->”, “[]”, “()”, “new” и “new[]”, “delete” и “delete[]”


и др.

Нужно добавить, что операторы присваивания "=", взятия


индекса "[]", вызова "()" и косвенного доступа "->" могут быть
определены только как методы класса.
Особый интерес вызывает оператор ->, при его перегрузке
переменная начинает себя вести как указатель, что подводит нас
к идиоме умных указателей. Оператор круглые скобки
используется для определения функциональных объектов.2

Рекомендации
Кроме всего выше сказанного нужно добавить еще
несколько слов о перегрузке операторов:
- нельзя определить оператор для встроенных типов, но они
могут участвовать в операции;
- нельзя определять один и тот же оператор и как метод
класса и как друг класса;
- все операторы должны возвращать результат типа
отличного от void; исключение operator();
- приоритет операторов не меняется;
- operator() может иметь любое количество аргументов, то
есть он не подпадает под обычную классификацию
операторов.

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

1. Являются ли операторы абсолютно необходимыми?


2. Какие зарезервированные слова используются для
определения операторов?
3. Могут ли операторы не возвращать результат?
4. Как классифицируются операторы?
5. Как компилятор различает префиксную и
постфиксную запись унарных операторов?
6. Какие операторы нельзя перегружать?
7. Каков синтаксис вызова операторов?
8. Как используется оператор "()"?
9. Как используется оператор "->"?
10. Зачем нужны две формы перегрузки (как метод
класса и как дружественная функция)?
11. Какому типу принадлежит оператор вывода в поток
"<<"?
12. В каких случаях обязательно определять оператор
присваивания?

33
Задания

Вариант 1
а) Создать класс целых чисел. Определить операторы "++" и "+",
как методы класса, а "- -" и "-" как дружественные функции.
Операторы должны позволять осуществления операций, как с
переменными данного класса, так и с переменными встроенного
целого.
b) Создать класс Set – множество целых чисел, используя
динамическую память. Определить операторы работы с
множествами: "+" – объединение, "*" – пересечение, "-"
вычитание, как дружественные функции, а "+=" – включение
нового элемента в множество, "==" – сравнения на равенство, и
др. как методы класса. Определить операторы "<<" и ">>". Также
определить функцию определения принадлежности элемента
множеству.
Вариант 2
а) Создать класс 2-D координат. Определить операторы "+" и "-"
как дружественные функции, а операторы присваивания и
сравнения как методы класса. Должны быть возможность
осуществления операций, как между координатами, так и между
координатами и обычными числами.
b) Создать класс Stack – стек, используя динамическую память.
Определить операторы "+" – сложения стеков, "=" –
присваивания, "()" – выдачи нового стека содержащего
последние n элементов - как методы класса. Определить
операторы сравнения "==", "!=", "<", ">", как дружественные
функции. Для реализации последних двух операторов
определить функцию, вычисляющую норму элементов стека.
Определить операторы ввода/вывода в поток. Класс должен быть
полностью функционален, то есть содержать все необходимые
конструкторы и деструктор.
34
Вариант 3
а) Создать класс 3-D координат. Определить операторы "+", "-",
"=" как методы класса, а операторы сравнения как
дружественные функции. Должны быть возможность
осуществления операций, как между координатами, так и между
координатами и обычными числами.
b) Создать класс Queue - очередь, используя динамическую
память. Определить операторы "+" – сложения стеков, "=" –
присваивания как методы класса. Определить операторы
сравнения "==", "!=", "<", ">", как дружественные функции. Для
реализации последних двух операторов определить функцию,
вычисляющую норму элементов очереди. Перегрузить
операторы "<<" и ">>" для ввода/вывода в поток, так и для
вставки/извлечения элементов в/из очереди.
Вариант 4
а) Создать класс Date – дата, содержащая поля: день, месяц, год.
Определить операторы "+" и "-", как методы класса, а "++" и "--"
в обеих формах (префиксная и постфиксная) как дружественные
функции. Оператор "+" должен позволять осуществление
операции только с переменными встроенного int. (x=y+5;).
Должна быть предусмотрена корректная работа с високосными
годами.
b) Создать класс List - очередь. Определить операторы "+" –
сложения списков, "=" – присваивания как методы класса.
Определить операторы сравнения "==", "!=", "<", ">", как
дружественные функции. Перегрузить операторы "<<" и ">>" для
ввода/вывода в поток, так и для вставки/извлечения элементов
в/из очереди. Класс должен быть полностью функционален, то
есть содержать все необходимые конструкторы и деструктор.
Для упрощения работы используйте класс либо структуру
ListItem для представления элементов списка, на которые и
ссылается List.
35
Вариант 5
а) Создать класс действительных чисел. Определить операторы
"++" и "+", как методы класса, а "- -" и "-" как дружественные
функции. Операторы должны позволять осуществления
операций, как с переменными данного класса, так и с
переменными встроенного double.
b) Создать класс Vector – вектор, используя динамическую
память. Определить операторы "+" – поэлементное сложения
векторов, "-" – поэлементное вычитание векторов, "=" –
присваивания, как методы класса. Определить операторы
сравнения "==", "!=", "<", ">", как дружественные функции. Для
реализации последних двух операторов определить функцию,
вычисляющую норму элементов вектора. Перегрузить операторы
"<<" и ">>" для ввода/вывода в поток. Класс должен содержать
все необходимые конструкторы и деструктор.
Вариант 6
а) Создать класс целых чисел большой величины. Определить
операторы "+" и "*", как методы класса, а "-" и "/" как
дружественные функции. Перегрузить операторы инкремента и
декремента в обеих формах (префиксная и постфиксная).
Операторы должны позволять осуществления операций, как с
переменными данного класса, так и с переменными встроенного
long.
b) Создать класс Matrix – матрица, используя динамическую
память. Определить операторы "+" –сложение матриц, "-" –
вычитание матриц, "=" – присваивания, как методы класса.
Определить операторы сравнения "==", "!=", "<", ">", как
дружественные функции. Для реализации последних двух
операторов определить функцию, вычисляющую норму
элементов матрицы. Определить оператор "[]" так, чтобы
обращение [][] к элементам имело смысл. Перегрузить
операторы "<<" и ">>" для ввода/вывода в поток.
36
Вариант 7
а) Создать класс Bool – логические переменные. Определить
операторы "+" – логическое ИЛИ, "*" – логическое И "^" –
ИСКЛЮЧИТЕЛЬНОЕ ИЛИ, как методы класса, а операторы
"==" и "!=" как дружественные функции. Операторы должны
позволять осуществления операций, как с переменными данного
класса, так и с переменными встроенного int. (Если целое число
отлично от нуля, считается что переменная истинна, в противном
случае ложна).
b) Создать класс String – строку, используя динамическую
память. Определить операторы "+" –сложение строк, "=" и "+=" –
присваивания, как методы класса. Определить операторы
сравнения "==", "!=", "<", ">", как дружественные функции.
Операторы должны работать как со String, так и с char*.
Определить оператор "[]" для доступа к каждому символу в
отдельности. Перегрузить операторы ввода/вывода в поток.
Вариант 8
b) Создать класс Stack – стек, используя динамическую память.
Определить операторы "+" – сложения стеков, "=" –
присваивания, "()" – выдачи нового стека содержащего
последние n элементов - как методы класса. Определить
операторы сравнения - "==", "!=", "<", ">", как дружественные
функции. Для реализации последних двух операторов
определить функцию, вычисляющую норму элементов стека.
Определить операторы ввода/вывода в поток.
а) Определить класс Complex – комплексные числа. Определить
все односимвольные операторы как дружественные операторы, а
двухсимвольные как методы класса. Исключение – оператор
присваивания, который может быть только методом класса и
операторы вводы/вывода в поток. Сложение и вычитание должно
производиться как с комплексными числами, так и со
встроенным double.
37
Вариант 9
а) Создать класс Time – время, содержащая поля: часы, минуты,
секунды. Определить операторы "+" и "-", как дружественные
функции, а "++" и "--" в обеих формах (префиксная и
постфиксная) как методы класса. Операторы должны позволять
осуществления операций, как с переменными данного класса, так
и с переменными встроенного int (обозначает секунды).
b) Создать класс Queue - очередь, используя динамическую
память. Определить операторы "+" – сложения стеков, "=" –
присваивания как методы класса. Определить операторы
сравнения "==", "!=", "<", ">", как дружественные функции. Для
реализации последних двух операторов определить функцию,
вычисляющую норму элементов очереди. Перегрузить
операторы "<<" и ">>" для ввода/вывода в поток, так и для
вставки/извлечения элементов в/из очереди.
Вариант 10
а) Создать класс Bool – логические переменные. Определить
операторы "+" – логическое ИЛИ, "*" – логическое И "^" –
ИСКЛЮЧИТЕЛЬНОЕ ИЛИ, как дружественные функции, а
операторы "==" и "!=" как методы класса. Операторы должны
позволять осуществления операций, как с переменными данного
класса, так и с переменными встроенного int. (Если целое число
отлично от нуля, считается что переменная истинна, в противном
случае ложна.)
b) Создать класс Set – множество целых чисел, используя
динамическую память. Определить операторы работы с
множествами: "+" – объединение, "*" – пересечение, "-"
вычитание, как методы класса, а "+=" – включение нового
элемента в множество, "==" – сравнения на равенство, и др. как
дружественные функции. Определить операторы "<<" и ">>".
Также определить функцию определения принадлежности
элемента множеству.
38
Вариант 11
а) Создать класс Date – дата, содержащая поля: день, месяц, год.
Определить операторы "+" и "-", как методы класса, а "++" и "--"
в обеих формах (префиксная и постфиксная) как дружественные
функции. Оператор "+" должен позволять осуществление
операции только с переменными встроенного int. (x=y+5;).
Должна быть предусмотрена корректная работа с високосными
годами.
b) Создать класс String – строку, используя динамическую
память. Определить операторы "+" –сложение строк, "=" и "+=" –
присваивания, как дружественные функции. Определить
операторы сравнения "==", "!=", "<", ">", как методы класса.
Операторы должны работать как со String, так и с char*.
Определить оператор "[]" для доступа к каждому символу в
отдельности. Перегрузить операторы "<<" и ">>" для
ввода/вывода в поток.
Вариант 12
а) Создать класс 2-D координат. Определить операторы "+" и "-"
как дружественные функции, а операторы присваивания и
сравнения как методы класса. Должны быть возможность
осуществления операций, как между координатами, так и между
координатами и обычными числами.
b) Создать класс List - очередь. Определить операторы "+" –
сложения списков, "-" – вычитание (как в множестве) как
дружественные функции. Определить операторы сравнения "==",
"!=", "<", ">", как методы класса. Перегрузить операторы "<<" и
">>" для ввода/вывода в поток, так и для вставки/извлечения
элементов в/из очереди. Класс должен быть полностью
функционален, то есть содержать все необходимые
конструкторы и деструктор. Для упрощения работы используйте
класс либо структуру ListItem для представления элементов
списка, на которые и ссылается List.
39
ЛАБОРАТОРНАЯ РАБОТА №4

Тема: Наследование и композиция


Цели работы:
 изучение наследования, преимущества и недостатки;
 изучение композиции;
 изучение правил определения наследования и композиции;
 изучение форм наследования;
 изучение инициализаторов;
 принцип подстановки;
 наследование и композиция – что выбрать.

Основные понятия

Мотивация
Наследование один из трех основных механизмов
объектно-ориентированного наследования. Наследование может
быть изучено с двух точек зрения: разработчика и пользователя
класса.
С точки зрения разработчика наследование означает, что
поведение и свойства производного класса, являются
расширением свойств базового класса. А с точки зрения
пользователя – наследование обозначает существование ряда
частично взаимозаменяемых классов с единым интерфейсом.
(Инженеры ИТ и машиностроения оба, в первую очередь
являются инженерами).

Преимущества наследования:
- уменьшение объема кода и его повторное
использование;
- уменьшение количества ошибок и себестоимости;
- единый интерфейс и подставляемость;
- увеличение скорости разработки;

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

Определение и использование наследования


Наследование обозначается указанием базового класса
после имени производного, через двоеточие. Пример
наследования:

class Animal{
int NofLegs;
public:
void Say(){ cout<<”!!!”; }
};
class Dog: public Animal{ // наследование
...
};
void main(){
Dog d;
d.Say();
}

Из примера видно, что функция и переменная,


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

41
Принцип подстановки
Кроме всего выше сказанного, наследование предполагает,
что объект производного класса может быть использован вместо
объекта базового:

void main(){
Animal *ptr = new Dog;
}

Из примера видно что имеется указатель базового класса –


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

Формы наследования
В отношении наследования, С++ очень богатый язык, так
как существуют пять разных форм наследования. На этой
лабораторной работе мы исследуем три из них, те что
называются простыми.
- открытое;
- закрытое;
- защищенное;
Разные формы наследования используются, для того чтобы
изменить статус доступа для переменных членов класса.
Например, при использовании закрытого наследования, все
доступные (открытые и защищенные) переменные базового
класса становятся закрытыми в производном. А при защищенном
наследовании – защищенными. Закрытые переменные базового
класса при любой форме наследования становятся
недоступными. Кроме того формы наследования, отличаются
тем, что открытое наследование создает подтип данных, то есть
соответствует принципу подстановки, а защищенное и открытое
– нет.

42
Композиция
Композиция это еще один механизм, связанный с ООП,
обозначающий отношение между объектами, тогда как
наследование это отношение между классами.
Наследование реализует отношение быть "is a". Собака
является млекопитающим, а млекопитающее – животным.
Композиция же реализует отношение содержать "has a".
Автомобиль содержит двигатель и колеса.

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

class Car{
Engine e;
};

Инициализаторы
Как известно закрытые переменные базового класса
становятся недоступными, следовательно, их невозможно
инициализировать в конструкторе производного и, кроме того,
это противоречит принципу повторного использования кода.
Выход один – вызов конструктора базового класса. Так оно и
происходит, но только для конструкторов по умолчанию и
копий, сгенерированного компилятором. Все остальные должны
быть вызваны вручную. Та же проблема и при композиции, так
же может быть использован только конструктор по умолчанию и
копий3. Для решения этих проблем используются
3
Причина того, что компилятор создает код вызова только для этих
конструкторов, состоит в том, что компилятор знает, как их вызывать и
что им передавать в параметрах.
43
инициализаторы – позволяющие вызывать любые конструкторы
и производить любую инициализацию.

class Engine{
int power;
public:
Engine(int p){power=p;}
};
class Transport{
...
public:
Transport(char*);
};
class Car:public Transport{ // наследование
Engine e; // композиция
public:
Car():Transport(“automobile”),e(10){}
};

Для вызова конструкторы базового класса, после круглых


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

Что выбрать
Так как и наследование и композиция являются
инструментами реутилизации кода, встает вполне осмысленный
вопрос: когда использовать наследование и когда композицию.
По этому поводу существует много разных рекомендаций,
однако, легче всего запомнить следующее правило: нужно
поставить себе вопрос: новый класс является ли подтипом (Dog
is an Animal), если мы получили утвердительный ответ значит
должно быть использовано наследование; в противном случае
другой вопрос: не является ли новый класс контейнером (Car has
a door) – в этом случае композиция.
44
К сожалению, для некоторых случаев данная стратегия
бесполезна. К примеру, класс множество можно создать на базе
класса список, но какой механизм использовать. Можно
наследование, а можно композицию. В этом случае правила
сложнее. Нужно задать себе несколько вопросов:
- будут ли объекты нового класса подставляться в
место старого?
- нужно ли переопределить какую-нибудь
виртуальную функцию?
- обрабатывает ли новый тип те же сообщения что и
старый?
- Не является ли базовый класс абстрактным?
Если ответы положительны, используйте наследование4.

Контрольные вопросы:

1. Чем различаются формы наследования?


2. Что собой представляет композиция?
3. Каковы преимущества наследования?
4. Как работают конструкторы при наследовании?
5. Что такое принцип подстановки?
6. В каких случаях используется наследование, а в
каких композиция?
7. Как инициализируются элементы контейнера?

4
Часто для замены композиции наследованием используется закрытое
наследование – создающее подкласс, а не подтип.
45
Задания

Вариант 1
а) Создать иерархию классов игра – спортивная игра – волейбол.
Определить конструкторы, деструктор, оператор присваивания и
другие необходимые функции.
b) Создать класс колесо, имеющий радиус. Определить
конструкторы и методы доступа. Создать класс автомобиль,
имеющий колеса и строку обозначающую фирму-производителя.
Создать производный класс грузовой автомобиль, отличающийся
грузоподъемностью. Определить конструкторы, деструктор и
другие необходимые функции.
Вариант 2
а) Создать класс студент, у которого есть имя, специальность,
год обучения и средний балл. Определить функции установки,
изменения данных, сравнения. Для задания текстовых полей
использовать оператор new. Определить конструкторы,
деструктор и другие необходимые функции. Создать
производный класс студент-дипломник, для которого
определена тема дипломной работы. Так же, необходимо
определить все необходимые функции.
b) Создать класс комната, имеющая площадь. Определить
конструкторы и методы доступа. Создать класс однокомнатных
квартир, содержащий комнату и кухню (ее площадь), этаж
(комната содержится в классе однокомнатная квартира).
Определить конструкторы, методы доступа. Определить
производный класс однокомнатные квартиры с адресом
(дополнительное поле - адрес). Определить конструкторы,
деструктор и вывод в поток.

46
Вариант 3
а) Создать класс мебель, содержащий информацию о цене, стиле
и области применения (офисная, кухонная и др. мебель). Для
задания текстовых полей использовать динамическую память.
Определить производные классы: стол и стул. Определить
конструкторы, деструктор, оператор присваивания и другие
необходимые функции.
b) Создать класс гараж, имеющий площадь. Определить
конструкторы и методы доступа. Создать класс дом, содержащий
комнаты, кухню (ее площадь) и гараж. Определить производный
класс дача (дополнительный параметр – количество земли).
Определить конструкторы, деструктор и вывод в поток.
Вариант 4
а) Создать иерархию классов человек и сотрудник, занимающий
соответствующий пост и получающий соответствующую
зарплату. Переопределить вывод в поток и ввод из потока,
конструктор копирования, оператор присваивания через
соответствующие функции базового класса.
b) Создать класс карта, имеющая ранг и масть. Карту можно
перевернуть и открыть. Создать класс - колода карт,
содержащий карты. Создать два производных класса от колоды
карт, в одном карты могут извлекаться только по порядку, в
другом – случайным образом.
Вариант 5
а) Создать класс жидкость, имеющий название (указатель на
строку), плотность. Определить конструкторы, деструктор и
операторы вывода в поток. Создать производный класс -
спиртные напитки, имеющий крепость. Определить функции
переназначения плотности и крепости.

47
b) Создать класс кнопка, содержащая некий текст. Определить
конструкторы и метод доступа. Создать класс окно, содержащий
кнопку и координаты окна. Определить конструкторы и
деструктор. Определить производный класс окно с кнопкой и
сообщением. Определить конструкторы, деструкторы и
операторы вывода в поток.
Вариант 6
а) Создать класс человек, имеющий имя (указатель на строку),
возраст, вес. Определить конструкторы, деструктор и оператор
присваивания. Создать производный класс - совершеннолетний,
имеющий номер паспорта. Определить конструкторы по
умолчанию и с разным числом параметров, деструкторы,
операторы вывода в поток. Определить функции переназначения
возраста и номера паспорта.
b) Определить класс корова состоящее из следующих полей:
идентификационный номер – должно быть гарантировано
уникально (для чего использовать статический счетчик), средний
надой, возраст, кличку и породу. Для задания текстовых полей
использовать оператор new. Определить класс стадо, состоящее
из неограниченного количества коров. Определить методы
вставки, удаление, определения среднего надоя по стаду и общий
надой, и другие необходимые функции.
Вариант 7
а) Создать иерархию классов здание, административное здание
и жилое здание. Переопределить вывод в поток и ввод из потока,
конструктор копирования, оператор присваивания через
соответствующие функции базового класса.
b) Создать класс студент, у которого есть имя, специальность,
год обучения и средний балл. Определить функции установки,
изменения данных, сравнения. Для задания текстовых полей
использовать оператор new. Определить конструкторы,
деструктор и другие функции. Создать класс группа содержащий
48
студентов (неограниченное количество). Определить методы
вставки, удаление студентов, определения среднего балла по
группе, конструкторы, деструкторы и др. необходимые функции.
Вариант 8
а) Создать иерархию классов: учебное заведение – абстрактный
базовый класс и дошкольное у.з., среднее у.з. и высшее у.з. –
производные классы. Переопределить ввод/вывод в поток,
конструктор копирования, оператор присваивания через
соответствующие функции базового класса.
b) Создать класс двигатель, у которого есть фирма-
производитель, тип, мощность. Определить функции установки,
изменения параметров двигателя. Создать иерархию классов:
корабль – базовый класс и пассажирский пароход –
производный. Корабль имеет двигатель, грузоподъемность,
водоизмещение, название, порт приписки. Для задания
текстовых полей использовать оператор new.
Вариант 9
а) Создать иерархию классов пресса - газета, журнал и
электронное издание. Определить поля: название издания,
тираж, подписной индекс, периодичность издания. Для задания
текстовых полей использовать оператор new. Переопределить
вывод в поток и ввод из потока, конструктор копирования,
оператор присваивания через соответствующие функции
базового класса.
b) Определить класс Processor - процессор, содержащий
информацию о названии процессора, его частоте, используемой
технологии производства и размере внутренней памяти.
Определить класс Computer - компьютер, состоящий из
процессора и других компонентов. Для задания текстовых полей
использовать оператор new. Определить конструкторы, функции
вывода в поток и другие необходимые функции.

49
Вариант 10
а) Создать иерархию классов транспорт – воздушный
транспорт – вертолет. Переопределить вывод в поток и ввод из
потока, конструктор копирования, оператор присваивания через
соответствующие функции базового класса.
b) Определить класс химический элемент, содержащий
информацию о названии элемента его химических свойствах.
Определить класс медикаменты, содержащий разное количество
хим. элементов и в разном количестве. Определить
конструкторы, функции вывода в поток и другие необходимые
функции.
Вариант 11
а) Создать иерархию классов датчик – абстрактный базовый
класс и датчики температуры, влажности и скорости ветра. Для
каждого класса определить свои единицы измерения и способ
снятия данных о значениях состояния окружающей среды.
Переопределить вывод в поток и ввод из потока, конструктор
копирования, оператор присваивания через соответствующие
функции базового класса.
b) Создать класс Устройство сбора информации о погоде
состоящее из датчиков по заданию а. Для снятия значений
создать класс генератор значений для каждого датчика.
Продемонстрировать работу устройства.
Вариант 12
а) Создать иерархию классов Шахматная фигура – абстрактный
класс, содержащий поле – цвет. Создать производные классы все
фигуры, содержащие свое название и координаты позиции на
доске. Для задания текстовых полей использовать оператор new.
Переопределить вывод в поток и ввод из потока, конструктор
копирования, оператор присваивания через соответствующие
функции базового класса.
50
b) Создать класс Шахматы, состоящее из набора фигур из
задания а, и шахматной доски – двумерный массив 8 на 8.
Должна быть возможность удаления фигур с поля. Определить
конструктор динамически создающий фигуры и задающий их
позиции в шахматной нотации (Е2). Определить конструктор
копий и оператор присваивания.

51
ЛАБОРАТОРНАЯ РАБОТА №5

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


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

Основные понятия

Мотивация
Множественное наследование, представляет собой
наследование от двух и более классов. Для того, что бы понять,
зачем нам необходимо множественное наследование нужно
вспомнить, что одиночное наследование не решает всех проблем,
так как иногда вынуждает выбирать между двумя подходящими
базовыми классами.
Интересно высказывание Буча относительно этого
механизма: «Множественное наследование - как парашют: как
правило, он не нужен, но, когда вдруг он понадобится, будет
жаль, если его не окажется под рукой»5.
Таким образом, данный механизм абсолютно необходим.
Однако, он используется далеко не во всех языках, но реализован
в С++. Приведем пример:
Необходимо описать класс Окно с кнопкой и заголовком,
при чем классы Окно, Окно с кнопкой, Окно с заголовком уже
созданы и представляют собой следующую иерархию.

5
Буч Г. «Объектно-ориентированный анализ и проектирование с
примерами приложений на С++». Москва «Бином» 1998 г.
52
Window

Button Titled
Window Window

Button
Titled
Window

Рис. 1 Наследование от двух родственных классов.

Оба класса подходят под определение базового, для


нового, еще не созданного класса. В отсутствии множественного
наследования пришлось бы выбирать и дописывать
недостающую функциональность. Мало того, что пользователь
ставится перед выбором, дописывание само по себе
противоречит принципу минимизации кода и его повторного
использования.
Множественное наследование имеет место и в жизни:
- Практикант является студентом и временным
сотрудником. Он получает оценку за свой труд, как
студент и, в тоже время, выполняет функции
возложенные на сотрудника, а возможно еще и получает
зарплату.
- Зубной техник является медработником, так как
закончил медицинское учебное заведение; в то же время
он выполняет работу более свойственную кузнецу или
ювелиру.
- Утконос кормит своих детенышей молоком, однако
откладывает яйца.
53
- Акции являются бумагой и ценной собственностью.
- Класс iostream наследует классы: istream и ostream.
Правда, последний пример не совсем из жизни, а опять-
таки из программирования.

Определение
Множественное наследование определяется следующим
образом:

class Student{
public:
int mark;
...
};
class Worker{
public:
int salary;
};
class Practicant: public Student, public Worker{
};
void PutMark(Student& s, int mark){
s.mark = mark;
}
void PutSalary(Worker& w, int salary){
w. salary = salary;
}

void main(){
Practicant p;
PutMark(p,5);
PutSalary(p,200);
}

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


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

class A{
public:
int x;
};
class B{
public:
int x;
};

class C: public A, public B{

};
void main(){
C c;
c.x = 10;
}

Таким образом класс С содержит две переменные с одним и


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

55
совпадать, но имена классов нет6. То есть, для указания какая
переменная используется нужно указать класс, от которого
наследована переменная:

c.A::x = 10;
c.B::x = 5;

Еще сложнее обстоят дела в том случае если классы A и B


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

class A{
public:
int x;
A(int x){this->x=x;}
};
class B: virtual public A{
public:
B(int x):A(x){}
};
class C: virtual public A{
public:
C(int x):A(x){}
};
class D: public B, public C{

6
В соответствии с новым стандартом могут существовать классы с
одинаковыми именами, но в разных пространствах имен.
56
public:
D(int x):A(x),B(x),C(x){}
}

Как видно классы B и C должны оба виртуально


наследовать класс A. Сразу же решается проблема
существования двух идентичных переменных. Кроме того, в
этом случае необходимо вызывать конструктор самого базового
класса вручную, так как показано в примере. Однако не всегда
вся иерархия проектируется одним программистом, а некоторые
классы уже возможно откомпилированы, в таких случаях решить
проблему практически не возможно.

Контрольные вопросы:

1. Каковы преимущества множественного


наследования?
2. Каковы сложности реализации множественного
наследования?
3. Классифицируйте проблемы связанные с
множественным наследованием?
4. Как решаются проблемы связанные с
множественным наследованием?
5. Зачем необходимо виртуальное наследование?
6. Как реализовано виртуальное наследование?
7. Как работают конструкторы при множественном и
виртуальном наследовании?

57
Задания

Для всех вариантов необходимо создать две программы,


которые иллюстрировали бы оба приведенных выше примера
множественного наследования.

Вариант 1
a) Создать иерархии наследования: студент, сотрудник -
практикант.
b) Создать иерархии наследования: человек - студент,
сотрудник - практикант.
Вариант 2
a) Создать иерархии наследования: млекопитающее,
пресмыкающееся – утконос.
b) Создать иерархии наследования: животное - млекопитающее,
пресмыкающееся – утконос.
Вариант 3
a) Создать иерархии наследования: телевизор, цифровое
устройство – монитор.
b) Создать иерархии наследования: электронное устройство -
телевизор, цифровое устройство – монитор.
Вариант 4
a) Создать иерархии наследования: авторучка, карандаш –
авторучка с грифелем.
b) Создать иерархии наследования: письменные
принадлежности - авторучка, карандаш – авторучка с
грифелем.

58
Вариант 5
a) Создать иерархии наследования: катер, мотоцикл – водный
мотоцикл.
b) Создать иерархии наследования: транспорт - катер, мотоцикл
– водный мотоцикл.
Вариант 6
a) Создать иерархии наследования: диван, кровать – диван-
кровать.
b) Создать иерархии наследования: мебель - диван, кровать –
диван-кровать.
Вариант 7
a) Создать иерархии наследования: воздушный транспорт,
пассажирский транспорт - лайнер Boing 747
b) Создать иерархии наследования: транспорт - воздушный
транспорт, пассажирский транспорт - лайнер Boing 747
Вариант 8
a) Создать иерархии наследования: удочка, телескоп –
телескопическая удочка.
b) Создать иерархии наследования: предмет - удочка, телескоп –
телескопическая удочка.
Вариант 9
a) Создать иерархии наследования: бумага, собственность –
акции.
b) Создать иерархии наследования: предмет - бумага,
собственность – акции.
Вариант 10
a) Создать иерархии наследования: легковой автомобиль,
грузовой автомобиль – внедорожник.
b) Создать иерархии наследования: автомобиль - легковой
автомобиль, грузовой автомобиль – внедорожник.
59
Вариант 11
a) Создать иерархии наследования: книга, тетрадь – записная
книжки.
b) Создать иерархии наследования: бумага - книга, тетрадь –
записная книжки.
Вариант 12
a) Создать иерархии наследования: самолет, корабль - водный
самолет.
b) Создать иерархии наследования: транспорт - самолет,
корабль - водный самолет.

60
ЛАБОРАТОРНАЯ РАБОТА №6
Тема: Полиморфизм. Виртуальные функции
Цели работы:
 исследование полиморфизма;
 изучение принципов позднего связывания;
 изучение виртуальных функций;
 полиморфизм ad-hoc;
 реализация виртуальных функций;
 изучение абстрактных классов.

Основные понятия
Слово полиморфизм греческого происхождения и
приблизительно переводится как "много форм" (poly — много,
morphos — форма). Слово morphos имеет отношение к
греческому богу сна Морфею (Morphus), который мог являться
спящим людям в любой форме, в какой только пожелает.
В жизни полиморфные виды — это те, которые
характеризуются наличием различных форм или характеристик.
В химии полиморфные соединения могут кристаллизоваться, по
крайней мере, в двух различных формах (например, углерод
имеет две кристаллические формы — графита и алмаза). С
другой стороны, инженер ИТ воспринимается в первую очередь
как человек, а потом как инженер (принцип подстановки).
В языках программирования полиморфный объект — это
сущность (переменная, аргумент функции), хранящая, во время
выполнения программы, значения различных типов.
Полиморфные функции — это те функции, которые имеют
полиморфные аргументы.
В С++ полиморфизм — естественное следствие:
 отношения "быть экземпляром";
 механизма пересылки сообщений;
 наследования;
 принципа подстановки.

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

Полиморфные функции — это одна из наиболее мощных


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

62
Позднее связывание
Под поздним связыванием нужно понимать механизм
позволяющий определять динамический тип во время
выполнения программы, а не во время компиляции. Похожий
механизм это дескрипторы файлов, так как файлы открываются
во время выполнения программы, а не во время компиляции.
Данный механизм – основа полиморфизма, так как реализуют
виртуальные функции.

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

#include<iostream.h>
class Animal{
public:
void Say(){ cout<<"!!!\n";}
};
class Dog: public Animal{
public:
void Say(){ cout<<"GAV\n";}
};
class Cat: public Animal{
public:
void Say(){ cout<<"MIAU\n";}
};
void FunSay(Animal a){
a.Say();
}
void main(){
Animal a;
63
Dog d;
Cat c;
FunSay(a);
FunSay(d);
FunSay(c);
}

В данном примере глобальная функция не является


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

Полиморфизм ad-hoc
Приводящий в замешательство аспект переопределения
методов в языке C++ — это разница между переопределением
виртуального и невиртуального методов.
Еще большее замешательство возникает, если программист
пытается переопределить виртуальную функцию в подклассе, но
при этом указывает (возможно, по ошибке) другой тип
аргументов. Например, родительский класс содержит описание:
virtual void display (char *, int);

Подкласс пытается переопределить метод:


virtual void display (char *, short);

Поскольку списки аргументов различаются, то второе


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

Абстрактные классы
Отложенный метод (иногда называемый абстрактным
методом, а в C++ — чисто виртуальным методом) может
рассматриваться как обобщение переопределения. В обоих
случаях поведение родительского класса изменяется для
потомка. Для отложенного метода, однако, поведение просто не
определено. Любая полезная деятельность задается в дочернем
классе.

class Shape {
public:
...
virtual void draw() = 0;
...
};

Компилятор не разрешает пользователю создавать


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

65
Контрольные вопросы:

1. Какие механизмы подходят под определение


полиморфизм?
2. И обычные и виртуальные функции могут быть
переопределены. В чем различия?
3. Как физически реализованы виртуальные функции?
4. Что обозначает позднее связывание? Приведите
примеры.
5. Каковы условия реализации полиморфизма?
6. Какие переменные полиморфны?
7. Что значит полиморфизм ad-hoc?
8. Чем отличаются абстрактные классы от обычных?
9. Как определяется чисто виртуальная функция?

66
Задания
Вариант 1
Создать абстрактный базовый класс Worker с виртуальной
функцией начисления зарплаты. Создать производные классы
StateWorker, HourlyWorker и CommissionWorker, в которых
данная функция переопределена. В функции main определить
массив указателей на абстрактный класс, которым
присваиваются адреса объектов производных классов.
Вариант 2
Создать абстрактный базовый класс Figure с виртуальной
функцией - площадь. Создать производные классы Square, Circle,
Triangle, Trapeze в которых данная функция переопределена. В
функции main определить массив указателей на абстрактный
класс, которым присваиваются адреса различных объектов.
Площадь трапеции: S=(a+b)h/2.
Вариант 3
Создать абстрактный базовый класс прогрессия с виртуальной
функцией - сумма прогрессии. Создать производные классы:
арифметическая прогрессия и геометрическая прогрессия.
Каждый класс имеет два поля типа double. Первое - первый член
прогрессии, второе (double) - постоянная разность (для
арифметической) и постоянное отношение (для геометрической).
Определить функцию вычисления суммы, где параметром
является количество элементов прогрессии.
Арифметическая прогрессия aj=a0+jd, j=0,1,2,…
Сумма арифметической прогрессии: sn=(n+1)(a0+an)/2
Геометрическая прогрессия: aj=a0rj, j=0,1,2,…
Сумма геометрической прогрессии: sn=(a0-anr)/(1-r)

67
Вариант 4
Создать абстрактный класс Mammal – млекопитающие с
виртуальной функцией - описание. Определить производные
классы - Animal и Human. Для животных определить
производные классы Dog - собака и Cow – корова, в которых
функция переопределяется.
Вариант 5
Создать абстрактный базовый класс Lines с виртуальной
функцией f(x). Создать производные классы StraightLine, Ellipse,
hyperbola в которых данная функция переопределена. В функции
main определить массив указателей на абстрактный класс,
которым присваиваются адреса различных объектов. Уравнение
прямой: y=ax+b , эллипса: x2/a2+y2/b2=1, гиперболы: x2/a2-y2/b2=1
Вариант 6
Создать абстрактный базовый класс Figure с виртуальной
функцией - периметр. Создать производные классы Rectangle,
Circle, Triangle, Rhomb в которых данная функция
переопределена. В функции main определить массив указателей
на абстрактный класс, которым присваиваются адреса различных
объектов.
Вариант 7
Создать абстрактный базовый класс Container с виртуальными
функциями вставки и извлечения. Создать производные классы
Stack и Queue, в которых данные функция определены. В
функции main определить массив указателей на абстрактный
класс, которым присваиваются адреса объектов производных
классов.

68
Вариант 8
Создать абстрактный базовый класс Figure с виртуальной
функцией - площадь поверхности. Создать производные классы
параллелепипед, тетраэдр, шар в которых данная функция
переопределена.
Площадь поверхности параллелепипеда: S=6xy.
Площадь поверхности шара: S=4 r2.
Площадь поверхности тетраэдра: S=a2 3
Вариант 9
Создать абстрактный базовый класс Number с виртуальной
функцией - норма. Создать производные классы Complex, Vector
из 10 элементов, Matrix 2 на 2, в которых данная функция
переопределена. В функции main определить массив указателей
на абстрактный класс, которым присваиваются адреса различных
объектов.
Вариант 10
Создать абстрактный базовый класс Учебное заведение с
виртуальной функцией - описания. Создать производные классы
дошкольное у.з., среднее у.з. и высшее у.з. в которых данная
функция переопределена. В функции main определить массив
указателей на абстрактный класс, которым присваиваются
адреса различных объектов.
Вариант 11
Создать абстрактный базовый класс уравнение с виртуальной
функцией - корни уравнения. Создать производные классы
линейное уравнение и квадратное уравнение, в которых данная
функция переопределена.

69
Вариант 12
Создать абстрактный базовый класс Figure с виртуальной
функцией - объем. Создать производные классы параллелепипед,
пирамида, тетраэдр и шар, в которых данная функция
переопределена. В функции main определить массив указателей
на абстрактный класс, которым присваиваются адреса различных
объектов.
Объем параллелепипеда - V=xyz (x,y,z – стороны).
Объем пирамиды: V=xyh (x,y, - стороны, h - высота).
Объем тетраэдра: V= a3 2/12.
Объем шара: V=4 r3/3.

70
ЛАБОРАТОРНАЯ РАБОТА №7

Тема: Шаблоны
Цели работы:
 изучение необходимости шаблонов;
 изучение правил определения и использования шаблонов;
 изучение специализации шаблонов;
 изучение потенциальных проблем решаемых при помощи
шаблонов;

Основные понятия

Мотивация
Шаблоны один из самых мощных конструкций языка С++,
но в тоже время, один из менее изученных и редко
используемых. Причина этого кроется в его относительной
сложности и необычном синтаксисе.
Итак, шаблоны представляют собой механизм
позволяющий записывать алгоритм, не привязанный к
определенному типу. Чаще всего шаблоны используются для
создания контейнеров и абстрактных алгоритмов. Контейнеры
это объекты, содержащие другие объекты или данные, чаще
всего неопределенного количества, такие как массивы, стеки,
очереди, ассоциативные списки и др. Под абстрактными
алгоритмами необходимо понимать хорошо изученные способы
обработки данных, как сортировка, поиск и др., записанные без
указания типа данных.
Шаблонами бывают классы и функции. Шаблоны пришли
на смену макросов, так как последние очень часто приводят к
трудноуловимым ошибкам, потому что компилятор не
проверяет, да и не имеет возможности проверить их на
синтаксические ошибки.
Программист, записывая шаблон, создает заготовку,
которая, в последствии, используется уже с указанными типами
71
данных. То есть, на базе шаблона компилятор создает
нормальные функции.
Если шаблон используется с несколькими разными типами
данных, компилятор создает необходимый код для каждого типа
в отдельности. Другими словами, шаблоны не уменьшают
откомпилированный модуль, скорее даже наоборот, но
значительно уменьшает исходный код, что способствует,
уменьшению количества ошибок, упрощает внесение изменений
в код и упрощает восприятие программы в целом, так как
уменьшается количество создаваемых типов и функций.

Определение
Шаблон определяется при помощи зарезервированного
слова template:

template <class T>


T& searchmax(T* ptr, int size);

Template <class T>


class Stack{
T mas[10];
public:
...
};

Из примера видна специфика определения шаблона, для его


создания необходимо указать ЗС template, в угловых скобках
слово class7 и некий абстрактный тип, который будет
использоваться в определении шаблона. Исторически сложилось
так, что чаще всего используется идентификатор T, от слова type.
Для класса, при определении функций вне класса перед каждой
функцией необходимо повторно указать слово template.

Использование
7
В соответствии с новым стандартом также может быть использовано ЗС
“typename“ вместо “class”.
72
Функции используются практически также как и обычные
функции.

void main(){
int masi[10];
float masf[20];
cout<<searchmax(masi,10);
cout<<searchmax(masf,20);
}

Видно, что синтаксис вызова совпадает с обычным. Зная


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

void main(){
Stack<int> sti;
Stack<float> stf;
}

Необходимо указывать тип данных, так как, в этом случае,


на данном этапе трансляции компилятор не в состоянии
определить, для какого типа данных необходимо генерировать
код.

Специализация
Иногда нас не устраивает работа шаблона для
определенного типа данных. Приведем пример:

template <class T>


T& max(T& a, T& b){
if(a>b)return a;
return b;
}

Данный пример прекрасно работает для встроенных типов,


таких как int, float и др. Но для строк – нет. Причина в том, что в
этом случае будут сравниваться указатели на строки, а не
73
содержимое строк. Для других типов данных может быть не
определен оператор >. Решений может быть несколько: можно
запретить использование указателей на строки и использовать
тип String, для которого определен оператор >, то в этом случае
усложняется процесс разработки и правила использования.
Кроме того, запрет может быть только информативным, то есть
если пользователь знает, что нельзя использовать указатели. Сам
язык не имеет возможности запретить использование
определенного типа данных. Другое решение состоит в
использовании специализации, что обозначает запись еще одной
функции для определенного типа данных. В случае функций это
обычная не шаблонная функция с таким же именем и с
предопределенными параметрами. Данная функция будет иметь
больший приоритет, чем шаблонная. В случае классов можно
конечно определить нешаблонный класс с таким же именем, но
это не интересно, так как отличия могут быть минимальны. В
этом случае может быть использована специализация метода
класса. Специализированным может быть только метод
определенный вне класса. Приведем пример:

template <class T>


class Stack{
public:
void push(T& t);
void sort();
friend ostream& operator<<(ostream& os, Stack<T>& s);
};
template <class T>
void Stack<T>::sort(){
...
// здесь записывается абстрактный алгоритм
}
void Stack<char*>::sort(){
... // а здесь специализированный
}
template <class T>
ostream& operator<<(ostream& os, Stack<T>& s){
return os; // вывод содержимого стека
74
}
void main(){
Stack<int> si;
si.push(5);
Stack<char*> sc;
sc.push("Hello");
si.sort(); // Вызов абстрактной функции
sc.sort(); //Вызов специализированной
cout<<si<<sc;
// Вызов перегруженного оператора вывода в поток
}

Шаблонные классы могут наследоваться, также как и


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

class One{
};
template <class T>
class Two: public One{
};
template <class T>
class Three: public Two<T>{
};
class Four: public Three<int>{
};
template <class T>
class Five: public T{
};

Особый интерес представляет собой последний вид


наследования, так как имеет место наследование от параметра
шаблона. В этом случае T конечно же должен быть классом или
структурой.
Как видно из всего выше сказанного шаблоны
представляют собой интересный и мощный механизм. Есть свои
трудности и сложности, но изучение шаблонов окупает себя, так
как способствует созданию красивого компактного кода. Кроме
того, шаблоны не реализованы в других современных языках
75
широкого использования, а также позволяет осуществить новые
технические решения, такие как умные указатели, поддержку
транзакций, управление памятью и т.д.

Контрольные вопросы:

1. Что собой представляют шаблоны?


2. Каковы преимущества использования шаблонов?
3. Зачем используется специализация?
4. Что может быть шаблоном?
5. Каковы проблемы использования шаблонов?
6. Способствует ли использование шаблонов экономии
памяти?
7. А времени выполнения и компиляции программы?
8. Может ли шаблонный класс наследовать обычный и
наоборот?

76
Задания

Вариант 1
а) Создать шаблонную функцию, меняющую порядок элементов
следующим образом: первая половина списка смещается в конец,
а вторая в начало. На пример: 1 2 3 4 5 6 - 4 5 6 1 2 3. Функция
должна работать с массивом любой длины. Если количество
элементов нечетное, то средний элемент обрабатывать не надо.
b) Создать параметризированный класс Stack. Класс должен
содержать конструкторы, деструктор, а также функции push, pop,
empty, full и операторы ввода/вывода. Для выделения памяти
использовать оператор new.
Вариант 2
а) Создать шаблонную функцию, меняющую порядок элементов
попарно. На пример: 1 2 3 4 5 6 - 2 1 4 3 6 5. Функция должна
работать с массивом любой длины. Если количество элементов
нечетное, то последний элемент обрабатывать не надо.
b) Создать параметризированный класс Vector. Класс должен
содержать конструкторы, деструктор, функции getLength,
операторы [], +, - и операторы ввода/вывода. Для выделения
памяти использовать оператор new.
Вариант 3
а) Создать шаблонную функцию, подсчитывающую количество
повторений заданного параметра в списке. На пример: список -
0 2 3 4 3 6, параметр - 3, результат - 2. Функция должна работать
с массивом любой длины.
b) Создать параметризированные классы List и ListItem. Классы
должны содержать конструкторы, деструкторы, функции add, in,
remove, getLength, операторы [] и ввода/вывода.

77
Вариант 4
а) Создать шаблонную функцию поиска по заданному ключу.
Функция возвращает позицию первого подходящего элемента.
На пример: список - 0 2 3 4 3 6, параметр - 2, результат - 1. В
случае отсутствия подходящего элемента вернуть код ошибки.
Функция должна работать с массивом любой длины.
b) Создать параметризированный класс Queue - очередь. Класс
должен содержать конструкторы, деструктор, функции add, in,
get, getLength, операторы [] и ввода/вывода.
Вариант 5
а) Создать шаблонную функцию поиска второго по величине
элемента списка. На пример: список - 0 2 3 4 3 6, результат - 4.
Функция должна работать с массивом любой длины.
b) Создать параметризированный класс Set - множество. Класс
должны содержать конструкторы, деструктор, функции add, in,
remove, getLength, операторы “+” - объединение, “*” -
пересечение, “-” - разность и операторы ввода/вывода.
Вариант 6
а) Создать шаблонную функцию сортировки по возрастанию
пузырьковым методом. Функция должна работать с массивом
любой длины.
b) Создать параметризированный класс Map – ассоциативный
список, который содержит поля ключ и значение. Одному ключу
соответствует одно значение. Класс должен содержать
конструкторы, деструктор, функции add, removeByKey, getLength,
getByKey, getByValue, операторы [] и ввода/вывода.

78
Вариант 7
а) Создать шаблонную функцию, изменяющую попарно
элементы массива следующим образом: первый элемент будет
равняться сумме пары, а второй разности пары.
На пример: список - 0 2 3 4 3 6, результат 2 –2 7 –1 9 –3.
b) Создать параметризированный класс Matrix – матрица. Класс
должен содержать конструкторы, деструктор, функции getRows,
getCols, операторы [], +, -, * и ввода/вывода.
Вариант 8
а) Создать шаблонную функцию, меняющую порядок элементов
на обратный. 
На пример: 1 2 3 4 5 6 - 6 5 4 3 2 1. Функция должна работать
с массивом любой длины.
b) Создать параметризированный класс Tree – бинарное дерево.
Класс должен содержать конструкторы, деструктор, функции
add, in, функции обхода дерева и операторы ввода/вывода.
Вариант 9
а) Создать шаблонную функцию, подсчитывающую количество
элементов, значение которых больше заданного параметра. На
пример: список - 0 2 3 4 3 6, параметр - 5, результат - 1. Функция
должна работать с массивом любой длины.
b) Создать параметризированный класс Stack. Класс должен
содержать конструкторы, деструктор, а также функции push, pop,
empty и операторы ввода/вывода. Для выделения памяти
использовать оператор new, по n элементов.

79
Вариант 10
а) Создать шаблонную функцию сортировки элементов массива
по убыванию методом вставки. Функция должна работать с
массивом любой длины.
b) Создать параметризированный класс MultiMap – мульти-
ассоциативный список, который содержит поля ключ и список
значений. То есть, одному ключу может соответствовать
несколько значений. Класс должен содержать конструкторы,
деструктор, функции add, removeByKey, getLength, getByKey,
getByValue, операторы [] и ввода/вывода.
Вариант 11
а) Создать шаблонную функцию, подсчитывающую количество
элементов, значение которых меньше заданного параметра. На
пример: список - 0 2 6 4 3 3, параметр - 3, результат - 2. Функция
должна работать с массивом любой длины.
b) Создать параметризированный класс PriorityQueue –
приоритетная очередь. Каждый элемент очереди имеет
определенный приоритет и при извлечении выбирается элемент с
наибольшим приоритетом, а элементы с одинаковыми
приоритетами - по времени добавления. Класс должен
содержать конструкторы, деструктор, функции add, in, get,
getLength, операторы [] и ввода/вывода.
Вариант 12
а) Создать шаблонную функцию поиска второго минимального
по величине элемента списка. На пример: список - 0 2 3 4 3 6,
результат - 2. Функция должна работать с массивом любой
длины.
b) Создать параметризированный класс Matrix – матрица. Класс
должен содержать конструкторы, деструктор, функции getRows,
getCols, операторы [], +=, -=, *= и ввода/вывода.

80
СПИСОК ЛИТЕРАТУРЫ

81
СОДЕРЖАНИЕ

82
ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ С++
Методические указания к лабораторным работам

Авторы: Магистр ИТ Кожухарь С.А.


Магистр ИТ Чорбэ Р. !!!

Bun de tipar Formatul hârtiei 61x84 1/16


Hârtie ofset. Tipar ofset Tirajul 100 ex.
Coli de tipar Comanda nr.

Universitatea Tehnică a Moldovei


MD-2004, Chişinău, bd. Ştefan cel Mare, 168.
Secţia Redactarea şi Editare a U.T.M.
MD-2068, Chişinău, str. Studenţilor, 11

83
84

Вам также может понравиться