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去访问这片存储区时,存储区的内容被解释为实数。

  • 相关阅读:
    MOSS中的User的Title, LoginName, DisplayName, SID之间的关系
    如何在Network Monitor中高亮间隔时间过长的帧?
    SharePoint服务器如果需要安装杀毒软件, 需要注意什么?
    如何查看SQL Profiler? 如何查看SQL死锁?
    什么是Telnet
    The name or security ID (SID) of the domain specified is inconsistent with the trust information for that domain.
    Windows SharePoint Service 3.0的某个Web Application无搜索结果
    网络连接不上, 有TCP错误, 如果操作系统是Windows Server 2003, 请尝试一下这里
    在WinDBG中查看内存的命令
    The virtual machine could not be started because the hypervisor is not running
  • 原文地址:https://www.cnblogs.com/freesblog/p/4089835.html
Copyright © 2011-2022 走看看