Не могли бы вы прокомментировать этот момент:
Используя виртуальные функции для обеспечения полиморфизма необходимо использовать указатель именно на базовый класс.
В каких случаях необходимо использовать указатель на класс наследник, а в каких на базовый класс?
Просто недавно наткнулся на такой пример, он корректно реализован?
#include
class Base { //Базовый класс
public:
int base_data;
Base(int base_data);//Конструктор класса
virtual void Virtual_method(); // Виртуальный метод
void Nonvirtual_method(); // Не виртуальный метод
virtual~Base(); //Виртуальный деструктор
};
Base::Base(int base_data){
this->base_data = base_data;
cout << "Конструктор базового" << endl;
}
void Base::Virtual_method() {
cout << "Виртуальный метод базового класса
";
}
void Base::Nonvirtual_method() {
cout << "Не виртуальный метод базового класса
";
}
Base::~Base(){
cout << "Деструктор базового класса
";
}
class Derived : public Base {//Класс наследник
public:
double derived_data;
Derived(double derived_data, int base_data);
void Virtual_method(); // Переопределённый виртуальный метод базового класса
void Nonvirtual_method(); //Не виртуальна функция
~Derived();
};
Derived::Derived(double derived_data, int base_data):Base(base_data){
this->derived_data = derived_data;
cout << "Конструктор наследника" << endl;
}
void Derived::Virtual_method() {
cout << "Переопределённый виртуальный метод класса наследника
";
}
void Derived::Nonvirtual_method() {
cout << "Не виртуальный метод класса наследника
";
}
Derived::~Derived(){
cout << "Деструтор класса наследника
";
}
int main() {
setlocale(LC_ALL, "RUS");
Derived derived(2.0, 1);//Объявили объект класса наследника
Derived *pDerived = &derived;//Объявили указатель на класс наследник, присвоив ему ссылку на объект класса наследника
Base *pBase = &derived;//Объявили указатель на базовый класс, присвоив ему ссылку на объект класса наследника
pBase->Virtual_method(); // Вызов виртуального метода
pBase->Nonvirtual_method(); // Вызов не виртуального метода
pDerived->Virtual_method(); // Вызов виртуального метода
pDerived->Nonvirtual_method(); // Вызов не виртуального метода
cout << "base_data = " << derived.base_data << "
derived_data = " << derived.derived_data << "
";
return 0;
}
Ответ
Обычно, когда говорят о полиморфизме в программировании, то пытаются дать определение этого слова в терминах языков программирования.
Однако на мой взгляд более удачное определение полиморфизма дается в биологии. Только слово организм в этом определении следует заменить словом объект в терминах программирования.:)
Полиморфи́зм в биологии (от др.-греч. πολύμορφος — многообразный) —
способность некоторых организмов существовать в состояниях с различной
внутренней структурой или в разных внешних формах
В вашей демонстрационной программе определяется указатель типа Base *, то есть статический тип объектов, адресуемых этим указателем является тип Base
Base *pBase;
Когда такому указателю присваивается адрес объекта производного класса, то объект, к которому происходит обращение через этот указатель, рассматривается как объект типа Base. Фактически, вся информация о том, что этот объект на самом деле является производного класса, а не базового, теряется. так как статический тип указателя Base *
Однако благодаря наличию такого средства, как виртуальные функции, позволяет получать многообразие поведения адресуемого указателем объекта в зависимости от того, какой тип на самом деле имеет адресуемый объект. То есть через виртуальные функции имеется возможность различать реальный тип адресуемых объектов и обращаться к их уникальным свойствам. И это демонстрируется ваша программа на примере данных предложения
Base *pBase = &derived;//Объявили указатель на базовый класс, присвоив ему ссылку на объект класса наследника
pBase->Virtual_method(); // Вызов виртуального метода
pBase->Nonvirtual_method(); // Вызов не виртуального метода
Как видно указатель типа Base * на самом деле адресует объект производного класса. Но если вызывать не виртуальный метод
pBase->Nonvirtual_method(); // Вызов не виртуального метода
то вся информация о производном классе недоступна, так как компилятор вызывает функции в соответствии со статическим типом объекта, адресуемого указателем, то есть в соответствии с типом Base. Компилятор при поиске имени функции, которую следует вызвать смотрит определение базового класса. Он ничего не знает о производных классах.
Ежели вы вызываете виртуальную функцию
pBase->Virtual_method(); // Вызов виртуального метода
то компилятор также при поиске имени вызываемой функции смотрит определение базового класса. Однако тут происходит некоторый фокус. Производный класс подменил определение виртуальной функции в базовом классе своим определением этой функции. Технически это делается следующим образом. Если класс объявляет виртуальную функцию, то он создает таблицу указателей на виртуальные функции, объявленные в своем определении. Производные же классы, которые переопределяют виртуальные функции подставляют в эту таблицу базового класса адреса своих определений функций. Поэтому динамически во время выполнения программы вызывается та функция, чей адрес находится в таблице адресов виртуальных функций.
Что касается вашего вопроса
В каких случаях необходимо использовать указатель на класс наследник,
а в каких на базовый класс?
То когда вы объявляете указатель на класс наследник, то вы теряете полиморфизм, так как вы можете работать только с объектами класса наследника (я предполагаю, что наследник в свою очередь не наследуется другими классами). А когда вы хотите достичь поведение, соответствующее полиморфизму, то
1) базовый класс должен содержать виртуальные функции т
2) следует объявить указатель или ссылку, имеющую статический тип указателя или ссылки на объекты базового класса, а инициализировать их объектами производных классов.
И тогда вы получите
способность некоторых объектов (организмов) существовать в состояниях
с различной внутренней структурой или в разных внешних формах
Примечание. Если при вызове виртуальной функции вы указываете ее квалифицированное имя, то "виртуальность" исчезает. Вызывается функция класса, определенная в этом же классе.
Рассмотрите пример
#include
struct Base
{
virtual ~Base() { }
virtual void virtual_function() const
{
std::cout << "Base::virtual_function() is called" << std::endl;
}
};
struct Derived : Base
{
void virtual_function() const override
{
std::cout << "Derived::virtual_function() is called" << std::endl;
}
};
int main()
{
Derived d;
Base &rBase = d;;
rBase.virtual_function();
rBase.Base::virtual_function();
return 0;
}
Ее вывод на консоль будет следующим
Derived::virtual_function() is called
Base::virtual_function() is called
В первом случае вступает в игру полиморфизм. Вызывается переопределенная виртуальная функция производного класса Derived, хотя базовый тип ссылки - это класс Base
Во втором случае, когда используется квалифицированное имя, "виртуальность" фукнции пропадает, и вызывается реализация функции базового класса Base
Комментариев нет:
Отправить комментарий