多态意思是一个事物有多种形态,运算符的重载,函数的重载都是多态现象。从系统实现的角度看,多态性分为两类:静态多态和动态的多态,之前函数的重载和运算符的重载都属于静态的多态,在程序编译时系统就能决定调用的是哪一个函数,因此静态的多态又称为编译时的多态性,动态的多态性是在程序运行过程中才动态的确定操作所针对的对象,因此又称为运行时的多态。动态的多态性是通过虚函数实现的。
#define New(_ptr,_type) _ptr = (_type *)malloc(sizeof(_type));new (_ptr)_type;
这是一个宏函数它是在预处理阶段进行宏替换并不是静态的多态,
对象的类型也分为静态和动态
Base b;
Derived d;
Base* pb = &b;
pb = &d;
此时pb的静态类型是Base *,动态类型是Derived *;
关于动态的多态:
必须要使用关键字virtual修饰基类的成员函数,指明该函数是虚函数,并且在派生类里面要重新实现,才能实现动态的多态。
看下面这个例子
#include<iostream>
#include<stdlib.h>
using namespace std;
class Base
{
public:
virtual void FunTest()
{
cout << "FunTest1()" << endl;
}
public:int _b;
};
class Derived :public Base
{
virtual void FunTest()
{
cout << "FunTest2()" << endl;
}
public:int _d;
};
void FunTest3(Base&b)
{
b.FunTest();
}
int main()
{
Base b;
Derived d;
FunTest3(b);
FunTest3(d);
system("pause");
return 0;
}
分析:在没加上virtual 这个关键字的时候并没有实现动态的多态,因为在基类里面的这个函数不是虚函数,因此你在传入派生类对象作为参数的时候他依然会去调用基类里面的函数,因此会打印两次基类里面的打印语句,而不会执行派生类的打印语句。并且要实现动态的多态只能用基类的指针和引用调用虚函数。
分析下面例子更深入理解动态绑定:
class Base
{
public:
void virtual FunTest1(int i)
{
cout << "Base::FunTest1" << endl;
}
void FunTest2(int i)
{
{
cout << "Base:: FunTest2" << endl;
}
}
virtual void FunTest3(int i)
{
{
cout << "Base:: FunTest3" << endl;
}
}
virtual void FunTest4(int i,int j)
{
{
cout << "Base:: FunTest4" << endl;
}
}
};
class Derived :public Base
{
public:
void virtual FunTest1(int i)
{
cout << "Derived:: FunTest1" << endl;
}
virtual void FunTest2(int i)
{
{
cout << "Derived:: FunTest2" << endl;
}
}
void FunTest3(int i)
{
{
cout << "Derived::FunTest3" << endl;
}
}
virtual void FunTest4(int i)
{
{
cout << "Derived::FunTest4" << endl;
}
}
};
void FunTest(Base *b)
{
b->FunTest1(0);
b->FunTest2(0);
b->FunTest3(0);
b->FunTest4(0, 0);
}
int main()
{
Base b;
Derived d;
Base*pb = &b;
FunTest(pb);
pb = &d;
FunTest(pb);
system("pause");
}
重写是实现动态多态的一种重要的方法,因此根据上述结果我们可以分析出FunTest1()这个函数形成了重写,而FunTest 2()这个函数没有形成重写,FunTest 3()这个函数形成了重写,而FunTest()这个函数没有形成重写。根据上面是否形成重写我们可以总结出判断是否产生重写的条件,
1、要形成重写必须声明基类里面的函数是虚函数
2、在派生类里面的这个函数必须和基类里面的这个函数是函数名相同,参数列表相同,返回值相同(协变除外)
所谓协变就是基类的虚函数的返回值是指针或者引用,而派生类的返回值是派生类的指针或者引用。
例如如下例子,它依然实现了重写,可以实现动态的多态
class Base
{
public:
virtual Base*pFunTest1()
{
cout << "Base::pFunTest1()" << endl;
return this;
}
};
class Derived :public Base
{
virtual Derived*pFunTest1()
{
cout << "Derived::pFunTest1()" << endl;
return this;
}
};
void pFunTest(Base*p)
{
p->pFunTest1();
}
int main()
{
Base b;
pFunTest(&b);
Derived d;
pFunTest(&d);
system("pause");
return 0;
}
3、派生类里面可嘉可不加virtual这个关键字,但最好加上
4、重写和它的访问修饰符没有关系。
为什么和访问限定符没有关系?
因为我们知道它是在编译期间就形成了虚表,如果你将基类的虚函数的访问限定符设置成私有的和你实现动态多态是没有关系的,因为这个虚函数的入口地址已经存放进入虚函数表了,你加访问限定符只是为了不让类外的成员去访问它或者说去调用它只能由基类自己的成员函数作为外部接口去调用它。一般实现动态的多态我们将派生类里重写时加访问限定符则不影响因为在调用虚函数的时候我们是要用基类的指针或引用去调用在编译时是将它作为基类的虚函数处理的(即静态的多态)。
调用是按基类里面的函数静态调用
执行是按照动态执行的
哪些函数是不能作为虚函数的:
1、构造函数不能作为虚函数
分析:调用构造函数是为了创建对象,而如果构造函数作为虚函数那么对象还没有创建成功是不可能将它的虚表指针(也就是一个地址)存放在对象的前四个字节中。
2、静态成员函数不能作为虚函数
分析:静态成员函数是没有this指针的,它不是特定的指向某一个对象我们可以通过类名就直接可以访问静态成员函数,但是我们知道虚表指针是要存放在对象的前四个字节中的。
3、赋值运算符可以作为虚函数
分析:赋值运算符作为虚函数是为了实现多态,但是根据赋值兼容规则我们知道派生类的对象可以直接赋值给基类的对象,而基类的对象不能直接赋值给派生类的对象,如果我们将赋值运算符作为虚函数是可以实现子类给父类赋值,但如果我们父类给子类赋值程序就有可能会崩溃。
4、友元函数不能作为虚函数
分析:友元函数根本就不是类的成员函数,因此它不能作为虚函数
5、析构函数可以作为虚函数(并且最好将析构函数写成虚函数)。
分析:
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
/*virtual*/~Base()
{
cout << "~Base()" << endl;
}
};
class Derived:public Base
{
public:
Derived()
{
cout << "Derived()" << endl;
}
/*virtual */~Derived()
{
cout << "~Derived()" << endl;
}
};
int main()
{
Base *pb = new Derived;
delete pb;
system("pause");
return 0;
}
如果不加上virtual这个关键字它只会调用基类里面的析构函数只是释放了一部分的空间,而加上这个关键字我们可以判断出它实际上开辟的是派生类对象的空间,因此会去调用派生类的析构函数又因为继承的关系再去调用基类的析构函数这样不会造成内存的泄露。
纯虚函数:
在成员函数(是虚函数)的形参列表后面写上=0,则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,纯虚函数在派生类中重新定义才能实例化出对象。
例如:
class Base
{
public:virtual void Display() = 0;
};
class Derived:public Base
{
public:int _d;
};
int main()
{
Derived d;
system("pause");
return 0;
}
在编译时便会出错:错误: “Derived”: 不能实例化抽象类 d:\c语言程序\vs程序编程\多态\多态\源.cpp
抽象类虽然不能实例化出对象,但可以创建指针和引用。
如果我们在声明的时候加了vitual这个关键字,在类外定义的时候则不能加上这个关键字,当然也可以声明的时候不用加在定义的时候加上,总之两者不能通同时加。否则会在编译期间出现错误: “Base::Display”:“virtual”说明符在函数定义上非法
如果一个类里面定义了一个虚函数,那么这个类的大小该如何计算?
class Base
{
public:
virtual void FunTest1()
{
cout << "FunTest1" << endl;
}
virtual void FunTest2()
{
cout << "FunTest2" << endl;
}
virtual void FunTest3()
{
cout << "FunTest3" << endl;
}
public:int _b;
};
int main()
{
Base b;
b._b = 10;
cout << sizeof(b) << endl;
system("pause");
return 0;
}
为什么打印出来会是8个字节,之前在计算类的对象的大小时候只计算累的成员变量的字节,那么多出来的这4个字节是什么?
通过看内存窗口我们可以看到:
可以看出这多出来的4个字节存放的是一段地址,而这段地址指向的是一段存储空间。关于这四个字节是如何存放进入我们对象的前四个字节的,我们可以通过反汇编来看出。
我们可以知道放在对象的前四个字节中的地址是在编译期间就完成的。
如果假设这多出来的四个字节是虚表指针指向一段虚函数表格:
我们也可以画出这个这个虚函数表格的模型:
上述只是关于基类的,那么派生类的这种单继承方如下:
class Base
{
public:
virtual void FunTest1()
{
int _a=1;
cout << "FunTest1" << endl;
}
virtual void FunTest2()
{
int _b=2;
cout << "FunTest2" << endl;
}
virtual void FunTest3()
{
int _c=3;
cout << "FunTest3" << endl;
}
public:int _b;
};
typedef void(*Fun)();
int main()
{
Base b;
b._b = 10;
Fun *pFun = (Fun*)(*((int*)&b));
while (*pFun)
{
(*pFun)();
pFun = (Fun*)((int*)pFun++);
}
cout << sizeof(b) << endl;
system("pause");
return 0;
}
我们可以先看程序运行的结果:
:
分析:
对于基类的对象:
对于派生类的对象:
通过上述例子可以看出合成构造函数就是在填充虚表。
关于虚表是如何形成的:
class Base1
{
public:
virtual void FunTest1()
{
cout << "Base1::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base2::FunTest2()" << endl;
}
void FunTest3()
{
cout << "Base2::FunTest3()" << endl;
}
int _b1;
};
class Derived:public Base1
{
public:
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Derived::FunTest3()" << endl;
}
int _d;
};
typedef void(*Fun)();
int main()
{
Base1 b;
b._b1 = 10;
Fun*pFun1 = (Fun*)(*((int *)&b));
while (*pFun1)
{
(*pFun1)();
pFun1 = (Fun*)((int*)pFun1++);
}
Derived d;
d._b1 = 10;
d._d = 11;
Fun*pFun2 = (Fun*)(*((int *)&d));
while (*pFun2)
{
(*pFun2)();
pFun2 = (Fun*)((int*)pFun2++);
}
system("pause");
return 0;
}
因此派生类的虚表是和基类的续表保持一致,它虚表里重写的虚函数的次序和基类的虚函数的顺序相同,自己的虚函数在重写的基类的虚函数的下面。
上述都是单继承,那么多继承呢?菱形继承呢?
关于多继承:
class Base1
{
public:
virtual void FunTest1()
{
cout << "Base1::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base1::FunTest2()" << endl;
}
void FunTest3()
{
cout << "Base1::FunTest3()" << endl;
}
int _b1;
};
class Base2
{
public:
virtual void FunTest4()
{
cout << "Base2::FunTest4()" << endl;
}
virtual void FunTest5()
{
cout << "Base2::FunTest5()" << endl;
}
void FunTest6()
{
cout << "Base2::FunTest6()" << endl;
}
int _b2;
};
class Derived:public Base1,public Base2
{
public:
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Derived::FunTest3()" << endl;
}
virtual void FunTest4()
{
cout << "Derived::FunTest4()" << endl;
}
virtual void FunTest5()
{
cout << "Derived::FunTest5()" << endl;
}
virtual void FunTest6()
{
cout << "Derived::FunTest6()" << endl;
}
int _d;
};
typedef void(*Fun)();
int main()
{
Base1 b1;
b1._b1 = 10;
Fun*pFun1 = (Fun*)(*((int *)&b1));
while (*pFun1)
{
(*pFun1)();
pFun1 = (Fun*)((int*)pFun1++);
}
Base2 b2;
b2._b2 = 10;
Fun*pFun2 = (Fun*)(*((int *)&b2));
while (*pFun2)
{
(*pFun2)();
pFun2 = (Fun*)((int*)pFun2++);
}
Derived d;
d._b1 = 10;
d._b2= 11;
d._d = 13;
Fun*pFun3 = (Fun*)(*((int *)&d));
while (*pFun3)
{
(*pFun3)();
pFun3 = (Fun*)((int*)pFun3++);
}
int *Temp =(int*) &d;
Temp += 2;
Fun*pFun4 = (Fun*)(*Temp);
while (*pFun4)
{
(*pFun4)();
pFun4 = (Fun*)((int*)pFun4++);
}
cout << sizeof(d) << endl;
system("pause");
return 0;
}
观察运行结果:
20个字节:是Base1里的8个字节加上Base2里面的8个字节再加上派生类的4个字节因此一共20个字节
分析多继承的时候派生类对象的模型布局:
关于菱形继承:
之前在如果不是虚拟继承在菱形继承中就会出现二义性的问题:
如果写成对虚函数进行重写会产生二义性吗?
class B
{
public:
virtual void FunTest1()
{
cout << "B::FunTest1()" << endl;
}
int _b;
};
class C1 :public B
{
public:
virtual void FunTest1()
{
cout << "C1::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "C1::FunTest2()" << endl;
}
int _c1;
};
class C2 :public B
{
public:
virtual void FunTest1()
{
cout << "C1::FunTest1()" << endl;
}
virtual void FunTest3()
{
cout << "C1::FunTest3()" << endl;
}
int _c2;
};
class D :public C1, public C2
{
public:
virtual void FunTest1()
{
cout << "C1::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "D::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "D::FunTest3()" << endl;
}
int _d;
};
typedef void(*Fun)();
int main()
{
D d;
d._c1 = 10;
d._c2 = 11;
d._d = 13;
d.FunTest1();
cout << sizeof(d)<< endl;
system("pause");
return 0;
}
如果对虚函数进行了重写那么它在派生类里相当于C1里的FunTested和C2里面的FunTest1是同一份不会产生二义性的问题,但如果不重写依然会出现以下错误:
但是如果你访问数据成员_b还是会产生二义性的问题。
对这种对虚函数重写不会在派生类里面访问不会产生二义性的问题对它的模型进行剖析
对于派生类的对象里面有两个虚表指针第一个是针对继承的C1里面的虚函数进行覆盖之后的虚表指针
第二个是针对继承的C2里面的虚函数进行覆盖之后的指针,如果在派生类里面加上自己的虚函数那么它是加在对C1的虚函数进行覆盖之后加上自己的。
如果变成虚拟继承呢?
#include<iostream>
#include<stdlib.h>
using namespace std;
class B
{
public:
virtual void FunTest1()
{
cout << "B::FunTest1()" << endl;
}
int _b;
};
class C1 :virtual public B
{
public:
virtual void FunTest1()
{
cout << "B::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "C1::FunTest2()" << endl;
}
int _c1;
};
class C2 :virtual public B
{
public:
virtual void FunTest1()
{
cout << "B::FunTest1()" << endl;
}
virtual void FunTest3()
{
cout << "C2::FunTest3()" << endl;
}
int _c2;
};
class D :public C1, public C2
{
public:
virtual void FunTest1()
{
cout << "B::FunTest1()" << endl;
}
virtual void FunTest4()
{
cout << "D::FunTest4()" << endl;
}
int _d;
};
typedef void(*Fun)();
int main()
{
D d;
d._b = 10;
d._c1 = 11;
d._c2 = 12;
d._d = 13;
Fun*pFun = (Fun*)(*(int*)&d);
while (*pFun)
{
(*pFun)();
pFun = (Fun*)((int*)pFun++);
}
int*Temp = (int*)&d;
Temp += 3;
Fun*pFun2 = (Fun*)(*Temp);
while (*pFun2)
{
(*pFun2)();
pFun2 = (Fun*)((int*)pFun2++);
}
cout << d._b << endl;
cout << sizeof(d) << endl;
system("pause");
return 0;
}
总结:关于多态我们首先学会用重写去实现多态,关于重写又要学会与重载,同名隐藏区分开。
在实现多态的时候要了解虚表指针的含义(所谓虚表指针就是一个虚函数表格,其实在不同编译器下面虚函数表格在存放完自己的虚函数的入口地址后不一定是存放的是NULL在VS2013中我们可以看出确实是存放的是NULL ,但是在Linux下面则会出现一个段错误。因此在调用虚函数的时候采用循环时做好给定循环次数而不是用while判空的形式。