C++ 笔记10 | 多态(polymorphic)

  • Post author:
  • Post category:其他


eg:实现图形库,用于显示各种图形
				 图形基类(位置、绘制)
				/					\
	矩形子类(宽和高、绘制)		圆形子类(半径、绘制)	

#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;//x坐标
    int m_y;//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 Rect(11,22,13,14);
    buf[3] = new Circle(15,16,71);
    buf[4] = new Rect(8,12,33,41);
    buf[5] = new Circle(18,66,9);
    render(buf);
    return 0;
}



二十二 多态(polymorphic)



1 虚函数覆盖(函数重写)、多态概念

1)如果基类中某个成员函数被声明为虚函数,那么子类中和该函数具有相同的成员函数就也是虚函数,并且对基类中版本形成覆盖,即函数重写。

2)满足虚函数覆盖关系后,这时通过指向子类对象的基类指针或者通过引用子类对象的基类引用,去调用虚函数,实际被执行的将是子类中的覆盖版本,而不是基类中的原始版本,这种语法现象被称为多态。

class Base{
	public:
		virtual void func(void){}//虚函数
	};
	class Derived:pubilc Base{
		void func(void){}//也是虚函数
	}	
	Derived d;
	Base* pb = &d;//pb指向子类对象的基类指针
	Base& rb = d;//rb引用子类对象的基类引用
	pb->func();//实际被执行的将是子类中的覆盖版本
	rb.func();//实际被执行的将是子类中的覆盖版本



2 虚函数覆盖(函数重载)条件

1)只有类中成员函数才能被声明为虚函数,而全局函数、静态成员函数、构造函数都不能声明为虚函数。

注:析构函数可以为虚函数(特殊,后面讲)

2)只有在基类中以virtual关键字修饰的成员函数才能作为虚函数被子类中版本覆盖,而与子类中虚函数是否加virtual关键字无关。

3)虚函数在基类中的原始版本和子类中的覆盖版本必须具有相同的函数签名,即函数名、参数表(参数类型和个数)和常属性一致。

#include <iostream>
using namespace std;
class A{};
class B:public A{};
class Base{
public:
    virtual void func(int i = 100)const{
        cout << "Base的func" << endl;
    }
    virtual /*A**/A& foo(void){//
        cout << "Base的foo" << endl;
    }
};

class Derived:public Base{
public:
    /*virtual*/ void func(int j = 200)const{//子类中加virtual或不加是没影响的
        cout << "Derived的func" << endl;
    }
    /*B**/B& foo(void){//
        cout << "Derived的foo" << endl;
    }
};
int main(void){
    Derived d;
    Base* pb = &d;//pb:指向子类对象的基类指针
    pb->func();
    pb->foo();
    return 0;
}

4)如果基类中的虚函数返回基本类型的数据,那么该函数在子类中覆盖版本必须返回相同类型的数据;而如果基类中的虚函数返回类类型的指针(A*)或引用(A&),那么允许子类中的覆盖版本返回其子类类型的指针(B*)或引用(B&)。

class A{};

class B:public A{};



3 多态的条件

1)多态的语法现象除了满足虚函数覆盖,还必须通过指针或引用调用虚函数才能表现出来。

#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,有多态现象,通过影藏的this指针
    }
};
class Derived:public Base{
public:
    int cal(int x,int y){
        return x * y;
    }
};
int main(void){
    Derived d;
    //Base b = d;//必须是执政或引用
    //cout << b.cal(10,20) << endl;//30,没有多态现象
    d.func();//func(&d)//通过这样的方式也可以
    return 0;
}

2)调用虚函数的指针也可以是this指针,如果通过子类对象调用基类中的成员函数,在该成员函数中的this指针将是一个指向子类对象的基类指针,再使用this去调用虚函数,也可以表现多态的语法现象。//重点掌握

eg:Qt中的多线程
	class QThread{//线程类,官方写好的类
	public:
		void start(void){//开启线程
			this->run();
		}
	protected:
		virtual void run(void){//线程入口函数
		}
	};
	
	class MyThread:public QThread{
	protected:
		void run(void){//重写线程入口函数
			//需要放在线程执行的代码
		}
	};
	MyThread thread;
	thread.start();//开启子线程,重写的run函数将在子线程中被执行



4 纯虚函数、抽象类和纯抽象类

1)纯虚函数

virtual 返回类型 函数名(形参表)[const] = 0;

2)抽象类

如果类中包含了纯虚函数,那么该类就是抽象类。

  • 注:抽象类不能创建对象.

3)纯抽象类(接口、接口类)

如果类中所有的成员函数都是纯虚函数,那么该类就是纯抽象类。

#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) = 0;//纯虚函数
protected:
    int m_x;//x坐标
    int m_y;//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 Rect(11,22,13,14);
    buf[3] = new Circle(15,16,71);
    buf[4] = new Rect(8,12,33,41);
    buf[5] = new Circle(18,66,9);
    render(buf);

    //Shape s;//error//抽象类不能创建对象

    return 0;
}

代码设计原理:

#include <iostream>
using namespace std;
class PDFParser{
public:
    void parse(const char* pdffile){
        cout << "解析出一行文本" << endl;
        onText();
        cout << "解析出一幅图片" << endl;
        onImage();
        cout << "解析出一个矩形" << endl;
        onRect();
    }
private:
    virtual void onText(void) = 0;
    virtual void onImage(void) = 0;
    virtual void onRect(void) = 0;
};

class PDFRender:public PDFParser{
private:
    void onText(void) {
        cout << "显示一行文本" << endl;
    }
    void onImage(void) {
        cout << "绘制一幅图片" << endl;
    }
    void onRect(void) {
        cout << "绘制一个矩形" << endl;
    }
};
int main(void){
    PDFRender render;
    render.parse("xx.pdf");
    return 0;
}



5 多态原理//了解

通过虚函数表和动态绑定实现了多态的语法//参考下图

多态原理图

1)虚函数表会增加内存的开销(4字节指针)

2)动态绑定有时间的开销

3)虚函数不能被内联优化

总结:实际开发中如果没有多态的语法要求,最好不要使用虚函数。



6 虚析构函数

问题:基类析构函数不会调用子类的析构函数,如果对一个指向子类对象的基类指针使用delete运算,实际被执行的将是基类的析构函数,子类的析构函数不会被执行,有内存泄漏风险

解决:如果将基类中的析构函数声明为虚函数,那么子类中的析构函数也就是一个虚析构函数,并且可以对基类中版本形成覆盖,可以表现多态的语法;这时如果对一个指向子类对象的基类指针使用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;
    //...
    //1)pb->析构函数
    //2)释放内存
    delete pb;
    pb = NULL;
    return 0;
}



版权声明:本文为jackJruit原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。