zoukankan      html  css  js  c++  java
  • 到底是使用指针还是引用 ,混合使用以及易错点

    看的这个视频教程快把我弄晕了,讲了怎么犯错误的,一步步解决这个错误,不同的解决办法又会带来什么错误,最后给出最合适的解决办法,但是我……现在有点晕了……,再加上我装了vs之后 不知道怎么回事儿,我的vc6不见了,这个教程用的vc,有时候运行结果不一样嘛……我是彻底晕了……,不过应该是加深了理解的……

    既然引用可以实现指针的功能,而且更加方便,容易理解,那我们为什么还要使用指针呢,这是因为指针可以为空,而引用不能为空,
    指针可以被赋值,但是引用只能被初始化,不可以被赋为另一个对象的别名,如果你想用一个变量记录不同对象的地址,那你就必须使用指针,

    另外在堆中创造一块内存空间,就必须用指针来访问它,否则该空间就找不到了,当然我们也可以,使用引用来引用指向内存空间的指针

    但是我们要明白一点,我们不可以使用引用来直接指向堆中的新建空间,因为引用只是个别名,它不可以作为指针来使用


    int *p; int &r = p; 这句话是定义了指针p的别名, 而int &r = *p是定义了一个p指向的空间的别名

    /*既然引用可以实现指针的功能,而且更加方便,容易理解,那我们为什么还要使用指针呢,这是因为指针可以为空,而引用不能为空,
    指针可以被赋值,但是引用只能被初始化,不可以被赋为另一个对象的别名,如果你想用一个变量记录不同对象的地址,
    那你就必须使用指针,另外在堆中创造一块内存空间,就必须用指针来访问它,否则该空间就找不到了,当然我们也可以
    使用引用来引用指向内存空间的指针
    
    但是我们要明白一点,我们不可以使用引用来直接指向堆中的新建空间,因为引用只是个别名,它不可以作为指针来使用
    int *p; int &r = p; 这句话是定义了指针p的别名, 而int &r = *p是定义了一个p指向的空间的别名*/
    #include <iostream>
    using namespace std;
    int main()
    {
        int *p = new int;
        int &r = *p; //这时候我们就可以用r这个别名来修改堆中数据了,r就是堆中p指向的对象的别名
        r = 4;
        cout << *p << endl;
        cout << r << endl;
        return 0;
    }

     易错点:

    在子函数中创建的对象要返回到主函数中的时候不能返回该对象的别名,因为当子函数结束的时候这个对象生命就结束了,这样返回的别名的是个并不存在的对象的别名;因此在子函数中创建的对象返回的时候要返回一个对象,
    既然是返回对象就是按值返回,按值返回就会调用复制构造函数创建一个对象,这个对象是要返回的对象的副本,
    这个对象可以用别名来接收,也可以再定义一个对象来接收,也可以定义一个指针来接收返回的对象的地址
    定义一个对象的别名来接收的话,为了返回一个对象而创建的那个副本的生命持续到主函数结束才被析构
    定义一个指针来接收的话为了返回一个对象而创建的对象的副本在被接收后它所占用的空间直接被释放

    这是因为:
    对于引用而言,如果引用的是一个临时变量,那么这个临时变量的生存周期不会小于这个引用的生存期,
    也就是说主函数结束的时候 在主函数中创建的接收该对象的别名的这个引用r的生命才结束,r所引用的临时变量的生命才结束
    这时候才会调用析构函数来释放内存,

    但是指针就没有这个特性,假如把该对象a的副本的地址赋给一个指针,那么在子函数返回对象的副本的时候就可以析构这个对象的副本
    这块内存就被释放了(虽然在这块内存再次被利用之前数据仍然没有变化,用接收到的地址仍然可以得到数据)

    下面是用别名来接收的代码

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A(int i) { cout << "执行构造函数创建一个对象" << endl; x = i; }
        A(A &a) { this->x = a.x; cout << "执行复制构建函数创建一个对象
    "; }
        ~A() { cout << "执行析构函数!
    "; }
        int get() { return x; }
    private:
        int x;
    };
    A fun()
    {
        cout << "跳转到fun函数中!
    " << endl;
        A a(23);            
        cout << "对象a的地址:" << &a << endl;
        return a;
    }
    int main()
    {
        A &r = fun();
        cout << "对象a的副本的地址:" << &r << endl;
        cout << r.get() << endl;
        return 0;
    }

     

    下面是用指针来接收地址的代码

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A(int i) { cout << "执行构造函数创建一个对象" << endl; x = i; }
        A(A &a) { this->x = a.x; cout << "执行复制构建函数创建一个对象
    "; }
        ~A() { cout << "执行析构函数!
    "; }
        int get() { return x; }
    private:
        int x;
    };
    A fun()
    {
        cout << "跳转到fun函数中!
    " << endl;
        A a(23);            
        cout << "对象a的地址:" << &a << endl;
        return a;
    }
    int main()
    {
        A *r = &fun();
        cout << "对象a的副本的地址:" << r << endl;
        cout << r->get() << endl;
        return 0;
    }

     

    我们可以看到在主函数结束之前因为fun中的return而创建的对象就被销毁了

    另外要注意,如果在子函数中创建一个堆中对象,调用完子函数后指向堆中对象的指针就被释放掉了,我们就找不到这段空间了,从而造成内存泄漏,就算返回了该对象,得到的也是该对象的副本,这个副本是在栈中创建的,而不是在堆中创建的,如果误以为这个副本是该对象而手动删除的话会造成系统崩溃,因为栈中空间由系统自动释放。

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A(int i) { cout << "执行构造函数创建一个对象" << endl; x = i; }
        A(A &a) { this->x = a.x; cout << "执行复制构建函数创建一个对象
    "; }
        ~A() { cout << "执行析构函数!
    "; }
        int get() { return x; }
    private:
        int x;
    };
    A fun()
    {
        cout << "跳转到fun函数中!
    " << endl;
        A *p = new A(99);
        cout << "堆中对象的地址:" << p << endl;
        return *p;
    }
    int main()
    {
        A &r = fun();
        cout << "堆中对象的副本的地址:" << &r << endl;
        cout << r.get() << endl;
        return 0;
    }

    要避免上面的内存泄漏,我们就不能用按值返回方式返回一个堆中对象,而必须按地址或者别名的方式返回一个别名或者内存地址,这样就不用调用复制构造函数创建一个该对象的副本,而是直接将该对象的别名或者地址返回。由于该对象的别名或者地址初始化给了main函数中的一个别名或者指针,因此即使被调用函数中的局部指针超出作用域被系统释放,也可由main函数中的引用或者指针找到该堆中空间,不会令该空间成为不可访问的区域,从而避免了内存泄漏

     但是这样做也隐藏着一个非常危险的不易察觉的错误,

    我们定义一个指针p来存放该别名代表的对象的地址,然后delete p; 这时候该别名就会成为一个空的别名, 我们再次使用这个别名的时候就会得到随机数据,这样的错误不会造成系统崩溃,但是却极难察觉

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A(int i) { cout << "执行构造函数创建一个对象" << endl; x = i; }
        A(A &a) { this->x = a.x; cout << "执行复制构建函数创建一个对象
    "; }
        ~A() { cout << "执行析构函数!
    "; }
        int get() { return x; }
    private:
        int x;
    };
    A& fun()
    {
        cout << "跳转到fun函数中!
    " << endl;
        A *p = new A(99);
        cout << "堆中对象的地址:" << p << endl;
        return *p;
    }
    int main()
    {
        A &r = fun();
        cout << "堆中对象的副本的地址:" << &r << endl;
        cout << r.get() << endl;
        A *p = &r;
        delete p;
        return 0;
    }

     为了解决上面的问题我们采用最合适的方法就是在哪里创建在哪里释放,我们在main函数中创建该对象,把该对象的别名传到子函数中,操作后再把别名传回来,这样即节省系统资源,又不至于内存泄漏,或者因为变量太多而混淆

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A(int i) { cout << "执行构造函数创建一个对象" << endl; x = i; }
        A(A &a) { this->x = a.x; cout << "执行复制构建函数创建一个对象
    "; }
        ~A() { cout << "执行析构函数!
    "; }
        int get() { return x; }
        void set(int i){ x = i; }
    private:
        int x;
    };
    void fun(A &a)
    {
        cout << "跳转到fun函数中!
    " << endl;
        a.set(66);
    }
    int main()
    {
        A *p = new A(99);
        cout << p->get() << endl;
        fun(*p);
        cout << p->get() << endl;
        delete p;
        return 0;
    }
  • 相关阅读:
    黑马前端2020就业Web全套课-2020.4月最新版
    什么是Redis雪崩、穿透和击穿? 全面掌握Redis
    ElasticStack高级搜索入门到项目实战,Elasticsearch全文检索
    阿里云盘邀请码+软件下载
    Intellij IDEA超实用设置汇总,高效便捷敲代码
    双11的亿级高并发架构,是怎么设计的?
    TensorFlow 卷积神经网络实用指南 | iBooker·ApacheCN
    TensorFlow 入门 | iBooker·ApacheCN
    TensorFlow 2.0 快速入门指南 | iBooker·ApacheCN
    深度学习快速参考 | iBooker·ApacheCN
  • 原文地址:https://www.cnblogs.com/rain-1/p/4854205.html
Copyright © 2011-2022 走看看