Наследование

Язык C++ / Наследование

Наследование — это механизм создания нового класса на основе уже существующего. При этом к существующему классу могут быть добавлены новые элементы (данные и функции), либо существующие функции могут быть изменены. Основное назначение механизма наследования — повторное использование кодов, так как большинство используемых типов данных являются вариантами друг друга, и писать для каждого свой класс нецелесообразно.
Объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.
наследование
Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми(иногда порождающими), а новые классы, формируемые на основе базовых, – производными (порожденными, классами-потомками или наследниками).

Производные классы «получают наследство» – данные и методы своих базовых классов, и могут пополняться собственными компонентами (данными и собственными методами). Наследуемые компоненты не перемещаются в производный класс, а остаются в базовых классах. Сообщение, обработку которого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сообщения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически в базовом классе.

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

Для порождения нового класса на основе существующего используется следующая общая форма

сlass Имя : МодификаторДоступа  ИмяБазовогоКласса
{ объявление_членов;};

При объявлении порождаемого класса МодификаторДоступа может принимать значения public, private, protected либо отсутствовать, по умолчанию используется значение private. В любом случае порожденный класс наследует все члены базового класса, но доступ имеет не ко всем. Ему доступны общие (public) члены базового класса и недоступны частные (private).

Для того, чтобы порожденный класс имел доступ к некоторым скрытым членам базового класса, в базовом классе их необходимо объявить со спецификацией доступа защищенные (protected). Члены класса с доступом protected видимы в пределах класса и в любом классе, порожденном из этого класса.

Общее наследование

При общем наследовании порожденный класс имеет доступ к наследуемым членам базового класса с видимостью public и protected. Члены базового класса с видимостью private – недоступны.

Спецификация доступа внутри класса в порожденном классе вне класса
private + - -
protected + + -
public + + +

Общее наследование означает, что порожденный класс – это подтип базового класса. Таким образом, порожденный класс представляет собой модификацию базового класса, которая наследует общие и защищенные члены базового класса.

Пример

class student {
protected:
  char fac[20];
  char spec[30];
  char name[15];
public:
  student(char *f, char *s, char *n);
  void print();
};
class grad_student : public student {
protected:
  int year;
  char work[30];
public:
  grad_student(char *f, char *s, char *n, char *w, int y);
  void print();
};

Порожденный класс наследует все данные класса student, имеет доступ к protected и public-членам базового класса. В новом классе добавлено два член-данных, и порожденный класс переопределяет функцию print().

student :: student(char *f, char *s, char *n) {
  strcpy(fac, f);
  strcpy(spec, s);
  strcpy(name, n);
}
grad_student :: grad_student(char *f, char *s, char *n, char *w, int y) :
    student(f,s,n) {
  year = y;
  strcpy(work, w);
}

Конструктор для базового класса вызывается в списке инициализации.
Перегрузка функции print().

void student :: print() {
cout << endl << "fac: " << fac << " spec: " << spec
<< " name: " << name;
}
void grad_student :: print() {
student :: print();
cout << " work: " << work << " year: " << year;
}
int main() {
  system("chcp 1251");
  system("cls");
  student s("МТ", "АМСП", "Сидоров Иван");
  grad_student stud("ПС", "УиТС", "Иванов Петр", "Метран", 2000);
  student *p = &s;
  p->print();
  grad_student *gs = &stud;
  student *m;
  gs->print();
  m = gs;
  m->print();
  cin.get();
  return 0;
}

Результат выполнения
Результат работы: наследование

Указатель на порожденный класс может быть неявно передан в указатель на базовый класс. При этом переменная-указатель m на базовый класс может указывать на объекты как базового, так и порожденного класса.

Указатель на порожденный класс может указывать только на объекты порожденного класса.

Неявные преобразования между порожденным и базовым классами называются предопределенными стандартными преобразованиями:

  • объект порожденного класса неявно преобразуется к объекту базового класса.
  • ссылка на порожденный класс неявно преобразуется к ссылке на базовый класс.
  • указатель на порожденный класс неявно преобразуется к указателю на базовый класс.

Частное наследование

Порожденный класс может быть базовым для следующего порождения. При порождении private наследуемые члены базового класса, объявленные как protected и public, становятся членами порожденного класса с видимостью private. При этом члены базового класса с видимостью public и protected становятся недоступными для дальнейших порождений. Цель такого порождения — скрыть классы или элементы классов от использования в дальнейших порождениях. При порождении private не выполняются предопределенные стандартные преобразования:

class grad_student : private student
{...};
int main() {...
  grad_student *gs = &stud;
  student *m;
  gs->print();
  m = gs;       // ошибка
  m->print();
  cin.get();
  return 0;
}

Однако порождение private позволяет отдельным элементам базового класса с видимостью public и protected сохранить свою видимость в порожденном классе. Для этого необходимо

  • в части protected порожденного класса указать те наследуемые члены базового класса с видимостью protected, уточненные именем базового класса, для которых необходимо оставить видимость protected и в порожденном классе;
  • в части public порожденного класса указать те наследуемые члены базового класса с видимостью public, уточненные именем базового класса, для которых необходимо оставить видимость public и в порожденном классе.
class X {
private:
  int n;
protected:
  int m;
  char s;
public:
  void func(int);
};
class Y : private X {
private:
  ...
protected:
  ...
  X :: s;
public:
  ...
  X :: func();
};

Возможен и третий вариант наследования – с использованием модификатора доступа protected.

Доступ к элементам базового класса из производного класса, в зависимости от модификатора наследования:

Модификатор наследования → public protected private
Модификатор доступа ↓
public public protected private
protected protected protected private
private нет доступа нет доступа нет доступа

Конструкторы и деструкторы при наследовании

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

Если и у базового и у производного классов есть конструкторы и деструкторы, то конструкторы выполняются в порядке наследования, а деструкторы – в обратном порядке. То есть если А – базовый класс, В – производный из А, а С – производный из В (А-В-С), то при создании объекта класса С вызов конструкторов будет иметь следующий порядок:

  • конструктор класса А
  • конструктор класса В
  • конструктор класса С.

Вызов деструкторов при удалении этого объекта произойдет в обратном порядке:

  • деструктор класса С
  • деструктор класса В
  • деструктор класса А.

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

Конструкторы могут иметь параметры. При реализации наследования допускается передача параметров для конструкторов производного и базового класса. Если параметрами обладает только конструктор производного класса, то аргументы передаются обычным способом. При необходимости передать аргумент из производного класса конструктору родительского класса используется расширенная запись конструктора производного класса:

КонструкторПроизводногоКласса (СписокФормальныхАргументов)
: КонструкторБазовогоКласса (СписокФактическихАргументов)
{ // тело конструктора производного класса }

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

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

student :: student(char *f, char *s, char *n) {
  strcpy(fac, f);
  strcpy(spec, s);
  strcpy(name, n);
}
grad_student :: grad_student(char *f, char *s, char *n, char *w, int y) :
     student(f,s,n) {
  year = y;
  strcpy(work, w);
}

В расширенной форме объявления конструктора производного класса описывается вызов конструктора базового класса.

Назад


Назад: Язык C++

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *