Конструкторы и деструктор

Методы класса

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

Пример

Основные этапы разработки класса

Разработка класса в ООП

Пример использования ООП (объектно-ориентированного проектирования)

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

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

Отсюда – этапы разработки: спроектировать и реализовать класс Rational – рациональная дробь; спроектировать и реализовать основную задачу; провести исследования решения; в случае необходимости – модифицировать класс Rational.

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

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

- определение имени класса (определяет новый тип; абстракция, с которой будем иметь дело);

- определение состояния класса (состав, типы и имена полей в классе, предназначенных для хранения информации, а также уровни их защиты); данные, определяющие состояние класса, получили название членов-данных класса;

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

Рассмотрим класс “рациональная дробь” – Rational [‘рэшенел].

Состояние класса: два поля типа “целое”, с именами num (от numerator [‘нью:мерэйте] – числитель) и den (от denominator [ди’номенэйте] – знаменатель. Пока ограничиваемся диапазоном представления в стандартных типах. Дополнительные требования: знаменатель не должен быть равен нулю, ни при каких условиях; знаменатель всегда положителен, знак дроби определяется знаком числителя; поля класса не должны быть доступны извне класса непредусмотренными классом способами.

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

Представление класса на языке программирования С++.

Для определения класса предусмотрено специальное ключевое слово class, но можно использовать и традиционное struct.

Синтаксис определения класса приведен на рис. 2-1.

Class имя_класса{ уровень_видимости: описания_полей_класса прототипы_функций-методов_класса уровень_видимости: . . . }; struct имя_класса{ уровень_видимости: описания_полей_класса прототипы_функций-методов_класса уровень_видимости: . . . };

Рис. 2-1. Определение класса

Уровень_видимости задается одним из трех ключевых слов:

- private [‘прайвит] – определяет закрытую часть класса, не доступную извне класса;

- protected [прэ’тэктид] – пока для нас аналогичен private; различия между ними проявляются при использовании наследования;

- public [‘паблик] – определяет открытую часть класса, видимую и доступную извне класса.

Определение класса можно проиллюстрировать следующим образом (рис. 2-2):

Рис. 2-2. Уровни видимости класса

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

Описания_полей_класса и прототипы_функций определяются в соответствии с обычными правилами С++ (рис. 2-3).

 

class X{ private: int a1; void f1(); protected: char a2; public: double a3; int f3(); }; struct X{ private: int a1; void f1(); protected: char a2; public: double a3; int f3(); };

Рис. 2-3. Пример определения класса

Объявляем экземпляр нового типа данных X – в соответствии с обычными правилами (независимо от того, определен класс с помощью struct или class):

X obj;

Тогда обращения:

obj.a1, obj.a2, obj.f1() – вызовут сообщения об ошибке (члены класса a1, a2 и f1() не видны (не доступны) извне класса;

obj.a3, obj.f3()– корректны.

Внутри функций-методов класса f1()и f3()можно без опасений использовать все имена: a1, a2, a3, f1()и f3().

Порядок следования ключевых слов, определяющих уровень видимости, произволен; они могут появляться неоднократно или отсутствовать в определении класса. Если в начале определения класса отсутствует уровень видимости, тогда для class предполагается private, а для struct – public (рис. 2-4).

 

определение class X{ int a1; void f1(); . . . }; эквивалентно class X{ private: int a1; void f1(); . . . }; a) определение struct X{ int a1; void f1(); . . . }; эквивалентно struct X{ public: int a1; void f1(); . . . }; b)

Рис. 2-4. Правила умолчания для class (a) и struct (b)

В дальнейшем для определения класса будем использовать ключевое слово class.

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

Члены-данные класса, определяющие его состояние, как правило, помещаются в private- или protected- область класса – они не должны быть непосредственно доступны извне класса. Доступ к состоянию класса должен определяться только интерфейсом класса.

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

Методы класса можно классифицировать по двум независимым критериям – по функциональному назначению и по их отношению к классу.

По функциональному назначению методы класса делятся на следующие категории:

- конструкторы – предназначены для инициализации состояния экземпляров класса при их создании;

- деструкторы – предназначены для выполнения каких-то дополнительных действий в момент уничтожения экземпляров класса;

- селекторы – предназначены для обработки состояния класса без его изменения;

- модификаторы – предназначены для изменения состояния класса;

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

По отношению к классу методы делятся на следующие две категории:

- функция-член класса – функция, принадлежащая самому классу и не существующая вне класса; прототипы функций-членов класса включены в определение класса;

- функция-друг класса – внешняя по отношению к классу функция, которая может существовать вне класса, но имеет доступ к закрытой (и защищенной) части класса. Прототип функции-друга класса также включается в определение класса, но начинается специальным ключевым словом friend.

Конструкторы и деструктор класса могут быть реализованы только функциями-членами класса и имеют специальный синтаксис. Другие методы класса имеют обычный синтаксис функций языка С++ и могут быть реализованы и функциями-членами, и функциями-друзьями класса. Мы пока ограничимся рассмотрением только функций-членов класса.

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

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

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

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

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

Копирующий конструктор инициализирует состояние класса значением другого экземпляра этого класса (создает копию существующего экземпляра класса). В списке параметров указывается единственный параметр, имеющий тип «ссылка на экземпляр класса».

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

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

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

В определении конструкторов отсутствует тип возвращаемого значения (конструктор ничего не возвращает); имя конструктора совпадает с именем класса; в классе может быть определено несколько конструкторов.

Деструктор служит для разрушения экземпляра класса. Опять же, память, занятая экземплярами класса, освобождается в соответствии с используемыми средствами языка; локальный объект уничтожается, когда осуществляется выход за пределы области видимости для этого объекта. Динамический объект уничтожается при выполнении оператора delete. Временный объект уничтожается по окончании вычислений, в которых он используется. В момент уничтожения объектов для них вызывается деструктор.

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

В определении деструктора также отсутствует тип возвращаемого значения; имя деструктора также совпадает с именем класса, но начинается символом ~.

Правила записи прототипов конструкторов разных типов и деструктора приведены на рис. 2-5.

 

Тип метода Прототип Примечания
Пустой конструктор имя_класса(); Инициализирует состояние предопределенными значениями
Инициализирующие конструкторы имя_класса(тип параметр, ...); Тип – любой; инициализирует состояние значениями, заданными в списке аргументов
Копирующий конструктор имя_класса(const имя_класса & параметр); Инициализирует состояние значением указанного в списке аргументов экземпляра данного класса; модификатор const указывает, что для инициализации экземпляра класса можно использовать константы
Деструктор ~ имя_класса ();  

Рис. 2-5. Правила записи прототипов конструкторов и деструктора

Пример определения класса Рациональная дробь (Rational) приведен ниже (рис. 2-6).

class Rational{

private:

int num, den; // состояние класса – числитель и знаменатель дроби

int gcd() const; // метод класса – нахождение наибольшего общего делителя

void reduce(); // метод класса – сокращение дроби

void correct(); // метод класса – коррекция дроби

protected:

/* отсутствует: можно совсем не включать данную часть класса, вместе с ключевым
словом
*/

public:

/* Конструкторы класса */

Rational(); // пустой конструктор

Rational(int num); // инициализирующий конструктор

Rational(int num, int den); // инициализирующий конструктор с 2 аргументами

/* Деструктор класса */

~Rational();

/* Методы класса: селекторы */

void print()const; // вывод значения дроби в поток

Rational add(const Rational &opd)const; // сложение дробей

/* Модификатор */

void assign(int x, int y); // присваивание дроби нового значения

};

Рис. 2-6. Пример определения класса Рациональная дробь