За последние несколько недель я изучаю и экспериментирую наследование и полиморфизм в C++.
Немного синтаксиса всегда путают меня, чтобы понять, главным образом, вызов объекта из основной функции.
например:
#include <iostream.h>
using namespace std;
class Base
{
public:
Base(){ cout<<"Constructing Base";}
virtual ~Base(){ cout<<"Destroying Base";}
};
class Derive: public Base
{
public:
Derive(){ cout<<"Constructing Derive";}
~Derive(){ cout<<"Destroying Derive";}
};
void main()
{
Base *basePtr = new Derive();
delete basePtr;
}
Вот мой вопрос:
Что на самом деле происходит, когда Base * basePtr = new Derive(); этот синтаксис называется? и каковы преимущества?
По моим сведениям, я понял, что он вызывает getive объект класса и сохраняет его в указателе на объект базового класса. Я прав? Если да, то почему мы храним его в базовом классе?
Чтобы устранить мои сомнения, я просмотрел схему памяти объектов класса и разборки, но это меня смущает больше.
Может ли кто-нибудь сказать мне, как понимать этот синтаксис?
Открытое наследование означает, что каждый объект производного класса IS одновременно является объектом базового класса (он предоставляет все интерфейсы базового класса). Итак, когда вы пишете:
Base *basePtr = new Derive();
создается новый объект класса Derive, чем указатель на него назначается basePtr
а через basePtr
вы можете получить доступ ко всем функциям, предоставляемым Base
классом.
И если вы затем вызываете любую из виртуальных функций Base
класса, например:
basePtr->callSomeVirtualFunction();
будет вызвана функция из фактического класса объекта, как это происходит с деструктором в конце вашей основной функции.
Когда вы используете указатель на объект базового класса вместо указателя на производный, вы говорите, что вам нужны только BASIC-свойства этого производного класса.
Это называется Polymorphism
. Это означает, что объект является Derive, а также Base и может использоваться как для обоих. Напр. Если собака является подклассом Animal. Объект собаки можно рассматривать как животное. Все собаки - животные, но не все животные - собака.
Поэтому вы можете назвать собаку животным, поэтому вы можете указать адрес объекта подкласса (Derive) на указатель суперкласса (Base). Но он останется объектом подкласса и будет функционировать как один. Это просто для того, чтобы обмануть компилятор в понимании того, что это объект базы.
Теперь вы можете иметь метод, который может принимать объект (или указатель в точном смысле) базового класса, но может быть передан любым из его подклассов. Здесь вы можете вызывать только методы, которые находятся в базовом классе и могут или не могут быть переопределены в классе вывода.
Хммм... Указатели сбивают с толку в начале.
Когда вы вызываете Base *basePtr = new Derive();
, вы создаете экземпляр объекта Derive
и просто сохраняете "закладку", где находится этот объект, но с указателем Base
.
Когда вы это сделаете, единственные доступные свойства (без трансляции) будут из Base
класса.
Почему это используется? Отвлечься. Представьте, что вы кодируете нечто, связанное с кружками, чашками, очками и кувшинами. В основном все типы этих объектов предназначены для хранения какой-либо жидкости. Поэтому я буду называть базовый класс LiquidContainer
:
class LiquidContainer
{
//...
};
class Mug : public LiquidContainer
{
//...
};
class Glass : public LiquidContainer
{
//...
};
class Cup : public LiquidContainer
{
//...
};
class Jug : public LiquidContainer
{
//...
};
Все остальные унаследованы от LiquidContainer
, хотя кувшин, кубок и кружка могут быть созданы в немного более сложном дереве наследования.
Во всяком случае, цель иметь базовый класс и использовать полиморфизм состоит в том, чтобы избежать репликации кода и абстракции thins, позволяя всем LiquidContainer
лечиться почти одинаково.
Возьмем, например, более полное определение класса.
class LiquidContainer
{
public:
LiquidContainer(unsigned int capacity, unsigned int color) :
mCapacity(capacity),
mColor(color)
{
}
unsigned int getCapacity() { return mCapacity; }
unsigned int getColor() { return mColor; }
virtual char* name() = 0;
protected:
unsigned int mCapacity;
unsigned int mColor;
};
class Mug : public LiquidContainer
{
public:
Mug() :
LiquidContainer( 250, 0xFFFF0000 ) // 250 ml yellow mug!
{
}
virtual char* name() { return "Mug"; }
};
class Glass : public LiquidContainer
{
public:
Glass() :
LiquidContainer( 200, 0x000000FF ) // 200 ml transparent glass!
{
}
virtual char* name() { return "Glass"; }
};
class Cup : public LiquidContainer
{
public:
Cup() :
LiquidContainer( 50, 0xFFFFFF00 ) // 50 ml white cup!
{
}
virtual char* name() { return "Cup"; }
};
class Jug : public LiquidContainer
{
public:
Jug() :
LiquidContainer( 1500, 0x0000FF00 ) // 1.5 l blue Jug!
{
}
virtual char* name() { return "Jug"; }
};
С помощью этих определений классов вы можете выполнить следующий тест:
#include <iostream>
#include <vector>
int main( int argc, char* argv[] )
{
std::vector< LiquidContainer* > things;
things.push_back( new Mug() );
things.push_back( new Cup() );
things.push_back( new Glass() );
things.push_back( new Jug() );
for ( auto container : things )
{
std::cout << "This is a '" << container->name() << "' with capacity of " << container->getCapacity() << "ml and color " << container->getColor() << std::endl;
}
return 0;
}
Эта небольшая программная продукция
This is a 'Mug' with capacity of 250ml and color 4294901760
This is a 'Cup' with capacity of 50ml and color 4294967040
This is a 'Glass' with capacity of 200ml and color 255
This is a 'Jug' with capacity of 1500ml and color 65280
Я надеюсь, что этого небольшого упражнения достаточно, чтобы показать вам, почему используется полиморфизм.