基类
class class_A
{
private:
enum{MAX=35};
char fullName[MAX];
long acctNum;
double balance;
public:
class_A(const char* s="Nullbody",long an= -1,double bal= 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt);
double Balance()const;
virtual void ViewAcct()const;
virtual ~class_A(){}
};
公有派生类
class class_B: public class_A
{
private:
double maxLoan,rate,owesBank;
public:
class_B(const char* s="Nullbody",long an= -1,double bn= 0.0,
double ml= 500,double r=0.10);
class_B(const class_A & ba,double ml= 500,double r=0.10);
virtual void ViewAcct()const;
virtual void Withdraw(double d);
void ResetMax(double m){maxLoan=m;}
void ResetRate(double r){rate=r;}
void ResetOwes(){owesBank=0;}
}
使用虚方法(虚函数)
关键字virtual,定义虚方法.只用于函数原型,不能用于函数定义中。
class_A类在声明ViewAcct()和Withdraw()时使用了关键字virtual。这些方法被称为虚方法(virtual method).
在基类中被声明为虚拟的后,他在派生类中将自动成为虚方法。不过,在派生类声明中使用关键字virtual来明确指出哪些函数是虚函数也不失为一个好办法。
基类版本的限定名为class_A::ViewAcct()。
派生类版本的限定名为class_B::ViewAcct()。
程序将使用对象类型来确定使用哪个版本:
class_A A("TypeCool",11224,4183.45);
class_B B("DorothyBanker",12118,2592.00);
A.ViewAcct(); //use class_A::ViewAcct()
B.ViewAcct(); //use class_B::ViewAcct()
当方法是通过引用或指针而不是对象调用时
对于没有使用关键字virtual,程序将根据引用类型或指针类型选择方法:
(等于是在派生类中直接重新定义基类的方法)
//behavior with non-virtual ViewAcct()
//method chosen according to reference type
class_A A("TypeCool",11224,4183.45);
class_B B("DorothyBanker",12118,2592.00);
class_A & A1=A;
class_A & A2=B;
A1.ViewAcct(); //use class_A::ViewAcct()
A2.ViewAcct(); //use class_A::ViewAcct()
如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法:
//behavior with virtual ViewAcct()
//method chosen according to object type
class_A A("TypeCool",11224,4183.45);
class_B B("DorothyBanker",12118,2592.00);
class_A & A1=A;
class_A & A2=B;
A1.ViewAcct(); //use class_A::ViewAcct()
A2.ViewAcct(); //use class_B::ViewAcct()
在派生类方法中,标准的技术是使用作用域解析操作符来调用基类方法:
void class_B::ViewAcct()const { class_A::ViewAcct(); }
由于使用的是公有继承模型,因此基类指针既可以指向基类对象,也可以指向派生类对象。那么可以使用一个基类指针数组来表示多种类型的对象。这就是多态性。关于派生类和基类之间的特殊关系的更多介绍请查看<<公有派生(公有继承) 及 该派生类和基类之间的特殊关系 >>。
虚拟析构函数
作为基类,应该包含一个虚拟析构函数。对于用基类指针数组来表示的多种类型对象来说,如果基类析构函数不是虚拟的,则将只调用对应指针类型的析构函数。这意味着只有基类的析构函数被调用,即使指针指向的是一个派生类对象。如果基类析构函数是虚拟的,将调用相应对象类型的析构函数。假如指针指向的是派生类对象,将调用派生类对象的析构函数,然后自动调用基类的析构函数。因此,使用基类虚拟析构函数可以确保正确的析构函数序列被调用,即使该析构函数不执行任何操作。
构造函数不能是虚函数
创建派生类对象时,将调用派生类的构造函数,而不是基类的。然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以类构造函数声明为虚拟的是没有意义的。
友元不能是虚函数
因为友元不是类成员,而只有成员才能是虚函数。
重新定义隐藏方法
class class_a
{public:
virtual void show(int a)const;
...
};
class class_b:public class_a
{public:
virtual void show()const;
...
};
代码将具有如下含义:
class_b x;
x.show(); //valid
x.show(10); //invalid
重新定义继承的方法并不是重载。
如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。
虚函数经验规则
1.如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变(covariance of return type),因为允许返回类型随类类型的变化而变化。
这种例外只适用于返回值,而不适用于参数。
class class_a
{public:
//a base method
virtual class_a & show(int a)const;
...
};
class class_b:public class_a
{public:
//a derived method with a covariant return type
virtual class_b & show(int a)const;
...
};
2.如果基类声明被重载了,则应在派生类重新定义所有的基类版本。
class class_a
{public:
//three overloaded show()
virtual void show(int a)const;
virtual void show(double x)const;
virtual void show()const;
...
};
class class_b:public class_a
{public:
//three redefined show()
virtual void show(int a)const;
virtual void show(double x)const;
virtual void show()const;
...
};
如果只重新定义一个版本,则剩下的版本将被隐藏,派生类对象将无法使用它们。注意,如果不需要修改,则新定义可只调用基类版本。