C++抽象类

1. 概念

1.1 纯虚函数

仅仅给出声明,不对虚函数实现定义,而是在派生类中实现。

  • 纯虚函数需要在声明之后加个=0;
  • 纯虚函数没有函数体。
  • 纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。
1
2
3
4
5
class <基类名>
{
virtual <类型><函数名>(<参数表>)=0;
......
};

##1.2 抽象类

含有纯虚函数的类被称为抽象类。

  • 抽象类只能作为派生类的基类,不能定义对象,但可以定义指针。
  • 抽象类不能用作参数类型、函数返回类型或显式转换的类型。
  • 在派生类实现该纯虚函数后,定义抽象类对象的指针或引用,并指向或引用子类对象,从而实现多态。(根据指针指向的类型的不同来决定调用哪个子类的方法)
  • 在定义纯虚函数时,不能定义虚函数的实现部分。
  • 在没有重新定义这种纯虚函数之前,是不能调用这种函数的。
  • 抽象类的唯一用途是为派生类提供基类。
  • 继承于抽象类的派生类如果不能实现基类中所有的纯虚函数,那么这个派生类也就成了抽象类。

1.3 接口和抽象类的区别

  • C++中的接口,表示对外提供的方法,提供给外部调用。是沟通外部跟内部的桥梁。也是以类的形式提供的,但一般该类只具有成员函数,不具有数据成员;
  • 抽象类可以既包含数据成员又包含方法

2. 实例

  1. 抽象类IShape作为基类:只有头文件,没有实现文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //Shape.h

    #ifndef SHAPE_H
    #define SHAPE_H
    using std::string;
    //interface
    class IShape
    {
    public:
    virtual float getArea()=0; //纯虚函数,获得面积
    virtual string getName()=0; //纯虚函数,返回图形的名称
    };
    #endif
  2. 派生类Circle类继承自抽象类IShape

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //Circle.h

    #ifndef CIRCLE_H
    #define CIRCLE_H
    #include"Shape.h"
    class CCircle : public IShape //公有继承自IShape类
    {
    public:
    CCircle(float radius); //构造函数
    public:
    virtual float getArea(); //实现声明实现两个基类的函数,声明的时候需要加virtual关键字,实现的时候就不需要加virtual关键字了。
    virtual string getName();
    private:
    float m_fRadius; //派生类可以拥有自己的成员
    };
    #endif
  3. 派生类Circle类实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //Circle.cpp

    #include"Circle.h"

    CCircle::CCircle(float radius)
    :m_fRadius(radius) //使用构造函数的初始化列表初始化
    {
    }

    float CCircle::getArea() //实现实现两个基类的函数
    {
    return 3.14 * m_fRadius * m_fRadius;
    }

    string CCircle::getName()
    {
    return "CCircle";
    }
  4. 测试文件main.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //main.cpp

    #include
    #include"Circle.h"
    using namespace std;
    int main()
    {
    IShape* pShape = NULL; //定义了一个抽象类的指针,注意抽象类不能定义对象但是可以定义指针
    pShape = new CCircle(20.2); //基类指针指向派生类的对象
    cout<getName()<<" "<getArea()<<endl; //根据指针指向的类型的不同来决定调用的方法
    delete pShape; //释放了CCirle对象所占的内存,但是指针是没有消失的,它现在就是一个野指针,我们在使用之前必须对它赋值
    return 0;
    }

3. 用“初始化列表”来初始化构造函数

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

如:

1
2
3
CCircle::CCircle(float r): m_r(r)	//将r赋值给m_r
{
}

等同于:

1
2
3
4
CCircle::CCircle(float r)
{
m_r = r;
}
  1. 使用初始化列表和没使用初始化列表的区别

    • 使用初始化列表的构造函数:是显式的初始化类的成员;
    • 没使用初始化列表的构造函数:是对类的成员赋值,并没有进行显式的初始化。
  2. 初始化和赋值的区别

    1. 对于内置类型、复合类型(指针和引用)成员变量:

      • 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
    2. 对于用户自定义类型(类类型)成员变量:

      • 结果上相同,但是性能上存在很大的差别。
      • 构造函数直接赋值:类类型的数据成员对象在进入函数体前先调用构造函数,完成该成员的构造;在进入函数体之后,调用相应的拷贝赋值操作,对已经构造好的类对象进行赋值
      • 使用初始列表:只需要调用一次拷贝构造函数。
      • 为了避免两次构造,推荐使用类构造函数初始化列表
  3. 但有的时候必须用带有初始化列表的构造函数:

    • 成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
    • const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
  4. 初始化列表的成员初始化顺序:

    • 按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
0%