版权声明:本文为博主原创文章,未经博主允许不得转载。
看了C++编程实战宝典(郝军编),觉得多态这方面写的不错,拿出来分享下,也便于自己理解学习。本文代码均经本人检验,在VS2013编译运行通过,如有不对,望指出。
多态是面向对象的C++特性之一(其余两个为继承与封装)。多态指具有不同函数功能的函数有用同一个函数名字,即使用同一个函数来调用不同参数的函数,从而实现相似的功能。
多态分两类:静态多态性和动态多态性,静态多态性是指程序编译时,系统就知道调用哪个函数,一般通过函数重载和运算符重载来实现,因此静态多态性成为编译器多态性,而动态多态性是在程序执行中动态地确定调用哪个函数,一般通过虚函数实现。多态在使用时函数名一样,但参数个数与类型不一样,调用时编译器能根据实参的类型与个数来确定调用的函数。
1,普通成员函数重载:
使用同一个函数名来实现多种类型参数的调用,实现一名多用,如下通过参数个数实现:
//==================多态练习==========艾文===============
#include "stdafx.h"
#include<iostream>
using namespace std;
void test(int x, int y, int z)
{
cout << "函数的参数为整型,参数个数为3个" << endl;
cout << "使用的参数为" << x << "和" << y << "和" << z << endl;
}
void test(int x, int y)
{
cout << "函数的参数为整型,参数个数为2个" << endl;
cout << "使用的参数为" << x << "和" << y << endl;
}
void test(int x)
{
cout << "函数的参数为整型,参数个数为1个" << endl;
cout << "使用的参数为" << x << endl;
}
void test(void)
{
cout << "函数的参数为空" << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
int num1 = 10;
int num2 = 20;
int num3 = 30;
test(num1); //调用函数,包含1个参数
test(num2, num3); //调用函数,包含2个参数
test(num1, num2, num3); //调用函数,包含3个参数
test(); //调用函数,没有参数
return 0;
}
运行结果为:
通过参数类型实现,如:
#include "stdafx.h"
#include<iostream>
using namespace std;
void test(int x, int y, int z) //函数定义,包含3个参数
{
cout << "函数的参数为整型,参数个数为3个" << endl;
cout << "使用的参数为" << x << "和" << y << "和" << z << endl;
}
void test(float x, float y) //函数定义,包含2个浮点型参数
{
cout << "函数的参数为浮点型,参数个数为2个" << endl;
cout << "使用的参数为" << x << "和" << y << endl;
}
void test(int x) // 函数定义,包含1个整型参数
{
cout << "函数的参数为整型,参数个数为1个" << endl;
cout << "使用的参数为" << x << endl;
}
void test(double x) // 函数定义,包含1个双精度参数
{
cout << "函数的参数为双精度,参数个数为1个" << endl;
cout << "使用的参数为" << x << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
test(10, 20, 30);; //调用函数,包含3个整型参数
test(2.3, 3.4); //调用函数,包含2个浮点型参数
test(100); //调用函数,包含1个整型参数
test(2.345); //调用函数,包含1个浮点型参数
return 0;
}
运行结果为:
2,构造函数重载
与普通函数一样,类的构造函数也可以通过多态来实现在一个类中定义多个构造函数,方便为类的对象提供不同的初始化方法。重载的构造函数也是具有相同的个函数名字,不同的参数类型和参数个数。
在调用构造函数时需要一个不必给出实参的构造函数,称为默认构造函数,它只是简单的创建一个对象。每个类中只能有一个默认构造函数,虽然多态可以实现一个类中有多个构造函数,但在建立对象时只能选择一个构造函数,执行对应的构造函数,并不是所有的构造函数都要执行。 如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CTest //定义类
{
public:
CTest() //定义类的构造函数
{
cout << "类的默认构造函数" << endl;
}
CTest(int num) //定义具有1个整型参数的构造函数
{
cout << "使用1个整型参数的构造函数" << endl;
}
CTest(int num1,int num2) //定义具有2个整型参数的构造函数
{
cout << "使用2个整型参数的构造函数" << endl;
}
CTest(char ch1,char ch2) //定义具有1个整型参数的构造函数
{
cout << "使用2个字符型参数的构造函数" << endl;
}
~CTest()
{
cout << "类的析构函数" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
cout << "类的默认构造函数创建对象" << endl;
CTest t; //定义1个CTest的空对象
cout << endl;
//定义CTest整型参数的对象
cout << "使用整型参数的构造函数创建对象" << endl;
CTest t1(10);
CTest t2(10, 20);
cout << endl;
//定义CTest字符型参数的对象
cout << "使用字符型参数的构造函数创建对象" << endl;
CTest t3('a', 'b');
return0;
}
运行结果为:
3,动态多态和虚函数
继承产生的派生类具有和基类相同的函数,而派生类中还可以定义新的函数吗,新函数名也可以和基类中的函数名相同,从而实现新的功能,当一个基类被多个派生类继承时,就会出现”一个接口,多个功能”的情形。调用时由编译器来确定这些”接口”的哪个函数被调用,这就是动态多态,也称动态绑定,一般通过虚函数实现。
3.1使用动态多态的原因
动态多态是在运行时确定调用同名函数中的哪个函数,当通过基类的指针或引用的形式来访问基类或派生类中的同名函数时,因派生类中存在基类的副本,这些副本既是基类的部分,也是派生类的内容,故可使用指向基类的指针来引用派生类的内容,派生类中重载该同名函数后,调用同名函数时,本来要调用派生类中的函数,实际却调用了基类中的函数,如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
CBase() //定义基类的构造函数
{
}
CBase(int x) //定义基类的构造函数
{
num = x;
}
int GetNum() //定义基类成员函数
{
return num;
}
void who() //定义基类成员函数
{
cout << "基类中的函数who()" << endl;
}
int num;
};
class CDerive: public CBase //定义派生类
{
public:
CDerive() //定义派生类的构造函数
{
}
CDerive(int x) //定义派生类的构造函数
{
num = x;
}
int GetNum() //定义派生类成员函数
{
return num;
}
void who() //定义派生类成员函数
{
cout << "派生类中的函数who()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CBase obj(10), *ptr;
CDerive obj1(20);
ptr = &obj;
ptr->who(); //使用基类的指针调用同名函数who()
cout << "使用基类指针调用num的值为:" << ptr->GetNum() << endl;
ptr = &obj1;
ptr->who(); //使用派生类的指针调用同名函数who()
cout << "使用派生类指针调用num的值为:" << ptr->GetNum() << endl;
return 0;
}
运行结果为:
由程序运行结果值,编译器根据指针的声明(指向基类的指针),认定指针ptr只能指向基类的空间,因此在函数调用或数据成员调用时只会去基类寻找,当指针指向派生类的数据对象obj1时,系统仍调用了基类中的函数,对派生类中的数据成员的调用也不会出错。
3.2实现动态多态
为解决上述系统只调用了基类中成员函数的问题,C++使用虚函数改变编译方式,将成员函数声明为虚函数,相当于告诉编译程序:由指针实际指向的对象类型来决定调用哪个类中定义的函数。如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
CBase() //定义基类的构造函数
{
}
CBase(int x) //定义基类的构造函数
{
num = x;
}
int GetNum() //定义基类成员函数
{
return num;
}
virtual void who() //定义基类成员函数
{
cout << "基类中的函数who()" << endl;
}
int num;
};
class CDerive: public CBase //定义派生类
{
public:
CDerive() //定义派生类的构造函数
{
}
CDerive(int x) //定义派生类的构造函数
{
num = x;
}
int GetNum() //定义派生类成员函数
{
return num;
}
void who() //定义派生类虚成员函数
{
cout << "派生类中的函数who()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CBase obj(10), *ptr;
CDerive obj1(20);
ptr = &obj;
ptr->who(); //使用基类的指针调用同名函数who()
cout << "使用基类指针调用num的值为:" << ptr->GetNum() << endl;
ptr = &obj1;
ptr->who(); //使用派生类的指针调用同名函数who()
cout << "使用派生类指针调用num的值为:" << ptr->GetNum() << endl;
return 0;
}
运行结果为:
3.3虚函数的定义方式
虚函数的定义方式就是在函数定义是添加关键字virtual ,说明该函数为虚函数,定义形式一般为:
class CBase
{
void test(); //普通函数
virtual void virtest(); //虚函数
};
虚函数可以在派生类中重新定义,从而使其具有新功能。当虚函数在类外定义函数体时,不必再添加virtual,和普通函数一样使用即可,虚函数也可以通过对象的指针进行调用。
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
virtual void virtest() //虚函数体在类内
{
cout << "基类CBase中的虚函数virtest()" << endl;
}
virtual void virtest1(); //虚函数体在类外
};
void CBase::virtest1()
{
cout << "基类CBase中的虚函数virtest(),在类外定义" << endl;
}
class CDerived: public CBase //定义派生类
{
public:
void virtest()
{
cout << "派生类CDrived类中重载虚函数virtest()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
cout << "使用派生类的对象操作" << endl;
CDerived d1;
d1.virtest();
d1.virtest1();
cout << endl;
return 0;
}
运行结果为:
虚函数在使用时,在基类(不一定是最开始的基类)中声明虚函数,用虚函数实现多态性时,派生类应从基类共有派生,在基类中的那些和派生类中的函数模型完全相同的成员函数需要设置为虚函数。
注意的是:只有非静态成员函数可以声明为虚函数,并且函数声明只出现在类声明中的函数原型中,不能在成员的函数体中实现。
3.4虚函数调用规则
如果派生类中存在与基类相同的函数,那么调用派生类中的函数,如果派生类中没有与虚函数同名的函数,则调用基类中的函数,如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
virtual void virtest() //虚函数体在类内
{
cout << "基类CBase中的函数virtest()" << endl;
}
virtual void virtest1()
{
cout << "基类CBase中的函数virtest1()" << endl;
}
};
class CDerived: public CBase //定义派生类
{
public:
void virtest()
{
cout << "派生类CDrived类中的函数virtest()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CDerived d1;
cout << "调用在派生类中存在的函数test()" << endl;
d1.virtest();
cout << "调用在派生类中不存在的函数test1()" << endl;
d1.virtest1();
return 0;
}
执行结果为:
通过结果看出,派生类为重载虚函数,调用直接调用基类的函数。否则就调用派生类中重载的函数
4.虚析构函数
析构函数的作用是在对象撤销之前做必要的“清理现场”的工作,当派生类的对象从内存中撤销时,一般先调用派生类的析构函数,在调用基类的析构函数。若基类中的析构函数声明为虚函数,则该基类所有派生类的析构函数都自动成为虚函数,如果用new运算符建立临时对象,若基类中有析构函数,并且定义了一个指向基类的指针变量,则程序用delete 运算符撤销对象时,系统只执行基类的析构函数,而不执行派生类的析构函数,如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
CBase()
{
cout << "基类的构造函数" << endl;
}
~CBase()
{
cout << "基类的析构函数" << endl;
}
};
class CDerived: public CBase //定义派生类,继承CBase类
{
int *p;
public:
CDerived() //构造函数
{
p = new int[10];
cout << "派生类的构造函数" << endl;
}
~CDerived() //析构函数
{
delete []p;
cout << "派生类的析构函数" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CBase *a = new CDerived; //定义指向基类CBase的对象的指针
delete a;
return 0;
}
运行结果为:
有结果可知只调用了基类的虚构函数,因为指针是指向基类CBase的指针,故在删除对象时之调用了基类的析构函数。而没有调用派生类CDerived的析构函数,导师指向基类的指针p指向的空间没有释放。如作一下更改,在基类的虚构函数加关键字virtual ,则调用了派生类的析构函数。
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
CBase()
{
cout << "基类的构造函数" << endl;
}
virtual ~CBase()
{
cout << "基类的析构函数" << endl;
}
};
class CDerived: public CBase //定义派生类,继承CBase类
{
int *p;
public:
CDerived() //构造函数
{
p = new int[10];
cout << "派生类的构造函数" << endl;
}
~CDerived() //析构函数
{
delete []p;
cout << "派生类的析构函数" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CBase *a = new CDerived; //定义指向基类CBase的对象的指针
delete a;
return 0;
}
运行结果为:
在设计类时,一般将基类中的析构函数设计为虚析构函数,即使基类不需要虚构函数,也要显示的定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。
5.纯虚函数
纯虚函数没有函数体,是一种特殊的虚函数。在基类中,如过不能给虚函数下一个准确的定义,则可以将其设置为纯虚函数,把它的定义放到派生类中。纯虚函数的定义格式:
Class 类名
{
virtual 返回值类型 函数名(参数列表) = 0;
}
上述函数定义直接将其定义为0,因此不需要在定义函数体部分,函数体部分该由在派生类中定义。因此,纯虚函数不仅仅是作为基类的函数由派生类继承,在派生类中需要重新定义函数体,从而确定该虚函数的确切功能。
纯虚函数在定义它的基类中没有给出它的函数体,在该基类的派生类中必须提供它的实现代码(函数体)。纯虚函数是一种特殊的虚函数,仅起到为派生类提供一个一致接口的作用,纯虚函数的使用如:
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
virtual void test() = 0;
};
class CDerived1: public CBase //定义派生类,继承CBase类
{
public:
void test()
{
cout << "派生类CDerived1重载纯虚函数test()" << endl;
}
};
class CDerived2 : public CDerived1 //定义派生类,继承CDerived1类
{
public:
void test()
{
cout << "派生类CDerived2重载纯虚函数test()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CDerived1 p1; //定义派生类CDerived1的对象
CDerived2 p2; //定义派生类CDerived1的对象
p1.test();
p2.test();
return 0;
}
运行结果为:
纯虚函数不等于空虚函数,空虚函数包括函数体,不过函数体内没有内容,而纯虚函数没有函数体。
空虚函数是一个完整的函数,它有函数体,只是函数体内没有任何语句。一个类中,如果含有空虚函数,但是没有纯虚函数的情况下,是可以实例化对象的。
纯虚函数只有声明,没有实现。含有纯虚函数的类不可以实例化对象。
5.1纯虚函数与抽象类
如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来然后重定义的,从基类继承来的纯虚函数在派生类中依然是纯虚函数
抽象类必须用作派生其他类的基类,不能用于直接创建对象实例,抽象类只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍然将被看做是一个抽象类。如
#include "stdafx.h"
#include<iostream>
using namespace std;
class CBase //定义基类
{
public:
virtual void PureVirTest() = 0; //定义纯虚函数
virtual void VirTest() //定义虚函数
{
cout << "类CBase中的虚函数VirTest()" << endl;
}
};
class CDerived1: public CBase //定义派生类,继承CBase类
{
public:
virtual void PureVirTest() //重载纯虚函数
{
cout << "派生类CDerived1重载纯虚函数PureVirTest()" << endl;
}
};
class CDerived2 : public CDerived1 //定义派生类,继承CDerived1类
{
public:
virtual void PureVirTest() //重载纯虚函数
{
cout << "派生类CDerived2重载纯虚函数PureVirTest()" << endl;
}
void VirTest()
{
cout << "派生类CDerived2重载虚函数VirTest()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CDerived1 p1; //定义派生类CDerived1的对象
CDerived2 p2; //定义派生类CDerived1的对象
cout << "调用纯虚函数" << endl;
p1.PureVirTest(); //调用纯虚函数
p2.PureVirTest();
cout << endl << "调用虚函数" << endl;
p1.VirTest();
p2.VirTest();
return 0;
}
运行结果为:
5.2纯虚函数与虚函数的区别
纯虚函数是没有函数体的虚函数,具有虚函数的特征,但和虚函数纯在一定的不同。
在一个类中,既可以定义函数为虚函数,也可以定义函数为纯虚函数,二者可以同时存在一个类中,并且都能作为基类的可继承成员被派生类继承,因此虚函数和纯虚函数都可以在派生类中被重载,以多态的形式被调用
纯虚函数与虚函数存在的目的是:将其定义在抽象基类中,被继承的子类重载,从而提供一个统一的接口。
含纯虚函数的类可以被称为抽象类,但是只含有虚函数的类不能称为抽象类,在函数调用是,虚函数可以被直接调用,也可以被子类重载,而纯虚函数必须在子类中实现以后才可以使用,因为纯虚函数在基类中只有声明没有定义。
若一个类中含有纯虚函数,则不要尝试使用该抽象类创建对象,因为抽象基类是不能被直接调用的,必须被子类重载后,根据要求再调用子类中的函数。