[第二节]C++ 引用 函数调用作为左值 用const限定引用 返回堆中变量的引用

来源:互联网 发布:ios开发播放网络视频 编辑:程序博客网 时间:2024/04/29 19:26

函数调用作为左值

  在上节中,对于第三种情况,也意味着返回一个引用使得一个函数调用表达式成为左值表达式。只要避免将局部栈中变量的地址返回,就能使函数调用表达式作为左值来使用运行得很好。
  例如,下面的程序是统计学生中A类学生与B类学生各占多少。 A类学生的标准是平均分在80分以上,其余都是B类学生,先看不返回引用的情况
    
//*********************
    //**   ch9_7.cpp  **
    //*********************

    #include <iostream.h>

    int array[6][4]={{60,80,90,75},
            {75,85,65,77},
            {80,88,90,98},
            {89,100,78,81},
            {62,68,69,75},
            {85,85,77,91}};

    int getLevel(int grade[], int size);

    void main()
    {
     int typeA=0,typeB=0;
     int student=6;
     int gradesize=4;

     for(int i=0; i<student; i++) //处理所有的学生
      if(getLevel(array[i], gradesize))
       typeA++;
      else
       typeB++;

     cout <<"number of type A is " <<typeA <<endl;
     cout <<"number of type B is " <<typeB <<endl;
    }

    int getLevel(int grade[], int size)

    {
     int sum=0;
     for(int i=0; i<size; i++) //成绩总分
      sum+=grade[i];

     sum/=size; //平均分

     if(sum>=80)
       return 1; //type A student
     else
       return 0; //type B student
    }


  运行结果为:
  
  number of type A iS 3
    number of type B iS 3

  该程序通过函数调用判明该学生成绩属于A类还是B类, 然后给A类学生人数增量或给B类学生人数增量。
  该程序也可以通过返回引用来实现。返回的引用作为左值直接增量。例如:
    
//*********************
    //**   ch9_8.cpp  **
    //*********************

    #include <iostream.h>

    int array[6][4]={{60,80,90,75},
            {75,85,65,77},
            {80,88,90,98},
            {89,100,78,81},
            {62,68,69,75},
            {85,85,77,91}};

    int& level(int grade[], int size, int& tA, int& tB);

    void main()
    {
     int typeA=0,typeB=0;
     int student=6;
     int gradesize=4;

     for(int i=0; i<student; i++) //处理所有的学生
      level(array[i],gradesize,typeA,typeB)++;

        //函数调用作为左值
     cout <<"number of type A is " <<typeA <<endl;
     cout <<"number of type B is " <<typeB <<endl;
    }

    int& level(int grade[], int size,int& tA, int& tB)
    {
     int sum=0;
     for(int i=0; i<size; i++) //成绩总分
      sum+=grade[i];

     sum/=size; //平均分

     if(sum>=80)
      return tA; //type A student
     else
      return tB; //type B student
    }


  该程序中的level()函数返回一个引用, 为了返回一个非局部变量的引用,就要传递两个引用参数typeA和typeB。当该学生属于A类时,就返回typeA的引用,否则就返回typeB的引用。
由于返回的是引用,所以可以作为左值直接进行增量操作。该函数调用代表typeA还是typeB的左值视具体的学生成绩统计结果而定。
  本例说明:返回引用的函数,可以使函数成为左值。在后面章节中,我们将会看到,这一应用是很多的,最典型的是cout和cin的操作符重载。
  ->上面各个图中,对引用的表示依赖于实现。引用变量概念上是不占空间的,引用变量被理解为粘附在初始化的实体上,它的实现对用户来说不可见。但并不等于具体实现的时候,非得不占任何空间。为了帮助理解引用,让你有个引用实现的感性认识,表示了一种实现的方案,图中引用空间用来存放所代表变量的地址。

用const限定引用

  传递指针和引用更大的目的是效率。 当一个数据类型很大(后面章节中介绍的自定义类型)时,因为传值要复制副本,所以不可取。
  另一方面,传递指针和引用存在传值所没有的危险。程序有时侯不允许传递的指针所指向的值被修改或者传递的引用被修改,但传递的地址特征使得所传的参数处于随时被修改的危险之中。
  保护实参不被修改的办法是传递const指针和引用:
  例如,下面的程序传递一个const double型的常量指针,返回一个指针:
    
//*********************
    //**   ch9_9.cpp  **
    //*********************

    #include <iostream.h>

    double* fn(const double* pd)
    {
     static double ad=32;
     ad+=*pd;
     cout<<"fn being called..the value is:"<<*pd<<endl;
     return &ad;
    }

    void main()
    {
     double a=345.6;
     const double* pa=fn(&a);
     cout <<*pa <<endl;
     a=55.5;
     pa = fn(&a);
     cout <<*pa <<endl;
    }


  运行结果为:
    fn being called...the value is:345.6
    377.6
    fn being called...the value iS:55.5
    433.1

  程序中fn()函数声明的参数为double型的常量指针,返回double型的指针。函数fn()中,没有生成实参a的副本,访问*pd就是直接访问a。尽管a是变量,但由于限定了pd的性质,所以在fn()函数的作用域范围内,pd只能以*pd的形式读出a,而不能修改a。
  函数fn()中,定义了一个静态局部变量ad。在返回时,将其地址返回给了主函数中的常量double的指针pa, 使之通过*pa能读出ad的值而不能修改之。但在函数fn()中,ad是变量。是可以被修改的。
  将上面的程序改成传递引用与返回引用,函数处理起来会更容易,可读性也更好:
    
//**********************
    //**   ch9_10.cpp  **
    //**********************

    #include <iostream.h>

    double& fn(const double& pd)
    {
     static double ad=32;
     ad+=pd;
      cout<<"fn being called..the value is:"<<pd<<endl;
     return ad;
    }

    void main()
    {
     double a=345.6;
     double& pa=fn(a);
     cout <<pa <<endl;
     a=55.5;
     pa = fn(a);
     cout <<pa <<endl;
    }

  运行结果为:
 
   fn being called...the value is:345.6
    377.6
    fn being called...the value iS:55.5
    433.1

  程序ch9_10.cpp与ch9_9.cpp的输出是一样的。 唯一明显的区别是现在的函数fn()以const double的引用为参数,返回double的引用。用引用比用指针更简单些,而且程序达到相同的效率, 也具有使用const所提供的安全性。
  ->C++不区分变量的const引用和const变量的引用。 程序决不能给引用本身重新赋值,使它指向另一个变量, 因此引用总是const的。如果对引用应用关键词const,其作用就是 使目标成为const变量。即没有:
    const double const &a=1;
  只有:
    const double &a=1;

返回堆中变量的引用

  对引用的初始化,可以是变量,可以是常量,也可以是一定类型的堆空间变量。但是,由于引用不是指针,所以,下面的代码直接从堆中获得的变量空间来初始化引用是错的:
    int& a=new int(2); //a不是指针
  考虑操作符new。 如果new不能在堆空间成功地获得内存分配, 它返回NULL。因为引用不能是NULL,在程序确认它不是NULL之前,程序不能用这一内存初始化引用。
  例如,下面的代码说明如何处理这一校验:
    
#include<iostream.h>
    void fn()
    {
     int * pInt=new int;
     if(pInt==NULL)
     {
      cout<<"error memory allocation!";
      return;
     }

     int& rInt*pInt;
     //...
    }

  int的指针pInt获得new返回的值,程序测试pInt中的地址,如果它是NULL,则报告错误信息并返回。 如果它不是NULL,则将*pInt初始化引用rInt。如此,rInt成为new返回的别名。
  用堆空间来初始化引用,要求该引用在适当时候释放堆空间。
  例如,下面的程序在堆中分配空间,求值,然后释放堆空间:
    
//**********************
    //**   ch9_11.cpp  **
    //**********************

    #include <iostream.h>

    int CircleArea()
    {
     double* pd=new double;
     if(!pd){
      cout <<"error memory allocation!";
      return true;
     }
     double& rd=*pd;
     cout <<"the radius is: ";
     cin >>rd;
     cout<<"the area of circle is "<<rd*rd*3.14 <<endl;
    
 delete &rd;
     return false;
    }

    void main()
    {
     if(CircleArea())
      cout <<"program failed.\n";
     else
      cout <<"program successed.\n";
    }

  运行结果为:
  
  the radius is:12
    the area of circle is 452.16
    program successed.

  在计算圆面积的CircleArea()中, double指针接受new返回的堆空间地址,然后进行有效性校验。如果有效, 则将*pd初始化引用rd,rd接受键盘输入,计算,打印输出圆面积,返还堆空间,并正常返回。否则输出错误信息,返回出错标志。
  这里,返还堆空间有两种方式,一个是deletepd,另一个是delete &rd,因为&rd和pd都指向同一个堆空间地址。对引用来说,同===样存在由一个函数建立的
堆内存由另一个函数释放的问题,我们在第8章有过类似的说明。
  对使用堆的引用,有下面的经验:
  ·必要时用值传递参数
  ·必要时返回值
  ·不要返回有可能退出作用域的引用
  ·不要引用空目标
  ->引用和指针使函数的“黑盒”性被打破。函数可以访问不属于自己栈空间的内存。这对把握不住C++的人来说是危险的, 而对熟练的程序员来说,正是引用和指针才使函数只能返回单一值的状态被打破,使得函数功能更趋强大。函数的副作用是良性还是恶性, 各A自有评说,对于函数潜在的破坏性,是放任自流,还是要适当地抑制, 专家们动尽了脑筋,直到C++类机制的实现,才使函数的恶性作用得以控制。

本章小结

  引用是C++独有的特性。 指针存在有种种问题,间接引用指针会使代码可读性差, 易编程出错。而引用正好扬弃了指针。 引用的使用中,单纯取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大对象的传递效率和空间都不如意的问题。本章主要介绍引用的原理和各种语法现象,未涉及到大对象,它在以后各章陆续介绍。引用能够保证参数传递中不产生副本,从而发挥指针的威力,提高传递的效率, 通过const的使用,保证了引用传递的安全性。
  引用是C++语言学习中的一个难点。有的人C的背景知识不是很强,他们经常一开始不管在什么地方都使用引用,除非有的地方必须使用指针。而另一些学习C++的C程厅员则通常避免使用引用,只是把它们作为另一种传递地址的方法来考虑。由于指针也能做这项工作.所以他们只使用指针。这都是片面的。
  引用具有表达清晰的优点。引用将对传递参数的责任赋给了编写函数的程序员,而不是使用它们的各个用户。引用是对操作符重载必不可少的补充(见14.7节)。引用通过传递地址提高了函数运行的效率。引用传递与值传递在使用方法上比较,唯一区别在函数的形式参数声明。
  不允许声明引用数组,可以用常量来初始化引用声明。返回引用时,要注意局部对象返回的危险。要注意引用隐藏函数所使用的参数传递的类型。


[
原创粉丝点击