基础知识:
Ø 数组:在C/C++中,数组是一块连续的内存,内存中数组元素紧密地排列在一起。内存的大小 = 元素个数 x 单个元素的大小。
Ø 操作符[]:使用索引访问数据中的元素。元素在内存中的位置为:数组开始位置 + 索引 x单个元素的大小。
Ø 类的实例:类的实例也占用内存。类占内存的大小和类的数据成员有关,类的数据成员越多,那么占用的内存也越大。
Ø 子类和父类的内存分配:父类中的数据成员在前,子类的数据成员在后。
Ø 指针的加减法:当指针加减一个整数时,实际的指针位置加减量为:该整数 * 指向类型的大小。如int *p; p+1的结果是p指向位置后的第4个字节。
Ø delete []:删除数据。从指针位置开始删除数组元素,一直到内存块结束。对于类数组,会对每个元素调用析构函数。
子类数组:
基于以上知识,子类数组的内存分布可以粗略表示为下图:
使用父类指针时的操作分析:
把一个子类数组变量赋值给一个数据元素父类的指针时,指针首先指向数组头的位置(如图)。
操作1:访问元素
访问第一个元素(元素0)时,没有任何问题。
访问下一个元素时,指针后移。按正常用法使用++操作或+=1操作。因为该指针是父类指针,所以偏移量为父类的大小sizeof(父类)。这时指针将指向图中“子类数据”的位置,而不是元素1的位置。这将导致出错。使用[]操作访问时也有同样的问题。
操作2:使用delete[]删除数据:
delete[]同样是按访问元素的方法逐个析构这些元素。因为它无法找到对象真正的起始位置。
特殊情况:
在优快云的讨论中,有人写出了验证代码,证明这样访问没有问题,如Chappell的代码:
class Base
{
public :
Base()
{
cout << " Base() " << endl;
}
virtual ~ Base()
{
cout << " ~Base() " << endl;
}
};
class Child: public Base
{
public :
Child()
{
cout << " Child() " << endl;
}
virtual ~ Child()
{
cout << " ~Child() " << endl;
}
};
int main( int argc, char * argv[])
{
Base * pBase = new Child[ 3 ];
delete []pBase;
return 0 ;
}
这段代码可以正确运行,但与刚才所说的并不矛盾。这段代码中的Child类属于特例,因为它没有数据成员,子类数据是空的。这时如果使用sizeof来检查Child类和Base类的大小,会发现它们是相同的。所以无论使用子类指针还是父类指针,元素定位的计算结果都一样。
总结:
不要用父类指针去操作子类数组,虽然你可能碰巧不出错。
作者:苏林