[英]Assigning derived class array to base class pointer
#include <iostream>
class B {
public:
B () : b(bCounter++) {}
int b;
static int bCounter;
};
int B::bCounter = 0;
class D : public B {
public:
D () : d(bCounter) {}
int d;
};
const int N = 10;
B arrB[N];
D arrD[N];
int sum1 (B* arr) {
int s = 0;
for (int i=0; i<N; i++)
s+=arr[i].b;
return s;
}
int sum2 (D* arr) {
int s = 0;
for (int i=0; i<N; i++) s+=arr[i].b+arr[i].d;
return s;
}
int main() {
std::cout << sum1(arrB) << std::endl;
std::cout << sum1(arrD) << std::endl;
std::cout << sum2(arrD) << std::endl;
return 0;
}
问题出在主要功能的第2行。 我期望当使用参数arrD(它是Derived类对象的数组)调用sum1()函数时,它只会“切断”D :: d,但在这种情况下,它会重新排列arrD中的顺序,并且求和是这样的:10 + 11 + 11 + 12 + 12 + 13 + 13 + 14 + 14 + 15它似乎在arrD [i]的b和d场之间交替,它应该只是b场的总和。 有人可以解释一下原因吗? 提前致谢。
你已经不幸遇到类型系统的一个最佳点,它允许编译完全无效的代码。
函数int sum1 (B* arr)
根据签名将一个指向B
对象的指针作为参数,但从语义上讲,它实际上是一个指向B
对象数组的指针。 当你调用sum1(arrD)
你是不是传递一个数组违反该合同B
对象,而是组成的数组 D
的对象。 他们有什么不同? 指针算法基于指针类型的大小完成, B
对象和D
对象具有不同的大小。
D
的数组不是B
的数组
通常,派生类型的容器不是基本类型的容器。 如果你考虑一下, D
的容器的合同就是它拥有D
对象,但如果D
的容器是B
的容器,那么你就可以添加B
对象(如果参数是扩展的话) ,您甚至可以考虑添加D1
对象 - 也来自B
!)。
如果你使用更高阶的构造来代替原始数组,比如std::vector
,编译器会阻止你传递std::vector<D>
来代替std::vector<B>
,但是为什么它没有在阵列的情况下阻止你?
如果D
的数组不是B
的数组,为什么程序会编译?
对此的回答早于C ++。 在C中,函数的所有参数都按值传递。 有些人认为你也可以传递指针 ,但这只是传递一个指针值 。 但是数组很大 ,按值传递数组会非常昂贵。 同时,当您动态分配内存时,您使用指针 ,虽然从概念上讲,当您使用malloc 10 int
您正在分配一个int
数组 。 C语言的设计者考虑了这一点,并对值传递值规则进行了例外处理:如果尝试按值传递数组,则获取指向第一个元素的指针,并传递该指针而不是数组(类似函数存在规则,你不能复制一个函数,所以传递一个函数隐式获得一个指向函数的指针,然后传递它。 从一开始就在C ++中使用了相同的规则。
现在,下一个问题是类型系统不能区分指向元素的指针,而是指向元素的指针,指向元素的一部分。 这会产生后果。 指向D
对象的指针可以隐式转换为指向B
的指针,因为B
是D
的基础,并且OO编程的整个对象能够使用派生类型代替基础对象(嗯,用于此目的)多态性)。
现在回到原始代码,当你写sum1( arrD )
, arrD
被用作右值 ,这意味着数组衰减到指向第一个元素的指针,所以它实际上被转换为sum1( &arrD[0] )
。 子表达式&arrD[0]
是一个指针,一个指针只是一个指针... sum1
是一个指向B
的指针,一个指向D
的指针可以隐式转换为一个指向B
的指针,所以编译器很乐意为你: sum1( static_cast<B*>(&arrD[0]) )
。 如果函数只使用指针并将其用作单个元素,那就没问题了,因为你可以传递D
来代替B
,但D
的数组不是B
的数组......即使编译器也是如此允许你这样传递它。
B
的大小小于D
的大小。 因此,当sum1
迭代指针arr
, arr[1]
指向它认为是数组中的第二个B
元素,它实际上位于第一个D
元素的中间。
所以(假设没有填充), arrD
有这样的布局:
arrD: | 2 ints | 2 ints | 2 ints | ...
但是,你设置一个B *arr
,使sum1
认为它是一个B的数组。所以sum1
会认为参数有这样的布局:
arr: | int | int | int | int | int | int | ...
所以, arr[1]
实际上是arrD[0]
的d
成员。
你的arr
是B*
类型,这意味着arr[i]
或(arr + i)
会在内存中提升sizeof(B) * i
。 内存看起来像这样:
10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20
并且for循环增加了
10 11 11 12 12 13 13 14 14 15
这正是内存中的第一个元素,而不是像sizeof(D) * i
那样前进sizeof(D) * i
喜欢你想要它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.