Требования к оформлению отчета

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

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

Разработка и описание программы включает описание вычислительного процесса на языке C++. Кроме текста программы, в отчёте представляется её пооператорное описание, даётся характеристика конструкций языка, обеспечивающих решение задачи конкретной лабораторной работы.

В приложении приводится текст программы, результат выполнения тестового примера.


ЛАБОРАТОРНАЯ РАБОТА №1
КЛАССЫ. КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ.
КОНСТРУКТОРЫ КОПИРОВАНИЯ.

ЦЕЛЬ РАБОТЫ

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

ТЕОРЕТИЧЕСКИЙ РАЗДЕЛ

Описание класса

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

class <имя>

{

[ private: ]

<описание скрытых элементов>

public:

<описание доступных элементов>

};

Описание заканчивается точкой с запятой.

Спецификаторы доступа private и public управляют видимостью элементов класса. Элементы, описанные после служебного слова private, видимы только внутри класса. Этот вид доступа принят в классе по умолчанию. Интерфейс класса описывается после спецификатора public. Спецификатор protected говорит о том, что данные-члены и функции-члены доступны для функций-членов данного класса и классов, производных от него. Действие любого спецификатора распространяется до следующего спецификатора или до конца класса. Можно задавать несколько секций спецификаторов доступа, порядок их следования значения не имеет.

Поля класса:

- могут иметь любой тип, кроме типа этого же класса (но могут быть указателями или ссылками на этот класс);

- могут быть описаны с модификатором const, при этом они инициализируются только один раз (с помощью конструктора) и не могут изменяться;

- могут быть описаны с модификатором static, но не как auto, extern и register.

Инициализация полей при описании не допускается.

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

сlass monstr

{ int health, ammo;

public:

monstr(int he = 100, int am = 10){ health = he: ammo = am;}

void draw(int x, int y, int scale, int position);

int get_health(){return health;}

int get_ammo(){return ammo;}

};

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

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

В приведенном классе содержится три определения методов и одно объявление (метод draw). Если тело метода определено внутри класса, он является встроенным (inline). Как правило, встроенными делают короткие методы. Если внутри класса записано только объявление (заголовок) метода, сам метод должен быть определен в другом месте программы с помощью операции расширения области видимости (::):

void monstr :: draw(int x, int у, int scale, int position){ /* тело метода*/ }

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

Описание объектов

Конкретные переменные типа “класс” называются экземплярами класса, или объектами. Время жизни и видимость объектов зависит от вида и места их описания и подчиняется общим правилам C++:

monstr Vasia; // Объект класса monstr с параметрами по умолчанию

monstr Super(200, 300); // Объект с явной инициализацией

monstr stado[100]; // Массив объектов с параметрами по умолчанию

monstr *beavis = new monstr (10); // Динамический объект

// (второй параметр задается по умолчанию)

monstr &butthead = Vasia; // Ссылка на объект

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

Доступ к элементам объекта аналогичен доступу к полям структуры. Для этого используются операция “.” (точка) при обращении к элементу через имя объекта и операция “>” при обращении через указатель, например:

int n = Vasia.get_ammo();

stado[5].draw(2,3,4,5);

cout << beavis>get_health();

Обратиться таким образом можно только к элементам со спецификатором public. Получить или изменить значения элементов со спецификатором private можно только через обращение к соответствующим методам.

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

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

- Конструктор не возвращает значение, даже типа void. Нельзя получить указатель на конструктор.

- Класс может иметь несколько конструкторов с разными параметрами для разных видов инициализации (при этом используется механизм перегрузки).

- Конструктор, вызываемый без параметров, называется конструктором по умолчанию.

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

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

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

- Конструкторы нельзя описывать с модификаторами const, virtual и static.

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

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

имя_класса имя_объекта [(список параметров)];

// Список параметров не должен быть пустым

имя_класса (список параметров);

// Создается объект без имени (список может быть пустым)

имя_класса имя_объекта = выражение;

// Создается объект без имени и копируется

Примеры:

monstr Super(200, 300), Vasia(50), Z;

monstr X = monstr(1000);

monstr Y = 500;

В первом операторе создаются три объекта. Значения не указанных параметров устанавливаются по умолчанию. Во втором операторе создается безымянный объект со значением параметра health = 1000 (значение второго параметра устанавливается по умолчанию). Выделяется память под объект X, в которую копируется безымянный объект. В последнем операторе создается безымянный объект со значением параметра health = 500 (значение второго параметра устанавливается по умолчанию). Выделяется память под объект Y, в которую копируется безымянный объект. Такая форма создания объекта возможна в том случае, если для инициализации объекта допускается задать один параметр.

В качестве примера класса с несколькими конструкторами усовершенствуем описанный ранее класс monstr, добавив в него поля, задающие цвет (skin) и имя (name):

enum color {red, green, blue}; // Возможные значения цвета

class monstr

{

int health, ammo;

color skin;

char *name;

public:

monstr(int he = 100, int am =10);

monstr(color sk);

monstr(char * nam);

int get_health(){return health;}

int get_ammo(){return ammo;}

};

//---------------------------

monstr::monstr(int he, int am)

{ health = he; ammo = am; skin = red; name = 0;

}

//---------------------------

monstr::monstr(color sk)

{

switch (sk)

{ case red : health = 100; ammo =10; skin = red; name = 0; break:

case green: health = 100; ammo = 20; skin = green; name = 0; break;

case blue : health = 100; ammo = 40; skin = blue; name = 0; break;

}

}

//---------------------------

monstr::monstr(char * nam)

{ name = new char [strlen(nam) + 1];

// К длине строки добавляется 1 для хранения нуль-символа

strcpy(name, nam);

health = 100; ammo = 10; skin = red;

}

//---------------------------

monstr * m = new monstr («Ork»);

monstr Green (green);

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

Существует еще один способ инициализации полей в конструкторе:

monstr::monstr(int he, int am):

health (he), ammo (am), skin (red), name (0){}

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

Конструктор копирования

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

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

Все конструкторы копирования имеют только один параметр — ссылку на объект своего класса. Разумно сделать эту ссылку постоянной, поскольку конструктор не должен изменять передаваемый ему объект. Например:

САТ(const CAT & theCat);

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

Стандартный конструктор копирования (создаваемый компилятором по умолчанию) просто копирует каждую переменную переданного ему объекта в переменные нового объекта. Такое копирование называется поверхностным (shallow copy). Хоть оно и подхо­дит для большинства случаев, могут возникнуть серьезные проблемы, если в классе полями являются указателями на объекты в динамической памяти.

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

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

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

Деструкторы

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

- для локальных объектов — при выходе из блока, в котором они объявлены;

- для глобальных — как часть процедуры выхода из main;

- для объектов, заданных через указатели, деструктор вызывается неявно при использовании операции delete.

Имя деструктора начинается с тильды (~), непосредственно за которой следует имя класса. Если деструктор явным образом не определен, компилятор автоматически создает пустой деструктор. Описывать в классе деструктор явным образом требуется в случае, когда объект содержит указатели на память, выделяемую динамически — иначе при уничтожении объекта память, на которую ссылались его поля-указатели, не будет помечена как свободная. Указатель на деструктор определить нельзя.

Деструктор:

- не имеет аргументов и возвращаемого значения;

- не может быть объявлен как const или static;

- не наследуется;

- может быть виртуальным.

Деструктор должен выглядеть так:

monstr::~monstr() {delete [] name;}