看上图,我们创建了四个类,有职员类,经理类,时薪员工类,销售员类。通过代码将一步步分析引入虚函数,纯虚函数,虚函数表,多态,抽象类。
为了解说的方便,我们先从c++程序代码分析,最后再来验证是否与.net的结果一致,欢ying您提出宝贵的意见。程序代码如下:
Code
#include <stdio.h>
#include <string.h>
class CEmployee
{
private:
char m_name[30];
public:
CEmployee();
CEmployee(char* nm)
{
strcpy(m_name,nm);
printf(m_name);
}
};
class CWage:CEmployee
{
private:
int m_wage;
int m_hour;
public:
CWage(char* nm):CEmployee(nm)
{
m_wage=10;
m_hour=100;
}
void set(int wage,int hour)
{
m_wage=wage;
m_hour=hour;
}
};
class CSales:CWage
{
private:
int n_sale;
int n_hour;
public:
CSales(char* nm):CWage(nm)
{
n_sale=20;
n_hour=100;
}
void set(int sale,int hour)
{
n_sale=sale;
n_hour=hour;
}
};
class CManage:public CEmployee
{
private:
int m_salary;
public:
CManage(char* nm):CEmployee(nm)
{
m_salary=15000;
}
void setsalary(int salary)
{
m_salary=salary;
}
};
void main()
{
CManage *ma=new CManage("经理");
CWage *wang=new CWage("时薪员");
CSales *zhu=new CSales("销售员");
}
运行后,我们跟踪了一下,其跟踪过程如图:
我们可以看到:
1.CSales对象zhu这个销售员,因为继承了CWage,而CWage又继承了CEmployee,所以CSales相当于拥有了基类的所有成员变量和成员函数。
2.当new一个派生类时,其构造函数的执行顺序都是从基类一层层往下执行,而析构函数则刚好相反。
如果你是公司的一个财务,那么如果现在要你计算经理,时薪员,销售员的工资,你会如何做呢?其中
经理:固定周薪计算
时薪员:钟点费×每周工时
销售员:钟点费×每周工时+佣金*销售额
如果是这样,设计一个成员函数computerpay(),这个成员函数可设计如下:
Code
int computerPay()
{
return m_wage*m_hour;
}
这是表示计算时薪员工资,如果我想用此方法表示销售员工资呢,能否这样写:
Code
int computerPay()
{
return n_sale*n_hour+computerPay();
}
很显然,其中的computerPay具有不确定性,编译器没那么智能到能自动判断究竟是哪个类中的方法,所以要调用父类的函数,必须使用CWage::computerPay()
接下来,该虚函数登场了,我们先来分析一下为什么要使用虚函数?
假设我们有两个对象,一个是时薪员,一个是销售员,表示如下:
CWage wage;
CSales sale;
我们能否这样做:
wage=sale;合理,为什么?因为销售员是属于时薪员
sale=wage;不合理,因为时薪员不一定是属于销售员
我们把wage=sale类似这样的情况,称为基类的指针指向派生类的对象。
现实世界中,我们总用动物来形容猫,狗,狐,狼等,那么我们能不能创建一个通用的指针来表示把有的职员类型呢。所以我们可以用如下代码来表示:
Code
void main()
{
CEmployee* emp;
CManage ma("经理");
emp=&ma;
emp->computerPay();
CWage wang("时薪员");
emp=&wang;
emp->computerPay();
CSales zhu("销售员");
emp=&zhu;
emp->computerPay();
}
但是,运行后,你会发现,生活并不是你想像中的那样,总是调用的是基类的ComputerPay方法。由此我们总结以下几点:
1.如果你以基类的指针指向派生类的对象,那么调用的总是基类所定义的函数。
2.如果你以派生类的对象指向基类的指针,这种不切合实际,往往最危险且容易使人迷糊。
3.如果基类和派生类调用了相同名称的成员函数,那么调用时视指针的原始类弄而定,而不是由指针所指的对象的类型而定。
如果我希望emp指向经理时,调用的是经理的computerPay();
emp指向时薪员时,调用的是时薪员的computerPay();
emp指向销售员时,调用的是销售员的computerPay();
那么此时我们应如何办呢:很简单,在所有类的computerPay方法前端加virtual修辞符。
这种做法就是虚函数,虚函数就是对“基类指针指向派生类对象时,总是调用基类所定义的方法”北道而弛的一种做法。如果你预期派生类中有可能重新定义某一个成员函数时,就是虚函数粉末登场的时候了。
看到了吗?我们以相同的指令却调用了不同的函数,这就是多态。编译器无法在编译时判断emp->computerPay()调用的是哪种方法,而只能在执行期才能判断,这称之为动态绑定。其它的变量和非虚函数在编译时就确认了固定地址的调用了,这称为静态绑定。
那么何时使用多态呢?
举一个例子:有一个图形类,他有几个派生类,如圆形,三角形,矩形。图形类有一个函数area();用来求面积,但是由于不同图形求面积的方法不同,要由具体的派生类来决定,所以可以把它定义为一个虚函数,由派生类来重载这个函数,所以不同的派生类里面area()函数的函数体是不同的。
让我们回头看看上一讲我们讲的Csharp例子,在上一讲我们谈到了CSharp形状基类,我们说它是抽象的,所以它根本就不该创建对象,因为没有任何意义,但为了在各派生类中绘图,我们又不得不加上display这个虚函数。所以在这种情况下你可以定义它什么都不做。看以下情况定义合理吗?如:
class CSharp
{
public:
virtual void display();{......}
}
不合理,因为这个函数根本就不应不调用,因为CSharp是抽像的,但我们又必须留一块空间给它(因为派生类要绘图用),在这样的情况下,就出现了纯虚函数,即virtual void display()=0即可。
好了,该说总结的时候了
1.如果你期望在派生类中重新定义方法,那么你应当在基类中声明虚函数。
2.以单一指令可调用不同的函数,这就是多态。
3.虚函数是实行动态与动态绑定的关健。
4.即然抽像类中的虚函数不打算被调用,那么我们可以设定它为纯虚函数。
5.我们可以说拥有纯虚函数者为抽像类。
6.抽像类不能产生对象,但我们可以定义一个基类指针指向派生类对象,对方便对派生类进行操作。
下面,附出这一节完整的c++源码,以方便大家调试:
Code
#include <stdio.h>
#include <string.h>
class CEmployee
{
private:
char m_name[30];
public:
CEmployee();
CEmployee(char* nm)
{
strcpy(m_name,nm);
printf(m_name);
}
virtual int computerPay()
{
printf("这是基类的计算方法");
return(2);
}
};
class CWage:public CEmployee
{
private:
int m_wage;
int m_hour;
public:
CWage(char* nm):CEmployee(nm)
{
m_wage=10;
m_hour=100;
}
void set(int wage,int hour)
{
m_wage=wage;
m_hour=hour;
}
virtual int computerPay()
{
return m_wage*m_hour;
}
};
class CSales:public CWage
{
private:
int n_sale;
int n_hour;
public:
CSales(char* nm):CWage(nm)
{
n_sale=20;
n_hour=100;
}
void set(int sale,int hour)
{
n_sale=sale;
n_hour=hour;
}
virtual int computerPay()
{
return n_sale*n_hour+CWage::computerPay();
}
};
class CManage:public CEmployee
{
private:
int m_salary;
public:
CManage(char* nm):CEmployee(nm)
{
m_salary=15000;
}
void setsalary(int salary)
{
m_salary=salary;
}
virtual int computerPay()
{
printf("经理的计算方法");
return m_salary;
}
};
void main()
{
CEmployee* emp;
CManage ma("经理");
emp=&ma;
emp->computerPay();
CWage wang("时薪员");
emp=&wang;
emp->computerPay();
CSales zhu("销售员");
emp=&zhu;
emp->computerPay();
}