Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Объектно-ориентированное программирование.pdf
Скачиваний:
121
Добавлен:
28.03.2015
Размер:
1.58 Mб
Скачать

Класс – абстрактный тип данных

Функции позднего связывания – это виртуальные нестатические компонентные функции класса. Главное преимущество позднего связывания – гибкость во время работы программы. Главный недостаток – снижение быстродействия программ за счет более медленного вызова виртуальных функций.

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

Вложенные и локальные классы

Класс может быть определен внутри другого класса. Такой класс называется вложенным (nested class) классом. Вложение классов – это не более чем соглашение о записи, поскольку вложенный класс не является скрытым в области видимости лексически объемлющего класса. Имя вложенного класса является локальным в его объемлющем классе.

Отметим характерные особенности вложенных классов:

объявления во вложенном классе могут использовать из объемлющего класса лишь имена типов, статические (static) компоненты и элементы перечислений;

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

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

Класс может быть определен внутри блока, например, внутри определения функции. Такой класс называется локальным (local class) классом. Имя локального класса является локальным в окружающем контексте. Локальный класс имеет тот же доступ к именам вне функции, что и сама функция.

Отметим характерные особенности локальных классов:

локализация класса предполагает недоступность его компонентов вне области определения класса (вне тела функции или блока, в котором он определен);

локальный класс не может иметь статических данных, так как компоненты локального класса не могут быть определены вне контекста класса;

в локальном классе разрешено использовать из окружающего контекста только имена типов, статические (static) переменные, внешние (extern) переменные, внешние функции и элементы перечислений;

в локальном классе запрещено использование переменных автоматической памяти (auto);

компонентные функции локального класса могут быть только встроенными;

вложенный класс локального класса сам является локальным.

143

Основы объектно-ориентированного программирования в примерах на С++

Ранее отмечалось, что при объявлении статического массива объектов класса явное указание аргументов вызова для его конструктора возможно не только в случае использования списка инициализации в стиле С, но также и в случае использования локального класса. Например, при объявлении статического одномерного массива объектов локального производного класса B_derived из иерархии с открытым одиночным наследованием инициализация его элементов будет выполнена благодаря явному вызову конструктора базового класса A_based для указанных аргументов:

//Пример 50

//C++ Локальный класс

#include <iostream> using namespace std; class A_based {

protected:

//Компонентные функции - все защищенные (protected)

//Конструктор объектов базового класса

A_based(int i = 0) : a_data(i)

{

cout << "Конструктор базового класса A_based" << endl;

}

//Деструктор объектов базового класса

~A_based()

{

cout << "Деструктор базового класса A_based" << endl;

}

//Визуализация компонента данных

friend ostream& operator<<(ostream& stream, A_based& item)

{

return stream << item.a_data;

}

// Компонентные данные - все защищенные (protected) int a_data;

};

const int size = 2; void create()

{

static int value;

class B_derived : public A_based { public:

//Компонентные функции - все общедоступные (public)

//Конструктор объектов производного класса

B_derived() : A_based(value++)

{

cout << "Конструктор производного класса B_derived" << endl;

}

// Деструктор объектов производного класса

~B_derived()

{

144

Класс – абстрактный тип данных

cout << "Деструктор производного класса B_derived" << endl;

}

};

B_derived array[size];

for (int i = 0; i < size; ++i) cout << array[i] << endl;

}

int main()

{

create(); return 0;

}

Результат работы программы:

Конструктор базового класса A_based Конструктор производного класса B_derived Конструктор базового класса A_based Конструктор производного класса B_derived 0 1

Деструктор производного класса B_derived Деструктор базового класса A_based Деструктор производного класса B_derived Деструктор базового класса A_based

Таким же образом можно инициализировать, например, и элементы динамических одномерных массивов объектов локального производного класса B_derived:

//Пример 51

//C++ Локальный класс

#include <iostream> using namespace std; class A_based {

protected:

//Компонентные функции - все защищенные (protected)

//Конструктор объектов базового класса

A_based(int i = 0) : a_data(i) {}

//Деструктор объектов базового класса virtual ~A_based() {}

//Визуализация компонента данных

friend ostream& operator<<(ostream& stream, A_based& item)

{

return stream << item.a_data;

}

// Компонентные данные - все защищенные (protected) int a_data;

};

145

Основы объектно-ориентированного программирования в примерах на С++

void create(int size)

{

static int value;

class B_derived : public A_based { public:

//Компонентные функции - все общедоступные (public)

//Конструктор объектов производного класса

B_derived() : A_based(value++) {}

// Деструктор объектов производного класса

~B_derived() {} };

B_derived* base = new B_derived[size];

for (int i = 0; i < size; ++i) cout << base[i] << endl; delete[] base;

}

int main()

{

create(2); return 0;

}

Результат работы программы:

0

1

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

деструктор является виртуальным;

указатель ссылается на настоящий тип объекта в свободной памяти;

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

так как для указателя base соблюдаются оба последних условия.

146