Опубликован: 26.06.2003 | Доступ: свободный | Студентов: 36634 / 5382 | Оценка: 4.07 / 3.80 | Длительность: 15:14:00
ISBN: 978-5-9556-0017-8
Лекция 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 >
Елена Шумова
Елена Шумова

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

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

Маргарита Башкатова
Маргарита Башкатова
Анатолий Федоров
Анатолий Федоров
Россия, Москва, Московский государственный университет им. М. В. Ломоносова, 1989
Рустам Новиков
Рустам Новиков
Эстония, Таллин