Опубликован: 26.06.2003 | Уровень: для всех | Доступ: свободно
Лекция 10:

Производные классы, наследование

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >
Аннотация: Наследование, виды наследования. Виртуальные методы. Абстрактные классы. Множественное наследование.

Важнейшим свойством объектно-ориентированного программирования является наследование. Для того, чтобы показать, что класс В наследует класс A (класс B выведен из класса A ), в определении класса B после имени класса ставится двоеточие и затем перечисляются классы, из которых B наследует:

class A
{
public:
     A();
     ~A();
     MethodA();
};
class B : public A 
{
public:
     B();
     . . .
};

Термин " наследование " означает, что класс B обладает всеми свойствами класса A, он их унаследовал. У объекта производного класса есть все атрибуты и методы базового класса. Разумеется, новый класс может добавить собственные атрибуты и методы.

B b;
b.MethodA();  // вызов метода базового класса

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

Пример иерархии классов.

Рис. 10.1. Пример иерархии классов.

Иерархия классов может быть сколь угодно глубокой. Если нужно различить, о каком именно классе идет речь, класс C называют непосредственным или прямым базовым классом класса D, а класс A – косвенным базовым классом класса D.

Предположим, что для библиотечной системы, которую мы разрабатываем, необходимо создать классы, описывающие различные книги, журналы и т.п., которые хранятся в библиотеке. Книга, журнал, газета и микрофильм обладают как общими, так и различными свойствами. У книги имеется автор или авторы, название и год издания. У журнала есть название, номер и содержание – список статей. В то же время книги, журналы и т.д. имеют и общие свойства: все это – "единицы хранения" в библиотеке, у них есть инвентарный номер, они могут быть в читальном зале, у читателей или в фонде хранения. Их можно выдать и, соответственно, сдать в библиотеку. Эти общие свойства удобно объединить в одном базовом классе. Введем класс Item, который описывает единицу хранения в библиотеке:

class Item
{
public:
   Item();
   ~Item();
   // истина, если единица хранения на руках

   bool IsTaken() const;
   // истина, если этот предмет имеется в библиотеке
   
   bool IsAvailable() const;
   long GetInvNumber() const;  // инвентарный номер  
   
   void Take();      // операция "взять"
   void Return();    // операция "вернуть"
                       
private:
     // инвентарный номер — целое число
     long invNumber; 
     // хранит состояние объекта - взят на руки
     bool taken;
};

Когда мы разрабатываем часть системы, которая имеет дело с процессом выдачи и возврата книг, вполне достаточно того интерфейса, который представляет базовый класс. Например:

// выдать на руки
void
TakeAnItem(Item& i)
{
     . . .
     if (i.IsAvailable())
          i.Take();
}

Конкретные свойства книги будут представлены классом Book.

class Book : public Item
{
public:  
     String Author(void) const;
     String Title(void) const;
     String Publisher(void) const;
     long YearOfPublishing(void) const;
     String Reference(void) const;

private:
     String author;
     String title;
     String publisher;
     short year;
};    // автор
  // название
  // издательство
  // год выпуска
  // полная ссылка
  // на книгу

Для журнала класс Magazine предоставляет другие сведения:

class Magazine : public Item
{
public:  
     String Volume(void) const;
     short Number(void) const;
     String Title(void) const;
     Date DateOfIssue() const;
private:
     String volume;
     short number;
     String title;
     Date date;
};  
  // том
  // номер
  // название
  // дата выпуска

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

У объекта класса Book имеются методы, непосредственно определенные в классе Book, и методы, определенные в классе Item.

Book b;
long in = b.GetInvNumber();
String t = b.Reference();

Производный класс имеет доступ к методам и атрибутам базового класса, объявленным во внешней и защищенной части базового класса, однако доступ к внутренней части базового класса не разрешен. Предположим, в качестве части полной ссылки на книгу решено использовать инвентарный номер. Метод Reference класса Book будет выглядеть следующим образом:

String
Book::Reference(void) const
{
     String result = author + "\n" 
               + title + "\n"
               + String(GetInvNumber());
     return result;
}

(Предполагается, что у класса String есть конструктор, который преобразует целое число в строку.) Запись:

String result = author + "\n"
               + title + "\n"
               + String(invNumber);

не разрешена, поскольку invNumber – внутренний атрибут класса Item. Однако если бы мы поместили invNumber в защищенную часть класса:

class Item
{
. . .
protected:
     long invNumber;
};

то методы классов Book и Magazine могли бы непосредственно использовать этот атрибут.

Назначение защищенной ( protected ) части класса в том и состоит, чтобы, закрыв доступ "извне" к определенным атрибутам и методам, разрешить пользоваться ими производным классам .

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

class A
{
public:
     . . .
     int foo();
     . . .
};
class B : public A
{
public:
     int foo();
     void bar();
};
void
B::bar()
{
     x = foo();   
	 // вызывается метод foo класса B
}

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

С помощью записи A::foo() можно явно указать, что нас интересует имя, определенное в классе A, и тогда запись:

x = A::foo();

вызовет метод базового класса.

Вообще, запись класс::имя уже многократно нами использовалась. При поиске имени она означает, что имя относится к заданному классу.

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >
Андрей Одегов
Андрей Одегов
Язык программирования C++
Елена Шумова
Елена Шумова

Здравствуйте! Я у Вас прошла курс Язык программировая Си++.

Заказала сертификат. Хочу изменить способ оплаты. Как это сделать?