繁体   English   中英

将派生类数组分配给基类指针

[英]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的指针,因为BD的基础,并且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迭代指针arrarr[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成员。

你的arrB*类型,这意味着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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM