- •Концепция класса
- •Переменные класса
- •Методы класса
- •Наследование
- •Конструктор и деструктор
- •Виды памяти и создание объектов
- •Управление доступом к переменным и методам класса
- •Интерфейс
- •Исключения
- •Шаблоны
- •Введение в наследование
- •Множественное наследование классов
- •Преобразование типов
- •Виртуальные функции
-
Введение в наследование
Применение абстрактных типов, хорошо согласующихся с особенностями конкретной решаемой проблемы, дает существенные преимущества по сравнению с использованием только типов данных, связанных с организацией ЭВМ, а не с задачей, над которой в данный момент работает программист. Достаточно ли того, что мы знаем об абстрактных типах, для разработки сложных универсальных программных средств.
Рассмотрим следующий случай. Вы создали класс и использовали его в нескольких программах, некоторые функции могут быть даже помещены в библиотеку объектных модулей. Взявшись за новую проблему, вы увидели, что желательно добавить новый член класса. Если вы это сделаете, то вам придется перекомпилировать все функции-члены этого класса и все функции, использующие данный класс.
Таким образом, для сохранения работоспособности ранее написанных программ (без их перекомпиляции) вам придется не изменять ранее созданный класс, а добавлять новый, мало чем отличающийся от предыдущего. Кроме того, что такая работа утомительна, для создания новой версии класса необходимо иметь исходные тексты функций-членов этого класса. При таких ограничениях задача создания универсального класса превращается в проблему.
Для решения подобного рода вопросов в парадигму языка C++ добавлена концепция наследования.
-
Создание производного класса.
Рассмотрим простой класс с конструктором и деструктором:
class Base {
int *baseMember;
public:
Base (int arg=0) { baseMember = new int [arg]; }
~Base () { delete baseMember; }
};
Предположим, что нам нужно изменить этот класс так, чтобы объект такого типа содержал не один, а два указателя. Вместо того, чтобы изменять класс Base, мы построим на его основе другой класс, который назовем Derived:
class Derived : public Base {
int *derivedMember;
public:
Derived (int arg) { derivedMember = new int [arg]; }
~Derived () { delete derivedMember; }
};
Запись вида class Derived : public Base говорит о том, что класс Derived строится на основе класса Base; при этом он наследует все свойства класса Base. Класс Derived называют производным от класса Base, а класс Base называют базовым для класса Derived. Если в программе будет создан объект типа Derived, то он будет содержать два указателя на две области динамической памяти – baseMember и derivedMember.
Процесс создания объекта типа Derived будет проходить в два этапа: сначала будет создан подобъект типа Base (посредством конструктора класса Base), а затем будет выполнен и конструктор класса Derived. Вызов деструкторов осуществляется в обратном порядке.
Поскольку конструктор класса Base может требовать наличия одного аргумента при обращении к нему, то этот аргумент необходимо передать. Чтобы передать список аргументов конструктору базового класса, этот список должен быть помещен в дефиниции (а не в декларации) конструктора производного класса:
Derived::Derived (int arg) : Base(arg)
{
derivedMember = new int (arg);
}
Если конструктор базового класса не имеет аргументов или использует аргументы по умолчанию, помещать пустой список в конструктор производного класса не надо:
Derived::Derived (int arg)
{
derivedMember = new int (arg);
}
Важно отметить, что построение производного класса не требует никаких действий над базовым классом.
-
Защищенные (protected) члены класса
Для решения широкого круга задач недостаточно двух уровней доступа: private и public. Например, ни пользователи класса Derived, ни даже функции-члены этого класса не могут получить доступ к элементу baseMember, хотя он является членом класса Derived:
void Derived::printMembers (ostream& s)
{
s<<*baseMember<<endl; // ошибка доступа
s<<*derivedMember<<endl; // все верно
}
Если разрешить функциям-членам производного класса обращаться к личным членам базового класса, то вся система защиты данных теряет смысл, т.к. нельзя будет гарантировать, что к личным членам класса Base обращаются только функции-члены этого класса или привилегированные в нем функции. Если же сделать baseMember общим членом класса Base, то доступ к нему получат не только функции-члены класса Derived, но и пользователи классов Base и Derived. Для решения подобных проблем в C++ был добавлен еще один модификатор уровня доступа – protected.
Если класс A не служит базовым ни для какого другого класса, то его защищенные члены ничем не отличаются от личных – доступ к ним имеют только функции-члены данного класса или привилегированные в этом классе функции. Если же класс B является производным от класса A, то пользователи как класса A, так и B не имеют доступа к защищенным членам A, но такой доступ могут иметь функции-члены класса B и функции привилегированные в классе B:
class Base {
private:
int privateMember;
protected:
int protectedMember;
};
class Derived : public Base {
memberFunc () {
cout<<privateMember; // ошибка
cout<<protectedMember; // все верно
}
};
// …
Base base;
cout << base.protectedMember; // ошибка
Derived derived;
cout << derived.protectedMember; // ошибка
-
Управление уровнем доступа к членам класса
Может ли программист при создании производного класса менять уровень доступа к членам класса базового, и если может, то каким образом? В предыдущих примерах базовый класс являлся общим базовым классом:
class Derived : public Base { /* … */ };
Если базовый класс является личным базовым классом, то личные члены базового класса по-прежнему недоступны ни в производном классе, ни для пользователя производного класса, а защищенные и общие члены базового класса становятся личными членами производного класса:
class Base
{
private:
int privateMember;
protected:
int protectedMember;
public:
int publicMember;
};
class privateDerived : Base {
public:
void f () { cout << privateMember; } // ошибка
void g () { cout << protectedMember; }
void h () { cout << publicMember; }
};
privateDerived derived;
derived.privateMember=1; // ошибка
derived.protectedMember=2; // ошибка
derived.publicMember=3; // ошибка
Еще раз подчеркнем: механизм производных классов не может обеспечить доступ к личным членам класса. К личным членам любого класса доступ имеют только функции-члены этого класса и функции, привилегированные в этом классе.
Базовый класс не может быть защищенным базовым классом. Если базовый класс является личным базовым классом, то для некоторых его членов, но не для всех сразу, в производном классе можно восстановить (но не изменить) уровень доступа базового класса. Для этого их полное имя приводятся в соответствующей части декларации класса:
class Derived : Base {
public:
Base::publicMember;
Base::protectedMember; // ошибка
protected:
Base::protectedMember;
Base::publicMember; // ошибка
};
Структуры (struct) могут использоваться подобно классам, но с одной особенностью: если производным классом является структура, то ее базовый класс всегда является общим базовым классам, т.е. объявление вида
struct B : A { /* … */ };
эквивалентно
class B : public A { /* … */ };
Если производный класс строится на основе структуры, все происходит точно так же, как и при использовании в качестве базового обычного класса. Если и базовым, и производным классами являются структуры, то запись вида
struct B : A { /* … */ };
эквивалентна
class B : public A { public: /* … */ };
Лекция №4.