C++ 为什么可以';派生类指针指向基类对象而不强制转换吗?

C++ 为什么可以';派生类指针指向基类对象而不强制转换吗?,c++,inheritance,pointers,C++,Inheritance,Pointers,对于这类基本问题,我看到的宠物和狗类型的例子很少,但它们对我来说没有意义,原因如下 假设我们有下面的类结构 class Pet {}; class Dog : public Pet {}; 然后是下面的陈述 a(狗)是(宠物) 在我看来,这在现实生活中可能是正确的,但在C++中却不是正确的。只需看看Dog对象的逻辑表示,它看起来如下所示: 更恰当的说法是 a(狗)有一只(宠物) 或 a(宠物)是(狗)的子集 如果你注意到这是“狗是宠物”的逻辑对立面 现在的问题是,下面的#1是允许的,而#2

对于这类基本问题,我看到的宠物和狗类型的例子很少,但它们对我来说没有意义,原因如下

假设我们有下面的类结构

class Pet {};
class Dog : public Pet {};
然后是下面的陈述

a(狗)是(宠物)

在我看来,这在现实生活中可能是正确的,但在C++中却不是正确的。只需看看Dog对象的逻辑表示,它看起来如下所示:

更恰当的说法是

a(狗)有一只(宠物)

a(宠物)是(狗)的子集

如果你注意到这是“狗是宠物”的逻辑对立面


现在的问题是,下面的#1是允许的,而#2不是:

Pet* p = new Dog;  // [1] - allowed!
Dog* d = new Pet;  // [2] - not allowed without explicit casting!
我的理解是,不允许在没有警告的情况下使用
[1]
,因为指针不可能指向其超集类型的对象(Dog对象是Pet的超集),因为Pet不知道Dog可能声明的新成员(上图中的Dog-Pet子集)

[1]
相当于试图指向
双精度对象的
int*

很明显,我在这里遗漏了一个关键点,这将使我的整个推理颠倒过来。你能告诉我是什么吗?


我相信,与现实世界的例子进行类比只会使事情复杂化。我更愿意从技术细节的角度来理解这一点。谢谢

在技术细节方面:

Dog
对象的附加信息被附加到
Pet
对象的末尾,因此
Dog
的前缀[位]实际上是
Pet
,因此将
Dog*
对象分配给
Pet*
变量没有问题。访问
Dog
对象的
Pet
字段/方法是完全安全的

然而,这种说法并不正确。如果您将
Pet*
的地址分配给
Dog*
变量,然后访问
Dog
[该字段不在
Pet
]中]的一个字段,您将离开分配的空间

键入推理:
还请注意,只有在变量类型正确且不强制转换[c++是langauge]的情况下,才可以将值指定给变量。由于
是一只
宠物
狗*
是一只
宠物*
-因此这并不矛盾,但反过来说就不正确。

狗是宠物,因为它源于宠物类。在C++中,几乎完全满足面向对象编程的IS-A要求。p>
当然这是不允许的,宠物也可以是猫或鹦鹉。

这是一个等级分类问题。如果我告诉我的孩子他们可以养宠物,那么狗是绝对可以养的。但是如果我告诉他们只能养猫,那么他们就不能要鱼。

我想你在混淆在面向对象上下文中is-a的意思。可以说
有一个子对象
Pet
,如果深入到对象的位表示,这是正确的。但重要的是编程是将现实建模为计算机可以处理的程序。继承是对关系进行建模的方式,如您的示例所示:

一只
是一只
宠物


通俗地说,这意味着它展示了宠物的所有行为,可能有一些不同的特征行为(吠叫),但它是一种动物,它提供了陪伴,你可以喂养它。。。所有这些行为都将由
Pet
类中的(虚拟)成员函数定义,并且可能会在
Dog
类型中被重写,就像定义其他操作一样。但重要的一点是,通过使用继承,所有的
Dog
实例都可以用于需要大量
Pet
的地方,您试图得出的陈述可能是“导致它成为宠物的狗的方面是狗的所有方面的子集”和“作为狗的所有实体的集合是作为宠物的实体集合的子集”

D、 使得D(x)=>x∈ 狗,P(x)=>x∈ 宠物

D(x)=>p(x)

(D(x)是真的,如果x具有狗的所有方面,那么这意味着狗的方面是宠物的方面的一个超集-p(x)是真的,如果D(x)是真的,但不一定相反)

狗⊇ 宠物=>

∀ x x∈ 狗=>x∈ 宠物(每只狗都是宠物)

但如果D(x)≡ x∈ 狗,那么这些都是相同的陈述


所以说“狗成为宠物的方面是整个狗的一个子集”等同于说“狗的集合是宠物集合的一个子集”

你已经混淆了基类和父类

Pet
是基类。
Pet*
可以指向任意数量的不同类型,只要它们继承自
Pet
。因此,
Pet*Pet=new Dog
被允许也就不足为奇了<代码>狗
是一种
宠物
。我有一个指向宠物的指针,它恰好是一只狗

另一方面,如果我有一个
宠物*
,我不知道它实际上指的是什么。它可以指向
,但也可以指向
,或者完全其他的东西。因此,该语言不允许我调用
Pet->bark()
,因为并非所有
Pet
s都能
bark()
Cat
s
meow()

但是,如果我有一只我知道的
宠物*
实际上是一只
,那么把它扔给
,然后叫
树皮()
,是完全安全的

因此:

考虑一下这个场景(很抱歉有这样一个俗气的例子):Dog* d = new Pet;  // [2] - not allowed without explicit casting!
Pet* p = new Dog;  // sure this is allowed, we know that all Dogs are Pets
Dog* d = new Pet;  // this is not allowed, because Pet doesn't support everything Dog does
class Vehicle
{
   int numberOfWheels;
   void printWheels();
};
class Car: public Vehicle
{
    // the wheels are inherited
    int numberOfDoors;
    bool doorsOpen;
    bool isHatchBack;
};
class Bike: public Vehicle
{
    int numberOfWings; // sorry, don't know exact name
    // the wheels are inherited
};
Pet* p = new Dog;
Pet: [pet data]
Dog: [pet data][dog data]
Cat: [pet data][cat data]
Pet* p = new Dog;  // [1] - allowed!
Dog* d = new Pet;  // [2] - not allowed without explicit casting!
d = p; // won't compile
d = static_cast<Dog *>(p); // [3]
d = dynamic_cast<Dog *>(p); // [4]