NO.5 C++ 虚函数&智能指针

零蚀


多态

  • 静态多态

    静态多态是编译期完成的,编译器会根据实参的类型来选择调用合适的函数,如果有就会调用。 在继承关系下,父类指针或者引用可以绑定到子类的对象上,在使用时不能确定是父类对象还是子类对象。

    class Parent{
    public:
        void parent_say(){
    
        }
    };
    
    class Child:public Parent{
    public:
        void child_say(){
    
        }
    };
    int main() {
        Parent *parent=new Child();
        //  Parent parent=Child();
        parent->parent_say(); //只能调用父类的方法。
        return 0;
    }
    

    在编译的时候,直接认定了parent是父类的类型,根本没有看右边返回的对象是什么。也就是父类的指针可以指向子类的对象。这种编译期的判定称为静态联编,而静态联编的效率高于动态联编,不需要在运行时还做过多的开销,所以默认就是静态联编。

  • 动态多态

    在运行时会考虑等式右边被创建的对象,所以在使用泛型的过程中,会默认指定对象为右侧的初始化类。例如虚函数的应用。

  • 虚函数

    虚函数使用了多态的机制,在将父类的指针指向子类的实例的前提下,通过父类指针或者引用拿到子类的成员函数,虚函数的申明,只要在函数前面添加virtual。

    class Parent{
    public:
        virtual void say(){
            cout << "parent" << endl;
        }
    };
    
    class Child:public Parent{
    public:
        //virtual void say ,加不加virtual
        void say(){
            cout << "child" << endl;
        }
    };
    int main() {
        // 走父类的同名函数
        Parent *parent=new Parent();
        parent->say();
        // 走子类的同名函数
        Child *child=new Child();
        child->say();
        // 父类有virtual情况下,走子类的同名函数
        Parent *parent=new Child();
        parent->say();
        return 0;
    }
    

    父类和子类具有同名函数时,虚函数添加时候,虚函数会给每一个对象赋予一个隐藏的成员指针,他指向一个数组,数组里存放着对象的所有的函数地址,这个数组称为虚函数表,父类包含的指针指向父类的虚函数表,子类包含的指针指向子类的虚函数表。

    Parent obj=Child();
    obj.say(); //parent
    Parent *obj1=new Child();
    obj1->say();//child
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8gKPzgJ-1583765754315)(media/15837186778359/15837336005518.jpg)]

    虚函数的同名函数,只能走其中一个,必须同名,使用泛型时候,父类会指向子类的方法。
    构造函数不能是虚函数,在构造的时候父类和子类的构造函数必须都走。析构函数只有在都是虚函数时(只需要在父类析构添加virtual),父子类都会走析构,如果正常的泛型情况下,不会走子类的析构。

    ⚠️如果父类函数和子类函数出现了同名函数,就应该加上虚函数。一般需要将析构函数设置为虚函数。

  • override

    只是用来标明是否是父类的重写的方法。

    class Parent{
    public:
        virtual void say(){
            cout << "parent" << endl;
        }
    };
    
    class Child:public Parent{
    public:
        void say() override {
            cout << "child" << endl;
        }
    
    };
    
  • final

    final禁止修饰的函数被重写,禁止修饰的类被继承。

    class Object final{}
    
  • default_delete

    delete表示没有这个函数,不给调用,删除了默认的。
    default表示默认实现的函数。

    Child()=delete;
    Child()=default;
    
  • 纯虚函数

    类似于java的接口,它没有函数体。

    class Parent{
    public:
        virtual void say()=0;
        void function(){
        }
    };
    

    写了纯虚函数,表示这个类是抽象类,这个=0的函数是纯虚函数。继承抽象类的子类也是一个抽象类。

    class Parent{
    public:
        virtual void say() =0;
    };
    
    class Child:public Parent{
    public:
        void say() override {
            cout << "fda" << endl;
        
        }
    };
    

智能指针

  • 介绍

    c++将指针的操作给程序员,但是在程序运行过程中经常可能由于程序的异常导致指针无法释放,或者形成野指针,或者内存释放,指针并没有设置为nullptr,所以内存会越来越多被占用,程序不得不重启解决指针问题。
    我们之前用的delete是将堆内存做个标记,表示这个内存可以被回收,但是他不会修改指针的指向。而且重复释放,可能会出现问题,也可能同时进行,不发生问题。忘记释放指针,会导致内存泄漏。

  • 智能指针使用

    C++ 11提供的智能指针分为3中,他们会自己回收堆内存,分别为unique_ptr,shaerd_ptr,weak_ptr,智能指针和指针没有什么太大差别,唯一不同是他们会自动回收指针的内存。

  • unique_ptr

    多次删除导致错误

    int *a =new int(3);
    int *b=a;
    
    delete a;
    delete b;
    

    智能指针的运用

    智能指针不允许两个智能指针指向相同的区域,

    //智能指针是对指针的包装(不能重复指向一个地址)
    unique_ptr<int> s(new int(8));
    unique_ptr<Child> s1(new Child);
    unique_ptr<int> s2=move(s);
    // s2=s;不能重复指向同一个地址
    
    // 重新指向一个指针
    s2.reset(new int(5));
    s1->say();
    
  • shared_ptr

    可以对个指针对象指向同一个地址。

    //智能指针是对指针的包装(不能重复指向一个地址)
    shared_ptr<int> s(new int(8));
    shared_ptr<int> s1=s;
    shared_ptr<int> s2=s;
    shared_ptr<int> s3=s;
    // 打印有多少指针指对象向同一个地址
    cout << s.use_count()<< endl;
    // 清空指针的指向
    s2.reset();
    // or s2= nullptr;
    // 获取指针对象的指针内容
    cout << *s1.get() << endl;
    
    

    共享指针问题

    shared_ptr免不了会有死锁的情况,Parent里面有child对象,Child里有Parent对象。

    class Parent{
    public:
        Parent(){
            cout << "Parent构造" << endl;
    
        }
        ~Parent(){
            cout << "Parent析构" << endl;
        }
    
        void set(shared_ptr<Child> c){
            child=c;
        }
    
        shared_ptr<Child> child;
    };
    
    class Child{
    public:
        Child(){
            cout << "Child构造" << endl;
        }
        ~Child(){
            cout << "Child析构" << endl;
        }
    
        void set(shared_ptr<Parent> p){
            parent=p;
        }
        shared_ptr<Parent> parent;
    };
    int main() {
        // 会调用析构
        shared_ptr<Parent> p(new Parent());
    
        shared_ptr<Child> c(new Child());
        
        // 如果不执行以下代码,两个类的析构函数都会执行
        p->set(c);
        c->set(p);
    
        return 0;
    
    
    }
    // print
    //Parent构造
    //Child构造
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NabIeVSu-1583765754316)(media/15837186778359/15837607210807.jpg)]

    在share_ptr进行释放操作的时候,发现类之间存在相互引用,然后进行计数,如果程序结束会将直接指向扣除,但是相互引用无法扣除,也就是形成环形引用的情况(死锁),则无法释放对应的对象。借此需要用到weak_ptr

  • weak_ptr

    他指向一个share_ptr指针,但是他不会count,也就是将一个weak_ptr绑定到一个shared_ptr上,一旦最后一个shared_ptr被销毁,对象就被销毁了。weak_ptr是用来解决环形引用的问题。

    class Parent{
    public:
        Parent(){
            cout << "Parent构造" << endl;
    
        }
        ~Parent(){
            cout << "Parent析构" << endl;
        }
    
        void set(shared_ptr<Child> c){
            child=c;
        }
    
        weak_ptr<Child> child;
    };
    
    class Child{
    public:
        Child(){
            cout << "Child构造" << endl;
        }
        ~Child(){
            cout << "Child析构" << endl;
        }
    
        void set(shared_ptr<Parent> p){
            parent=p;
        }
        weak_ptr<Parent> parent;
    };
    int main() {
        // 会调用析构
        shared_ptr<Parent> p(new Parent());
    
        shared_ptr<Child> c(new Child());
        p->set(c);
        c->set(p);
    
        return 0;
    
    
    }
    //print
    //Parent构造
    //Child构造
    //Child析构
    //Parent析构
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46J3Qc6e-1583765754316)(media/15837186778359/15837615431059.jpg)]


动态内存

  • new & delete
    • new

      • new的底层是对c语言的malloc的包装
      • new内存申请成功之后会返回一个该内存的地址
      • new内存申请失败会抛出异常
      • new 申请成功,如果是程序员定义的类型,会走其构造函数
    • delete

      • delete的底层是对c语言的free的包装
      • delete用于释放空间,如果是空指针则不会做任何操作(但是重复删除,只是删除内存没有修改指针指向,会有问题)
      • delete的对象是自定义类型,会执行析构函数
  • malloc和free

    int *a=(int*)malloc(sizeof(int));
    
    free(a);
    
    • malloc

      • malloc申请成功返回的是void*类型的指针,粗腰将void*指针转换成我们需要的类型。
      • malloc要求定制申请的内存大小,而new由编译器自行计算。
      • 申请失败返回的是NULL(内存不足)
      • 不会执行自定义类型的构造函数
    • free

      • 空指针释放没有问题,非空指针多次释放会有问题
      • 不执行析构
  • 动态数组
    // 创建动态数组
    int *score=nullptr;
    int size;
    score=new int[size];
    //回收数组空间
    delete []score;
    score=nullptr;
    

    动态数组的item构造

    // 会默认走10次无参构造方法,因为new默认打开构造方法的,如果想走其他构造参数会很麻烦。
    Obj *obj=new Obj[10];
    

    allocator

    // 创建动态数组
    allocator<Parent> temp;
    // 申请内存
    auto area = temp.allocate(10);
    /**
     * construct (内存地址,...构造函数的形参)
     */
    temp.construct(area,"context");
    temp.construct(area+1,"context1");
    temp.construct(area+2,"context2");
    
    // 取值
    cout << (area+1)->gender << endl;
    // 执行析构函数
    temp.destroy(area+1);
    // 资源回收,释放空间
    // deallocate(空间index,无实际意义)
    temp.deallocate(area+2,5);
    

🔗 前言
🔗 C++ 高级列表
🔗 NO.1 C++ 基础
🔗 NO.2 C++ 指针
🔗 NO.3 C++ 特殊函数
🔗 NO.4 C++ 重载&继承&lambda
🔗 NO.6 C++ I/O
🔗 NO.7 C++ 模版编程&容器
🔗 NO.8 C++ 常用函数&线程
🔗 NO.9 C++ QT 入门
🔗 NO.10 C++ QT 绘制&自定义组合控件
🔗 NO.11 C++ QT 绘制

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零蚀zero eclipse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值