为什么要对派生类使用基类指针
Why use base class pointers for derived classes
class base{
.....
virtual void function1();
virtual void function2();
};
class derived::public base{
int function1();
int function2();
};
int main()
{
derived d;
base *b = &d;
int k = b->function1() // Why use this instead of the following line?
int k = d.function1(); // With this, the need for virtual functions is gone, right?
}
我不是CompSci工程师,我想知道这一点。如果我们可以避免基类指针,为什么要使用虚函数?
在你的简单示例中,多态性的力量并不明显,但如果你稍微扩展一下,它可能会变得更加清晰。
class vehicle{
.....
virtual int getEmission();
}
class car : public vehicle{
int getEmission();
}
class bus : public vehicle{
int getEmission();
}
int main()
{
car a;
car b;
car c;
bus d;
bus e;
vehicle *traffic[]={&a,&b,&c,&d,&e};
int totalEmission=0;
for(int i=0;i<5;i++)
{
totalEmission+=traffic[i]->getEmission();
}
}
这使您可以循环访问指针列表,并根据基础类型调用不同的方法。基本上,它允许您编写代码,在编译时不需要知道子类型是什么,但代码无论如何都会执行正确的功能。
你是对的,如果你有一个对象,你不需要通过指针引用它。当对象将作为创建对象的类型销毁时,也不需要虚拟析构函数。
当您从另一段代码中获取指向对象的指针时,就会出现该实用程序,并且您并不真正知道派生最多的类型是什么。可以在同一基上生成两个或多个派生类型,并具有返回指向基类型的指针的函数。虚拟函数将允许您使用指针,而不必担心您使用的是哪个派生类型,直到需要销毁对象。虚拟析构函数将在您不知道它对应于哪个派生类的情况下销毁该对象。
以下是使用虚函数的最简单示例:
base *b = new derived;
b->function1();
delete b;
它来实现多态性。除非有基类指针指向派生对象,此处不能具有多态性。
派生类的主要功能之一是指向 派生类与指向其基类的指针类型兼容。 多态性是利用这种简单但 强大而通用的功能,带来了面向对象 充分发挥其潜力的方法论。
在C++中,存在一种特殊的类型/子类型关系,其中有一个基数 类指针或引用可以寻址其任何派生类 没有程序员干预的子类型。这种操纵能力 具有指针或对基类的引用的多个类型是 被称为多态性。
子类型多态性允许我们编写应用程序的内核 独立于我们希望操作的单个类型。相反,我们 为抽象基类的公共接口编程 通过基类指针和引用。在运行时,实际 解析所引用的类型,并解析 调用公共接口。运行时分辨率 要调用的相应函数称为动态绑定(默认情况下, 函数在编译时静态解析(。在C++,动态 通过称为类虚拟的机制支持绑定 功能。通过遗传和动态的亚型多态性 绑定为面向对象编程奠定了基础
继承层次结构的主要好处是我们可以编程 到抽象基类的公共接口,而不是到 以这种方式形成其继承层次结构的各个类型 保护我们的代码免受该层次结构中的更改。我们定义 eval((, 例如,作为抽象查询库的公共虚函数 .class。通过编写诸如
_rop->eval();
用户代码不受我们查询语言的多样性和波动性的影响。这不仅允许添加、修订、 或删除类型而无需更改用户程序,但 使新查询类型的提供程序不必重新编码行为 或层次结构本身中所有类型通用的操作。这是 由遗传的两个特殊特征支持:多态性 和动态绑定。当我们谈到C++内的多态性时,我们 主要是指指针或基类引用的能力 以寻址其任何派生类。例如,如果我们定义一个 非成员函数 eval(( 如下所示,//pquery 可以寻址任何 派生自查询的类void eval( const Query *pquery ) { pquery->eval(); }
我们可以合法地调用它,传入任何对象的地址 四种查询类型:
int main()
{
AndQuery aq;
NotQuery notq;
OrQuery *oq = new OrQuery;
NameQuery nq( "Botticelli" ); // ok: each is derived from Query
// compiler converts to base class automatically
eval( &aq );
eval( ¬q );
eval( oq );
eval( &nq );
}
而尝试使用非从查询派生的对象的地址调用 eval(( 导致编译时错误:
int main()
{ string name("Scooby-Doo" ); // error: string is not derived from Query
eval( &name);
}
在 eval(( 中,执行 pquery->eval((; 必须调用 基于实际类的适当 eval(( 虚拟成员函数 对象查询地址。在前面的示例中,依次进行查询 地址 AndQuery 对象、NotQuery 对象、OrQuery 对象, 和一个名称查询对象。在执行期间的每个调用点 在我们的程序中,Pquery 解决的实际类类型是 确定,并调用相应的 eval(( 实例。动态 绑定是实现这一目标的机制。 在面向对象的范式中,程序员操作一组绑定但无限的类型集的未知实例。(这套 类型受其继承层次结构的约束。然而,从理论上讲,有 对层次结构的深度和广度没有限制。在C++这个 通过基类操作对象来实现 仅限指针和引用。在基于对象的范式中, 程序员 操作在编译点完全定义的固定单一类型的实例。虽然 对象的多态操作要求对象 通过指针或引用访问,操作 C++中的指针或引用本身不一定会产生 在多态性中。例如,考虑
// no polymorphism
int *pi;
// no language-supported polymorphism
void *pvi;
// ok: pquery may address any Query derivation
Query *pquery;
在C++,多态性 仅存在于单个类层次结构中。类型的指针 void* 可以被描述为多态的,但它们没有明确的 语言支持 — 也就是说,它们必须由程序员管理 通过显式转换和某种形式的判别器来跟踪 要解决的实际类型。
你似乎问了两个问题(在标题和结尾(:
-
为什么对派生类使用基类指针?这就是多态性的用法。它允许您统一处理对象,同时允许您进行特定的实现。如果这困扰你,那么我想你应该问:为什么是多态性?
-
如果我们可以避免基类指针,为什么要使用虚拟析构函数?这里的问题是,你不能总是避免基类指针来利用多态性的力量。
- 如何使用基类指针引用派生类成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 使用基类指针调用基类的值构造函数的语法是什么?
- 如何在基类指针向量的元素上应用重载的多态函数
- C++函数解析基类指针到派生类指针
- 在将派生类指针类型转换为派生类指针后,从基类指针调用派生类函数
- 我们可以在不知道其真实类型的情况下将基类指针转换为派生类指针吗?
- 从基类指针派生派生类的模板类型
- 当键是虚拟继承中涉及的基类指针时,对 std::unordered_map 项的访问崩溃
- 如何使用函数(而不是构造函数)将派生类对象分配给基类指针
- 创建基类指针的向量并将派生类对象传递给它(多态性)
- 无法将派生类存储在基类指针的向量中
- 有没有办法在没有虚拟的情况下使用基类指针调用派生类函数
- 基类指针:在哪里使用 new 和 delete
- 如何在CRTP实现中传递基类指针
- C++继承从基类指针访问派生类中的非虚拟函数
- 通过基类指针获取派生类对象的引用
- C++通过基类指针提升子类的序列化
- 将派生对象分配给函数内的基类指针
- 如果基类指针无法访问派生类成员函数,那么多态性有什么方便的呢?