Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методические указания.doc
Скачиваний:
9
Добавлен:
18.04.2015
Размер:
619.52 Кб
Скачать
  1. Введение в наследование

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

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

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

Для решения подобного рода вопросов в парадигму языка C++ добавлена концепция наследования.

  1. Создание производного класса.

Рассмотрим простой класс с конструктором и деструктором:

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);

}

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

  1. Защищенные (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; // ошибка

  1. Управление уровнем доступа к членам класса

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

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.