C++虚析构函数、纯虚函数和抽象类

目录

3.1C++ 中构造函数不能定义为虚函数

3.2虚析构函数

3.3纯虚函数的概念

3.4抽象类的概念

3.5抽象类的主要作用

3.6抽象类的使用规则

3.7实际设计类型

3.8接口继承和实现继承


3.1C++ 中构造函数不能定义为虚函数

1、构造函数的用途: 1)创建对象,2) 初始化对象中的属性,3) 类型转换

2、在类中定义了虚函数就会有一个虚函数表,对象模型中就含有一个指向虚表的指针(_vfptr)。在定义对象时构造函数设置虚表指针指向虚函数表。

3、使用指针和引用调用虚函数,在编译只需要知道函数接口,运行时指向具体对象,才能关联具体对象的虚方法(通过虚函数指针查虚函数表得到具体对象中的虚方法)。

4、构造函数是类的一个特殊的成员函数:1)定义对象由系统自动调用构造函数,对象自己是不可以调用构造函数2)构造函数的调用属于静态联编,在编译时必须知道具体的类型信息。

5、如果构造函数可以定义为虚构造函数,使用指针调用虚构造函数,如果编译器采用静态联编,构造函数就不能为虚函数。如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数相当于已经实例化的对象在调用构造函数,这是不容许的调用,对象的构造函数只执行一次。

6、如果指针可以调用虚构造函数,通过查虚函数表,调动虚构造函数,那么,当指针为nullptr,如何查虚函数表呢。

7、构造函数的调用是在编译时确定,如果是虚构造函数,编译器怎么知道你想构建是继承树上的哪种类型呢?

3.2虚析构函数

析构函数是类的一个特殊的成员函数:

1.当一个对象的生命周期结束时,系统会自动调用析构函数注销该对象并进行善后工作,对象自身也可以调用析构函数;

2.析构函数的善后工作是:释放对象在生命期内获得的资源(如动态分配的内存,内核资源);

3.析构函数也用来执行对象即将被撤销之前的任何操作。

根据赋值兼容规则,可以用基类的指针指向派生类对象,如果使用基类型指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,否则可能存在内存泄漏问题。

总结:在实现运行时的多态,无论其他程序员怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。

注意:类中没有虚函数,就不要把析构函数定义为虚。

3.3纯虚函数的概念

纯虚函数是指没有具体实现的虚成员函数。它用于这样的情况:设计一个类型时,会遇到无法定义类型中虚函数的具体实现,其实现依赖于不同的派生类。

定义纯虚函数的一般格式为:virtual 返回类型函数名 (参数表) = 0;

“=0”表明程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置。“=0”本质上是将指向函数体的指针定义为nullptr,但对于VS编译器,是将指针指一个默认函数。

3.4抽象类的概念

含有纯虚函数的类是抽象类。抽象类是一种特殊的类,它是为抽象的目而建立的,它处于继承层次结构的较上层。抽象类不能实例化对象,因为纯虚函数没有实现部分,所以含有纯虚函数类型不能实例化对象。

3.5抽象类的主要作用

将相关的类型组织在一个继承层次结构中,抽象类为派生类型提供一个公共的根,相关的派生类型是从这个根派生而来。

class Shape//抽象类
{
    string _sname;
public:
	Shape(const string& name="\0"):_sname(name){}
    virtual ~Shape(){}
    virtual void draw()const=0;
    virtual void area()const=0;
};
class Circle: public Shape
{
    float _r;
    static float _pi;
public:
    Circle(float r=0.0f):_r(r){}
    ~Circle(){}
    void draw()const
    {
        cout<<"Draw ==> Circle"< Square"<

3.6抽象类的使用规则

(1)抽象类只能用作其他类的基类,不能直接创建抽象类的对象,但可以包含在派生类的对象中。

(2)抽象类不能用作参数类型、函数返回类型或显式类型转换。

(3)可以定义抽象类的指针和引用,此指针可以指向(引用可以引用)它的派生类的对象,从而实现运行时多态。

注意:抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类中所有纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类型。

问题:既然抽象类不能定义直接对象,那为什么抽象类仍然要写构造函数?原因是抽象类的所有函数都是为派生类进行服务的。

3.7实际设计类型

//应用类型,不提供派生类,也不继承:
class CDateTime
{};
//节点类型,提供了继承和多态的基础,但没有纯虚函数:
class Shape
{
    string sname;
public:
	virtual float area()const {return 0.0f;}
    string getname()const;
};
//抽象类型,抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出:
class Shape
{
    string sname;
public:
	virtual float area()const=0;
    string getname()const;
};
//接口类型,或者协议类型属于类型中的最顶层,没有属性,所有的函数都是纯虚函数:
class IShape
{
public:
	virtual float area()const=0;
    virtual void draw()const=0;
};
//实现类型,是继承了接口类型或抽象类型,定义了纯虚函数的实现:
class Circle : public IShape
{
public:
    virtual void draw()const{}
    virtual void area()const{return 0;}
}

3.8接口继承和实现继承

公有继承的概念看起来很简单,进一步分析,会发现它由两个可分的部分组成:函数接口的继承和函数实现的继承。

1.有时希望派生类只继承成员函数的接口 (声明),纯虚函数;

2.有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现,虚函数。

3.有时则希望同时继承接口和实现,并且不允许派生类改写任何东西,非虚函数。

class Shape
{
public:
	virtual void area()const = 0;
    virtual void error(const string &msg);
    int objectID() const;
};
class Circle : public Shape {};
class Square : public Shape {};

纯虚函数area使得Shape成为一个抽象类。所以,用户不能创建Shape类的实例,只能创建它的派生类的实例。但是,从Shape (公有)继承而来的所有类都受到Shape的巨大影响,因为:成员函数的接口总会被继承。公有继承的含义是"是一个",所以对基类成立的所有事实也必须对派生类成立。

Shape类中声明了三个函数:第一个函数,area,计算当前对象的面积;第二个函数,error,被其它成员函数调用,用于报告出错信息;第三个函数,objectlD,返回当前对象的一个唯一整数标识符。

每个函数以不同的方式声明:area是一个纯虚函数;error是一个虚函数;obiectlD是一个非虚函数。声明一个除纯虚函数外什么都不包含的类很有用,这样的类叫做协议类或者接口。

声明纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。

声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。

你可能感兴趣的:(c++,开发语言)