谁能解释一下它们的实际用途是什么?事实是,我了解它们的工作机制,但我不明白它们的用途和用途。
让我们看一个例子:
class Animal
{
public:
Animal():itsAge(1) { cout << "Animal constructor...\n"; }
virtual ~Animal() { cout << "Animal destructor...\n"; }
virtual void Speak() const { cout << "Animal speak!\n"; }
protected:
int itsAge;
};
class Dog : public Animal
{
public:
Dog() { cout << "Dog constructor...\n"; }
virtual ~Dog() { cout << "Dog destructor...\n"; }
void Speak() const { cout << "Woof!\n"; }
void WagTail() { cout << "Wagging Tail...\n"; }
}
int main()
{
Animal *pDog = new Dog;
pDog->Speak();
return 0;
}
结果:
- 动物构造器...
- 狗构造器...
- 纬!
使用虚函数的本质是,当通过指针访问一个方法时,恰好会调用在基类中声明为虚函数而在派生类中被重写的选项。
但首先,使用 Animal 类指针 *pDog,您仍然无法访问WagTail()方法(摇尾巴),因为它未在 Animal 类中定义。其次,使用这种机制,您将不得不支付与创建 v 表相关的某些成本(其中每个元素都占用 RAM 资源)。
另外,我不明白为什么在期望指向基类对象的指针时传递指向派生类对象的指针?
上述两个问题都可以通过在基类和派生类中声明非虚拟方法然后编写以下内容来解决:
Dog *pDog = new Dog;
代替:
Animal *pDog = new Dog;
“利润”在哪里?
考虑图形形状的经典示例。
您可以定义一个类
Shape,其中包含您打算在应用程序中使用的所有几何形状的通用方法。此类为所有几何形状定义了一个通用接口。
假设您有一个要在其上放置几何形状的窗体。表单事先并不知道它必须包含哪些几何形状。它将几何形状称为某种抽象对象,这些对象被赋予了一些方法,表单可以使用这些方法在控制台上显示这些形状。
由于可以在表格中添加任意数量的各种类型的几何形状,那么问题来了,如何将它们存储在表格中呢?如果这些形状没有共同的抽象表示,那么它们就不能以一种形式存储,因为需要一种特定的对象类型,这样它们就可以全部存储在一个容器中,而不关心形状实际上是不同的。
如果所有形状都从同一个类继承,这很容易做到,在本例中是从 class 继承的
Shape,并且在这个类中定义了表单可以使用的虚拟方法,而不管表单正在处理哪个特定对象。下面是一个简单的演示程序,它实现了所描述的想法。
有一个类
Form将所有几何形状(在本例中为类的对象LeftTriangle和RightTriangle)存储Rectangle在一个标准容器中std::vector,它有一个方法display允许您在控制台上显示所有形状,将显示自身的过程委托给每个图形.将程序输出到控制台
虚方法为所有派生类定义了一个公共接口,允许它们定义自己的接口实现。为了能够将派生类的对象引用为具有共同属性的相同类型的对象,必须将它们强制转换为某种共同类型。此公共类型可以是这些对象的公共基类之一。这样就实现了多态性,即看起来像同一类型对象的对象具有多种形式的行为和表示。
当然,每个派生类都可以另外定义自己的数据成员和方法。但在这种情况下,这就是它们与其他派生类的对象的区别。
例如,您可以说每个女人和每个男人都是一个人。但是你不能说,例如,每个人都是女人,或者每个人都是男人。如果您将女性和男性视为人,那么您可以不分性别地称呼他们,向他们发送各种信息,正如他们在巴解组织中所说的那样。例如,如果您是公共汽车的售票员,您可能需要出示车票。对你来说,公交车上的男女都是乘客,他们必须有共同的属性,比如有车票。为此,您必须将男性和女性视为某种一般类型的对象,在本例中为乘客。然而,男人和女人作为他们各自阶级的客体是不同的。比如女人可以生孩子,男人不能(除非男人是女人,
我不会描述任何狗或几何形状的优点,我会表达一种听起来平庸的考虑,但这对我的理解来说意义重大。
当他们说继承使重用代码更容易时,我们在谈论什么样的代码?关于派生类使用基类代码的事实?一点也不。这些事情可以通过简单的函数调用来完成。
继承使得以新的方式使用已经编写的代码(甚至编译为动态库)成为可能。
有些
f(Base*);在没有任何更改的情况下被重用,使用的代码甚至还没有接近编写,甚至可能在编写和编译这个代码时就没有设计f()过——它只是从Base类派生的虚函数的代码。是的,在某种程度上,这类似于将指向其他函数的指针传递给函数,但只是在非常一定程度上。而且,除此之外,“为什么我们需要将其他功能传递给功能?”这个问题,我希望不会让您感到困惑“利润在哪里?”
大家知道,C++是一种编译型静态类型语言,也就是在编译时进行类型解析,所以虚方法的机制将这种可能性扩展到了程序员想要在运行时判断对象类型的情况,使用,例如,
dynamic_cast在 NOT 多态类的情况下,这将不起作用,但会产生编译错误。假设有这样的代码
假设我们有这样一种情况,我们想要检查什么
p_smth指向类型SomeThingConcrete,以便可以调用特定于SomeThingConcrete-的方法someConcreteOperation()。验证如下
也就是说,如果它
downcast适用于该类型SomeThing(满足条件p_concrete != NULL),那么我们的假设得到证实并p_smth真正指向SomeThingConcrete,否则它p_smth指向其他某个派生SomeThing类。此外,如果基类不是多态的,它会变成一堆额外的代码,例如,如果您将指向基类的指针传递给函数
void f(SomeThing*),那么由于静态绑定属性,您将不得不f为SomeThingConcrete每个新继承人重载函数SomeThing- 以便它是在派生中重新定义的那个被称为继承方法的版本。动态绑定,使用调用虚拟方法的内置机制,将完全摆脱这种重载——即