C++虚函数多态和纯虚函数多态的经典用法

一、虚函数与纯虚函数区别

1、基类有纯虚函数,则该基类为抽象类,也可以看作是一个接口类,纯虚函数必须在子类中实现,如果子类未实现基类的纯虚函数,则子类与基类一样也不能生成对象。

2、虚函数可实现C++的动态多态,即可由基类指针指向动态生成的子类,基类指针调用的方法取决于创建对象对应子类的成员函数,是否有对应基类虚函数的重写函数,如果有重写函数,则调用子类重写函数,如果没有重写函数,则调用基类虚函数。

以下设计一个经典的多态实现案例,来体现和说明虚函数与纯虚函数的差别。

二、经典设计

1、思路

(1)Shape基类,包含纯虚函数和虚函数,也有一般成员函数。Shape基类的现实意义是指形状基类。

(2)Circle子类,实现了Shape的纯虚函数和虚函数。Circle子类的现实意义是指圆形子类。

(3)Square子类,实现了Shape的纯虚函数和虚函数。Square子类的现实意义是指方形子类。

通过Shape基类和Circle、Square子类,实现了C++的经典多态设计模式。Shape的纯虚函数,子类Circle、Square必须全部实现,现实意义是指基类不清楚怎么做,而且子类各自具有独特的特征,子类必须去实现这些具体特征,不去实现则没有现实意义,也就是不能创建对象。Shape的虚函数,子类Circle、Square可去重写,也可不去重写,现实意义是指基类清楚怎么做则子类不用重写,基类不清楚怎么做,则子类实现重写,当然,也可以是基类做一部分工作,子类也做一部分工作,这种特别的形式可以通过先执行子类重写函数,然后通过子类函数显式调用基类虚函数,也可以通过基类普通成员函数再去调用虚函数,基类做了一部分工作后自然去执行子类的重写函数了。

2、源码

(1)Shape.h

扫描二维码关注公众号,回复: 15265261 查看本文章
#pragma once
#include <string>
class Shape
{
public:
    Shape(){};
    Shape(std::string strName) { _strName = strName; };
    virtual ~Shape() {};
public:
    virtual void init() = 0;//纯虚函数,子类具体实现独特特征
    virtual float getArea() = 0;//纯虚函数,子类具体实现独特特征
    void setPos(int iX, int iY) { _iX = iX; _iY = iY; };//基类普通成员函数,子类可继承使用
    std::string getName() { return _strName; };//基类普通成员函数,子类可继承使用
    void getPos(int& iX, int& iY) { iX = _iX; iY = _iY; };//基类普通成员函数,子类可继承使用
    virtual int getSideLength() { return 0; };//虚函数,子类可重写也可不重写
    virtual int getRadius() { return 0; };//虚函数,子类可重写也可不重写
private:
    int _iX;//图形中心位置x,基类的通用特征,各子类都可以用
    int _iY;//图形中心位置y,基类的通用特征,各子类都可以用
    std::string _strName;//图形名称,基类的通用特征,各子类都可以用
};

(2)Circle.h

#pragma once
#include "Shape.h"
class Circle:public Shape
{
public:
    Circle(){};
    Circle(std::string strName):Shape(strName) {};
    ~Circle(){};
    void init() override;//子类实现基类的纯虚函数
    void setRadius(int iRadius);//子类普通成员函数
    virtual float getArea() override;//子类实现基类的纯虚函数
    virtual int getRadius() override;//子类重写基类的虚函数。也可以删除对应基类的虚函数,该函数改为子类的普通成员函数。
private:
    int _iRadius;//子类独特特征,只有本子类才有意义
};

(3)Circle.cpp

#include "Circle.h"
void Circle::init()
{
    setPos(0,0);
    _iRadius= 6;
}
void Circle::setRadius(int iRadius)
{
    _iRadius= iRadius;
}
float Circle::getArea()
{
    return 3.14*_iRadius*_iRadius;
}
int Circle::getRadius()
{
    return _iRadius;
}

(4)Square.h

#pragma once
#include "Shape.h"
class Square :public Shape
{
public:
    Square(){};
    Square(std::string strName):Shape(strName) {};
    ~Square(){};
    void init() override;//子类实现基类的纯虚函数
    void setSideLength(int iLen);//子类的普通成员函数
    virtual int getSideLength() override;//子类重写基类的虚函数。也可以删除对应基类的虚函数,该函数改为子类的普通成员函数。
    virtual float getArea() override;//子类实现基类的纯虚函数
private:
    int _iSideLen;//子类的独特特征,只有本子类才有意义
};

(5)Square.cpp

#include "Square.h"
void Square::init()
{
    setPos(0,0);
    _iSideLen= 6;
}
void Square::setSideLength(int iLen)
{
    _iSideLen= iLen;
}
float Square::getArea()
{
    return _iSideLen *_iSideLen;
}
int Square::getSideLength()
{
    return _iSideLen;
}

(6)main.cpp

#include <iostream>
#include "Circle.h"
#include "Square.h"
int main()
{
   std::cout << "Please input a number :1 or 2. 1 means a circleshape,2 means a square shape. " << std::endl;
   int iShapeType = 0;//1或者2分别代表圆和正方形
   std::cin >>iShapeType;//用户键盘输入数字
   Shape* pShape= nullptr;
   if(iShapeType == 1)//依据用户cmd输入值判断要创建的图形是圆形还是方形
       pShape = new Circle("Circle");
   else if (iShapeType == 2)
       pShape = new Square("Square");
   else {
       std::cout << "Input error.";
       return 0;
   }
   pShape->init();//具体子类实现独特的初始化
   float fArea =pShape->getArea();//具体子类取得独特的面积计算结果
   std::cout <<pShape->getName()<<"Area:"<<fArea<<std::endl;//getName()是基类的普通成员函数
   int iPosX,iPosY;
   pShape->getPos(iPosX, iPosY);//getPos()是基类的普通成员函数
   std::cout <<pShape->getName() << " CenterPosX:" << iPosX << " CenterPosY:" << iPosY << std::endl;
   int iSideLen =pShape->getSideLength();//如果是方形则能取得方形的边长,问题1
   std::cout <<pShape->getName() << " SideLength:" << iSideLen << std::endl;
   int iRadius =pShape->getRadius();//如果是圆形,则能取得圆形的半径,问题2
   std::cout <<pShape->getName() << " Radius:" << iRadius << std::endl;
}

问题1:

此处为取得边长函数,只有方形才有边长,圆形是没有边长的,在这里基类Shape实现了getSideLength()的虚函数,只不过Shape只是返回了0,因为基类不清楚怎么做,Square子类重写了虚函数getSideLength()的具体实现,如果用户输入2则创建Square子类对象,因此在这里调用了Square子类的重写函数getSideLength(),如果用户输入1则创建Circle子类对象,Circle子类没有重写基类虚函数getSideLength(),因此在这里调用了基类Shape的虚函数getSideLength()。

实际编码中,也可以采用Shape基类不声明虚函数getSideLength()的方式实现,只在Square子类实现getSideLength()普通成员函数,这种情况下此处要用类型转换将基类指针转为Square子类对象指针,再调用getSideLength()函数。例如:

if(iShapeType==2){
    int iSideLen = dynamic_cast<Square*>(pShape)->getSideLength();
    std::cout<<pShape->getName() << " SideLength:" << iSideLen << std::endl;
}

问题2:

此处为取得半径函数,只有圆形才有半径,方形是没有半径的,在这里基类Shape实现了getRadius()的虚函数,只不过Shape只是返回了0,因为基类不清楚怎么做,Circle子类重写了虚函数getRadius()的具体实现,如果用户输入1则创建Circle子类对象,因此在这里调用了Circle子类的重写函数,如果用户输入2则创建Square子类对象,Square子类没有重写基类虚函数getRadius(),因此在这里调用了基类Shape的虚函数getRadius()。

实际编码中,也可以采用Shape基类不声明虚函数getRadius()的方式实现,只在Circle子类实现getRadius()的普通成员函数,这种情况下此处要用类型转换将基类指针转为Circle子类对象指针,再调用getRadius ()函数。例如:

if(iShapeType==1){
    int iRadius = dynamic_cast<Circle*>(pShape)->getRadius();
    std::cout<<pShape->getName() << " Radius:" << iRadius << std::endl;
}

3、运行

(1)程序运行后,输入1的情形:

(2)程序运行后,输入2的情形:

CSDN:免费下载C++虚函数多态和纯虚函数多态的经典示例源码

猜你喜欢

转载自blog.csdn.net/weixin_43369786/article/details/129210324