3.2.6 Введение в наследование

Наследование - это механизм посредством которого один класс (производный) может наследовать свойства другого класса (базового).

Базовый класс определяет все качества, которые являются общими для всех прпоизводных классов.

Пример:

//Базовый класс

class B {

int i;

public:

void set_i(int n);

int get_i();

};

//Производный класс D

class D : public B {

int j;

public:

void set_j(int n);

int mul();

};

После имени класса D стоит двоеточие, за которым стоит ключевое слово public и имя класса B. Это означает, что класс D будет наследовать все компоненты класса B. Само ключевое слово public информирует компилятор о том, что т.к. B будет наследоваться, то все открытые элементы базового класса будут открытыми элементами производного класса. Однако все закрытые элементы базового класса остаются закрытыми.

Пример:

// Простой пример наследования.

#include <iostream.h>

// Задание базового класса

class base {

int i;

public:

void set_i(int n);

int get_i();

};

// Задание производного класса

class derived : public base {

int j;

public:

void set_j(int n);

int mul();

};

// Установка значения i в базовом классе

void base::set_i(int n)

{

i = n;

}

// Возврат значения i в базовом классе

int base::get_i()

{

return i;

}

// Установка значения j в производном классе

void derived::set_j(int n)

{

j = n;

}

// Возврат значения i из base и, одновременно, j из derived

int derived::mul()

{

// производный класс может вызывать функции-члены базового класса

return j * get_i();

}

main()

{

derived ob;

ob.set_i(10); // загрузка i в base

ob.set_j(4); // загрузка j в derived

cout << ob.mul(); // вывод числа 40

return 0;

}

Важно! При определении mul() вызывается функция get_i()- базового класса B, а не производного D, что указывает на то, что открытые члены базового класса становятся открытыми членами производного. Но в функции mul() вместо прямого доступа к i, необходимо вызывать get_i(), потому что закрытые члены базового класса(i) остаются закрытыми для производных классов.

3.2.7 Виртуальные функции

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

#include <stdio.h>

class base {

public:

 int i;

 base(int x); //конструктор

 void func()

 {

 printf("Базовая функция %d",i);

 return;

 };

};

//текст конструктора

base::base(int x)

{

 i=x;

 return;

};

class der1: public base {

public:

 der1(int x) :base(x) {}; //конструктор

 void func()

 {

 printf("Функция из производного класса %d", i*i);

 return;

 }

};

main()

{

 base * pc; //указатель на базовый класс

 base ob(2); //создать экземпляр объекта базового класса

 der1 ob1(2); //создать экземпляр объекта производного класса

 pc=&ob; //указатель на объект базового класса

 pc->func(); //вызов функции базового класса

 pc=&ob1; //указатель на объект производного класса

 pc->func(); //попытка вызова функции производного класса

 return 0;

 }

На первый взгляд, кажется, что в перврм случае будет вызываться функция базового класса, а во втором функция производного. Однако при проверке Вы легко убедитесь, что и в том и в другом случае будет вызвана функция функция базового класса. В чем тут дело? Дело в том, что компилятору трудно понять, какую реально функцию мы имеем в виду и он на стадии компилирования подставляет во всех тех случаях, где встречается имя func() адрес функции базового класса. Такой процесс установки адресов называется "ранним связыванием". Иногда употребляется термин "статическое связывание". Если же мы хотим, чтобы во втором случае, т.е. когда указатель pc указывал на производный класс вызывалась функция этого класса, ее еще в базовом классе следует указать как виртуальную. В нашем случае вместо строки void func() следует написать virtual void func(). После этого наш пример будет работать как надо.

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

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

Для дальнейшего уяснения свойств виртуальных функций рассмотрим еще один пример, являющийся развитием первого.

#include <stdio.h>

class base {

public:

 int i;

 base(int x); //конструктор

 virtual void func()

 {

 printf("Базовая функция %d\n",i);

 return;

 };

};

//текст конструктора

base::base(int x)

{

 i=x;

 return;

};

class der1: public base {

public:

 der1(int x) :base(x) {}; //конструктор

 void func()

 {

 printf("Функция из производного класса %d\n", i*i);

 return;

 }

};

class der2: public base {

public:

 der2(int x) :base(x) {}; //конструктор

};

main()

{

 base * pc; //указатель на базовый класс

 base ob(2); //создать экземпляр объекта базового класса

 der1 ob1(2); //создать экземпляр объекта производного класса 1

 der2 ob2(2); //создать экземпляр объекта производного класса 2

 pc=&ob; //указатель на объект базового класса

 pc->func(); //вызов функции базового класса

 pc=&ob1; //указатель на объект производного класса 1

 pc->func(); //попытка вызова функции производного класса

 pc=&ob2; //указатель на объект производного класса 2

 pc->func(); //попытка вызова функции производного класса

 return 0;

 }

Как видите, мы ввели еще один производный класс. В нем функция func() не определена. В этом случае будет вызываться функция класса родителя. Т.е. появится строка: Базовая функция 2. Как видите принцип очень прост: если Вы хотите, чтобы вызывалась функция родительского класса, не определяйте ее в производном. Еще один вопрос может возникнуть в связи с данным примером: как быть, если мы хотим, чтобы для класса объектов der2 вызывалась функция класса der1. Решение очень просто - сделайте класс der2 наследником не класса base, а класса der1.

И последнее. Как мы видели, в производных классах функция, определенная в базовом классе как виртуальная может определяться, а может и нет. Если Вы хотите, чтобы во всех производных классах обязательно была определена виртуальная функция, то в базовом классе ее надо определить следующим образом:

virtual void func() = 0;

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


Информация о работе «Основы C»
Раздел: Информатика, программирование
Количество знаков с пробелами: 200759
Количество таблиц: 5
Количество изображений: 11

Похожие работы

Скачать
20474
0
0

... . Имеет ли право на существование эта биологизаторская интерпретация экологии? Видимо, да. Она широко представлена, и с этим следует считаться. Но она не может служить концептуальной основой комплексного непрерывного экологического образования. В структуре научного знания при таком подходе не остаётся места для географической и социальной экологии, экологии человека, а сама биология превращается ...

Скачать
5443
0
0

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

Скачать
8522
1
2

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

Скачать
11185
0
0

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

0 комментариев


Наверх