zoukankan      html  css  js  c++  java
  • C++中的引用

    c++比起c来除了多了类类型外还多出一种类型:引用。这个东西变量不象变
    量,指针不象指针,我以前对它不太懂,看程序时碰到引用都稀里糊涂蒙过去。
    最近把引用好好地揣摩了一番,小有收获,特公之于社区,让初学者们共享。 
        引用指的是对一个对象的引用。那么什么是对象?在c++中狭义的对象指的是
    用类,结构,联合等复杂数据类型来声明的变量,如 MyClass myclass,CDialo
    g  mydlg,等等。广义的对象还包括用int,char,float等简单类型声明的变量
    ,如int a,char b等等。我在下文提到“对象”一词全指的是广义的对象。c++
    的初学者们把这个广义对象的概念建立起来,对看参考书是很有帮助的,因为大
    多数书上只顾用“对象”这个词,对于这个词还有广义和狭义两种概念却只字不
    提。 

    一。引用的基本特性 

        首先让我们声明一个引用并使用它来初步认识引用。 
    例一: 
           1。     int v,k,h; 
           2。     int &rv=v; 
           3。     rv=3;      //此时v的值也同时变成了3。 
           4。     v=5; 
           5。     k=rv+2;    //此时k=5+2=7。 
           6。     h=12; 
           7。     rv=h; 
           8。     rv=20; 
        第1句声明了三个对象(简单变量)。 
        第2句的意思是:声明了一个引用,名字叫rv,它具有int类型,或者说它是
    对int类型的引用,而且它被初始化为与int类型的对象v“绑定”在一起。此时r
    v叫做对象v的引用。 
        第3句把rv的值赋为3。引用的神奇之处就在这里,改变引用的值的同时也改
    变了和引用所绑定在一起的对象的值。所以此时v的值也变成了3。 
        第4句把v的值改为5,此时指向v的引用的值也被改成了5。所以第5句的中k的
    值是5+2等于7。 

        上述5句说明了引用及其绑定的对象的关系:在数值上它们是联动的,改变你
    也就改变了我,改变我也就改变了你。事实上,访问对象和访问对象的引用,就
    是访问同一块内存区域。 

        第6,7,8三句说明了引用的另一个特性:从一而终。什么意思?当你在引用
    的声明语句里把一个引用绑定到某个对象后,这个引用就永远只能和这个对象绑
    定在一起了,没法改了。所以这也是我用了“绑定”一词的原因。而指针不一样
    。当在指针的声明语句里把指针初始化为指向某个对象后,这个指针在将来如有
    需要还可以改指别的对象。因此,在第7句里把rv赋值为h,并不意味着这个引用
    rv被重新绑定到了h。事实上,第7句只是一条简单的赋值语句,执行完后,rv和
    v的值都变成了12。第8句执行完后,rv和v的值都是20,而h保持12不变。 

        引用还有一个特性:声明时必须初始化,既必须指明把引用绑定到什么对象
    上。大家知道指针在声明时可以先不初始化,引用不行。所以下列语句将无法通
    过编译: 
                  int v; 
                  int &rv; 
                  rv=v; 

        再举一例: 
    例二: 
              class MyClass 
              { 
                  public: 
                      int a; 
                      ... 
                      ... 
              }; 
               
              MyClass  myclass; 
              Myclass& cc=myclass; 
              myclass.a=20;          //等价于cc.a=20 
              cc.a=60;               //等价于myclass.a=60 

        从以上例子可以看到,无论这个对象有多复杂,使用该对象的引用或是使用
    该对象本身,在语法格式上是一样的,在本质上我们都使用了内存中的同一块区
    域。 
        取一个引用的地址和取一个对象的地址的语法是一样的,都是用取地址操作
    符"&"。例如: 
              int i; 
              int &ri; 
              int *pi=&ri;//这句的作用和int *pi=&i是一样的。 
        当然了,取一个对象的地址和取这个对象的引用的地址,所得结果是一样的
    。 

    二。引用在函数参数传递中的作用 

        现在让我们通过函数参数的传递机制来进一步认识引用。在c++中给一个函数
    传递参数有三种方法:1,传递对象本身。2,传递指向对象的指针。3,传递对象
    的引用。 
    例三: 
              class MyClass 
              { 
                  public: 
                      int a; 
                      void method(); 
              }; 
               
              MyClass  myclass; 
               
              void fun1(MyClass); 
              void fun2(MyClass*); 
              void fun3(MyClass&); 

              fun1(myclass);     //执行完函数调用后,myclass.a=20不变。 
              fun2(&myclass);    //执行完函数调用后,myclass.a=60,改变了。

              fun3(myclass);     //执行完函数调用后,myclass.a=80,改变了。

              //注意fun1和fun3的实参,再次证明了:使用对象和使用对象的引用
    ,在语法格式上是一样的。 

              void fun1(MyClass mc) 
              { 
                    mc.a=40; 
                    mc.method(); 
              } 

              void fun2(MyClass* mc) 
              { 
                    mc->a=60; 
                    mc->method(); 
              } 

              void fun3(MyClass& mc) 
              { 
                    mc.a=80; 
                    mc.method(); 
              } 
        我们有了一个MyClass类型的对象myclass和三个函数fun1,fun2,fun3,这三个
    函数分别要求以对象本身为参数;以指向对象的指针为参数;以对象的引用为参
    数。 

        请看fun1函数,它使用对象本身作为参数,这种传递参数的方式叫传值方式
    。c++将生成myclass对象的一个拷贝,把这个拷贝传递给fun1函数。在fun1函数
    内部修改了mc的成员变量a,实际上是修改这个拷贝的成员变量a,丝毫影响不到
    作为实参的myclass的成员变量a。 
        fun2函数使用指向MyClass类型的指针作为参数。在这个函数内部修改了mc所
    指向的对象的成员变量a,这实际上修改的是myclass对象的成员变量a。 
        fun3使用myclass对象的引用作为参数,这叫传引用方式。在函数内部修改了
    mc的成员变量a,由于前面说过,访问一个对象和访问该对象的引用,实际上是访
    问同一块内存区域,因此这将直接修改myclass的成员变量a。 

        从fun1和fun3的函数体也可看出,使用对象和使用对象的引用,在语法格式
    上是一样的。 

        在fun1中c++将把实参的一个拷贝传递给形参。因此如果实参占内存很大,那
    么在参数传递中的系统开销将很大。而在fun2和fun3中,无论是传递实参的指针
    和实参的引用,都只传递实参的地址给形参,充其量也就是四个字节,系统开销
    很小。 



    三。返回引用的函数 

        引用还有一个很有用的特性:如果一个函数返回的是一个引用类型,那么该
    函数可以被当作左值使用。什么是左值搞不懂先别管,只需了解:如果一个对象
    或表达式可以放在赋值号的左边,那么这个对象和表达式就叫左值。 
        举一个虽然无用但很说明问题的例子: 
    例四: 
            1。     int i; 
            2。     int& f1(int&); 
            3。     int  f2(int); 
            4。     f1(i)=3; 
            5。     f2(i)=4; 

                    int& f1(int&i) 
                    { 
                       return i; 
                    } 
        
                    int f2(int i) 
                    { 
                       return i; 
                    } 
        试试编译一下,你会发现第4句是对的,第5句是错的。对这个例子而言,i的
    引用被传递给了f1,然后f1把这个引用原样返回,第4句的意义和i=3是一样的。

        查了查书,引用的这个特性在重载操作符时用得比较多。但是我对重载操作
    符还是稀里糊涂,所以就举不出例子了。 
        强调一个小问题,看看如下代码有何错误: 

                    int &f1(); 
                     
                    f1()=5; 
                    ... 
                    ... 
                    int &f1() 
                    { 
                        int i; 
                        int &ri=i; 
                        return ri; 
                    } 

        注意函数f1返回的引用ri是在函数体内声明的,一旦函数返回后,超出了函
    数作用域,ri所指向的内存区域,即对象i所占据的内存区域就被收回了,再对这
    片内存区域赋值会出错的。 

    四。引用的转换 

        前面所举的例子,引用的类型都是int类型,并且这些引用都被初始化为绑定
    到int类型的对象。那么我们设想是否可以声明一个引用,它具有int类型,却被
    初始化绑定到一个float类型的对象?如下列代码所示: 
                   float f; 
                   int &rv=f; 
        结果证明这样的转换不能通过msvc++6.0的编译。但是引用的转换并非完全不
    可能,事实上一个基类类型的引用可以被初始化绑定到派生类对象,只要满足这
    两个条件:1,指定的基类是可访问的。2,转换是无二义性的。举个例子: 
    例五: 
              class A 
              { 
                public: 
                    int a; 
              }; 
              class B:public A 
              { 
               public: 
                    int b; 
              }; 
              A Oa; 
              B Ob; 
              A& mm=Ob; 
              mm.a=3; 

        我们有一个基类A和派生类B,并且有一个基类对象Oa和派生类对象Ob,我们
    还声明了一个引用mm,它具有基类类型但被绑定到派生类对象Ob上。由于我们的
    这两个类都很简单,满足那两个条件,因此这段代码是合法的。在这个例子中,
    mm和派生类Ob中的基类子对象是共用一段内存单元的。所以,语句mm.a=3相当于
    Ob.a=3,但是表达式mm.b却是不合法的,因为基类子对象并不包括派生类的成员
    。 


    五。总结 

        最后把引用给总结一下: 
    1。对象和对象的引用在某种意义上是一个东西,访问对象和访问对象的引用其实
    访问的是同一块内存区。 
    2。使用对象和使用对象的引用在语法格式上是一样的。 
    3。引用必须初始化。 
    4。引用在初始化中被绑定到某个对象上后,将只能永远绑定这个对象。 
    5。基类类型的引用可以被绑定到该基类的派生类对象,只要基类和派生类满足上
    文提到的两个条件   。这时, 该引用其实是派生类对象中的基类子对象的引用
    。 
    6。用传递引用的方式给函数传递一个对象的引用时,只传递了该对象的地址,系
    统消耗较小。在函数体内访问    形参,实际是访问了这个作为实参的对象。 
    7。一个函数如果返回引用,那么函数调用表达式可以作为左值。 



    六。其他 
    1。本文中的代码在msvc++6.0中调试验证过。 
    2。第四节“引用的转换”中的例子: 
                   float f; 
                   int &rv=f; 
       查看bc++3.1的资料,据说是合法的。此时编译器生成了一个float类型的临时
    对象,引用rv被绑定到了这个临时对象上,就是说,此时rv并不是f的引用。不知
    道bc++3.1里的这个特性有什么用。 
    3。可以在msvc++6.0里声明这样的引用: 
                   const int &rv=3; 
       此时rv的值就是3,而且无法更改。这可能没有有什么用。因为如果我们要使
    用一个符号来代表常数的话,有的是更常见的方法: 
                   #define rv 3 
    4。把第四节中的例子稍稍修改一下: 
                   float f; 
                   int &rv=(int&)f; 
       这时就可以通过msvc++6.0的编译了。此时rv被绑定到了f上,rv和f共用一片
    存储区。不过由于引用rv的类型是int,所以通过rv去访问这片存储区时,存储区
    的内容被解释为整数;通过f去访问这片存储区时,存储区的内容被解释为实数。

  • 相关阅读:
    软件工程概论第一阶段站立会议(八)
    软件工程概论第一阶段站立会议(七)
    浪潮之巅阅读笔记(一)
    软件工程概论学习进度条(四)
    软件工程概论第一阶段站立会议(六)
    软件工程概论第一阶段站立会议(五)
    软件工程概论第一阶段站立会议(四)
    易校小程序典型需求分析
    pip/easy_install failure: failed to create process
    mysq修改密码
  • 原文地址:https://www.cnblogs.com/freesblog/p/4089835.html
Copyright © 2011-2022 走看看