成员函数中调用虚函数
一个基类或派生类的成员函数中可以直接调用该类等级中的虚函数;
#include<iostream> using namespace std; class Base { public: virtual void func1() //虚函数 { cout << "Tis is Base class"; } void func2() { func1(); } //调用了虚函数; }; //一般类中的成员都是通过对象来调用,但是这里用虚函数就可以,除了虚函数,还有就是使用静态static关键字,表明他已经被被分配内存; class Subclass :public Base { public: virtual void func1() { cout << "This is Subclass func1()"; } }; int main() { Subclass sc; sc.func2(); return 0; }
上面的例子中,我们可以得出,在满足公有继承的情况下,成员函数中调用虚函数使用的是动态联编;
构造函数和析构函数中调用虚函数
在构造函数和析构函数中调用虚函数时,采用静态联编,即它们所调用的虚函数是自己的类定义的函数。如果在自己的类中没有实现这个函数,则调用的时基类中虚函数,但绝不是任何在派生类中重新定义的虚函数。这是因为在建立派生类的对象时,它所包含的基类成员在派生类中定义的成员建立之前被建立。在对象撤销时,该对象所包含的在派生类中定义的成员要先与基类成员的撤销。
示例代码:
#include<iostream> using namespace std; class Base { public: Base() { func1(); } //构造函数调用虚函数,静态联编; virtual void func1() //虚函数; { cout << "This is Base func1()" << endl; } virtual void func2() { cout << "This is Base func2()" << endl; } ~Base() { func2(); } //析构函数调用虚函数,静态联编; }; class Subclass :public Base { public: virtual void fnc1() { cout << "This is Subclass func1()" << endl; } virtual void func2() { cout << "This is Subclass func2()" << endl; } }; int main() { Subclass sc; cout << "Exit main" << endl; return 0; }
上面的程序中,我们定义了一个派生类对象,派生类中没有构造函数,所以调用了基类中的构造函数,基类中的构造函数调用了它自身定义的虚函数,这里是静态联编。
纯虚函数和抽象类
当虚函数没有被派生类重新定义时,将使用基类中的虚函数。然而基类常用来表示一些抽象的概念,基类中没有有意义的虚函数定义。另外,在某些情况下,需要一个虚函数被所有派生类覆盖。为了处理上述两种情况,可以将虚函数定义为纯虚函数,相应的类就变成了抽象类。
纯虚函数
如果不能在基类中给出有意义的虚函数的实现,但又必须让基类为派生类提供一个公共界面函数。这时可以将它说明为纯虚函数,留给派生类去实现,说明纯虚函数的格式:
virtual 返回类型 函数名(参数)=0;
纯虚函数的定义是在虚函数定义的基础上再让函数等于0,这只是表示纯虚函数的形式,并不是说他的返回值为0;
#include<iostream> const double PI = 3.14; using namespace std; class Figure { public: Figure() {}; virtual double area()const = 0; //定义为纯虚函数;如果不是纯虚函数,就要返回一个double值; }; class Circle :public Figure { public: Circle(double myr) { R = myr; } virtual double area()const { return PI * R * R; } //定义为虚函数; protected: double R; }; class Rectangle :public Figure { public: Rectangle(double myl, double myw) { L = myl; W = myw; } virtual double area()const { return L * W; } //定义为虚函数; private: double L ,W; }; void fun(Figure& p) { cout << p.area() << endl; } int main() { Circle c(3.0); cout << "Area of cicle is"; fun(c); Rectangle rec(4.0, 5.0); cout << "Area of rectangle is"; fun(rec); return 0; }
上面的代码中,Figure中的虚函数仅仅起到为派生类提供接口的作用。
抽象类
一个类可以说明多个纯虚函数,对于包含有纯虚函数的类被成为抽象类。一个抽象类只能作为基类来派生新类,不能说明抽象类的对象。应为抽象类中有一个或者多个函数没有被定义,也不能用作参数类型,函数返回类型或显示类型类型。但剋以说明指向抽象类对象的指针(和引用),以支持运行时的多态性;
例如: Figure fig; //错误; Figure func1();//错误; int func2(Figure); //错误; void func&(Figure&p);//正确;
虚析构函数
一个类中经所有的成员函数总是有益的,它除了会增加一些资源开销,没有其他坏处。但设置虚函数有许多限制,一般只有非静态的成员函数才可以说明为虚函数。除此之外,析构函数也可以是虚函数,并且最好在动态联编的时候说明为虚函数;
虚析构函数的定义和使用
c++目前不支持构造虚构造函数。由于析构函数不能有参数,所以一个类只能有一个虚析构函数;
格式:
virtual ~<类名>()
入宫一个类的析构函数是虚函数,那么,由它派生而来的所有子类的析构函数也是虚函数。如果析构函数是虚函数,则这个调用是动态联编;
#include<iostream> using namespace std; class Base { public: Base(){} virtual ~Base() { cout << "Base destructor is called" << endl; } }; class Subclass :public Base { public: Subclass(){} ~Subclass(); }; Subclass::~Subclass() { cout << "Subclass destructor is called" << endl; } int main() { Base* b = new Subclass; delete b; return 0; }
上面的程序中,我们构造了虚析构函数,那么我们构造虚析构函数的意义又在哪里?
虚析构函数的必要性
如果我们在类中定义了一个虚函数,我们在定义了一个对象后,如果要删除这个对象(delete),那么就会自动调用析构函数,也就是说,为了满足能够删除所有的成员函数,也就是构造虚析构函数;
#include<iostream> using namespace std; class Base { public: Base(){} ~Base() { cout << "Base destructor is called" << endl; } }; class Subclass :public Base { public: Subclass(); ~Subclass(); private: int* ptr; }; Subclass::Subclass() { ptr = new int; } Subclass::~Subclass() { cout << "Subclass destructor is called" << endl; delete ptr; } int main() { Base* b = new Subclass; delete b; return 0; }
上面的程序我们会发现,用delete删除对象的时候,只调用了基类的析构函数,尾调用派生类的析构函数,,这造成ptr的内存未得到释放,现在我们将~Base()改为虚析构函数;看它的运行:
为什么把析构函数改为虚函数就会成功?这是因为将析构函数改为虚函数后,在撤销对象的时候,会先执行派生类的析构函数,由于这里采用了动态联编,所以才会得到这个结果;
应用实例
编写一个小型公司的工资管理程序,该公司主要有4类人员:经理。兼职技术人员,销售人员,销售经理。经理固定月薪8000,尖子技术人员100元每小时,销售员为当月销售额的4%,销售经理保底工资为5000,加上部门销售额的5%;
代码实现:
#include<iostream> #include<string> using namespace std; //基类Employee的定义 class Employee { protected: int no; //编号; string name; //姓名 float salary; //月星总额 static int totalno; //编号最大值 public: Employee() { no = totalno++; //输入员工号加1; cout << "职工姓名:"; cin >> name; salary = 0.0; //总额赋值为0; } virtual void pay() = 0; //计算月薪函数; virtual void display() = 0; //输出员工信息函数; }; //兼职技术人员类Technician的定义 class Technician :public Employee { private: float hourlyrate; //每小时酬金; int workhours; //当月工作时数; public: Technician() //构造函数; { hourlyrate = 100; //每小时酬薪100; cout << name << "本月工作时间:"; cin >> workhours; } void pay() { salary = hourlyrate * workhours; //计算月薪,按小时计算; } void display() { cout << "兼职技术员:" << name << ",编号:"; cout << no << ",本月工资:" << salary << endl; } }; //销售人员类Salesman的定义 class Salesman :virtual public Employee //派生类,销售员; { protected: float commrate; //按销售额提取酬金的百分比; float sales; //当月销售额; public: Salesman() //构造函数; { commrate = 0.04f; //销售提成比例为4%; cout << name << "本月销售额:"; cin >> sales; } void pay() //计算销售员月薪函数; { salary = sales * commrate; //月薪=本月销售额*销售提成比例; } void display() //显示销售员信息函数; { cout << "销售员:" << name << ",编号:"; cout << no << ",本月工资:" << salary << endl; } }; class Manager :virtual public Employee { protected: float monthlypay; //固定月薪; public: Manager() { monthlypay = 8000; } void pay() //计算经理月薪总数; { salary = monthlypay; //月薪总额; } void display() { cout << "经理:" << name << "编号"; cout << no << ",本月工资:" << salary << endl; } }; //销售经理类Salesmanager的定义: class Salesmanager :public Manager, public Salesman { public: Salesmanager() { monthlypay = 5000; commrate = 0.005f; cout << name << "所有部门月销售量:"; cin >> sales; } void pay() { salary = monthlypay + commrate * sales; } void display() { cout << "销售经理:" << name << ",编号:" << no << ",本月工资:" << salary << endl; } }; int Employee::totalno = 10000; //主函数; int main() { Manager m1; Technician t1; Salesman s1; Salesmanager sm1; Employee* em[4] = { &m1,&t1,&s1,&sm1 }; cout << "上述人员的基本信息:" << endl; for (int i = 0; i < 4; i++) { em[i]->pay(); em[i]->display(); } return 0; }
上面的程序就是一个简单的应用,公司的工资管理系统。c++的灵魂就是动态联编,所以掌握好动态联编是学好c++的基础。