c++之引用

引用与指针相似,但是不同。&是引用符,而不是取地址符,引用只是变量的另一个别名,他们指向同一个地址。引用是别名常量,只能进行初始化,而且初始化之后所引用的对象都不能再改变。可以通过引用来改变所绑定对象的值。

一、对变量的引用

#include<iostream>
using namespace std;
int main()
{
    int a=9;
    int &ra=a;
    cout<<"a:"<<a<<endl;
    cout<<"&a:"<<&a<<endl;
    cout<<"ra:"<<ra<<endl;
    cout<<"&ra:"<<&ra<<endl;

    int b=30;
    ra=b;    ///ra只是拥有了b中存放的值,ra的地址并没有改变
    cout<<"b:"<<b<<endl;
    cout<<"&b:"<<&b<<endl;
    cout<<"ra:"<<ra<<endl;
    cout<<"&ra:"<<&ra<<endl;

    ra=999;
    cout<<"a:"<<a<<endl;
    cout<<"b:"<<b<<endl;
    cout<<"ra:"<<ra<<endl;
    return 0;
}

二、对对象的引用

#include<iostream>
using namespace std;
class A
{
public:
    int get(){return i;}
    void set(int x){i=x;}
private:
    int i;
};
int main()
{
    A a;
    A &b=a;///对对象的引用
    a.set(20);
    cout<<"a.get():"<<a.get()<<endl;
    cout<<"b.get():"<<b.get()<<endl;
    b.set(26);
    cout<<"a.get():"<<a.get()<<endl;
    cout<<"b.get():"<<b.get()<<endl;
    return 0;
}

三、对堆中对象的引用

引用只是一个变量的别名,当堆中对象被删除之后,这个别名也不存在了。因为堆中都是匿名对象,所以引用不可用于堆中,但是引用可以是堆中指针的别名。一般不常用,因为堆中的指针没必要有别名。

#include<iostream>
using namespace std;
int main()
{
    int *p=new int;
    int &r=*p;
    *p=12;
    cout<<"*p:"<<*p<<endl;
    r=7;
    cout<<"r:"<<r<<endl;
    cout<<"*p:"<<*p<<endl;
    return 0;
}

四、传递类中对象的几种方式

1.按值传递

#include<iostream>
using namespace std;
class A
{
public:
    A(){cout<<"执行构造函数,创建对象"<<endl;}
    A(A&){cout<<"执行复制构造函数创建副本"<<endl;}
    ~A(){cout<<"执行析构函数,删除对象"<<endl;}
};
A func(A one)
{//one的删除
    return one;//析构函数one的副本,返回one,返回方式是按值返回,所以又创建one的副本,调用复制构造函数
}
//返回的临时对象没有被接收,所以在func结束时调用析构函数
int main()
{
    A a; //创建对象
    func(a);//按值传递,复制构造函数创建a的副本
    return 0;
}

在调用func函数时,由于是按值传递对象,所以程序调用复制构造函数创建了一个a的副本;此时程序跳转到func函数处,将a的副本传递给one。接着函数又返回one,由于返回方式是返回一个A类对象,也是按值返回,所以又创建一个one的临时副本。但这个临时副本没有被接收,所以丢弃。函数调用了三次析构函数,第一次是析构one的副本,第二次是析构a的副本,第三次是析构对象a。

由此可见,按值传递对象需要调用两次复制构造函数,两次析构函数,这对于程序的开销很大,所以下面的函数按地址来传参。


2.按址传递(返回类型为对象)

class A
{
public:
    A(){cout<<"执行构造函数,创建对象"<<endl;}
    A(A&){cout<<"执行复制构造函数创建副本"<<endl;}
    ~A(){cout<<"执行析构函数,删除对象"<<endl;}
};
A func(A *one)//用指针接收地址
{
    return *one;//*one读取的是对象a处的值,返回的是对象,返回方式是按值返回,所以此处调用复制构造函数创建临时对象;并在函数结束时析构该对象
}
int main()
{
    A a; 
    func(&a);//传递对象a的地址
    return 0;
}

可以看到结果比上个程序少调用了一次复制构造函数和析构函数


3.按址传递对象(返回类型为指针)

class A
{
public:
    A(){cout<<"执行构造函数,创建对象"<<endl;}
    A(A&){cout<<"执行复制构造函数创建副本"<<endl;}
    ~A(){cout<<"执行析构函数,删除对象"<<endl;}
};
A *func(A *one)
{
    return one;//返回指针
}
int main()
{
    A a;
    func(&a);//传递地址
    return 0;
}
程序比上个结果少调用了一次析构函数和复制构造函数


但是使用指针还有一个弊端,那就是可以通过指针随意改变所指向地址的内容。假如不想让指针改变内容,就用const来加以限制。程序中

4.按别名传递对象(返回类型为对象)

#include<iostream>
using namespace std;
class A
{
public:
    A(){cout<<"构造函数在执行...\n";}
    A(A &p){cout<<"复制构造函数在执行...\n";}
    ~A(){cout<<"析构函数在执行...\n";}
    void set(int x){i=x;}
    int get(){return i;}
private:
    int i;
};
A func(A &one)
{
    return one;
}
int main()
{
    A a;
    a.set(99);
    func(a);
    A b;
    b=func(a);
    return 0;
}

5.按别名传递对象(返回类型为别名)

返回类型是别名,并不是一个对象,引用不会调用复制构造函数

#include<iostream>
using namespace std;
class A
{
public:
    A(){cout<<"构造函数在执行..."<<endl;}
    A(A&){cout<<"复制构造函数在执行..."<<endl;}
    ~A(){cout<<"析构函数在执行..."<<endl;}
    int get(){return i;}
    void set(int x){i=x;}
private:
    int i;
};
A &fun(A &one) ///返回类型为A &,one自动转换为函数的返回类型
{
    return one;
}
int main()
{
    A a;
    a.set(11);
    cout<<a.get()<<" ";
    A &p=fun(a);
    cout<<p.get()<<endl;
    p.set(90);
    cout<<a.get()<<" "<<p.get()<<endl;
    return 0;
}


五、使用引用常见错误

假如对象不存在或被销毁,继续引用这个对象会出现什么后果?

#include<iostream>
using namespace std;
class A
{
public:
    A(int i){x=i;}
    int get(){return x;}
    void set(int i){x=i;}
private:
    int x;
};
A & func()
{
    A a(23);          //a为局部对象,生存期只在func函数中
    return a;         //func结束后a消失,返回的是不存在的对象的别名
}
int main()
{
    A &r=func();       //r接收的也是不存在对象的别名
    cout<<r.get()<<endl;  //输出结果为随机数
    return 0;
}
去掉func函数中的&符号,为什么输出的不是随机值?因为此时函数返回的是一个对象的值,这是会调用复制构造函数创建对象的副本,这个副本赋给引用r,引用会延长副本的生存期至程序结束。r接收的是对象a的副本,这个程序验证了引用的是临时变量,临时变量的生存期不少于引用的生存期。但是指针就没有延长临时副本的功能

#include <iostream>
using namespace std;
class A
{
public:
   A(int i){cout<<"执行构造函数创建一个对象\n";x=i;}
   A(const A&a){x=a.x;cout<<"执行复制构造函数创建一个对象\n";}
   ~A(){cout<<"执行析构函数!\n";}
   int get() {return x;}
   void set(int i){x=i;}
private:
int x;
};
A func( )
{
   cout<<"跳转到func函数中!\n";
   A a(23);
   cout<<"对象a的地址:"<<&a<<endl;
   return a;                       //返回对象a的副本
}
int main()
{
    A &r=func();                   //引用延长了临时副本的生存期,结果中第一个析构函数析构的是对象a;最后才析构临时副本,说明引用延长
    cout<<"对象a的副本的地址:"<<&r<<endl;<span style="font-family: Arial, Helvetica, sans-serif;">//了副本的生存期</span>
    cout<<r.get()<<endl;
return 0;
}


指针没有延长临时变量生存期的功能例如:

 因为析构函数析构某个对象之后,只是告诉编译器这块内存不再为某个对象独占,别的对象可以访问并存放数据,但是在别的对象使用之前,这块内存中的数据并没有被删除,因此指针r访问该区域仍能够得到未被修改的值23。由运行结果可以看出,接连两次的析构函数,分别析构了对象a和临时副本,所以指针没有延长临时变量的生存期。

#include <iostream>
using namespace std;
class A
{
public:
   A(int i){cout<<"执行构造函数创建一个对象\n";x=i;}
   A(const A&a){x=a.x;cout<<"执行复制构造函数创建一个对象\n";}   
~A(){x=0;cout<<"执行析构函数!\n";}	   
   int get() {return x;}
   void set(int i){x=i;}
private:
int x;
};
A  func( )
{
   cout<<"跳转到func函数中!\n";
   A a(23);
   cout<<"对象a的地址:"<<&a<<endl;
   return a;
}
int main()
{
    A *r=&func();           //当指针r接收临时对象之后,这个临时对象就被释放了,那么程序最后为什么能输出23呢?
    cout<<"对象a的副本的地址:"<<r<<endl;
    cout<<r->get()<<endl;
return 0;}

六、引用堆中对象常见错误

r的地址就是堆中对象的副本的内存地址,不过这个副本是保存在栈中,在func函数结束时指针p被自动释放,因为指针生存期只在func函数中,但堆中的指针,只能由delete释放,所以p指针指向的空间找不到,造成了内存泄露。

#include <iostream>
using namespace std;
class A
{
public:
   A(int i){cout<<"执行构造函数创建一个对象\n";x=i;}
   A(const A&a){x=a.x;cout<<"执行复制构造函数创建一个对象\n";}
   ~A(){cout<<"执行析构函数!\n";}
   int get(){return x;}
private:
int x;
};
A func()					//返回的是一个对象,按值返回
{
  cout<<"跳转到func函数中!"<<endl;
   A *p=new A(99);		     //创建一个堆中的对象
   cout<<"堆中对象的地址:"<<p<<endl;
   return *p;                //返回指针p指向的对象,但是指针p是局部对象,在func函数结束时被释放
                             //由此在堆中创建的内存产生泄露,r接收的是堆中对象的副本,
                             //并不是p指向的对象本身
}
int main()
{
   A&r=func();			    //用r接收返回的对象,引用会延长临时变量的生命
   cout<<"堆中对象的副本的地址:<<"&r<<endl;
   cout<<r.get()<<endl;
   return 0;
}

解决办法:返回别名

A &func()				
{
   A *p=new A(99);
   cout<<"堆中对象的地址:"<<p<<endl;
   return *p;			                 //返回堆中对象的别名
}

int main()
{
   A &r=func();				//r作为*p的别名,也就是堆中对象的别名
   cout<<"堆中对象副本的地址&r:"<<&r<<endl;	//r的地址就是堆中对象的地址
   cout<<r.get()<<endl;		                //使用r这个别名来访问get()函数
   A*pa=&r;					//将别名的内存地址,也就是堆中对象的地址赋给pa
   delete pa;				        //删除pa指向的堆中对象的内存空间
   cout<<r.get()<<endl;		                //别名r成了个空引用
   return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值