提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
多态性就是同一符号或名字在不同情况下具有不同解释的现象,既是指同一个函数的多种形态。c++可以支持两种多态性,编译时的多态性和运行时的多态性。
对一个函数的调用,要在编译时或在运行时确定将其
连接上相应的函数体的代码,这一过程称为函数联编(简称联编)。c++中两种联编形式:静态联编和动态联编。
静态联编是指在程序编译连接阶段进行的联编。编译器根据源代码调用固定的函数标识符,然后由连接器接管这些标识符,并用物理地址代替他们。这种联编又被称为早期联编,应为这种联编工作是在程序运行之前完成的。
静态联编所支持的多态性称为编译时的多态性,当调用重载函数时,编译器可以根据调用时所使用的实参在编译时就确定下来应调用哪个函数。下面来看在层次关系中一个静态联编的例子。
#include
const double PI = 3.14;
using namespace std;
class Figure //定义基类;
{
public:
Figure() {};
double area() const { return 0.0; }
};
class Circle :public Figure //定义派生类,公有继承;
{
public:
Circle(double myr) { R = myr; }
double area() const { return PI * R * R; }
protected:
double R;
};
class Rectangle :public Figure //定义派生类,公有继承;
{
public:
Rectangle(double myl, double myw) { L = myl; W = myw; }
double area() const { return L * W; }
private:
double L, W;
};
int main()
{
Figure fig; //基类Figure对象;
double area;
area = fig.area();
cout << "Area of figure is" <<area<< endl;
Circle c(3.0); //派生类Circle对象;
area = c.area();
cout << "Area of Circle is" << area << endl;
Rectangle rec(4.0, 5.0);
area = rec.area();
cout << "Area of Rectangle is" << area << endl;
return 0 ;
}
上面的程序我们可以看出,在静态联编中,其实就是对重载函数的使用。定义不同的对象,通过对象来引用不同的函数从而实现我们要实现的功能。
静态联编的最大优点就是速度快,运行时的开销仅仅是传递参数,执行函数调用,清除等。不过,程序员必预测在每一种情况下所有的函数调用时,将要使用那些对象。这样,不仅有局限性,有时也是不可能实现的。
下面的程序就会说明这种情况:
#include
using namespace std;
const double PI = 3.14;
class Figure
{
public:
Figure() {};
double area() const { return 0.0; }
};
class Circle :public Figure //定义派生类,公有继承;
{
public:
Circle(double myr) { R = myr; }
double area()const { return PI*R*R; }
protected:
double R;
};
class Rectangle :public Figure //定义派生类,公有继承;
{
public:
Rectangle(double myl, double myw) { L = myl, W = myw; }
double area()const { return L * W; }
private:
double L, W;
};
void func(Figure &p) //形参为基类的引用;
{
cout << p.area() << endl;
}
int main()
{
Figure fig;
cout << "Area of figure is";
func(fig);
Circle c(3.0); //Circle派生类对象;
cout << "Area of Circle is";
func(c);
Rectangle rec(4.0, 5.0);
cout << "Area of Rectangle is";
func(rec);
return 0;
}
上面的程序没有报错,但是结果正确,那是为什么?
在编译时,编译器将函数中的形参p所执行的area()操作联编到Figure类的area()上,这样访问的知识从基类继承来的成员。
那有没有什么方法来改变这种局限性喃?
动态联编是指在程序运行时进行的联编。只有向具有多态性的函数传递一个实际对象的时候,该函数才能与多种可能的函数中的一种来联系起来。这种联编又被称为晚期联编。
动态联编所支持的多态性被称为运行时多态性。在程序代码中要知名某个成员函数具有多态性需要进行动态联编,且需要关键字virtual来标记。这种用virtual关键字标记的函数称为虚函数.
动态联编的优点 | 动态联编的缺点 |
---|---|
增强了编程灵活性,问题抽象性和程序易维护性 | 函数调用顺序慢 |
虚函数是一个成员函数,该成员函在基类内部声明并且被派生类重新定义。为了创建虚函数,应在基类中该函数生命的前面加上关键字virtual。
virtual<返回值类型><函数名>(<形式参数>)
{
《函数体》
}
如果某类中一个成员函数被说明为虚函数,这便意味着该成员函数在派生类中可能存在不同的实现方式。当继承包含虚函数的类时,派生类将重新定义该虚函数衣服和自身的需要。从本质上讲,虚函数实现了“相同界面,多种实现”的理念。而这种理念时运行时的多态性的基础,既是动态联编的基础。
动态联编需要满足的条件:
(1).类之间满足类型兼容规则;
(2).要声明虚函数;
(3).要由成员函数来调用或者是通过指针,引用来访问虚函数。
#include
using namespace std;
const double PI = 3.14;
class Figure
{
public:
Figure() {};
virtual double area()const { return 0.0; } //定义为虚函数;
};
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()
{
Figure fig;
cout << "Figure of area is";
fun(fig);
Circle c(3.0);
cout << "Area of Circle is";
fun(c);
Rectangle rec(4.0, 5.0);
cout << "Area of Retangle is";
fun(rec);
return 0;
}
这个时候我们发现,答案正确了。看到这里,有没有一种熟悉的感觉,和我们前面学的虚基是不是相似。所以这里我们以可以理解为动态联编之所以能够不断地联编,就是因为成员函数产生了副本。
乍一看,上面的程序,虚函数类似于重载函数。但它不属于重载函数,虚函数与一般的重载函数的区别:
虚函数 | 重载函数 |
---|---|
虚函数不仅要求函数名相同,而且要求函数的签名,返回类型也相同。也就是说函数原型必须完全相同,而且虚函数的特性必须是体现在基类和派生类的类层次结构中 | 重载函数只要求函数有相同的函数名,并且重载函数 是在相同作用域定义的名字相同的不同函数 |
虚函数只能是非静态成员函数 | 重载函数可以是成员函数或友元函数 |
构造函数不能定义为虚函数,析构函数能定义为虚函数 | 构造函数可以重载,析构函数不可以 |
虚函数是根据对象来调用的 | 重载函数调用是以传递参数 序列的差别 来调用 |
虚函数是在运行时联编 | 重载函数是在编译时联编 |
基类中说明的虚函数具有自动向下传给他的派生类的性质。不管经历多少派生类层,所有界面相同的函数都熬吃虚特性。因为派生类也是基类。
在派生类中重新定义虚函数时,要求与基类中说明的虚函数原型完全相同,这时对派生类的虚函数中virtual说明可以省略,但是为了提高程序的可读性。为了区分重载函数,而把一个派生类中重定义基类的虚函数称为覆盖。
示例:
#include
using namespace std;
class Base
{
public:
virtual int func(int x) //虚函数;
{
cout << "This is Base class";
return x;
}
};
class Subclass :public Base //派生类,公有继承;
{
public:
int func(int x) //没有使用virtual关键字,但是依旧为虚函数;
{
cout << "This is Subclass class";
return x;
}
};
void fun(Base& x)
{
cout << "x=" << x.func(5) << endl;
}
int main()
{
Base bc;
fun(bc);
Subclass be;
fun(be);
return 0;
}
#include
using namespace std;
class Base
{
public:
virtual int func(int x) //虚函数;
{
cout << "This is Base class";
return x;
}
};
class Subclass :public Base //派生类,公有继承;
{
public:
virtual float func(int x) //函数返回类型不同
{
cout << "This is Subclass class";
return x;
}
};
void fun(Base& x)
{
cout << "x=" << x.func(5) << endl;
}
int main()
{
Base bc;
fun(bc);
Subclass be;
fun(be);
return 0;
}
这里派生类中的虚函数,仅仅只是使用了不同的返回类型,但是就报错了,这是用为在c++中,只靠返回类型不同的信息,进行函数匹配是模糊的。
#include
using namespace std;
class Base
{
public:
virtual int func(int x) //虚函数;
{
cout << "This is Base class";
return x;
}
};
class Subclass :public Base //派生类,公有继承;
{
public:
virtual int func(float x) //函数形参不同
{
cout << "This is Subclass class";
return x;
}
};
void fun(Base& x)
{
cout << "x=" << x.func(5) << endl;
}
int main()
{
Base bc;
fun(bc);
Subclass be;
fun(be);
return 0;
}
如果派生类与基类的虚函数仅函数名相同,其他不同,则c++认为是重定义函数,是隐藏的,丢失了虚特性。
一个类中的虚函数说明只对派生类中重定义的函数有影响,对他的基类并没有影响。
#include
using namespace std;
class Base
{
public:
int func(int x) //不是虚函数;
{
cout << "This is Base class";
return x;
}
};
class Subclass :public Base
{
public:
virtual int func(int x) //虚函数;
{
cout << "This is Subclass class";
return x;
}
};
class Subclass2 :public Subclass
{
public:
int func(int x) //自动成为虚函数;
{
cout << "This is Baseclass2 class";
return x;
}
};
int main()
{
Subclass2 sc2;
Base& bc = sc2;
cout << "x=" << bc.func(5) << endl;
Subclass& sc = sc2;
cout << "x=" << sc.func(5) << endl;
return 0;
}
从结果看出,func()的操作贝莱你白难道Subclasses类中,显然进行的是动态联编。
一个基类或派生类的成员函数中可以直接调用该类等级中的虚函数;
#include
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
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
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
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
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
#include
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;
}