我正在阅读一些C ++文本并获得以下代码:
class A { };
class B : public A { };
void main() {
A* p1 = new B; // B may be larger than A :OK [Line 1]
B* p2 = new A; // B may be larger than A :Not OK [Line 2]
}
我有两个问题:
答案 0 :(得分:8)
嗯,“更大”不是关键所在。真正的问题是“是一种”关系。
class B
的任何对象也属于class A
类型(由于继承,class B
也是class A
),所以第一行是可以的(指向{{的指针) 1}}也可以指向class A
)的对象,但反之则不正确(class B
不是class A
,甚至可能不知道class B
存在),所以第二行不会编译。
答案 1 :(得分:7)
作者向您展示他不了解C ++(或一般的编程)。没有涉及尺寸(“更大”)的问题。问题是B
“isA”A
,因此可以使用指向A
的指针初始化指向B
的指针。但事实恰恰相反。
答案 2 :(得分:1)
评论很愚蠢,真的。对象大小与它没有太大关系。问题是你可以隐式地向上转换指针类型,但不能向下转换。
BTW,main
必须的返回类型为int
。不是void
。
答案 3 :(得分:1)
这里的每个人都给出了正确的答案,但我想指出作者所说的“更大”等等 考虑这两个类:
class Animal {
public:
bool bIsHungry;
};
class Bird : public Animal {
public:
bool bIsFlying;
}
然后我打电话
Animal* animal = new Bird; // B may be larger than A :OK [Line 1]
程序分配足够的空间以适应变量“bIsHungry”和变量“bIsFlying”。 (但是,除非你将“动物”命名为“动物”,否则你只能访问“bIsHungry”,即使“bIsFlying”也为记忆中的“动物”保留。)
致电时
Bird* parrot = new Animal; // B may be larger than A :Not OK [Line 2]
程序只分配足够的空间来适应变量“bIsHungry”。然而,“parrot”的用户可能想要编写诸如
之类的代码if(parrot->bIsFlying)
{ //doSomething()
...
}
这不起作用,因为使用“new Animal”,程序只为Animal类分配空间,即“bIsHungry”,并且没有为“bIsFlying”分配内存。编译器已“看到”并将“抱怨”,即报告错误。
答案 4 :(得分:0)
因为B来自A,指针B *不能指向A.
答案 5 :(得分:0)
B* p2 = new A;
无效,因为指向B的指针可能希望B
获得的信息多于A
。
示例:
class B : public A {
public:
int notInA;
};
B* p2 = new A;
p2->notInA = 5; // Wait, which notInA are we talking about?
// p2 is really an A, and As don't have notInA!
答案 6 :(得分:0)
当您使用指针时,指针值是一个内存位置。这意味着您可以将指针设置为指向内存中的任何点。为了“取消引用”该内存并使用该点存储的对象,您需要知道期望的对象类型。
当B类继承A类时,B将包含“A”。 Sharptooth是正确的说是存在“Is-A”关系。 “B”是“A”,但“A”不是“B”
问题在以下代码中完全相同:
string* s = new string("");
int a = 44;
s = (string*)&a; //compiler error if not cast
cout << s; // randomness printed.
答案 7 :(得分:0)
class B : public A { };
A* p1 = new B; // B may be larger than A :OK [Line 1]
B* p2 = new A; // B may be larger than A :Not OK [Line 2]
我不明白作者在第1行和第2行的评论意思。
为什么我们不能在2号线做?
class B
派生自class A
,从它包含的成员变量的角度来看 - 意味着它拥有A所拥有的一切以及它选择添加的任何东西。在你的简单代码中,B没有添加任何内容,但如果它确实有其他数据成员,那么显然需要比更简单的A
类型更多的内存。如果它添加了一个虚拟成员函数,其中A没有,则可以期望编译器在B中添加一个指针,该指针记录虚拟调度表的地址,列出其virtual
成员函数的地址。如果感觉像编译器也可以自由添加填充。
因此,一般情况是派生类的大小是其基类大小的>=
。
A* p1 = new B; // B may be larger than A :OK [Line 1]
这里,从堆/免费存储中分配实际需要的空间B
,以及p1
中存储的内存地址。如果B
大于A
,则没有任何区别 - 无论如何它都在其他地方 - 关键是保证B*
能够存储在{{1}中}}
A*
这里,在堆上创建了一个新的B* p2 = new A; // B may be larger than A :Not OK [Line 2]
,但程序员正试图告诉编译器该地址有一个A
。编译器不会相信它(除非被强制) - 你只会得到编译器时间错误。如果您强制编译器(例如p2 =(B *)(新A)B
p2 ) to treat the memory address in
B as if it were an
B , then it may later try to access additional data it expects to be part of any
A`:额外数据成员,虚拟调度指针等。