c++之引用

来源:互联网 发布:全球最出名的程序员 编辑:程序博客网 时间:2024/05/21 14:56

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

一、对变量的引用

#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