3
#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; 
}

问题出在 main 函数的第 2 行。我希望当 sum1() 函数使用参数 arrD(它是派生类对象的数组)调用时,它会简单地“切断”D::d,但在这种情况下,它会重新排列 arrD 中的顺序,并且求和是这样的: 10+11+11+12+12+13+13+14+14+15 它似乎在 arrD[i] 的 b 和 d 字段之间交替,它应该只总结 b 字段. 有人可以解释为什么吗?提前致谢。

4

3 回答 3

7

您很不幸地碰到了允许编译完全无效代码的类型系统的最佳位置之一。

该函数根据签名将指向对象int sum1 (B* arr)的指针作为参数,但从语义上讲,它实际上需要指向对象数组的指针。当您调用时,您通过传递的不是对象数组而是对象数组而违反了该合同。它们有何不同?指针运算是根据指针类型的大小来做的,一个对象和一个对象有不同的大小。BBsum1(arrD)BDBD

一个数组D不是一个数组B

通常,派生类型的容器不是基类型的容器。如果您考虑一下,容器的契约D是它包含D对象,但是如果容器D是 的容器B,那么您将能够添加B对象(如果参数是扩展的,您甚至可以考虑添加D1对象——也派生自B!)。

如果您使用的是高阶构造而不是原始数组,例如std::vector编译器会阻止您传递 astd::vector<D>代替 a std::vector<B>,但是为什么在数组的情况下它没有阻止您呢?

如果数组 ofD不是 数组B,那么程序为什么会编译?

这个问题的答案早于 C++。在 C 中,函数的所有参数都是按值传递的。有些人认为您也可以按指针传递,但这只是按值传递指针。但是数组很大,按值传递数组会非常昂贵。同时,当您动态分配内存时,您使用指针,尽管从概念上讲,当您 malloc 10 s 时,int您正在分配一个数组int. C 语言的设计者考虑到了这一点,并对按值传递规则做了一个例外:如果您尝试按值传递数组,则会获得指向第一个元素的指针,并且传递的是该指针而不是数组(类似函数存在规则,您不能复制函数,因此传递函数隐式获取指向函数的指针并传递它)。从一开始,相同的规则就出现在 C++ 中。

现在,下一个问题是类型系统并没有区分指向元素的指针(当它是所有元素时)和指向作为数组一部分的元素的指针。这会产生后果。指向D对象的指针可以隐式转换为指向 的指针B,因为B它是 的基础D,并且 OO 编程的整个对象能够使用派生类型代替基础对象(嗯,为了多态性)。

现在回到你的原始代码,当你写的时候sum1( arrD ), ,arrD被用作一个右值,这意味着数组衰减到一个指向第一个元素的指针,所以它实际上被转换为sum1( &arrD[0] )。子表达式&arrD[0]是一个指针,而一个指针只是一个指针...sum1接受一个指向 a 的指针B,而指向的指针可以D隐式转换为指向 的指针B,因此编译器很乐意为您进行转换sum1( static_cast<B*>(&arrD[0]) ):如果函数只是获取指针并将其用作单个元素,那很好,因为您可以传递 aD代替 a B,但数组 ofD不是B... 即使编译器允许您传递它就这样。

于 2012-08-22T00:46:40.707 回答
2

a 的大小B小于 a 的大小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]实际上是 的d成员arrD[0]

于 2012-08-21T23:17:40.453 回答
2

arr是 type 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像你想要的那样前进。

于 2012-08-21T23:25:11.453 回答