Объекты не существуют изолированно, а подвергаются воздействию или сами воздействуют на другие объекты.
Поведение – это то, как объект действует и реагирует; поведение выражается в терминах состояния объекта и передачи сообщений. Поведение объекта – это его наблюдаемая и проверяемая извне деятельность.
Операция – это услуга, которую можно запросить у любого объекта класса для воздействия на его поведение.
Например, клиент может активизировать операции push и pop для того, чтобы управлять объектом-стеком (добавить или изъять элемент).
В чисто объектно-ориентированном языке принято говорить о передаче сообщений между объектами. В C++ мы говорим, что один объект вызывает функцию-член другого. В основном понятие «сообщение» совпадает с понятием «операция над объектами».
Передача сообщений – это один уровень, задающий поведение. Из нашего определения следует, что состояние объекта также влияет на его поведение.
Рассмотрим торговый автомат. Мы можем сделать выбор, но поведение автомата будет зависеть от его состояния. Если мы не опустили в него достаточную сумму, скорее всего, ничего не произойдет. Если же денег достаточно, автомат выдаст нам желаемое (и тем самым изменит свое состояние).
|
|
Некоторые операции изменяют состояние. В связи со сказанным можно заключить, что состояниеобъекта представляет суммарный результат его поведения.
Операция – это услуга, которую класс может предоставить своим клиентам. На практике типичный клиент совершает над объектами операции следующих видов:
– модификатор – это операция, которая изменяет состояние объекта (например, set-функция);
– селектор – это операция, считывающая состояние объекта, но не меняющая состояния (например, get-функция);
– конструктор – это операция создания объекта и/или его инициализации; в С++ конструктор имеет то же имя, что и класс;
– деструктор – это операция, освобождающая ресурсы, которые использует объект, и/или разрушающая сам объект; в С++ имя деструктора состоит из имени класса, перед которым ставится знак «тильда» – «~».
Две последние операции являются универсальными. Они обеспечивают инфраструктуру, необходимую для создания и уничтожения экземпляров класса. Если у класса есть конструктор, то он вызывается всегда, когда создается объект класса. Если у класса есть деструктор, то он вызывается всегда, когда объект класса уничтожается. Если программист не описал в классе конструктор и деструктор, то они будут созданы автоматически.
Объекты могут создаваться следующим образом:
– автоматический объект создается каждый раз, когда его описание встречается при выполнении программы, и уничтожается каждый раз при выходе из блока, в котором оно появилось;
|
|
– статический объект создается один раз, при запуске программы, и уничтожается один раз, при ее завершении;
– объект в свободной памяти создается с помощью операции new и уничтожается с помощью операции delete;
– объект-член создается как подобъект другого класса.
Пример. Расширим описание класса Stack, с тем чтобы программист мог задавать максимальный размер каждого создаваемого стека (размер массива s).
class Stack {
int *s, length, top;
...
public:
Stack(int n = 100){ // конструктор, n – максимальный размер,
// значение максимального размера по умолчанию – 100
length = n; s = new int [length]; top = 0;}
~Stack() { delete [ ] s; } // деструктор
void push(const int el); // модификатор
int pop(); // модификатор
bool isFull() const; // селектор
bool isEmpty() const; // селектор
...
};
Теперь мы можем объявить нужные нам стеки:
int len=100;
Stack st1(len), st2(200);
Конструктор с одним аргументом может служить также для преобразования типа своего аргумента в тип конструктора.
Пример. Рассмотрим определение класса complex.
class complex {
double re, im;
public:
complex(double r, double i);
complex(double r);
...
};
Мы определили два конструктора, один из которых имеет один аргумент и служит для инициализации комплексного числа (его действительной части) значением вещественного числа. Теперь мы можем записать два эквивалентных оператора
complex a = complex(1);
complex a = 1;
Последнее присваивание имеет однозначный смысл с точки зрения предметной области. В то же время для стека аналогичное присваивание
Stack st = 100;
будет неоднозначным по смыслу и может быть потенциальным источником ошибок.
Можно запретить использование конструктора для таких преобразований, объявив его с ключевым словом explicit.
class Stack {
public:
explicit Stack(int n = 100); // конструктор, задающий максимальный
... // размер стека, нельзя использовать для преобразования
};
Для инициализации отдельных частей объекта с помощью конструктора служат инициализаторы конструктора. Важность инициализаторов в том, что только с их помощью можно инициализировать константные члены, члены, являющиеся ссылками, а также члены, являющиеся объектами класса, в котором есть один или несколько конструкторов, но отсутствует конструктор по умолчанию (конструктор без параметров).
Важно также, что такая инициализация выполняется эффективнее, поскольку создание объекта в C++ начинается с инициализации его атрибутов конструктором по умолчанию, после чего выполняется вызываемый конструктор. Использование инициализаторов позволяет сразу же вызвать нужный конструктор.
Для инциализаторов используется синтаксис следующего примера:
class Id_Stack {
const int id; Stack s;
public: Id_Stack(int i, int n): id(i), s(n){ };
};
В чисто объектно-ориентированных языках определять процедуры и функции вне классов не допускается. В гибридных языках, выросших из процедурных языков, таких как C++, допускается описывать операции как независимые от объектов подпрограммы.
Операции, определенные вне классов, называют свободными подпрограммами. В C++ они называются функциями-нечленами.
bool check_stack(Stack & my_stack, int el)
{
Stack temp_stack;
... // используя дополнительный стек temp_stack, проверить,
//есть ли в my_stack элемент el
}
Свободные подпрограммы – это процедуры и функции, которые выполняют роль операций высокого уровня над объектом или объектами одного или разных классов. Свободные подпрограммы обычно группируются в соответствии с классами, для которых они создаются.