七. 虚函数和构造函数
当创建一个包含有虚函数的对象时,必须初始化它的VPTR以指向相应的VTABLE。这必须在对虚函数进行任何调用之前完成,显然这是构造函数的工作。编译器在构造函数的开头部分秘密地插入初始化VPTR的代码。
构造函数的初始化顺序和普通的情况一样,基类构造函数首先调用。不过,如果我们在构造函数中调用了虚函数会发生什么?对于在构造函数中调用一个虚函数的情况,被调用的只是这个函数的本地版本,也就是说,虚机制在构造函数中不工作。
构造函数是不能为虚函数的。但析构函数能够且常常必须是虚的。
#include <iostream>
using namespace std;
class Base1
{
public:
~Base1() { cout<<"~Base1() "; }
};
class Derived1 : public Base1
{
public:
~Derived1() { cout<<"~Derived1() "; }
};
class Base2
{
public:
virtual ~Base2() { cout<<"~Base2() "; }
};
class Derived2 : public Base2
{
public:
~Derived2() { cout<<"~Derived2() "; }
};
int main()
{
Base1* bp=new Derived1; //Upcast
delete bp;
Base2* b2p=new Derived2; //Upcast
delete b2p;
cout<<"end of file"<<endl;
}
通常,析构函数的执行是相当充分的。但是,如果想通过指向某个对象基类的指针操纵这个对象,会发生什么现象呢?这在面向对象的程序设计中确实很重要。当我们想delete在栈中已经用new 创建的对象的指针时,就会出现这个问题。如果这个指针是指向基类的,在delete期间,编译器只能知道调用这个析构函数的基类版本。因此,不把析构函数设为虚函数是一个隐匿的错误。
纯虚析构函数: 纯虚析构函数在标准C++中是合法的,但在使用时有一个额外的限制:必须为纯虚析构函数提供一个函数体。它的主要作用是阻止基类的实例化。
八. 运算符重载
#include <iostream>
using namespace std;
class Matrix;
class Scalar;
class Vector;
class Math
{
public:
virtual Math& operator*(Math& rv) = 0;
virtual Math& multiply(Matrix*) = 0;
virtual Math& multiply(Scalar*) = 0;
virtual Math& multiply(Vector*) = 0;
virtual ~Math() {}
};
class Matrix : public Math
{
public:
Math& operator*(Math& rv)
{
return rv.multiply(this); //2nd dispatch
}
Math& multiply(Matrix*)
{
cout<<"Matrix * Matrix"<<endl;
return *this;
}
Math& multiply(Scalar*)
{
cout<<"Scalar * Matrix"<<endl;
return *this;
}
Math& multiply(Vector*)
{
cout<<"Vector * Matrix"<<endl;
return *this;
}
};
class Scalar : public Math
{
public:
Math& operator*(Math& rv)
{
return rv.multiply(this);
}
Math& multiply(Matrix*)
{
cout<<"Matrix * Scalar"<<endl;
return *this;
}
Math& multiply(Scalar*)
{
cout<<"Scalar * Scalar"<<endl;
return *this;
}
Math& multiply(Vector*)
{
cout<<"Vector * Scalar"<<endl;
return *this;
}
};
class Vector : public Math
{
public:
Math& operator*(Math& rv)
{
return rv.multiply(this);
}
Math& multiply(Matrix*)
{
cout<<"Matrix * Vector"<<endl;
return *this;
}
Math& multiply(Scalar*)
{
cout<<"Scalar * Vector"<<endl;
return *this;
}
Math& multiply(Vector*)
{
cout<<"Vector * Vector"<<endl;
return *this;
}
};
int main()
{
Matrix x;
Vector v;
Scalar s;
Math* math[]={&x,&v,&s};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
Math& m1=*math[i];
Math& m2=*math[j];
m1*m2;
}
}
这里只是给出一个比较简单的重载。重载的目的是使任意两个Math 对象相乘并且生成所需的结果。main()中的问题在于,表达式m1*m2 包含两个向上类型转换的Math 引用,因此不知道这两个对象的类型。一个虚函数仅能进行单一指派——即判定一个未知对象的类型。这个例子中所使用的判定两个对象类型的技术称之为多重指派(multiple dispatching),一个单一虚函数调用引起了第二个虚函数的调用。不过这是比较高级的主题了,这就不详述了。
小结:
多态,这是C++实现面向对象的一个很重要的特性,它意味着"具有相同的形式,实现不同功能"。