首先我们来讲讲多态的概念,多态即一个指令对应多个行为,举个游戏中的例子,比如打副本时带两个角色去打怪物,我们点哪个角色就用哪个角色的技能攻击怪物,这样就是同一个攻击指令就对应不同的一套行为.
多态又分为静多态和动多态(又叫编译期多态和运行期多态),区别在于发生的时间段,静多态即在预编译期绑定,动多态则是在运行阶段绑定,下面分别举点列子.
静多态:多用模板实现,也可以用宏实现,我就直接上代码了
#include<iostream>
using namespace std;
#define Fun(a) a.show()
class A
{
public:
int a;
A(int a=10) :a(a){}
void show()
{
cout << a << endl;
cout << "我是a" << endl;
}
void show1()
{
cout << a+1 << endl;
cout << "我是a" << endl;
}
protected:
private:
};
class B: public A
{
public:
int b;
B(int b = 20) :b(b){}
void show()
{
cout << b<< endl;
cout << "我是b" << endl;
}
void show1()
{
cout << b+1<< endl;
cout << "我是b" << endl;
}
protected:
private:
};
class C :public A
{
public:
int c;
C(int c=30 ) :c(c){}
void show()
{
cout << c << endl;
cout << "我是c" << endl;
}
void show1()
{
cout << c+1 << endl;
cout << "我是c" << endl;
}
protected:
private:
};
template <typename T>
void fun(T& a)
{
a.show();
}
void fun1(A& a)
{
a.show();
}
int main()
{
C f;
B g;
A d;
cout << "普通调用" << endl;
fun1(f);
fun1(g);
fun1(d);
cout << "宏定义实现调用" << endl;
/*宏定义实现多态*/
Fun(d);
Fun(g);
Fun(f);
cout << "模板实现调用" << endl;//这里提一下派生类中有与基类同名函数(且参数列表也相同)时,派生类函数会覆盖基类的同名函数([]派生类对象访问默认调用自己的成员函数),要访问基类函数有两种方法:1.加域名访问2.派生类对象指针赋值基类指针去访问.
/*模板实现多态*/
fun(d);
fun(g);
fun(f);
system("pause");
return 0;
}
运行截图如下:
由运行结果可以看出,普通调用时是无法打印多种行为的,原因是派生类构造时在类中也构造了一个基类,所以能传给A&a,但是由于类型不一致,A类型只能访问派生类内存中
前面基类内存大小的内存,所以永远都只能访问A 的行为,因此无法实现多态.
下面两种方法即是静多态对类之间的关系没有要求,可以毫无关系(在预编译期间就绑定了)宏定义一般不用!
静多态的优缺点:
优点:由于是在预编译期间就绑定,所以执行效率高 .(目前就知道这么多)
缺点:,但是却增加了预编译的耗时.
动多态;动多态是依靠虚函数来实现的,且是依附类的继承实现
讲动多态之前,先来提一下虚函数,关键字virtual ,要实现多态必须在基类中声明虚函数作为接口,在基类中定义虚函数,并创建基类对象时,对象内存中会多出4字节用来存储指向虚 函数指针数组的指针,这就是虚表,该数组内存不属于类而是在类外存在所以不占类内存不会继承,但是虚表这个概念还是会继承下来了,也就是说派生类中也会产生一个虚表,这 个虚表一开始指向基类虚函数,但是如果派生类中有同名函数就覆盖成派生类函数的地址.然后该对象调用virtual 修饰的函数的方式会变为通过该指针数组的成员(虚表)来调用. 这就是虚函数(一切都是通过Virtual 函数来实现,具体怎么实现有兴趣的可以看下原型).+++++++++++
动多态原理:
在继承中派生类中如果有与基类中同类型参数同参数个数的函数,该函数也成为虚函数,可以理解为,当这种情况发生时派生类中基类中的虚表中的成员(virtual 修饰的函数)会被派生类自己成员函数的地址所覆盖,然后通过派生类对象的指针赋值基类指针,由于是基类类型所以只能调用基类对象大小的内存范围,但是我们调用函数的方式变了,是通过虚表去调用函数,但此 时虚表中保存的是派生类中的函数,至此多态就产生了.
原理图如下:
动多态 以及 通过派生类对象地址得到虚表地址进而调用虚函数(注意如果函数里有成员数据,通过虚表地址直接调用函数是打印不出数据的只有乱码,原因是直接跳过成员数据的地址,没有把值给函数)代码奉上:
#include<iostream>
using namespace std;
class A
{
public:
int a;
A(int a=10) :a(a){}
virtual void show()
{
cout << "我是a" << endl;
}
virtual void show1()
{
cout << "我是a1" << endl;
}
protected:
private:
};
class B: public A
{
public:
int b;
B(int b = 48) :b(b){}
void show()
{
cout << "我是b" << endl;
}
void show1()
{
cout << "我是b1" << endl;
}
protected:
private:
};
class C :public A
{
public:
int c;
C(int c=12 ) :c(c){}
void show()
{
cout << "我是c" << endl;
}
void show1()
{
cout << "我是c1" << endl;
}
protected:
private:
};
void Fun(A* a)
{
a->show();
a->show1();
}
typedef void(*p)();
int main()
{
A a;
B b;
C c;
cout << "虚表地址调用函数:" <<endl;
for (int i = 0; i < 2; ++i)
{
p s = *(*((p **)(&a))+i);//第一次将&a强转为函数指针二级指针,第一次解引用
//取前四字节里的值是个函数指针一级指针再次解引用得到个函数指针,最后赋给函数指针s
s();
s = *(*((p **)(&b))+i);
s();
s = *(*((p**)(&c))+i);
s();
}
cout << "动多态调用函数:" <<endl;
Fun(&a);
Fun(&b);
Fun(&c);
system("pause");
return 0;
}
运行结果截图: