【C++碎碎念】面向对象(菱形继承、虚继承、动静态联编、多态、纯虚函数、虚析构与纯虚析构)

本文详细探讨了C++中的菱形继承问题及其解决方案——虚继承,解释了虚继承的实现原理,涉及vbptr和虚基类表。接着讲解了静态联编与动态联编的区别,通过实例展示了虚函数在多态中的关键作用。同时,阐述了多态的概念、纯虚函数的功能以及虚析构与纯虚析构在内存管理和抽象类中的应用。
摘要由CSDN通过智能技术生成

目录

一、菱形(钻石)继承

二、虚继承实现原理

 三、静态联编

 四、动态联编

五、多态、纯虚函数

 六、虚析构与纯虚析构


一、菱形(钻石)继承

1、如果有两个派生类继承了同一个基类,然后又有一个类同时继承这两个派生类,这种继承我们称之为菱形继承,或者钻石型继承。

2、菱形继承带来的问题:
        1)歌手继承了人的成员,服务员同样继承了人类的成员
        当唱歌的服务员调用访问成员的时候,会产生二义性。(加上作用域)

        2)唱歌的服务员继承自人的数据有两份,浪费了内存。

3、菱形继承带来的问题的解决方案:虚继承

#include <iostream>
using namespace std;
class Person{
public:
    int m_A;
};
class Singer : public Person{
public:
};
class Waiter : public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
    SingerWaiter S;
    S.m_A;//ambiguous,模糊的,因为不清楚到底是继承谁的
    return 0;
}

 通过观察我们发现,没有加virtual,会出现模棱两可的现象,所以我们必须加上virtual这个关键词

#include <iostream>
using namespace std;
class Person{
public:
    int m_A;
};
class Singer : virtual public Person{
public:
};
class Waiter : virtual public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
    SingerWaiter S;
    S.m_A;//正常运行,不会报错
    return 0;
}

  这次加上virtual后就正常运行,不会报错。

可是,这是为什么呢?为啥加上virtual就不会报错了呢?接下来我们一起来看第二部分,就会解决这个问题。

二、虚继承实现原理

vbptr:virtual base pointer(虚基类指针)

原理:只有一个唯一的成员,通过保存虚基类指针,这个指针指向的是一张表(虚基类表)

这个表中保存了当前获得到唯一的数据的偏移量。

#include <iostream>
using namespace std;
class Person{
public:
    int m_A;
};
class Singer : virtual public Person{
public:
};
class Waiter : virtual public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
    SingerWaiter S;
    S.m_A=20;
}

用linux打开后

Singer偏移量是0 ,Waiter偏移量是4

 三、静态联编

比如我接下来要写的案例中,调用了Animal中的speak()函数,属于地址早绑定,静态联编,因为在编译期间就知道了speak函数的地址,是动物的speak();

#include <iostream>
using namespace std;
class Animal{
public:
    void speak()
    {
        cout<<"动物在说话"<<endl;
    }
};
class Cat : public Animal{
public:
    void speak() 
    {
        cout<<"猫🐱在说话"<<endl;
    }
};
class Dog : public Animal{
public:
    void speak()
    {
        cout<<"狗🐕在说话"<<endl;
    }
};
void dospeak(Animal& animal)
{
    animal.speak();
}
int main()
{
    Cat c;
    dospeak(c);
    Dog d;
    dospeak(d);
    cout<<sizeof(Animal)<<endl;//占1个字节,就是表示这个对象存在
    return 0;
}

结果为:

 四、动态联编

通过上述结果我们会发现,那个并不是我们想要的那个结果,如果传进来的是猫对象,就执行猫的speak();如果传进来的是狗对象,就执行狗的speak();  那么这个地址就是晚绑定,动态联编,这样才是我们想要的结果。

#include <iostream>
using namespace std;
class Animal{
public:
    virtual void speak()//虚函数,虚函数表指针,32位系统占4个字节,64位系统占8个字节
    {
        cout<<"动物在说话"<<endl;
    }
};
class Cat : public Animal{
public:
    void speak()//重写、覆写
    {
        cout<<"猫🐱在说话"<<endl;
    }
};
class Dog : public Animal{
public:
    void speak()
    {
        cout<<"狗🐕在说话"<<endl;
    }
};
void dospeak(Animal& animal)
{
    animal.speak();
}
int main()
{
    Cat c;
    dospeak(c);
    Dog d;
    dospeak(d);
    cout<<sizeof(Animal)<<endl;//占4个字节
    return 0;
}

 通过观察发现,我只是在基类中的void speak()的前面加上了virtual,就实现了不同的动物实现不同的说话方式。这就是动态多态

拓展  

1、虚函数

  被virtual修饰过的函数就是虚函数

2、虚函数的重写

   派生类中有一个跟基类中完全相同的虚函数(派生类的虚函数与基类中的虚函数的返回值,函数名,参数列表完全相同),称子类的虚函数重写了基类的虚函数 

                                                                    (图片来源于牛客网)

五、多态、纯虚函数

1、多态:通俗的讲就是多种形态,就是去完成某个行为,不同的对象去完成某个行为会呈现出不同的状态。

多态构成的条件:多态是在不同的继承关系的类对象,去调用同一函数,产生了不同的行为,那么在继承中够成多态要具备两个条件:

1)必须通过基类的指针或者引用调用虚函数;

2)被调用的函数必须是虚函数,且派生类必须是对基类虚函数的重写。 

 

 虚函数覆盖

 企业开发中,提倡开闭原则,对扩展进行开放,对修改进行关闭;

多态的好处:代码组织结构清晰,提高代码的可读性,提高可扩展性.

2、纯虚函数

语法:virtual 函数返回值 函数名(参数列表)=0; 当类中有了纯虚函数,这个类也被称为抽象类,抽象类的特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

#include <iostream>
using namespace std;
class Calculator{
public:
    int m_A;
    int m_B;
    virtual int getResult()=0;//纯虚函数,本身就是抽象函数
};
//加法计算器
class Add : public Calculator{
public:
    virtual int getResult()//必须重写父类,如果不重写的化,继承过来还是抽象函数
    {
        return m_A + m_B;
    }
};
//减法计算器
class Sub : public Calculator{
public:
    virtual int getResult()
    {
        return m_A - m_B;
    }
};
//乘法计算器
class Mul : public Calculator{
public:
    virtual int getResult()
    {
        return m_A * m_B;
    }
};
int main()
{
    Calculator *cal = new Add;
    cal->m_A = 20;
    cal->m_B = 10;
    cout<<cal->getResult()<<endl;//30
    return 0;
}

 六、虚析构与纯虚析构

1、虚析构函数:子类继承了父类,但是普通析构函数是不会调用子类的析构函数的,所以会导致内存释放的不干净,所以我们需要使用虚析构函数来解决。

写法:

        Virtual ~Animal(){}

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

2、纯析构函数

        写法:

                声明:Virtual ~Animal()=0;

                实现:Animal::~Animal(){...}

        纯虚析构,需要声明,也需要实现。类内声明,类外实现。

        若类中有纯虚函数,则类也是抽象类,不可实例化。

#include <bits/stdc++.h>
using namespace std;

class People{
public:
    int m_A;
    People(){
        this->m_A = 20;
        cout<<"People构造函数"<<endl;
    }
    virtual void func() = 0;
    virtual ~People()=0;//纯虚析构,类内声明
};
People :: ~People(){//类外实现
    cout<<"People 的析构函数"<<endl;
}
class Worker : public People{
public:
    char *pName;
    Worker(const char* name){
        cout<<"Worker构造函数"<<endl;
        this->pName = new char[strlen(name)+1];
        strcpy(this->pName,name);
    }
    void doWork(){
        cout<<this->pName<<"在工作"<<endl;
    }
    ~Worker()
    {
        cout<<"Worker析构"<<endl;
        if(this->pName != NULL)
        {
            delet[] pName;
            this->pName=NULL;
        }
    } 
}; 
int main()
{
    People *p=new Worker("Tom");
    p->doWork();
    delete p;

    return 0;
}

 

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值