Лекция 11

Тема: «Наследование и полиморфизм»

План

1.    Наследование в классах

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

3.    Конструкторы

4.    Спецификатор protected

5.    Запрет наследования

 

1.      Наследование в классах

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

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

Возможности, предоставляемые механизмом наследования:

ü  добавлять в производном классе данные, которые представляет базовый класс;

ü  дополнять в производном классе функциональные возможности базового класса;

ü  модифицировать в производном классе методы базового класса.

Возможности, которых нет:

ü  модифицировать в производном классе данные, представленные базовым классом (сохранив их идентификаторы).

Что происходит в порожденном классе:

ü  поля данных и методы-члены класса наследуются от базового класса;

ü  можно считать, что они описаны в порожденном классе. Однако, возможность доступа к ним из методов производного класса и извне этого класса, определяется спецификатором доступа (private, protected, public) к членам в базовом классе и спецификатором доступа к базовому классу, задаваемому при описании производного класса; 

ü  в производном классе можно добавлять свои поля – члены класса;

ü  в производном классе можно добавлять свои методы – члены класса;

ü  в производном классе можно переопределять методы базового класса (сохраняя точное совпадение с исходным прототипом, то есть количество и типы аргументов и возвращаемый тип). Исключение: если возвращаемый тип является указателем или ссылкой на базовый класс, он может быть заменен указателем или ссылкой на порождаемый класс;

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

ü  если в классе-наследнике имя метода и его прототип совпадают с именем метода базового класса, то метод производного класса скрывает метод базового класса;

ü  ограничений в наследовании вложенных классов нет: внешний класс может наследовать от вложенного и наоборот.

 

Необходимость наследования рассмотрим на примере. Пусть у нас есть классы, которые представляют человека и работника предприятия:

 

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

     cin.get();

     };

     //метод вывода информации о человеке

     void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     }

};

 

//класс работник предприятия

class workman

{

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //место работы

     char firm[20];

     public:

     //метод ввода информации о человеке

     void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //метод вывода информации о человеке

     void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<" Предприятие: "<<firm<<endl;

     }

};

В данном случае класс workman фактически содержит функционал класса man: свойства fam, name и age и функции vvodInfo, vivodInfo. Чтобы не повторять функциональность одного класса в другом классе (сотрудник предприятия в любом случае является человеком). Поэтому будем использовать механизм наследования. Унаследуем класс workman от класса man:

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

};

 

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

Перед названием базового класса также можно указать спецификатор доступа, как в данном случае используется спецификатор public, который позволяет использовать в производном классе все открытые члены базового класса. Если мы не используем модификатор доступа, то класс workman ничего не будет знать о переменных fam, name и age и функциях vvodInfo, vivodInfo.

После установки наследования можно убрать из класса workman те переменные, которые уже определены в классе man.

 

#include <iostream.h>

#include <string.h>

 

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

         cin.get();

     };

     //метод вывода информации о человеке

     void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     }

};

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

};

 

int main()

{

     system("chcp 1251>nul");

     //описываем переменную для работы с классом человек

     man m;

     //вводим данные о человеке

     m.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о человеке

     m.vivodInfo();

 

     //описываем переменную для работы с классом работник предприятия    

     workman m1;

     cin.get();

     //вводим данные о работнике предприятия  

     m1.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о работнике предприятия 

     m1.vivodInfo();

    

     system("pause");

     return 0;

}

 

Таким образом, через переменную класса workman можем обращаться ко всем открытым членам класса man.

 

Результат работы программы:

 

 

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

Класс workman наследует функции vvodInfo и vivodInfo и может использовать их для ввода и вывода информации о сотруднике предприятия. Но реализация этих функций в классе man позволяет ввести только фамилию, имя и возраст (без предприятия) и выводит на консоль только фамилию, имя и возраст. Тогда как в классе workman определена дополнительная переменная – firm, которая хранит название предприятия, в котором работает объект workman. И необходимо, чтобы для объекта workman функция vvodInfo вводила бы также значение переменной firm, а функция vivodInfo выводила бы также и ее значение.

Эта проблема решается с помощью виртуальных функций. То есть, чтобы в производном классе можно было переопределить функцию (изменить ее функционал), в базовом классе подобная функция должна быть объявлена с ключевым словом virtual.

Замечание: при описании переменных в классе человек (man) спецификатор доступа private для переменных фамилия, имя, возраст (fam, name, age) необходимо изменить на public, чтобы использовать их в производном классе workman.

 

//класс человек

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     public:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     //метод ввода информации о человеке

     virtual void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

         cin.get();

     };

     //метод вывода информации о человеке

     virtual void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     }

};

 

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

В нашем примере переопределим виртуальные функции (добавляем нужные команды) в производном классе workman:

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

     public:

     //переопределяем метод ввода информации о сотруднике предприятия

     void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //переопределяем метод вывода информации о сотруднике предприятия

     void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<" Предприятие: "<<firm<<endl;

     }

    

};

 

Класс, который определяет или наследует виртуальную функцию, называется полиморфным. То есть в данном примере man и workman являются полиморфными классами.

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

 

Рассмотрим второй способ переопределения виртуальных функций через обращение к базовому классу.

Пусть в классе человек (man) переменные фамилия, имя, возраст (fam, name, age) будут защищенными, то есть иметь спецификатор доступа private.

 

//класс человек

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     virtual void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

     };

     //метод вывода информации о человеке

     virtual void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     }

};

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

     public:

     //переопределяем метод ввода информации о сотруднике предприятия

     void vvodInfo()

     {

         //реализуем функцию для ввода данных:

         //(фамилии, имени, возраста) из класса man

         man::vvodInfo();

         //выполняем недостающие действия для объекта класса workman:

         //ввод названия предприятия

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //переопределяем метод вывода информации о сотруднике предприятия

     void vivodInfo()

     {

         //реализуем функцию для вывода данных:

         //(фамилии, имени, возраста) из класса man

         man::vivodInfo();

         //выполняем недостающее действие для объекта класса workman

         //вывод информации о предприятии     

         cout<<" Предприятие: "<<firm<<endl;

     }

};

 

С помощью вызова функции man::vvodInfo() будет выполняться реализация функции vvodInfo из класса man, которая заполнит поля фамилия, имя, возраст.

С помощью вызова функции man::vivodInfo() будет выполняться реализация функции vivodInfo из класса man, которая выведет на консоль фамилию, имя и возраст. Мы только определили код для ввода предприятия и вывода предприятия в соответствующих функциях класса workman. Таким образом, можем обращаться к любому функционалу базового класса за исключением приватных переменных и функций.

Применим вышесказанное в программе:

 

int main()

{

     system("chcp 1251>nul");

     //описываем переменную для работы с классом человек

     man m;

     //вводим данные о человеке

     m.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о человеке

     m.vivodInfo();

     //описываем переменную для работы с классом работник предприятия    

     workman m1;

     cin.get();

     //вводим данные о работнике предприятия  

     m1.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о работнике предприятия 

     m1.vivodInfo();

    

     system("pause");

     return 0;

}

 

Результат работы программы:

 

 

2.      Конструкторы

Конструкторы при наследовании не наследуются. Если базовый класс содержит только конструкторы с параметрами, то производный класс должен вызывать в своем конструкторе один из конструкторов базового класса. Например, добавим в классы man и workman конструкторы без параметров и вызовем их в главной программе:

 

//класс человек

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     virtual void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

     };

     //метод вывода информации о человеке

     virtual void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     };

     //конструктор без параметров

     man()

     {

         //задаем начальные значения для полей объекта

         strcpy(fam, "Антонов");

         strcpy(name, "Антон");

         age=20;

     }

};

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

     public:

     //переопределяем метод ввода информации о сотруднике предприятия

     void vvodInfo()

     {

         //реализуем функцию для ввода данных:

         //(фамилии, имени, возраста) из класса man

         man::vvodInfo();

         //выполняем недостающие действия для объекта класса workman:

         //ввод названия предприятия

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //переопределяем метод вывода информации о сотруднике предприятия

     void vivodInfo()

     {

         //реализуем функцию для вывода данных:

         //(фамилии, имени, возраста) из класса man

         man::vivodInfo();

         //выполняем недостающее действие для объекта класса workman

         //вывод информации о предприятии     

         cout<<" Предприятие: "<<firm<<endl;

     };

//вызов конструктора базового класса

     workman():man()

     {

         //задаем начальное значение для поля название предприятия

         //значения для фамилии, имени и возраста

//берутся из конструктора родительского класса man

         strcpy(firm, "AMK");

     }

};

 

Результат работы программы:

 

 

Теперь конструкторы без параметров заменим конструкторами с параметрами:

 

//класс человек

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //фамилия, имя

     char fam[15], name[15];

     //возраст

     int age;

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     virtual void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

     };

     //метод вывода информации о человеке

     virtual void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     };

     //конструктор с параметрами: фамилия, имя, возраст

     man(char f[15], char n[15], int a)

     {

         //задаем начальные значения для полей

         //переданными в конструктор параметрами

         strcpy(fam, f);

         strcpy(name, n);

         age=a;

     }

};

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

     public:

     //переопределяем метод ввода информации о сотруднике предприятия

     void vvodInfo()

     {

         //реализуем функцию для ввода данных:

         //(фамилии, имени, возраста) из класса man

         man::vvodInfo();

         //выполняем недостающие действия для объекта класса workman:

         //ввод названия предприятия

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //переопределяем метод вывода информации о сотруднике предприятия

     void vivodInfo()

     {

         //реализуем функцию для вывода данных:

         //(фамилии, имени, возраста) из класса man

         man::vivodInfo();

         //выполняем недостающее действие для объекта класса workman

         //вывод информации о предприятии     

         cout<<" Предприятие: "<<firm<<endl;

     };

     //вызов конструктора базового класса

     workman(char f[15], char n[15], int a,char fi[20]):man(f,n,a)

     {

         //задаем начальные значения для поля название предприятия

         strcpy(firm, fi);

     }

};

 

После списка параметров конструктора производного класса через двоеточие идет вызов конструктора базового класса, в который передаются значения параметров f, n и a.

 

//главная программа

int main()

{

     system("chcp 1251>nul");

     //описываем переменную для работы с классом человек

     //задаем начальные данные, которыми конструктор заполнит поля    

     man m("Иванов", "Иван", 25);

     //выводим на экран данные о человеке, назначенные конструктором

     cout<<"Начальные данные:"<<endl;

     m.vivodInfo();

     //описываем переменную для работы с классом работник предприятия

     //задаем начальные данные, которыми конструктор заполнит поля    

     workman m1("Петров", "Петр", 35, "AMK");

     cin.get();

     //выводим на экран данные о работнике предприятия, назначенные конструктором

     cout<<"Начальные данные:"<<endl;

     m1.vivodInfo();   

     system("pause");

     return 0;

}

 

В строке

 

workman m1("Петров", "Петр", 35, "AMK");

 

Вначале будет вызываться конструктор базового класса man, в который будут передаваться значения "Петров", "Петр" и 35. И таким образом будут установлены фамилия, имя и возраст. Затем будет выполняться конструктор workman, который установит название предприятия.

 

Результат работы программы:

 

 

3.      Спецификатор protected

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

Например, определим переменную fam со спецификатором protected:

 

#include <iostream.h>

#include <string.h>

//класс человек

class man

{

     //описываем переменные, используемые и доступные только внутри класса

     private:

     //имя

     char name[15];

     //возраст

     int age;

     protected:

     //фамилия - доступна внутри класса man и внутри производных классов

     char fam[15];

     //описываем функции, которые будут доступны за пределами класса (например, в главной программе)

     public:

     //метод ввода информации о человеке

     virtual void vvodInfo()

     {

         cout<<"Введите информацию о человеке: "<<endl;

         cout<<"-Фамилию ";

         cin.getline(fam,15);

         cout<<"-Имя ";

         cin.getline(name,15);

         cout<<"-Возраст ";

         cin>>age;

     };

     //метод вывода информации о человеке

     virtual void vivodInfo()

     {

         cout<<fam<<" "<<name<<" "<<age<<" лет"<<endl;

     };

     // метод вывода фамилии человека

     void vivodFam()

     {

         cout<<"Фамилия сотрудника: "<<fam<<endl;

     }

};

 

//класс работник предприятия - наследуемый от класса человек

class workman:public man

{

     private:

     //место работы

     char firm[20];

     public:

     //переопределяем метод ввода информации о сотруднике предприятия

     void vvodInfo()

     {

         //реализуем функцию для ввода данных:

         //(фамилии, имени, возраста) из класса man

         man::vvodInfo();

         //выполняем недостающие действия для объекта класса workman:

         //ввод названия предприятия

         cin.get();

         cout<<"-Предприятие ";

         cin.getline(firm,20);

     };

     //переопределяем метод вывода информации о сотруднике предприятия

     void vivodInfo()

     {

         //реализуем функцию для вывода данных:

         //(фамилии, имени, возраста) из класса man

         man::vivodInfo();

         //выполняем недостающее действие для объекта класса workman

         //вывод информации о предприятии     

         cout<<" Предприятие: "<<firm<<endl;

     };

};

 

int main()

{

     system("chcp 1251>nul");

     //описываем переменную для работы с классом человек

     //задаем начальные данные, которыми конструктор заполнит поля    

     man m;

     //вводим данные о человеке

     m.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о человеке

     m.vivodInfo();

//cout<<"Фамилия человека: "<<m.fam<<endl; //ОШИБКА

 

     //описываем переменную для работы с классом работник предприятия

     //задаем начальные данные, которыми конструктор заполнит поля    

     workman m1;

     cin.get();

     //вводим данные о работнике предприятия  

     m1.vvodInfo();

     cout<<"Вы ввели:"<<endl;

     //выводим на экран введенные данные о работнике предприятия 

     m1.vivodInfo();

//cout<<"Фамилия сотрудника: "<<m1.fam<<endl; //ОШИБКА

 

     //вызов функции, доступной в производном классе

m1.vivodFam();

    

     system("pause");

     return 0;

}

 

Таким образом, мы можем использовать переменную fam в производном классе, например, в методе vivodFam, но извне мы к ней обратиться не можем.

 

4.      Запрет наследования

Иногда наследование от класса может быть нежелательно. И с помощью спецификатора final мы можем запретить наследование:

 

class man final

{

};

После этого не сможем унаследовать другие классы от класса man. И, например, если напишем, как в случае ниже, то столкнемся с ошибкой:

 

class workman : public man

{

};