Объектно-ориентированное программирование, страница 3

}

Заметим, что во втором способе: (&obj)->Method(); скобки обязательны, так как приоритет операции выбора с помощью указателя выше, чем операции взятия адреса.

Вопросы и упражнения

Можно ли сделать поля name, age структуры Man недоступными извне? Устраните произвол при вводе полей. Что произойдет, если метод In перенести в private-секцию класса Man? Какие объекты декларированы в фрагменте?

 A obj, &ref=obj;

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

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

Деструктор носит имя класса, в котором он определен, но перед его именем должен стоять символ ~. Например, описание A::~A(){}; задает деструктор класса A, а A::A(){}; определяет его конструктор. Напомним, что локально декларированные объекты класса, так же как и любые локальные переменные действительны только внутри функции или блока, где они определены.

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

В нашем примере следует обратить внимание на функционирование конструктора и деструктора, а также декомпозицию программы на отдельные модули.

const int maxSize = 1000;  // Ограничитель размера стека

class Stack

{

char *top, *bottom;   // Указатели на вершину и дно стека

int length;           // Длина стека

public:

Stack (int n);        // Прототип конструктора

void Push (char c)    // Запись в стек

{

  if (top < bottom + length)

    *top++ = c;

}

char Pop() // Выбор из стека

{

  return top > bottom ? *--top : 0;

}

~Stack () { delete [] bottom; } // Деструктор

};

Stack::Stack (int n)       // Тело конструктора

{

if (0 < n && n < maxSize)

{

  length = n;

  top = bottom = new char [n];  // Память для стека

  memset (top, 0, n);

}

else

{

  top = bottom = 0;        // Стек пуст

  length = 0;

}

}

void Reverse (char *s)     // Глобальная функция — реверс строки

{

Stack stack (int(strlen(s))); // Инициализация стека

char *p;                  // Рабочая переменная

for (p=s; *p; p++)        // Помещаем строку в стек

  stack.Push (*p);

for (p=s; *p=stack.Pop(); p++) // Добываем строку из стека

  ;

}

void main()                // Демонстрация работы

{

char buf [maxSize +1], c; // Буфер строки

int i=1;                  // Счетчик символов

while (i)                 // Пока есть символы (строки)

{

  puts("\n\n Enter a string:\n");

  for (i=0; (c=getc(stdin)) != '\n' && i < maxSize; i++)

    buf[i] = c;

  buf[i] = '\0';

  if (i)

  {

    Reverse(buf);

    printf ("\n The reversed string:\n\n%s", buf);

  }

}

}

Каждый представитель класса Stack содержит три private-переменных:

¨  int length — объем стека,

¨  char *top, *bottom; — адреса вершины и дна стека.

Поля данных класса не имеют описателя private, но они тем не менее являются скрытыми или внутренними данными класса, так как в классе (в отличие от структуры) по умолчанию действует описатель private.

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

¨  Забыть выделить память для элементов стека.

¨  Забыть освободить память, динамически выделенную в области heap, после окончания работы функции, в которой определены объекты класса Stack.

¨  Обратиться к несуществующему элементу.

Cтек используется для хранения строки символов. В первом цикле for функции Reverse имя метода Push посылается объекту stack, в результате чего символы строки, предварительно введенной пользователем, поочередно помещаются в стек. Так как дисциплина очереди стека есть LIFO (Last In — First Out, или: последним вошел — первым вышел), то по завершении второго цикла for, в котором метод Pop обратно выталкивает символы из стека в ту же самую строку s, первоначальная строка оказывается перевернутой. Работа идет с содержимым по адресу, поэтому исходная строка в функции main также будет перевернута. Отметьте, что в примере не было явного вызова деструктора. Он неявно вызывается при выходе из области действия объекта класса. Язык С++ предоставляет также возможность явного вызова деструктора. Если объект класса был создан с помощью операции динамического выделения памяти new, то при выполнении операции delete для объекта будет вызван деструктор и такой способ обращения называется явным уничтожением объекта.