Тема: «Наследование и
полиморфизм»
План
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
{
};