七. 虚函数和构造函数
当创建一个包含有虚函数的对象时,必须初始化它的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++实现面向对象的一个很重要的特性,它意味着"具有相同的形式,实现不同功能"。