-
虚函数覆盖(函数重写),多态的概念
1)如果将基类中某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数就也是虚函数,并且对基类中的版型形成覆盖,即函数重写(override).
2)满足虚函数覆盖要求后,通过指向子类对象的基类指针或者通过引用子类对象的基类引用,调用虚函数,实际被执行的将会子类中重写的覆盖版本,而不是基类中原始版本,这种语法现象就是多态。class Base{ public: virtual void func(void){}//声明为虚函数 }; class Derived:public Base{ void func(void){}//自动变成虚函数 }; int main(){ Derived d; Base* pb = &d;//pb:指向子类对象的基类指针 Base& rb = d;//rb:引用子类对象的基类引用 pb->func();//Derived::func() rb.func();//Derived::func() }
#include <iostream> using namespace std; class Shape{//图形基类 public: Shape(int x=0,int y=0):m_x(x),m_y(y){} virtual void draw(void){//虚函数 cout << "绘制图形:" << m_x << "," << m_y << endl; } protected: int m_x;//位置坐标 int m_y; }; class Rect:public Shape{//矩形子类 public: Rect(int x,int y,int w,int h) :Shape(x,y),m_w(w),m_h(h){} void draw(void){//自动变成虚函数 cout << "绘制矩形:" << m_x << "," << m_y << m_w << "," << m_h << endl; } private: int m_w;//宽 int m_h;//高 }; class Circle:public Shape{//圆形子类 public: Circle(int x,int y,int r):Shape(x,y),m_r(r){} void draw(void){//自动变成虚函数 cout << "绘制圆形:" << m_x << "," << m_y << "," << m_r << endl; } private: int m_r;//半径 }; void render(Shape* buf[]){ /* 正常通过指针调用成员函数,根据指针的类型去 * 调用;但是如果调用的是虚函数,不再根据指针的 * 类型,而会根据指针所指向的实际目标对象类型 * 取调用. * 如果是一个基类指针,实际指向目标对象可以是 * 任何子类对象,就可以产生不同结果,这个语法现 * 象就是多态. * */ for(int i=0;buf[i]!=NULL;i++) buf[i]->draw(); } int main(void){ Shape* buf[1024] = {NULL}; buf[0] = new Rect(1,2,3,4); buf[1] = new Circle(5,6,7); buf[2] = new Circle(8,61,71); buf[3] = new Rect(15,26,17,81); buf[4] = new Circle(8,9,10); render(buf); return 0; }
-
虚函数覆盖(函数重写)的条件
1)只有类中的成员函数才能被声明为虚函数,而全局函数、静态成员函数、构造函数都不能被声明为虚函数.
注:析构函数可以是虚函数
2)只有基类中以virtual关键字修饰的成员函数才能做为虚函数被子类覆盖,而与子类中的virtual关键字无关.
3)虚函数在子类中覆盖版本和基类中的原始版本必须具有相同的函数签名,即函数名、参数表、常属性一致
4)如果基类中的虚函数版本返回基本类型数据,那么该函数在子类中覆盖版本必须返回相同类型的数据
5)如果基类中的虚函数版本返回类类型的指针(A*)或引用(A&),那么允许子类中的覆盖版本返回其子类类型的指针(B*)或引用(B&).
class A{};
class B:public A{}; -
多态的条件
1)多态语法特性除了要满足虚函数的覆盖条件,还必须是通过指针或引用调用虚函数,才能表现出来.
2)调用虚函数的指针也可以是this指针,当通过子类对象调用基类中的成员函数时,其this将是指向子类对象的基类指针,再通过它调用虚函数,同样可以表现多态的语法特性.#include <iostream> using namespace std; class Base{ public: virtual int cal(int x,int y){ return x + y; } //void func(Base* this=&d) void func(void){ //cout << this->cal(10,20) << endl; cout << cal(10,20) << endl;//200 } }; class Derived:public Base{ public: int cal(int x,int y){ return x * y; } }; int main(void){ Derived d; //Base b = d;//不能体现多态特性 //Base& b = d;//可以体现多态特性 //cout << b.cal(10,20) << endl; d.func();//func(&d) return 0; }
-
多态原理:通过"虚函数表"和"动态绑定"来实现
1)虚函数表有内存开销
2)动态绑定有时间开销
3)虚函数不能内联优化
结论:实际开发中如果没有多态语法要求,不要滥用虚函数
#include <iostream> using namespace std; class A{ public: A(void){ cout << "A(void):" << this << endl; } virtual void func(){ cout << "testA" << endl; } }; class B:public A{ public: B(void):A(){ cout << "B(void):" << this << endl; } private: void func(){ cout << "testB" << endl; } virtual void func2(){ cout << "testB2" << endl; } }; int main(void) { B b; //下三行注释掉的内容和下边没被注释的两行调用语句等价 //void(***pfunc)(void) = (void(***)(void))&b; //(**pfunc)(); //(*(*pfunc+1))(); (*(*(void(***)(void))&b))(); (*(*(void(***)(void))&b+1))(); return 0; }
结果分析:在创建对象时,先创建基类子对象A,A对象里有一个指针,这个地址上存的是“虚函数表的地址”。比如,指针假定为p,*p则表示虚函数表首地址,*p类似二维动态数组名。虚函数表,是一个二维表,整个表格的首地址就是虚函数表。所以实际上*p拿到的是虚函数表的首地址。虚函数表里存的是函数指针,指向各个虚函数,所以,**p拿到的就是第一个函数指针,用这个函数指针去调函数即可,当然,也可以解引用再调。同理,*(*p+1)拿到的就是第二个函数指针。
-
纯虚函数、抽象类和纯抽象类
1)纯虚函数
virtual 返回类型 函数名(形参表) = 0;
2)抽象类
如果类中包含了纯虚函数,那么这个类就是抽象类.
注:抽象类不能创建对象
3)纯抽象类
如果类中所有的成员函数都是纯虚函数,那么它就是纯抽象类.#include <iostream> using namespace std; class PDFParser{ public: void parse(const char* pdffile){ cout << "解析出一些图形" << endl; onImage(); cout << "解析出一些文本" << endl; onText(); } private: virtual void onImage(void) = 0; virtual void onText(void) = 0; }; class PDFRender:public PDFParser{ private: void onImage(void){ cout << "显示图形" << endl; } void onText(void){ cout << "显示文本" << endl; } }; int main(void){ PDFRender render; render.parse("xx.pdf"); return 0; }
-
虚析构函数
1)基类的析构函数不能调用子类的析构函数,所以delete一个指向子类对象的基类指针,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄漏风险.2)虚析构函数:可以将基类的析构函数声明为虚函数,那么子类中的析构函数就也是虚函数,并且可以对基类中版本形成覆盖,也可以表现多态的语法特性,这时在delete一个指向子类对象的基类指针,实际被执行的将是子类中的虚析构函数,子类的析构函数在执行结束后又会自动调用基类的析构函数,从而避免内存泄漏.
#include <iostream> using namespace std; class Base{ public: Base(void){ cout << "基类动态内存分配" << endl; } virtual ~Base(void){//虚析构函数 cout << "基类动态内存释放" << endl; } }; class Derived:public Base{ public: Derived(void){ cout << "子类动态内存分配" << endl; } ~Derived(void){//自动变成虚析构函数 cout << "子类动态内存释放" << endl; } }; int main(void){ Base* pb = new Derived; //pb->析构函数 delete pb; pb = NULL; return 0; }
C++学习之多态
最新推荐文章于 2020-07-07 14:30:23 发布