继承的最重要性质之一就是:可以通过"指向base class objects"的pointers和references,来操作derived class objects。如此的pointers和references,其行为是多态的。
有如下例子:
class BST{...};
class BalanceBST : public BST{...};
考虑有个函数,打印BSTs数组中的每一个BST的内容:
void PrintBSTArray(ostream& s, const BST array[], int numElements)
{
for (int i = 0; i < numElements; ++i)
{
s << array[i];
}
}
当你将一个BST对象的数组传给此函数,没问题:
BST bstArray[10];
...
PrintBSTArray(cout, bstArray, 10); // 良好运行
但是如果将一个BalanceBST组成的数组传给此函数:
BalanceBST bstArray[10];
...
PrintBSTArray(cout, bstArray, 10); // 未定义行为
行为未定义。这是因为PrintBSTArray函数时,使用下标i访问array涉及到对数组array的处理(array[i] = *(array + i))
。array是个指针,指向数组起始处。array所指内存和array + i
相距多远?是i * sizeof(数组中的对象)
。它会将传入的数组都处理成BST类型,因为其函数声明中array就是BST类型,这里不会有虚函数类似的处理。
如果你尝试删除数组,依旧传入BalanceBST数组
void DeleteArray(ostream& logStream, BST array[])
{
...
delete [] array;
}
BalanceBST* pArray = new Balance[50];
...
delete [] pArray;
虽然看不到,但其中依旧有指针运算表达式的存在。实际有如下处理:
for (int i the number of elements in the array - 1; i >= 0; --i)
{
array[i].BST::~BST(); // 调用array[i]的destructor
}
错误原因同上。
总结
通过base class指针删除一个有derived classes objects构成的数组,其结果未定义。多态和指针算术不能混用,数组对象几乎总是会涉及到指针的算数运算,所以数组和指针不能混用。